mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-02 13:11:03 -07:00
Compare commits
329 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29d4b1e6b8 | |||
| 2a8e1a8cc1 | |||
| c9dbf70043 | |||
| eb88343419 | |||
| 217f277580 | |||
| 02e68e4554 | |||
| 982fd1fc29 | |||
| 6289c3ee3f | |||
| b1f7a7e6f7 | |||
| ea0e3de430 | |||
| f6a5fa0e03 | |||
| de86bccd0d | |||
| 46079d9daa | |||
| 1d5e792a41 | |||
| cd7a5c9604 | |||
| bb6c574144 | |||
| 66e7b479ae | |||
| 8030404b08 | |||
| 933bc5774f | |||
| 4afd3741df | |||
| 428e602882 | |||
| 04f65d7b4e | |||
| eda47f587c | |||
| 8dbaa2bcea | |||
| 778de55fd8 | |||
| c7c409c68f | |||
| d66ec38f82 | |||
| 63c918fe7d | |||
| b2e866585d | |||
| b518125c46 | |||
| e8be252b75 | |||
| 92e31e3c4a | |||
| e931ebe581 | |||
| 91fcca3b1c | |||
| aa52462550 | |||
| 0f7a136612 | |||
| 8faa23cea6 | |||
| a6dca02344 | |||
| 304caa4e43 | |||
| 7bbfaabffa | |||
| 6adae9f775 | |||
| b81fe68325 | |||
| 2fc61685a3 | |||
| c572b9e9ac | |||
| 8d3e93cdb0 | |||
| 548641c952 | |||
| 6ef2a92233 | |||
| e9c9dd1d67 | |||
| ca6786a28b | |||
| 2e8c6cfdbb | |||
| 95d9a33996 | |||
| b8cc414d5b | |||
| d7bff8610f | |||
| e049d5e4e8 | |||
| 8437ce940a | |||
| 7d92242011 | |||
| d65eab01d2 | |||
| 2306e60be4 | |||
| 3b678a4da0 | |||
| 64cde8d439 | |||
| 0167392f22 | |||
| 15891721ad | |||
| 8a2e0fac0d | |||
| 8656ce8a27 | |||
| ed7bcf9968 | |||
| 465ec9759d | |||
| 950244f6b0 | |||
| 64c75cb767 | |||
| 7b7f2fc69e | |||
| d315f4d3da | |||
| 9703fe73cf | |||
| 93b57b82c1 | |||
| 0e955da171 | |||
| 39b3f20a22 | |||
| b9762a3ee1 | |||
| 33e3ed0f6c | |||
| 446058cb1c | |||
| d130d99ff0 | |||
| 72dae7e0ee | |||
| 6f7d798189 | |||
| 3090008b1c | |||
| 461c277bf2 | |||
| b54e688c75 | |||
| b08b0d715b | |||
| c87d1aed4c | |||
| 356f76e545 | |||
| 1fb55dcb2e | |||
| d74bf9ef2f | |||
| e04a5f0cb0 | |||
| ea7393f7fd | |||
| c7d17dda49 | |||
| 9d187e041c | |||
| 14f0cb4538 | |||
| ca48661423 | |||
| 041463d112 | |||
| c1401682ed | |||
| 8bc3cfe29a | |||
| 77e226c55f | |||
| b9f8858bfb | |||
| f7b97ef55e | |||
| 88f1ec8d0a | |||
| aa480e5fbb | |||
| 41e627a7ee | |||
| e1e3efc9d0 | |||
| 18dd399cb5 | |||
| f1ca7fa40a | |||
| 6166d7f6ec | |||
| ffb80c2426 | |||
| 4ab1b9895a | |||
| 84710b1953 | |||
| a7f758eb3a | |||
| 26505b580c | |||
| e51f3e11f1 | |||
| d75792703a | |||
| e5f7a9c424 | |||
| 9062a943e7 | |||
| 7e02ef697d | |||
| 41a8809280 | |||
| 01d2d43737 | |||
| 16da6918cb | |||
| fbfad06307 | |||
| f8138262fa | |||
| 1a4ae41397 | |||
| 75bc41fc20 | |||
| ced5110dab | |||
| 76d020511f | |||
| 02cf264ee1 | |||
| eb3f3cfdb8 | |||
| 660368f249 | |||
| 97ad3d97cb | |||
| cf021ccae4 | |||
| eb75f59a96 | |||
| 030847a80a | |||
| 722c4933dc | |||
| 3e2f4eb8ba | |||
| aca6bf6aa0 | |||
| d48c934357 | |||
| 41cc6cf105 | |||
| dd04b46e86 | |||
| 1bd4f9d8b6 | |||
| 1aa35c8796 | |||
| 51d3f44d51 | |||
| d4b418ba01 | |||
| bd77515fd9 | |||
| 1c77bac146 | |||
| 0be8b5b1ed | |||
| d5996fea99 | |||
| a1dd19738e | |||
| 17b3eb730a | |||
| 4c961df313 | |||
| 19bdd95eab | |||
| 143bb63483 | |||
| c64b5ec4a3 | |||
| 57012ae5b3 | |||
| db99beda36 | |||
| 97b31c4eef | |||
| d1eb87c81f | |||
| 8f9bb6bccc | |||
| 416d243027 | |||
| 5fe5d1da46 | |||
| 2d683bb6f8 | |||
| 7956eb239e | |||
| a26463b056 | |||
| 982eee63b6 | |||
| b54215f0a5 | |||
| 521dc7f26c | |||
| da85e3f8f2 | |||
| 59a18e710d | |||
| 5f027cb63a | |||
| 4086abf375 | |||
| 8f5bf33eac | |||
| 7eeb7bd74c | |||
| 96b9be3ec4 | |||
| 1e31427da8 | |||
| 7feb2f8f42 | |||
| 2122604b32 | |||
| d4b4aede2f | |||
| 4b5c044272 | |||
| 2fe45834dd | |||
| 56092bd782 | |||
| c31f05356a | |||
| 61dbab03e0 | |||
| 86b5995f12 | |||
| d2849fda8a | |||
| a61fb058b7 | |||
| 7edd803034 | |||
| 0c54136244 | |||
| cce4574143 | |||
| 9172e28315 | |||
| 6f4b2ad0b9 | |||
| 9a3ff6510f | |||
| 4c67eef0f2 | |||
| 7d4f97de7a | |||
| cbd2eee9c4 | |||
| 384fb6a465 | |||
| 2cb33b2f76 | |||
| 8f0324d868 | |||
| fd7b6bf40a | |||
| ed8bad8c26 | |||
| 6d1e27633a | |||
| 2da911e4a0 | |||
| e5d183031a | |||
| b13c6b57ae | |||
| 3c92666ec2 | |||
| dc6dda5c37 | |||
| 3997c7ff80 | |||
| b4b49e7029 | |||
| 615b218ff7 | |||
| f3625aab13 | |||
| d3563e2f0e | |||
| dd84c2fb83 | |||
| bdb349e7f6 | |||
| 12c7c9cc42 | |||
| 90be9c3587 | |||
| eec5d5ebf8 | |||
| d3c206c677 | |||
| f0a039f7c0 | |||
| 30f5c4af4a | |||
| e78c3fe4f0 | |||
| c0ccb22460 | |||
| 764b195977 | |||
| 0f3555a4d2 | |||
| 006de1dd31 | |||
| 18fef0db31 | |||
| 958284dc24 | |||
| 788bb04f5c | |||
| 8a0190ca3b | |||
| c29a8c12b3 | |||
| 05049b5abf | |||
| 3ebe4e6a8f | |||
| 0eb84f5133 | |||
| de1233b8ca | |||
| ec11b8afbf | |||
| ec79fe1ab2 | |||
| 90eb1e0281 | |||
| 15c9f88da6 | |||
| 4e6fee7fcd | |||
| 1e08b150f7 | |||
| fb22f5b8ee | |||
| 848e8485cd | |||
| 07e597de40 | |||
| 10ae84869a | |||
| dcd2449b1a | |||
| a3d214f8d7 | |||
| 37be162435 | |||
| 5566292cc8 | |||
| 65e2144b3d | |||
| a26d195404 | |||
| 21388a0a40 | |||
| acecd80afa | |||
| 69fc75c0b2 | |||
| 9cdb267ba5 | |||
| 56b050422d | |||
| 546baf9934 | |||
| e6344a8c24 | |||
| 873d10df42 | |||
| 02a36afc38 | |||
| 563d81e08e | |||
| 5f28614760 | |||
| b6b0727e28 | |||
| e9a601c1fe | |||
| 308aa70718 | |||
| 6be034392f | |||
| 3b1dbcd42d | |||
| 2ac9fe08f7 | |||
| 24c722454b | |||
| 0a216b28f3 | |||
| 9c48cd849b | |||
| 0843d9af58 | |||
| b0d5c4c058 | |||
| d18c96d6a1 | |||
| dced409ac4 | |||
| d6a2f1d670 | |||
| 58fd00a3df | |||
| b923604602 | |||
| 8643d60b88 | |||
| 8feeffb29b | |||
| e64146914a | |||
| 6084708cc2 | |||
| 8ed0f8981f | |||
| 41a1a3eed1 | |||
| 10ba348a3a | |||
| db67bb106a | |||
| b828b4754b | |||
| 9383b54d50 | |||
| 181da07dd9 | |||
| 419464a8c2 | |||
| 2e229d3bb6 | |||
| 3c92bdb1ad | |||
| e0f159085e | |||
| 402148dbc4 | |||
| 70696e364b | |||
| 1e10492e55 | |||
| 9e6914d641 | |||
| 0c4fb6afd2 | |||
| edab97997a | |||
| 2515b89e2b | |||
| 60f0f19d76 | |||
| 322232e514 | |||
| 54466a3ea8 | |||
| 7da060c149 | |||
| 088f48d60f | |||
| 2b426c1d91 | |||
| bc168bbae4 | |||
| 124a6da743 | |||
| 739c02bd6d | |||
| 5d13145995 | |||
| c28ff3d5a5 | |||
| 6ddd5abd7b | |||
| bf6d0485ce | |||
| 3d486ec1e9 | |||
| a6d1245a54 | |||
| de7e1937f6 | |||
| b465e12747 | |||
| 948401a450 | |||
| bb8f181ef1 | |||
| bf90b59935 | |||
| 18698d6929 | |||
| 80c4225286 | |||
| d02f3f6809 | |||
| da85aed5aa | |||
| 7f2d33458a | |||
| 3e9a0a7628 | |||
| 26c115a4fb | |||
| db643e9166 | |||
| ba100642e3 | |||
| cc52839f19 | |||
| e79b149985 | |||
| 5e21c8c03c |
@@ -0,0 +1,29 @@
|
||||
description="Injects context of all relevant cli files"
|
||||
prompt = """
|
||||
The following output contains the complete
|
||||
source code of packages/core.
|
||||
**Pay extremely close attention to these files.** They define the project's
|
||||
core architecture, component patterns, and testing standards.
|
||||
|
||||
The source code contains the content of absolutely every source code file in
|
||||
packages/core.
|
||||
You should very rarely need to read any other files from packages/core to resolve
|
||||
prompts.
|
||||
|
||||
!{find packages/core \\( -path packages/cli/dist -o -path packages/core/dist -o -name node_modules \\) -prune -o -type f \\( -name "*.ts" -o -name "*.tsx" \\) ! -name "*.test.ts" ! -name "*.test.tsx" ! -name "*.d.ts" -exec echo "--- {} ---" \\; -exec cat {} \\;}
|
||||
|
||||
In addition to the code context, you MUST strictly adhere to the following frontend-specific development guidelines when writing code in packages/core.
|
||||
|
||||
## Configuration & Settings
|
||||
* **Settings vs Args**: Use settings for user-configurable options; do not add new CLI arguments.
|
||||
* **Schema**: Add new settings to `packages/cli/src/config/settingsSchema.ts`.
|
||||
* **Documentation**:
|
||||
* If `showInDialog: true`, document in `docs/get-started/configuration.md`.
|
||||
* Ensure `requiresRestart` is correctly set.
|
||||
|
||||
## General
|
||||
* **Logging**: Use `debugLogger` for errors. NEVER leave `console.log/warn/error` in the code.
|
||||
* **TypeScript**: Avoid the non-null assertion operator (`!`).
|
||||
|
||||
{{args}}.
|
||||
"""
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"experimental": {
|
||||
"skills": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: pr-creator
|
||||
description:
|
||||
Use this skill when asked to create a pull request (PR). It ensures all PRs
|
||||
follow the repository's established templates and standards.
|
||||
---
|
||||
|
||||
# Pull Request Creator
|
||||
|
||||
This skill guides the creation of high-quality Pull Requests that adhere to the
|
||||
repository's standards.
|
||||
|
||||
## Workflow
|
||||
|
||||
Follow these steps to create a Pull Request:
|
||||
|
||||
1. **Locate Template**: Search for a pull request template in the repository.
|
||||
- Check `.github/pull_request_template.md`
|
||||
- Check `.github/PULL_REQUEST_TEMPLATE.md`
|
||||
- If multiple templates exist (e.g., in `.github/PULL_REQUEST_TEMPLATE/`),
|
||||
ask the user which one to use or select the most appropriate one based on
|
||||
the context (e.g., `bug_fix.md` vs `feature.md`).
|
||||
|
||||
2. **Read Template**: Read the content of the identified template file.
|
||||
|
||||
3. **Draft Description**: Create a PR description that strictly follows the
|
||||
template's structure.
|
||||
- **Headings**: Keep all headings from the template.
|
||||
- **Checklists**: Review each item. Mark with `[x]` if completed. If an item
|
||||
is not applicable, leave it unchecked or mark as `[ ]` (depending on the
|
||||
template's instructions) or remove it if the template allows flexibility
|
||||
(but prefer keeping it unchecked for transparency).
|
||||
- **Content**: Fill in the sections with clear, concise summaries of your
|
||||
changes.
|
||||
- **Related Issues**: Link any issues fixed or related to this PR (e.g.,
|
||||
"Fixes #123").
|
||||
|
||||
4. **Create PR**: Use the `gh` CLI to create the PR.
|
||||
```bash
|
||||
gh pr create --title "type(scope): succinct description" --body "..."
|
||||
```
|
||||
- **Title**: Ensure the title follows the
|
||||
[Conventional Commits](https://www.conventionalcommits.org/) format if the
|
||||
repository uses it (e.g., `feat(ui): add new button`,
|
||||
`fix(core): resolve crash`).
|
||||
|
||||
## Principles
|
||||
|
||||
- **Compliance**: Never ignore the PR template. It exists for a reason.
|
||||
- **Completeness**: Fill out all relevant sections.
|
||||
- **Accuracy**: Don't check boxes for tasks you haven't done.
|
||||
+144
-108
@@ -4,134 +4,170 @@ set -euo pipefail
|
||||
# Initialize a comma-separated string to hold PR numbers that need a comment
|
||||
PRS_NEEDING_COMMENT=""
|
||||
|
||||
# Function to process a single PR
|
||||
process_pr() {
|
||||
if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
|
||||
echo "‼️ Missing \$GITHUB_REPOSITORY - this must be run from GitHub Actions"
|
||||
return 1
|
||||
# Global cache for issue labels (compatible with Bash 3.2)
|
||||
# Stores "ISSUE_NUM:LABELS" pairs separated by spaces
|
||||
ISSUE_LABELS_CACHE_FLAT=""
|
||||
|
||||
# Function to get area and priority labels from an issue (with caching)
|
||||
get_issue_labels() {
|
||||
local ISSUE_NUM=$1
|
||||
if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "${GITHUB_OUTPUT:-}" ]]; then
|
||||
echo "‼️ Missing \$GITHUB_OUTPUT - this must be run from GitHub Actions"
|
||||
return 1
|
||||
# Check cache
|
||||
case " ${ISSUE_LABELS_CACHE_FLAT} " in
|
||||
*" ${ISSUE_NUM}:"*)
|
||||
local suffix="${ISSUE_LABELS_CACHE_FLAT#* ${ISSUE_NUM}:}"
|
||||
echo "${suffix%% *}"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2
|
||||
local gh_output
|
||||
if ! gh_output=$(gh issue view "${ISSUE_NUM}" --repo "${GITHUB_REPOSITORY}" --json labels -q '.labels[].name' 2>/dev/null); then
|
||||
echo " ⚠️ Could not fetch issue #${ISSUE_NUM}" >&2
|
||||
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:"
|
||||
return
|
||||
fi
|
||||
|
||||
local labels
|
||||
labels=$(echo "${gh_output}" | grep -E "^(area|priority)/" | tr '\n' ',' | sed 's/,$//' || echo "")
|
||||
|
||||
# Save to flat cache
|
||||
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}"
|
||||
echo "$labels"
|
||||
}
|
||||
|
||||
# Function to process a single PR with pre-fetched data
|
||||
process_pr_optimized() {
|
||||
local PR_NUMBER=$1
|
||||
local IS_DRAFT=$2
|
||||
local ISSUE_NUMBER=$3
|
||||
local CURRENT_LABELS=$4 # Comma-separated labels
|
||||
|
||||
echo "🔄 Processing PR #${PR_NUMBER}"
|
||||
|
||||
# Get closing issue number with error handling
|
||||
local ISSUE_NUMBER
|
||||
if ! ISSUE_NUMBER=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json closingIssuesReferences -q '.closingIssuesReferences.nodes[0].number' 2>/dev/null); then
|
||||
echo " ⚠️ Could not fetch closing issue for PR #${PR_NUMBER}"
|
||||
local LABELS_TO_ADD=""
|
||||
local LABELS_TO_REMOVE=""
|
||||
|
||||
if [[ -z "${ISSUE_NUMBER}" || "${ISSUE_NUMBER}" == "null" || "${ISSUE_NUMBER}" == "" ]]; then
|
||||
if [[ "${IS_DRAFT}" == "true" ]]; then
|
||||
echo " 📝 PR #${PR_NUMBER} is a draft and has no linked issue"
|
||||
if [[ ",${CURRENT_LABELS}," == ",status/need-issue,"* ]]; then
|
||||
echo " ➖ Removing status/need-issue label"
|
||||
LABELS_TO_REMOVE="status/need-issue"
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ No linked issue found for PR #${PR_NUMBER}"
|
||||
if [[ ",${CURRENT_LABELS}," != ",status/need-issue,"* ]]; then
|
||||
echo " ➕ Adding status/need-issue label"
|
||||
LABELS_TO_ADD="status/need-issue"
|
||||
fi
|
||||
|
||||
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
|
||||
PRS_NEEDING_COMMENT="${PR_NUMBER}"
|
||||
else
|
||||
PRS_NEEDING_COMMENT="${PRS_NEEDING_COMMENT},${PR_NUMBER}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo " 🔗 Found linked issue #${ISSUE_NUMBER}"
|
||||
|
||||
if [[ ",${CURRENT_LABELS}," == ",status/need-issue,"* ]]; then
|
||||
echo " ➖ Removing status/need-issue label"
|
||||
LABELS_TO_REMOVE="status/need-issue"
|
||||
fi
|
||||
|
||||
local ISSUE_LABELS
|
||||
ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}")
|
||||
|
||||
if [[ -n "${ISSUE_LABELS}" ]]; then
|
||||
local IFS_OLD=$IFS
|
||||
IFS=','
|
||||
for label in ${ISSUE_LABELS}; do
|
||||
if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then
|
||||
if [[ -z "${LABELS_TO_ADD}" ]]; then
|
||||
LABELS_TO_ADD="${label}"
|
||||
else
|
||||
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
IFS=$IFS_OLD
|
||||
fi
|
||||
|
||||
if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then
|
||||
echo " ✅ Labels already synchronized"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${ISSUE_NUMBER}" ]]; then
|
||||
echo "⚠️ No linked issue found for PR #${PR_NUMBER}, adding status/need-issue label"
|
||||
if ! gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label "status/need-issue" 2>/dev/null; then
|
||||
echo " ⚠️ Failed to add label (may already exist or have permission issues)"
|
||||
fi
|
||||
# Add PR number to the list
|
||||
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
|
||||
PRS_NEEDING_COMMENT="${PR_NUMBER}"
|
||||
else
|
||||
PRS_NEEDING_COMMENT="${PRS_NEEDING_COMMENT},${PR_NUMBER}"
|
||||
fi
|
||||
echo "needs_comment=true" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "🔗 Found linked issue #${ISSUE_NUMBER}"
|
||||
|
||||
# Remove status/need-issue label if present
|
||||
if ! gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --remove-label "status/need-issue" 2>/dev/null; then
|
||||
echo " status/need-issue label not present or could not be removed"
|
||||
fi
|
||||
|
||||
# Get issue labels
|
||||
echo "📥 Fetching labels from issue #${ISSUE_NUMBER}"
|
||||
local ISSUE_LABELS=""
|
||||
if ! ISSUE_LABELS=$(gh issue view "${ISSUE_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels -q '.labels[].name' 2>/dev/null | tr '\n' ',' | sed 's/,$//' || echo ""); then
|
||||
echo " ⚠️ Could not fetch issue #${ISSUE_NUMBER} (may not exist or be in different repo)"
|
||||
ISSUE_LABELS=""
|
||||
fi
|
||||
|
||||
# Get PR labels
|
||||
echo "📥 Fetching labels from PR #${PR_NUMBER}"
|
||||
local PR_LABELS=""
|
||||
if ! PR_LABELS=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json labels -q '.labels[].name' 2>/dev/null | tr '\n' ',' | sed 's/,$//' || echo ""); then
|
||||
echo " ⚠️ Could not fetch PR labels"
|
||||
PR_LABELS=""
|
||||
fi
|
||||
|
||||
echo " Issue labels: ${ISSUE_LABELS}"
|
||||
echo " PR labels: ${PR_LABELS}"
|
||||
|
||||
# Convert comma-separated strings to arrays
|
||||
local ISSUE_LABEL_ARRAY PR_LABEL_ARRAY
|
||||
IFS=',' read -ra ISSUE_LABEL_ARRAY <<< "${ISSUE_LABELS}"
|
||||
IFS=',' read -ra PR_LABEL_ARRAY <<< "${PR_LABELS}"
|
||||
|
||||
# Find labels to add (on issue but not on PR)
|
||||
local LABELS_TO_ADD=""
|
||||
for label in "${ISSUE_LABEL_ARRAY[@]}"; do
|
||||
if [[ -n "${label}" ]] && [[ " ${PR_LABEL_ARRAY[*]} " != *" ${label} "* ]]; then
|
||||
if [[ -z "${LABELS_TO_ADD}" ]]; then
|
||||
LABELS_TO_ADD="${label}"
|
||||
else
|
||||
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Apply label changes
|
||||
if [[ -n "${LABELS_TO_ADD}" || -n "${LABELS_TO_REMOVE}" ]]; then
|
||||
local EDIT_CMD=("gh" "pr" "edit" "${PR_NUMBER}" "--repo" "${GITHUB_REPOSITORY}")
|
||||
if [[ -n "${LABELS_TO_ADD}" ]]; then
|
||||
echo "➕ Adding labels: ${LABELS_TO_ADD}"
|
||||
if ! gh pr edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label "${LABELS_TO_ADD}" 2>/dev/null; then
|
||||
echo " ⚠️ Failed to add some labels"
|
||||
fi
|
||||
echo " ➕ Syncing labels to add: ${LABELS_TO_ADD}"
|
||||
EDIT_CMD+=("--add-label" "${LABELS_TO_ADD}")
|
||||
fi
|
||||
|
||||
if [[ -z "${LABELS_TO_ADD}" ]]; then
|
||||
echo "✅ Labels already synchronized"
|
||||
if [[ -n "${LABELS_TO_REMOVE}" ]]; then
|
||||
echo " ➖ Syncing labels to remove: ${LABELS_TO_REMOVE}"
|
||||
EDIT_CMD+=("--remove-label" "${LABELS_TO_REMOVE}")
|
||||
fi
|
||||
echo "needs_comment=false" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
("${EDIT_CMD[@]}" 2>/dev/null || true)
|
||||
fi
|
||||
}
|
||||
|
||||
# If PR_NUMBER is set, process only that PR
|
||||
if [[ -n "${PR_NUMBER:-}" ]]; then
|
||||
if ! process_pr "${PR_NUMBER}"; then
|
||||
echo "❌ Failed to process PR #${PR_NUMBER}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Otherwise, get all open PRs and process them
|
||||
# The script logic will determine which ones need issue linking or label sync
|
||||
echo "📥 Getting all open pull requests..."
|
||||
if ! PR_NUMBERS=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number -q '.[].number' 2>/dev/null); then
|
||||
echo "❌ Failed to fetch PR list"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_NUMBERS}" ]]; then
|
||||
echo "✅ No open PRs found"
|
||||
else
|
||||
# Count the number of PRs
|
||||
PR_COUNT=$(echo "${PR_NUMBERS}" | wc -w | tr -d ' ')
|
||||
echo "📊 Found ${PR_COUNT} open PRs to process"
|
||||
|
||||
for pr_number in ${PR_NUMBERS}; do
|
||||
if ! process_pr "${pr_number}"; then
|
||||
echo "⚠️ Failed to process PR #${pr_number}, continuing with next PR..."
|
||||
continue
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
|
||||
echo "‼️ Missing \$GITHUB_REPOSITORY - this must be run from GitHub Actions"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${GITHUB_OUTPUT:-}" ]]; then
|
||||
echo "‼️ Missing \$GITHUB_OUTPUT - this must be run from GitHub Actions"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
JQ_EXTRACT_FIELDS='{
|
||||
number: .number,
|
||||
isDraft: .isDraft,
|
||||
issue: (.closingIssuesReferences[0].number // (.body // "" | capture("(^|[^a-zA-Z0-9])#(?<num>[0-9]+)([^a-zA-Z0-9]|$)")? | .num) // "null"),
|
||||
labels: [.labels[].name] | join(",")
|
||||
}'
|
||||
|
||||
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // "null") | tostring)\t\(.labels)"'
|
||||
|
||||
if [[ -n "${PR_NUMBER:-}" ]]; then
|
||||
echo "🔄 Processing single PR #${PR_NUMBER}"
|
||||
PR_DATA=$(gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
|
||||
echo "❌ Failed to fetch data for PR #${PR_NUMBER}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
line=$(echo "$PR_DATA" | jq -r "$JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT")
|
||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line"
|
||||
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels"
|
||||
else
|
||||
echo "📥 Getting all open pull requests..."
|
||||
PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
|
||||
echo "❌ Failed to fetch PR list"
|
||||
exit 1
|
||||
}
|
||||
|
||||
PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length')
|
||||
echo "📊 Found ${PR_COUNT} open PRs to process"
|
||||
|
||||
while read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line"
|
||||
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels"
|
||||
done < <(echo "${PR_DATA_ALL}" | jq -r ".[] | $JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT")
|
||||
fi
|
||||
|
||||
# Ensure output is always set, even if empty
|
||||
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
|
||||
echo "prs_needing_comment=[]" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "prs_needing_comment=[${PRS_NEEDING_COMMENT}]" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
echo "✅ PR triage completed"
|
||||
echo "✅ PR triage completed"
|
||||
@@ -168,6 +168,7 @@ jobs:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
KEEP_OUTPUT: 'true'
|
||||
VERBOSE: 'true'
|
||||
BUILD_SANDBOX_FLAGS: '--cache-from type=gha --cache-to type=gha,mode=max'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
if [[ "${{ matrix.sandbox }}" == "sandbox:docker" ]]; then
|
||||
@@ -276,6 +277,37 @@ jobs:
|
||||
shell: 'pwsh'
|
||||
run: 'npm run test:integration:sandbox:none'
|
||||
|
||||
evals:
|
||||
name: 'Evals (ALWAYS_PASSING)'
|
||||
needs:
|
||||
- 'merge_queue_skipper'
|
||||
- 'parse_run_context'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
if: |
|
||||
always() && (needs.merge_queue_skipper.result !='success' || needs.merge_queue_skipper.outputs.skip != 'true')
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
ref: '${{ needs.parse_run_context.outputs.sha }}'
|
||||
repository: '${{ needs.parse_run_context.outputs.repository }}'
|
||||
|
||||
- name: 'Set up Node.js 20.x'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
|
||||
- name: 'Run Evals (Required to pass)'
|
||||
env:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
run: 'npm run test:always_passing_evals'
|
||||
|
||||
e2e:
|
||||
name: 'E2E'
|
||||
if: |
|
||||
@@ -283,13 +315,15 @@ jobs:
|
||||
needs:
|
||||
- 'e2e_linux'
|
||||
- 'e2e_mac'
|
||||
- 'evals'
|
||||
- 'merge_queue_skipper'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
steps:
|
||||
- name: 'Check E2E test results'
|
||||
run: |
|
||||
if [[ ${{ needs.e2e_linux.result }} != 'success' || \
|
||||
${{ needs.e2e_mac.result }} != 'success' ]]; then
|
||||
${{ needs.e2e_mac.result }} != 'success' || \
|
||||
${{ needs.evals.result }} != 'success' ]]; then
|
||||
echo "One or more E2E jobs failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+45
-15
@@ -43,13 +43,15 @@ jobs:
|
||||
- id: 'merge-queue-ci-skipper'
|
||||
uses: 'cariad-tech/merge-queue-ci-skipper@1032489e59437862c90a08a2c92809c903883772' # ratchet:cariad-tech/merge-queue-ci-skipper@main
|
||||
with:
|
||||
secret: '${{ secrets.GITHUB_TOKEN }}'
|
||||
secret: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
|
||||
|
||||
lint:
|
||||
name: 'Lint'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
needs: 'merge_queue_skipper'
|
||||
if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}"
|
||||
env:
|
||||
GEMINI_LINT_TEMP_DIR: '${{ github.workspace }}/.gemini-linters'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
@@ -63,9 +65,21 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 'Cache Linters'
|
||||
uses: 'actions/cache@v4'
|
||||
with:
|
||||
path: '${{ env.GEMINI_LINT_TEMP_DIR }}'
|
||||
key: "${{ runner.os }}-${{ runner.arch }}-linters-${{ hashFiles('scripts/lint.js') }}"
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Cache ESLint'
|
||||
uses: 'actions/cache@v4'
|
||||
with:
|
||||
path: '.eslintcache'
|
||||
key: "${{ runner.os }}-eslint-${{ hashFiles('package-lock.json', 'eslint.config.js') }}"
|
||||
|
||||
- name: 'Validate NOTICES.txt'
|
||||
run: 'git diff --exit-code packages/vscode-ide-companion/NOTICES.txt'
|
||||
|
||||
@@ -111,10 +125,9 @@ jobs:
|
||||
args: '--verbose --accept 200,503 ./**/*.md'
|
||||
fail: true
|
||||
test_linux:
|
||||
name: 'Test (Linux)'
|
||||
name: 'Test (Linux) - ${{ matrix.node-version }}, ${{ matrix.shard }}'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
needs:
|
||||
- 'lint'
|
||||
- 'merge_queue_skipper'
|
||||
if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}"
|
||||
permissions:
|
||||
@@ -127,6 +140,9 @@ jobs:
|
||||
- '20.x'
|
||||
- '22.x'
|
||||
- '24.x'
|
||||
shard:
|
||||
- 'cli'
|
||||
- 'others'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
@@ -146,7 +162,14 @@ jobs:
|
||||
- name: 'Run tests and generate reports'
|
||||
env:
|
||||
NO_COLOR: true
|
||||
run: 'npm run test:ci'
|
||||
run: |
|
||||
if [[ "${{ matrix.shard }}" == "cli" ]]; then
|
||||
npm run test:ci --workspace @google/gemini-cli
|
||||
else
|
||||
# Explicitly list non-cli packages to ensure they are sharded correctly
|
||||
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present
|
||||
npm run test:scripts
|
||||
fi
|
||||
|
||||
- name: 'Bundle'
|
||||
run: 'npm run bundle'
|
||||
@@ -162,7 +185,7 @@ jobs:
|
||||
${{ always() && (github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
uses: 'dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3' # ratchet:dorny/test-reporter@v2
|
||||
with:
|
||||
name: 'Test Results (Node ${{ matrix.node-version }})'
|
||||
name: 'Test Results (Node ${{ runner.os }}, ${{ matrix.node-version }}, ${{ matrix.shard }})'
|
||||
path: 'packages/*/junit.xml'
|
||||
reporter: 'java-junit'
|
||||
fail-on-error: 'false'
|
||||
@@ -172,14 +195,13 @@ jobs:
|
||||
${{ always() && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
|
||||
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'test-results-fork-${{ matrix.node-version }}-${{ runner.os }}'
|
||||
name: 'test-results-fork-${{ runner.os }}-${{ matrix.node-version }}-${{ matrix.shard }}'
|
||||
path: 'packages/*/junit.xml'
|
||||
|
||||
test_mac:
|
||||
name: 'Test (Mac)'
|
||||
runs-on: '${{ matrix.os }}'
|
||||
name: 'Test (Mac) - ${{ matrix.node-version }}, ${{ matrix.shard }}'
|
||||
runs-on: 'macos-latest'
|
||||
needs:
|
||||
- 'lint'
|
||||
- 'merge_queue_skipper'
|
||||
if: "${{needs.merge_queue_skipper.outputs.skip == 'false'}}"
|
||||
permissions:
|
||||
@@ -189,12 +211,13 @@ jobs:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- 'macos-latest'
|
||||
node-version:
|
||||
- '20.x'
|
||||
- '22.x'
|
||||
- '24.x'
|
||||
shard:
|
||||
- 'cli'
|
||||
- 'others'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
@@ -214,7 +237,14 @@ jobs:
|
||||
- name: 'Run tests and generate reports'
|
||||
env:
|
||||
NO_COLOR: true
|
||||
run: 'npm run test:ci -- --coverage.enabled=false'
|
||||
run: |
|
||||
if [[ "${{ matrix.shard }}" == "cli" ]]; then
|
||||
npm run test:ci --workspace @google/gemini-cli -- --coverage.enabled=false
|
||||
else
|
||||
# Explicitly list non-cli packages to ensure they are sharded correctly
|
||||
npm run test:ci --workspace @google/gemini-cli-core --workspace @google/gemini-cli-a2a-server --workspace gemini-cli-vscode-ide-companion --workspace @google/gemini-cli-test-utils --if-present -- --coverage.enabled=false
|
||||
npm run test:scripts
|
||||
fi
|
||||
|
||||
- name: 'Bundle'
|
||||
run: 'npm run bundle'
|
||||
@@ -230,7 +260,7 @@ jobs:
|
||||
${{ always() && (github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||
uses: 'dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3' # ratchet:dorny/test-reporter@v2
|
||||
with:
|
||||
name: 'Test Results (Node ${{ matrix.node-version }})'
|
||||
name: 'Test Results (Node ${{ runner.os }}, ${{ matrix.node-version }}, ${{ matrix.shard }})'
|
||||
path: 'packages/*/junit.xml'
|
||||
reporter: 'java-junit'
|
||||
fail-on-error: 'false'
|
||||
@@ -240,7 +270,7 @@ jobs:
|
||||
${{ always() && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
|
||||
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'test-results-fork-${{ matrix.node-version }}-${{ runner.os }}'
|
||||
name: 'test-results-fork-${{ runner.os }}-${{ matrix.node-version }}-${{ matrix.shard }}'
|
||||
path: 'packages/*/junit.xml'
|
||||
|
||||
- name: 'Upload coverage reports'
|
||||
@@ -248,7 +278,7 @@ jobs:
|
||||
${{ always() }}
|
||||
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'
|
||||
name: 'coverage-reports-${{ runner.os }}-${{ matrix.node-version }}-${{ matrix.shard }}'
|
||||
path: 'packages/*/coverage'
|
||||
|
||||
codeql:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 'Evals: Nightly'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *' # Runs at 1 AM every day
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_all:
|
||||
description: 'Run all evaluations (including usually passing)'
|
||||
type: 'boolean'
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
checks: 'write'
|
||||
actions: 'read'
|
||||
|
||||
jobs:
|
||||
evals:
|
||||
name: 'Evals (USUALLY_PASSING) nightly run'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
run_attempt: [1, 2, 3]
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
|
||||
- name: 'Create logs directory'
|
||||
run: 'mkdir -p evals/logs'
|
||||
|
||||
- name: 'Run Evals'
|
||||
env:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
RUN_EVALS: "${{ github.event.inputs.run_all != 'false' }}"
|
||||
run: 'npm run test:all_evals'
|
||||
|
||||
- name: 'Upload Logs'
|
||||
if: 'always()'
|
||||
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'eval-logs-${{ matrix.run_attempt }}'
|
||||
path: 'evals/logs'
|
||||
retention-days: 7
|
||||
|
||||
aggregate-results:
|
||||
name: 'Aggregate Results'
|
||||
needs: ['evals']
|
||||
if: 'always()'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
|
||||
- name: 'Download Logs'
|
||||
uses: 'actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806' # ratchet:actions/download-artifact@v4
|
||||
with:
|
||||
path: 'artifacts'
|
||||
|
||||
- name: 'Generate Summary'
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: 'node scripts/aggregate_evals.js artifacts >> "$GITHUB_STEP_SUMMARY"'
|
||||
@@ -101,7 +101,6 @@ jobs:
|
||||
"FIRESTORE_DATABASE_ID": "(default)",
|
||||
"GOOGLE_APPLICATION_CREDENTIALS": "${GOOGLE_APPLICATION_CREDENTIALS}"
|
||||
},
|
||||
"enabled": true,
|
||||
"timeout": 600000
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,9 +14,15 @@ on:
|
||||
description: 'issue number to triage'
|
||||
required: true
|
||||
type: 'number'
|
||||
workflow_call:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'issue number to triage'
|
||||
required: false
|
||||
type: 'string'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number }}'
|
||||
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number || inputs.issue_number }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -34,12 +40,11 @@ permissions:
|
||||
jobs:
|
||||
triage-issue:
|
||||
if: |-
|
||||
github.repository == 'google-gemini/gemini-cli' &&
|
||||
(github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli') &&
|
||||
(
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
(github.event_name == 'issues' || github.event_name == 'issue_comment') &&
|
||||
contains(github.event.issue.labels.*.name, 'status/need-triage') &&
|
||||
(github.event_name != 'issue_comment' || (
|
||||
contains(github.event.comment.body, '@gemini-cli /triage') &&
|
||||
(github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')
|
||||
@@ -58,10 +63,11 @@ jobs:
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
script: |
|
||||
const issueNumber = ${{ github.event.inputs.issue_number || inputs.issue_number }};
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: ${{ github.event.inputs.issue_number }},
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
core.setOutput('title', issue.title);
|
||||
core.setOutput('body', issue.body);
|
||||
@@ -72,14 +78,9 @@ jobs:
|
||||
if: |-
|
||||
github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
ISSUE_NUMBER_INPUT: '${{ github.event.inputs.issue_number }}'
|
||||
ISSUE_NUMBER_INPUT: '${{ github.event.inputs.issue_number || inputs.issue_number }}'
|
||||
LABELS: '${{ steps.get_issue_data.outputs.labels }}'
|
||||
run: |
|
||||
if ! echo "${LABELS}" | grep -q 'status/need-triage'; then
|
||||
echo "Issue #${ISSUE_NUMBER_INPUT} does not have the 'status/need-triage' label. Stopping workflow."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "${LABELS}" | grep -q 'area/'; then
|
||||
echo "Issue #${ISSUE_NUMBER_INPUT} already has 'area/' label. Stopping workflow."
|
||||
exit 1
|
||||
@@ -92,6 +93,9 @@ jobs:
|
||||
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
env:
|
||||
APP_ID: '${{ secrets.APP_ID }}'
|
||||
if: "${{ env.APP_ID != '' }}"
|
||||
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ secrets.APP_ID }}'
|
||||
@@ -102,7 +106,7 @@ jobs:
|
||||
id: 'get_labels'
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token }}'
|
||||
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
script: |-
|
||||
const { data: labels } = await github.rest.issues.listLabelsForRepo({
|
||||
owner: context.repo.owner,
|
||||
@@ -133,7 +137,7 @@ jobs:
|
||||
ISSUE_BODY: >-
|
||||
${{ github.event_name == 'workflow_dispatch' && steps.get_issue_data.outputs.body || github.event.issue.body }}
|
||||
ISSUE_NUMBER: >-
|
||||
${{ github.event_name == 'workflow_dispatch' && github.event.inputs.issue_number || github.event.issue.number }}
|
||||
${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.issue_number || inputs.issue_number) || github.event.issue.number }}
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
|
||||
with:
|
||||
@@ -259,7 +263,7 @@ jobs:
|
||||
LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}'
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token }}'
|
||||
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
script: |
|
||||
const rawOutput = process.env.LABELS_OUTPUT;
|
||||
core.info(`Raw output from model: ${rawOutput}`);
|
||||
@@ -325,7 +329,7 @@ jobs:
|
||||
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token }}'
|
||||
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
script: |-
|
||||
github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: 'Run Gemini PR size and complexity labeller'
|
||||
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # Use the specific commit SHA
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
||||
GH_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
||||
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
with:
|
||||
|
||||
@@ -81,7 +81,6 @@ jobs:
|
||||
"FIRESTORE_DATABASE_ID": "(default)",
|
||||
"GOOGLE_APPLICATION_CREDENTIALS": "${GOOGLE_APPLICATION_CREDENTIALS}"
|
||||
},
|
||||
"enabled": true,
|
||||
"timeout": 600000
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
name: '📋 Gemini Scheduled Issue Triage'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- 'opened'
|
||||
- 'reopened'
|
||||
schedule:
|
||||
- cron: '0 * * * *' # Runs every hour
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}'
|
||||
group: '${{ github.workflow }}-${{ github.event.number || github.run_id }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -35,7 +39,19 @@ jobs:
|
||||
private-key: '${{ secrets.PRIVATE_KEY }}'
|
||||
permission-issues: 'write'
|
||||
|
||||
- name: 'Get issue from event'
|
||||
if: "github.event_name == 'issues'"
|
||||
id: 'get_issue_from_event'
|
||||
env:
|
||||
ISSUE_EVENT: '${{ toJSON(github.event.issue) }}'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ISSUE_JSON=$(echo "$ISSUE_EVENT" | jq -c '[{number: .number, title: .title, body: .body}]')
|
||||
echo "issues_to_triage=${ISSUE_JSON}" >> "${GITHUB_OUTPUT}"
|
||||
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
|
||||
|
||||
- name: 'Find untriaged issues'
|
||||
if: "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"
|
||||
id: 'find_issues'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
||||
@@ -45,11 +61,11 @@ jobs:
|
||||
|
||||
echo '🔍 Finding issues without labels...'
|
||||
NO_LABEL_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search 'is:open is:issue no:label' --json number,title,body)"
|
||||
--search 'is:open is:issue no:label' --limit 10 --json number,title,body)"
|
||||
|
||||
echo '🏷️ Finding issues that need triage...'
|
||||
NEED_TRIAGE_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search "is:open is:issue label:\"status/need-triage\"" --limit 1000 --json number,title,body)"
|
||||
--search "is:open is:issue label:\"status/need-triage\"" --limit 10 --json number,title,body)"
|
||||
|
||||
echo '🔄 Merging and deduplicating issues...'
|
||||
ISSUES="$(echo "${NO_LABEL_ISSUES}" "${NEED_TRIAGE_ISSUES}" | jq -c -s 'add | unique_by(.number)')"
|
||||
@@ -77,12 +93,13 @@ jobs:
|
||||
|
||||
- name: 'Run Gemini Issue Analysis'
|
||||
if: |-
|
||||
${{ steps.find_issues.outputs.issues_to_triage != '[]' }}
|
||||
(steps.get_issue_from_event.outputs.issues_to_triage != '' && steps.get_issue_from_event.outputs.issues_to_triage != '[]') ||
|
||||
(steps.find_issues.outputs.issues_to_triage != '' && steps.find_issues.outputs.issues_to_triage != '[]')
|
||||
uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0
|
||||
id: 'gemini_issue_analysis'
|
||||
env:
|
||||
GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs
|
||||
ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}'
|
||||
ISSUES_TO_TRIAGE: '${{ steps.get_issue_from_event.outputs.issues_to_triage || steps.find_issues.outputs.issues_to_triage }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
|
||||
with:
|
||||
@@ -116,12 +133,12 @@ jobs:
|
||||
1. You are only able to use the echo command. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
|
||||
2. Check environment variable for issues to triage: $ISSUES_TO_TRIAGE (JSON array of issues)
|
||||
3. Review the issue title, body and any comments provided in the environment variables.
|
||||
4. Identify the most relevant labels from the existing labels, focusing on kind/*, area/*, sub-area/* and priority/*.
|
||||
5. If the issue already has area/ label, dont try to change it. Similarly, if the issue already has a kind/ label don't change it. And if the issue already has a priority/ label do not change it for example:
|
||||
If an issue has area/core and kind/bug you will only add a priority/ label.
|
||||
Instead if an issue has no labels, you will could add one lable of each kind.
|
||||
4. Identify the most relevant labels from the existing labels, focusing on kind/* and priority/*.
|
||||
5. If the issue already has a kind/ label don't change it. And if the issue already has a priority/ label do not change it for example:
|
||||
If an issue has kind/bug you will only add a priority/ label.
|
||||
Instead if an issue has no labels, you could add one label of each kind.
|
||||
6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc.
|
||||
7. For area/* and kind/* limit yourself to only the single most applicable label in each case.
|
||||
7. For kind/* limit yourself to only the single most applicable label.
|
||||
8. Give me a single short explanation about why you are selecting each label in the process.
|
||||
9. Output a JSON array of objects, each containing the issue number
|
||||
and the labels to add and remove, along with an explanation. For example:
|
||||
@@ -147,7 +164,7 @@ jobs:
|
||||
11. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
|
||||
- After identifying appropriate labels to an issue, add "status/need-triage" label to labels_to_remove in the output.
|
||||
12. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
|
||||
13. If you are uncertain and have not been able to apply one each of kind/, area/ and priority/ , apply the status/manual-triage label.
|
||||
13. If you are uncertain and have not been able to apply one each of kind/ and priority/ , apply the status/manual-triage label.
|
||||
|
||||
## Guidelines
|
||||
|
||||
@@ -157,9 +174,8 @@ jobs:
|
||||
- Do not add comments or modify the issue content.
|
||||
- Do not remove the following labels maintainer, help wanted or good first issue.
|
||||
- Triage only the current issue.
|
||||
- Identify only one area/ label
|
||||
- Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue)
|
||||
- Identify all applicable sub-area/* and priority/* labels based on the issue content. It's ok to have multiple of these.
|
||||
- Identify all applicable priority/* labels based on the issue content. It's ok to have multiple of these.
|
||||
- Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario.
|
||||
Categorization Guidelines:
|
||||
P0: Critical / Blocker
|
||||
@@ -206,51 +222,6 @@ jobs:
|
||||
- If users are talking about issues where the model gets downgraded from pro to flash then i want you to categorize that as a performance issue
|
||||
- This product is designed to use different models eg.. using pro, downgrading to flash etc.
|
||||
- When users report that they dont expect the model to change those would be categorized as feature requests.
|
||||
Definition of Areas
|
||||
area/ux:
|
||||
- Issues concerning user-facing elements like command usability, interactive features, help docs, and perceived performance.
|
||||
- I am seeing my screen flicker when using Gemini CLI
|
||||
- I am seeing the output malformed
|
||||
- Theme changes aren't taking effect
|
||||
- My keyboard inputs arent' being recognzied
|
||||
area/platform:
|
||||
- Issues related to installation, packaging, OS compatibility (Windows, macOS, Linux), and the underlying CLI framework.
|
||||
area/background: Issues related to long-running background tasks, daemons, and autonomous or proactive agent features.
|
||||
area/models:
|
||||
- i am not getting a response that is reasonable or expected. this can include things like
|
||||
- I am calling a tool and the tool is not performing as expected.
|
||||
- i am expecting a tool to be called and it is not getting called ,
|
||||
- Including experience when using
|
||||
- built-in tools (e.g., web search, code interpreter, read file, writefile, etc..),
|
||||
- Function calling issues should be under this area
|
||||
- i am getting responses from the model that are malformed.
|
||||
- Issues concerning Gemini quality of response and inference,
|
||||
- Issues talking about unnecessary token consumption.
|
||||
- Issues talking about Model getting stuck in a loop be watchful as this could be the root cause for issues that otherwise seem like model performance issues.
|
||||
- Memory compression
|
||||
- unexpected responses,
|
||||
- poor quality of generated code
|
||||
area/tools:
|
||||
- These are primarily issues related to Model Context Protocol
|
||||
- These are issues that mention MCP support
|
||||
- feature requests asking for support for new tools.
|
||||
area/core:
|
||||
- Issues with fundamental components like command parsing, configuration management, session state, and the main API client logic. Introducing multi-modality
|
||||
area/contribution:
|
||||
- Issues related to improving the developer contribution experience, such as CI/CD pipelines, build scripts, and test automation infrastructure.
|
||||
area/authentication:
|
||||
- Issues related to user identity, login flows, API key handling, credential storage, and access token management, unable to sign in selecting wrong authentication path etc..
|
||||
area/security-privacy:
|
||||
- Issues concerning vulnerability patching, dependency security, data sanitization, privacy controls, and preventing unauthorized data access.
|
||||
area/extensibility:
|
||||
- Issues related to the plugin system, extension APIs, or making the CLI's functionality available in other applications, github actions, ide support etc..
|
||||
area/performance:
|
||||
- Issues focused on model performance
|
||||
- Issues with running out of capacity,
|
||||
- 429 errors etc..
|
||||
- could also pertain to latency,
|
||||
- other general software performance like, memory usage, CPU consumption, and algorithmic efficiency.
|
||||
- Switching models from one to the other unexpectedly.
|
||||
|
||||
- name: 'Apply Labels to Issues'
|
||||
if: |-
|
||||
@@ -267,16 +238,23 @@ jobs:
|
||||
core.info(`Raw labels JSON: ${rawLabels}`);
|
||||
let parsedLabels;
|
||||
try {
|
||||
// First, try to parse the raw output as JSON.
|
||||
parsedLabels = JSON.parse(rawLabels.trim());
|
||||
} catch (jsonError) {
|
||||
// If that fails, check for a markdown code block.
|
||||
core.info(`Direct JSON parsing failed: ${jsonError.message}. Trying to extract from a markdown block.`);
|
||||
const jsonMatch = rawLabels.match(/```json\s*([\s\S]*?)\s*```/);
|
||||
if (!jsonMatch || !jsonMatch[1]) {
|
||||
throw new Error("Could not find a ```json ... ``` block in the output.");
|
||||
if (jsonMatch && jsonMatch[1]) {
|
||||
try {
|
||||
parsedLabels = JSON.parse(jsonMatch[1].trim());
|
||||
} catch (markdownError) {
|
||||
core.setFailed(`Failed to parse JSON even after extracting from markdown block: ${markdownError.message}\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
core.setFailed(`Output is not valid JSON and does not contain a JSON markdown block.\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
const jsonString = jsonMatch[1].trim();
|
||||
parsedLabels = JSON.parse(jsonString);
|
||||
core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`);
|
||||
} catch (err) {
|
||||
core.setFailed(`Failed to parse labels JSON from Gemini output: ${err.message}\nRaw output: ${rawLabels}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of parsedLabels) {
|
||||
|
||||
@@ -39,3 +39,7 @@ jobs:
|
||||
GITHUB_REPOSITORY: '${{ github.repository }}'
|
||||
run: |-
|
||||
./.github/scripts/pr-triage.sh
|
||||
# If prs_needing_comment is empty, set it to [] explicitly for downstream steps
|
||||
if [[ -z "$(grep 'prs_needing_comment' "${GITHUB_OUTPUT}" | cut -d'=' -f2-)" ]]; then
|
||||
echo "prs_needing_comment=[]" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
name: 'Label Child Issues for Project Rollup'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: ['opened', 'edited', 'reopened']
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
issues: 'write'
|
||||
steps:
|
||||
- name: 'Check for Parent Workstream and Apply Label'
|
||||
uses: 'actions/github-script@v7'
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const labelToAdd = 'workstream-rollup';
|
||||
|
||||
// --- Define the FULL URLs of the allowed parent workstreams ---
|
||||
const allowedParentUrls = [
|
||||
'https://api.github.com/repos/google-gemini/gemini-cli/issues/15374',
|
||||
'https://api.github.com/repos/google-gemini/gemini-cli/issues/15456',
|
||||
'https://api.github.com/repos/google-gemini/gemini-cli/issues/15324'
|
||||
];
|
||||
|
||||
// Check if the issue has a parent_issue_url and if it's in our allowed list.
|
||||
if (issue && issue.parent_issue_url && allowedParentUrls.includes(issue.parent_issue_url)) {
|
||||
console.log(`SUCCESS: Issue #${issue.number} is a child of a target workstream (${issue.parent_issue_url}). Adding label.`);
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [labelToAdd]
|
||||
});
|
||||
} else if (issue && issue.parent_issue_url) {
|
||||
console.log(`FAILURE: Issue #${issue.number} has a parent, but it's not a target workstream. Parent URL: ${issue.parent_issue_url}`);
|
||||
} else {
|
||||
console.log(`FAILURE: Issue #${issue.number} is not a child of any issue. No action taken.`);
|
||||
}
|
||||
@@ -12,6 +12,8 @@ on:
|
||||
|
||||
jobs:
|
||||
linkChecker:
|
||||
if: |-
|
||||
${{ github.repository == 'google-gemini/gemini-cli' }}
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
|
||||
@@ -31,6 +31,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: "github.repository == 'google-gemini/gemini-cli'"
|
||||
environment: "${{ github.event.inputs.environment || 'prod' }}"
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
.gemini/*
|
||||
!.gemini/config.yaml
|
||||
!.gemini/commands/
|
||||
!.gemini/skills/
|
||||
!.gemini/settings.json
|
||||
|
||||
# Note: .gemini-clipboard/ is NOT in gitignore so Gemini can access pasted images
|
||||
|
||||
@@ -55,3 +57,6 @@ gha-creds-*.json
|
||||
patch_output.log
|
||||
|
||||
.genkit
|
||||
.gemini-clipboard/
|
||||
.eslintcache
|
||||
evals/logs/
|
||||
|
||||
@@ -17,5 +17,6 @@ eslint.config.js
|
||||
**/generated
|
||||
gha-creds-*.json
|
||||
junit.xml
|
||||
.gemini-linters/
|
||||
Thumbs.db
|
||||
.pytest_cache
|
||||
|
||||
@@ -391,6 +391,15 @@ When working in the `/docs` directory, follow the guidelines in this section:
|
||||
Only write high-value comments if at all. Avoid talking to the user through
|
||||
comments.
|
||||
|
||||
## Logging and Error Handling
|
||||
|
||||
- **Avoid Console Statements:** Do not use `console.log`, `console.error`, or
|
||||
similar methods directly.
|
||||
- **Non-User-Facing Logs:** For developer-facing debug messages, use
|
||||
`debugLogger` (from `@google/gemini-cli-core`).
|
||||
- **User-Facing Feedback:** To surface errors or warnings to the user, use
|
||||
`coreEvents.emitFeedback` (from `@google/gemini-cli-core`).
|
||||
|
||||
## General requirements
|
||||
|
||||
- If there is something you do not understand or is ambiguous, seek confirmation
|
||||
|
||||
@@ -39,7 +39,7 @@ Learn all about Gemini CLI in our [documentation](https://geminicli.com/docs/).
|
||||
|
||||
```bash
|
||||
# Using npx (no installation required)
|
||||
npx https://github.com/google-gemini/gemini-cli
|
||||
npx @google/gemini-cli
|
||||
```
|
||||
|
||||
#### Install globally with npm
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -18,6 +18,63 @@ on GitHub.
|
||||
| [Preview](preview.md) | Experimental features ready for early feedback. |
|
||||
| [Stable](latest.md) | Stable, recommended for general use. |
|
||||
|
||||
## Announcements: v0.22.0 - 2025-12-22
|
||||
|
||||
- 🎉**Free Tier + Gemini 3:** Free tier users now all have access to Gemini 3
|
||||
Pro & Flash. Enable in `/settings` by toggling "Preview Features" to `true`.
|
||||
- 🎉**Gemini CLI + Colab:** Gemini CLI is now pre-installed. Can be used
|
||||
headlessly in notebook cells or interactively in the built-in terminal
|
||||
([pic](https://imgur.com/a/G0Tn7vi))
|
||||
- 🎉**Gemini CLI Extensions:**
|
||||
- **Conductor:** Planning++, Gemini works with you to build out a detailed
|
||||
plan, pull in extra details as needed, ultimately to give the LLM guardrails
|
||||
with artifacts. Measure twice, implement once!
|
||||
|
||||
`gemini extensions install https://github.com/gemini-cli-extensions/conductor`
|
||||
|
||||
Blog:
|
||||
[https://developers.googleblog.com/conductor-introducing-context-driven-development-for-gemini-cli/](https://developers.googleblog.com/conductor-introducing-context-driven-development-for-gemini-cli/)
|
||||
|
||||
- **Endor Labs:** Perform code analysis, vulnerability scanning, and
|
||||
dependency checks using natural language.
|
||||
|
||||
`gemini extensions install https://github.com/endorlabs/gemini-extension`
|
||||
|
||||
## Announcements: v0.21.0 - 2025-12-15
|
||||
|
||||
- **⚡️⚡️⚡️ Gemini 3 Flash + Gemini CLI:** Better, faster and cheaper than 2.5
|
||||
Pro - and in some scenarios better than 3 Pro! For paid tiers + free tier
|
||||
users who were on the wait list enable **Preview Features** in `/settings.`
|
||||
- For more information:
|
||||
[Gemini 3 Flash is now available in Gemini CLI](https://developers.googleblog.com/gemini-3-flash-is-now-available-in-gemini-cli/).
|
||||
- 🎉 Gemini CLI Extensions:
|
||||
- Rill: Utilize natural language to analyze Rill data, enabling the
|
||||
exploration of metrics and trends without the need for manual queries.
|
||||
`gemini extensions install https://github.com/rilldata/rill-gemini-extension`
|
||||
- Browserbase: Interact with web pages, take screenshots, extract information,
|
||||
and perform automated actions with atomic precision.
|
||||
`gemini extensions install https://github.com/browserbase/mcp-server-browserbase`
|
||||
- Quota Visibility: The `/stats` command now displays quota information for all
|
||||
available models, including those not used in the current session. (@sehoon38)
|
||||
- Fuzzy Setting Search: Users can now quickly find settings using fuzzy search
|
||||
within the settings dialog. (@sehoon38)
|
||||
- MCP Resource Support: Users can now discover, view, and search through
|
||||
resources using the @ command. (@MrLesk)
|
||||
- Auto-execute Simple Slash Commands: Simple slash commands are now executed
|
||||
immediately on enter. (@jackwotherspoon)
|
||||
|
||||
## Announcements: v0.20.0 - 2025-12-01
|
||||
|
||||
- **Multi-file Drag & Drop:** Users can now drag and drop multiple files into
|
||||
the terminal, and the CLI will automatically prefix each valid path with `@`.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/14832) by
|
||||
[@jackwotherspoon](https://github.com/jackwotherspoon))
|
||||
- **Persistent "Always Allow" Policies:** Users can now save "Always Allow"
|
||||
decisions for tool executions, with granular control over specific shell
|
||||
commands and multi-cloud platform tools.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/14737) by
|
||||
[@allenhutchison](https://github.com/allenhutchison))
|
||||
|
||||
## Announcements: v0.19.0 - 2025-11-24
|
||||
|
||||
- 🎉 **New extensions:**
|
||||
@@ -54,8 +111,7 @@ on GitHub.
|
||||
- **Experimental permission improvements:** We are now experimenting with a new
|
||||
policy engine in Gemini CLI. This allows users and administrators to create
|
||||
fine-grained policy for tool calls. Currently behind a flag. See
|
||||
[https://geminicli.com/docs/core/policy-engine/](../core/policy-engine.md) for
|
||||
more information.
|
||||
[policy engine documentation](../core/policy-engine.md) for more information.
|
||||
- Blog:
|
||||
[https://allen.hutchison.org/2025/11/26/the-guardrails-of-autonomy/](https://allen.hutchison.org/2025/11/26/the-guardrails-of-autonomy/)
|
||||
- **Gemini 3 support for paid:** Gemini 3 support has been rolled out to all API
|
||||
|
||||
+137
-138
@@ -1,6 +1,6 @@
|
||||
# Latest stable release: v0.19.0 - v0.19.4
|
||||
# Latest stable release: v0.22.0 - v0.22.5
|
||||
|
||||
Released: December 1, 2025
|
||||
Released: December 30, 2025
|
||||
|
||||
For most users, our latest stable release is the recommended release. Install
|
||||
the latest stable version with:
|
||||
@@ -11,144 +11,143 @@ npm install -g @google/gemini-cli
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Zed integration:** Users can now leverage Gemini 3 within the Zed
|
||||
integration after enabling "Preview Features" in their CLI’s `/settings`.
|
||||
- **Interactive shell:**
|
||||
- **Click-to-Focus:** Go to `/settings` and enable **Use Alternate Buffer** to
|
||||
click within the embedded shell output to focus it for input.
|
||||
- **Loading phrase:** Clearly indicates when the interactive shell is awaiting
|
||||
user input. ([vid](https://imgur.com/a/kjK8bUK),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/12535) by
|
||||
[@jackwotherspoon](https://github.com/jackwotherspoon))
|
||||
- **Comprehensive quota visibility:** View usage statistics for all available
|
||||
models in the `/stats` command, even those not yet used in your current
|
||||
session. ([pic](https://imgur.com/a/cKyDtYh),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/14764) by
|
||||
[@sehoon38](https://github.com/sehoon38))
|
||||
- **Polished CLI statistics:** We’ve cleaned up the `/stats` view to prioritize
|
||||
actionable quota information while providing a detailed token and
|
||||
cache-efficiency breakdown in `/stats model`
|
||||
([login with Google](https://imgur.com/a/w9xKthm),
|
||||
[api key](https://imgur.com/a/FjQPHOY),
|
||||
[model stats](https://imgur.com/a/VfWzVgw),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/14961) by
|
||||
[@jacob314](https://github.com/jacob314))
|
||||
- **Multi-file drag & drop:** Multi-file drag & drop is now supported and
|
||||
properly translated to be prefixed with `@`.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/14832) by
|
||||
[@jackwotherspoon](https://github.com/jackwotherspoon))
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Use lenient MCP output schema validator by @cornmander in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13521
|
||||
- Update persistence state to track counts of messages instead of times banner
|
||||
has been displayed by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13428
|
||||
- update docs for http proxy by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13538
|
||||
- move stdio by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13528
|
||||
- chore(release): bump version to 0.19.0-nightly.20251120.8e531dc02 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13540
|
||||
- Skip pre-commit hooks for shadow repo (#13331) by @vishvananda in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13488
|
||||
- fix(ui): Correct mouse click cursor positioning for wide characters by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/13537
|
||||
- fix(core): correct bash @P prompt transformation detection by @pyrytakala in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13544
|
||||
- Optimize and improve test coverage for cli/src/config by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13485
|
||||
- Improve code coverage for cli/src/ui/privacy package by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13493
|
||||
- docs: fix typos in source code and documentation by @fancive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13577
|
||||
- Improved code coverage for cli/src/zed-integration by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13570
|
||||
- feat(ui): build interactive session browser component by @bl-ue in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13351
|
||||
- Fix multiple bugs with auth flow including using the implemented but unused
|
||||
restart support. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13565
|
||||
- feat(core): add modelAvailabilityService for managing and tracking model
|
||||
health by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13426
|
||||
- docs: fix grammar typo "a MCP" to "an MCP" by @noahacgn in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13595
|
||||
- feat: custom loading phrase when interactive shell requires input by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/12535
|
||||
- docs: Update uninstall command to reflect multiple extension support by
|
||||
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/13582
|
||||
- bug(core): Ensure we use thinking budget on fallback to 2.5 by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13596
|
||||
- Remove useModelRouter experimental flag by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13593
|
||||
- feat(docs): Ensure multiline JS objects are rendered properly. by @joshualitt
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13535
|
||||
- Fix exp id logging by @owenofbrien in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13430
|
||||
- Moved client id logging into createBasicLogEvent by @owenofbrien in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13607
|
||||
- Restore bracketed paste mode after external editor exit by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13606
|
||||
- feat(core): Add support for custom aliases for model configs. by @joshualitt
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13546
|
||||
- feat(core): Add `BaseLlmClient.generateContent`. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13591
|
||||
- Turn off alternate buffer mode by default. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13623
|
||||
- fix(cli): Prevent stdout/stderr patching for extension commands by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13600
|
||||
- Improve test coverage for cli/src/ui/components by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13598
|
||||
- Update ink version to 6.4.6 by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13631
|
||||
- chore/release: bump version to 0.19.0-nightly.20251122.42c2e1b21 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13637
|
||||
- chore/release: bump version to 0.19.0-nightly.20251123.dadd606c0 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13675
|
||||
- chore/release: bump version to 0.19.0-nightly.20251124.e177314a4 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13713
|
||||
- fix(core): Fix context window overflow warning for PDF files by @kkitase in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13548
|
||||
- feat :rephrasing the extension logging messages to run the explore command
|
||||
when there are no extensions installed by @JayadityaGit in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13740
|
||||
- Improve code coverage for cli package by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13724
|
||||
- Add session subtask in /stats command by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13750
|
||||
- feat(core): Migrate chatCompressionService to model configs. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12863
|
||||
- feat(hooks): Hook Telemetry Infrastructure by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9082
|
||||
- fix: (some minor improvements to configs and getPackageJson return behaviour)
|
||||
by @grMLEqomlkkU5Eeinz4brIrOVCUCkJuN in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12510
|
||||
- feat(hooks): Hook Event Handling by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9097
|
||||
- feat(hooks): Hook Agent Lifecycle Integration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9105
|
||||
- feat(core): Land bool for alternate system prompt. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13764
|
||||
- bug(core): Add default chat compression config. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13766
|
||||
- feat(model-availability): introduce ModelPolicy and PolicyCatalog by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/13751
|
||||
- feat(hooks): Hook System Orchestration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9102
|
||||
- feat(config): add isModelAvailabilityServiceEnabled setting by @adamfweidman
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13777
|
||||
- chore/release: bump version to 0.19.0-nightly.20251125.f6d97d448 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13782
|
||||
- chore: remove console.error by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13779
|
||||
- fix: Add $schema property to settings.schema.json by @sacrosanctic in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12763
|
||||
- fix(cli): allow non-GitHub SCP-styled URLs for extension installation by @m0ps
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13800
|
||||
- fix(resume): allow passing a prompt via stdin while resuming using --resume by
|
||||
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13520
|
||||
- feat(sessions): add /resume slash command to open the session browser by
|
||||
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13621
|
||||
- docs(sessions): add documentation for chat recording and session management by
|
||||
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13667
|
||||
- Fix TypeError: "URL.parse is not a function" for Node.js < v22 by @macarronesc
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13698
|
||||
- fallback to flash for TerminalQuota errors by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13791
|
||||
- Update Code Wiki README badge by @PatoBeltran in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13768
|
||||
- Add Databricks auth support and custom header option to gemini cli by
|
||||
@AarushiShah in https://github.com/google-gemini/gemini-cli/pull/11893
|
||||
- Update dependency for modelcontextprotocol/sdk to 1.23.0 by @bbiggs in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13827
|
||||
- fix(patch): cherry-pick 576fda1 to release/v0.19.0-preview.0-pr-14099
|
||||
[CONFLICTS] by @gemini-cli-robot in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14402
|
||||
- feat(ide): fallback to GEMINI_CLI_IDE_AUTH_TOKEN env var by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14843
|
||||
- feat: display quota stats for unused models in /stats by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14764
|
||||
- feat: ensure codebase investigator uses preview model when main agent does by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14412
|
||||
- chore: add closing reason to stale bug workflow by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14861
|
||||
- Send the model and CLI version with the user agent by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14865
|
||||
- refactor(sessions): move session summary generation to startup by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/14691
|
||||
- Limit search depth in path corrector by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14869
|
||||
- Fix: Correct typo in code comment by @kuishou68 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14671
|
||||
- feat(core): Plumbing for late resolution of model configs. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14597
|
||||
- feat: attempt more error parsing by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14899
|
||||
- Add missing await. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14910
|
||||
- feat(core): Add support for transcript_path in hooks for git-ai/Gemini
|
||||
extension by @svarlamov in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14663
|
||||
- refactor: implement DelegateToAgentTool with discriminated union by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14769
|
||||
- feat: reset availabilityService on /auth by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14911
|
||||
- chore/release: bump version to 0.21.0-nightly.20251211.8c83e1ea9 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14924
|
||||
- Fix: Correctly detect MCP tool errors by @kevin-ramdass in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14937
|
||||
- increase labeler timeout by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14922
|
||||
- tool(cli): tweak the frontend tool to be aware of more core files from the cli
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/14962
|
||||
- feat(cli): polish cached token stats and simplify stats display when quota is
|
||||
present. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14961
|
||||
- feat(settings-validation): add validation for settings schema by @lifefloating
|
||||
in https://github.com/google-gemini/gemini-cli/pull/12929
|
||||
- fix(ide): Update IDE extension to write auth token in env var by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14999
|
||||
- Revert "chore(deps): bump express from 5.1.0 to 5.2.0" by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14998
|
||||
- feat(a2a): Introduce /init command for a2a server by @cocosheng-g in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13419
|
||||
- feat: support multi-file drag and drop of images by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14832
|
||||
- fix(policy): allow codebase_investigator by default in read-only policy by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15000
|
||||
- refactor(ide ext): Update port file name + switch to 1-based index for
|
||||
characters + remove truncation text by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/10501
|
||||
- fix(vscode-ide-companion): correct license generation for workspace
|
||||
dependencies by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15004
|
||||
- fix: temp fix for subagent invocation until subagent delegation is merged to
|
||||
stable by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15007
|
||||
- test: update ide detection tests to make them more robust when run in an ide
|
||||
by @kevin-ramdass in https://github.com/google-gemini/gemini-cli/pull/15008
|
||||
- Remove flex from stats display. See snapshots for diffs. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14983
|
||||
- Add license field into package.json by @jb-perez in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14473
|
||||
- feat: Persistent "Always Allow" policies with granular shell & MCP support by
|
||||
@allenhutchison in https://github.com/google-gemini/gemini-cli/pull/14737
|
||||
- chore/release: bump version to 0.21.0-nightly.20251212.54de67536 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14969
|
||||
- fix(core): commandPrefix word boundary and compound command safety by
|
||||
@allenhutchison in https://github.com/google-gemini/gemini-cli/pull/15006
|
||||
- chore(docs): add 'Maintainers only' label info to CONTRIBUTING.md by @jacob314
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14914
|
||||
- Refresh hooks when refreshing extensions. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14918
|
||||
- Add clarity to error messages by @gsehgal in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14879
|
||||
- chore : remove a redundant tip by @JayadityaGit in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14947
|
||||
- chore/release: bump version to 0.21.0-nightly.20251213.977248e09 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15029
|
||||
- Disallow redundant typecasts. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15030
|
||||
- fix(auth): prioritize GEMINI_API_KEY env var and skip unnecessary key… by
|
||||
@galz10 in https://github.com/google-gemini/gemini-cli/pull/14745
|
||||
- fix: use zod for safety check result validation by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15026
|
||||
- update(telemetry): add hashed_extension_name to field to extension events by
|
||||
@kiranani in https://github.com/google-gemini/gemini-cli/pull/15025
|
||||
- fix: similar to policy-engine, throw error in case of requiring tool execution
|
||||
confirmation for non-interactive mode by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14702
|
||||
- Clean up processes in integration tests by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15102
|
||||
- docs: update policy engine getting started and defaults by @NTaylorMullen in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15105
|
||||
- Fix tool output fragmentation by encapsulating content in functionResponse by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/13082
|
||||
- Simplify method signature. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15114
|
||||
- Show raw input token counts in json output. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15021
|
||||
- fix: Mark A2A requests as interactive by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15108
|
||||
- use previewFeatures to determine which pro model to use for A2A by @sehoon38
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15131
|
||||
- refactor(cli): fix settings merging so that settings using the new json format
|
||||
take priority over ones using the old format by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15116
|
||||
- fix(patch): cherry-pick a6d1245 to release/v0.22.0-preview.1-pr-15214 to patch
|
||||
version v0.22.0-preview.1 and create version 0.22.0-preview.2 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15226
|
||||
- fix(patch): cherry-pick 9e6914d to release/v0.22.0-preview.2-pr-15288 to patch
|
||||
version v0.22.0-preview.2 and create version 0.22.0-preview.3 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15294
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.18.4...v0.19.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.21.3...v0.22.0
|
||||
|
||||
+117
-127
@@ -1,6 +1,6 @@
|
||||
# Preview release: Release v0.19.0-preview.0
|
||||
# Preview release: Release v0.23.0-preview.0
|
||||
|
||||
Released: November 25, 2025
|
||||
Released: December 22, 2025
|
||||
|
||||
Our preview release includes the latest, new, and experimental features. This
|
||||
release may not be as stable as our [latest weekly release](latest.md).
|
||||
@@ -11,131 +11,121 @@ To install the preview release:
|
||||
npm install -g @google/gemini-cli@preview
|
||||
```
|
||||
|
||||
## What's changed
|
||||
## What's Changed
|
||||
|
||||
- Use lenient MCP output schema validator by @cornmander in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13521
|
||||
- Update persistence state to track counts of messages instead of times banner
|
||||
has been displayed by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13428
|
||||
- update docs for http proxy by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13538
|
||||
- move stdio by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13528
|
||||
- chore(release): bump version to 0.19.0-nightly.20251120.8e531dc02 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13540
|
||||
- Skip pre-commit hooks for shadow repo (#13331) by @vishvananda in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13488
|
||||
- fix(ui): Correct mouse click cursor positioning for wide characters by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/13537
|
||||
- fix(core): correct bash @P prompt transformation detection by @pyrytakala in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13544
|
||||
- Optimize and improve test coverage for cli/src/config by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13485
|
||||
- Improve code coverage for cli/src/ui/privacy package by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13493
|
||||
- docs: fix typos in source code and documentation by @fancive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13577
|
||||
- Improved code coverage for cli/src/zed-integration by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13570
|
||||
- feat(ui): build interactive session browser component by @bl-ue in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13351
|
||||
- Fix multiple bugs with auth flow including using the implemented but unused
|
||||
restart support. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13565
|
||||
- feat(core): add modelAvailabilityService for managing and tracking model
|
||||
health by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13426
|
||||
- docs: fix grammar typo "a MCP" to "an MCP" by @noahacgn in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13595
|
||||
- feat: custom loading phrase when interactive shell requires input by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/12535
|
||||
- docs: Update uninstall command to reflect multiple extension support by
|
||||
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/13582
|
||||
- bug(core): Ensure we use thinking budget on fallback to 2.5 by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13596
|
||||
- Remove useModelRouter experimental flag by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13593
|
||||
- feat(docs): Ensure multiline JS objects are rendered properly. by @joshualitt
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13535
|
||||
- Fix exp id logging by @owenofbrien in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13430
|
||||
- Moved client id logging into createBasicLogEvent by @owenofbrien in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13607
|
||||
- Restore bracketed paste mode after external editor exit by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13606
|
||||
- feat(core): Add support for custom aliases for model configs. by @joshualitt
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13546
|
||||
- feat(core): Add `BaseLlmClient.generateContent`. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13591
|
||||
- Turn off alternate buffer mode by default. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13623
|
||||
- fix(cli): Prevent stdout/stderr patching for extension commands by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13600
|
||||
- Improve test coverage for cli/src/ui/components by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13598
|
||||
- Update ink version to 6.4.6 by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13631
|
||||
- chore/release: bump version to 0.19.0-nightly.20251122.42c2e1b21 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13637
|
||||
- chore/release: bump version to 0.19.0-nightly.20251123.dadd606c0 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13675
|
||||
- chore/release: bump version to 0.19.0-nightly.20251124.e177314a4 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13713
|
||||
- fix(core): Fix context window overflow warning for PDF files by @kkitase in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13548
|
||||
- feat :rephrasing the extension logging messages to run the explore command
|
||||
when there are no extensions installed by @JayadityaGit in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13740
|
||||
- Improve code coverage for cli package by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13724
|
||||
- Add session subtask in /stats command by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13750
|
||||
- feat(core): Migrate chatCompressionService to model configs. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12863
|
||||
- feat(hooks): Hook Telemetry Infrastructure by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9082
|
||||
- fix: (some minor improvements to configs and getPackageJson return behaviour)
|
||||
by @grMLEqomlkkU5Eeinz4brIrOVCUCkJuN in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12510
|
||||
- feat(hooks): Hook Event Handling by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9097
|
||||
- feat(hooks): Hook Agent Lifecycle Integration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9105
|
||||
- feat(core): Land bool for alternate system prompt. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13764
|
||||
- bug(core): Add default chat compression config. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13766
|
||||
- feat(model-availability): introduce ModelPolicy and PolicyCatalog by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/13751
|
||||
- feat(hooks): Hook System Orchestration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9102
|
||||
- feat(config): add isModelAvailabilityServiceEnabled setting by @adamfweidman
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13777
|
||||
- chore/release: bump version to 0.19.0-nightly.20251125.f6d97d448 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13782
|
||||
- chore: remove console.error by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13779
|
||||
- fix: Add $schema property to settings.schema.json by @sacrosanctic in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12763
|
||||
- fix(cli): allow non-GitHub SCP-styled URLs for extension installation by @m0ps
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13800
|
||||
- fix(resume): allow passing a prompt via stdin while resuming using --resume by
|
||||
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13520
|
||||
- feat(sessions): add /resume slash command to open the session browser by
|
||||
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13621
|
||||
- docs(sessions): add documentation for chat recording and session management by
|
||||
@bl-ue in https://github.com/google-gemini/gemini-cli/pull/13667
|
||||
- Fix TypeError: "URL.parse is not a function" for Node.js < v22 by @macarronesc
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13698
|
||||
- fallback to flash for TerminalQuota errors by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13791
|
||||
- Update Code Wiki README badge by @PatoBeltran in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13768
|
||||
- Add Databricks auth support and custom header option to gemini cli by
|
||||
@AarushiShah in https://github.com/google-gemini/gemini-cli/pull/11893
|
||||
- Update dependency for modelcontextprotocol/sdk to 1.23.0 by @bbiggs in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13827
|
||||
- Code assist service metrics. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15024
|
||||
- chore/release: bump version to 0.21.0-nightly.20251216.bb0c0d8ee by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15121
|
||||
- Docs by @Roaimkhan in https://github.com/google-gemini/gemini-cli/pull/15103
|
||||
- Use official ACP SDK and support HTTP/SSE based MCP servers by @SteffenDE in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13856
|
||||
- Remove foreground for themes other than shades of purple and holiday. by
|
||||
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/14606
|
||||
- chore: remove repo specific tips by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15164
|
||||
- chore: remove user query from footer in debug mode by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15169
|
||||
- Disallow unnecessary awaits. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15172
|
||||
- Add one to the padding in settings dialog to avoid flicker. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15173
|
||||
- feat(core): introduce remote agent infrastructure and rename local executor by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15110
|
||||
- feat(cli): Add `/auth logout` command to clear credentials and auth state by
|
||||
@CN-Scars in https://github.com/google-gemini/gemini-cli/pull/13383
|
||||
- (fix) Automated pr labeler by @DaanVersavel in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14885
|
||||
- feat: launch Gemini 3 Flash in Gemini CLI ⚡️⚡️⚡️ by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15196
|
||||
- Refactor: Migrate console.error in ripGrep.ts to debugLogger by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15201
|
||||
- chore: update a2a-js to 0.3.7 by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15197
|
||||
- chore(core): remove redundant isModelAvailabilityServiceEnabled toggle and
|
||||
clean up dead code by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15207
|
||||
- feat(core): Late resolve `GenerateContentConfig`s and reduce mutation. by
|
||||
@joshualitt in https://github.com/google-gemini/gemini-cli/pull/14920
|
||||
- Respect previewFeatures value from the remote flag if undefined by @sehoon38
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15214
|
||||
- feat(ui): add Windows clipboard image support and Alt+V paste workaround by
|
||||
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/15218
|
||||
- chore(core): remove legacy fallback flags and migrate loop detection by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15213
|
||||
- fix(ui): Prevent eager slash command completion hiding sibling commands by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15224
|
||||
- Docs: Update Changelog for Dec 17, 2025 by @jkcinouye in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15204
|
||||
- Code Assist backend telemetry for user accept/reject of suggestions by
|
||||
@gundermanc in https://github.com/google-gemini/gemini-cli/pull/15206
|
||||
- fix(cli): correct initial history length handling for chat commands by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15223
|
||||
- chore/release: bump version to 0.21.0-nightly.20251218.739c02bd6 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15231
|
||||
- Change detailed model stats to use a new shared Table class to resolve
|
||||
robustness issues. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15208
|
||||
- feat: add agent toml parser by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15112
|
||||
- Add core tool that adds all context from the core package. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15238
|
||||
- (docs): Add reference section to hooks documentation by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15159
|
||||
- feat(hooks): add support for friendly names and descriptions by @abhipatel12
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15174
|
||||
- feat: Detect background color by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15132
|
||||
- add 3.0 to allowed sensitive keywords by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15276
|
||||
- feat: Pass additional environment variables to shell execution by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15160
|
||||
- Remove unused code by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15290
|
||||
- Handle all 429 as retryableQuotaError by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15288
|
||||
- Remove unnecessary dependencies by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15291
|
||||
- fix: prevent infinite loop in prompt completion on error by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14548
|
||||
- fix(ui): show command suggestions even on perfect match and sort them by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15287
|
||||
- feat(hooks): reduce log verbosity and improve error reporting in UI by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15297
|
||||
- feat: simplify tool confirmation labels for better UX by @NTaylorMullen in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15296
|
||||
- chore/release: bump version to 0.21.0-nightly.20251219.70696e364 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15301
|
||||
- feat(core): Implement JIT context memory loading and UI sync by @SandyTao520
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14469
|
||||
- feat(ui): Put "Allow for all future sessions" behind a setting off by default.
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/15322
|
||||
- fix(cli):change the placeholder of input during the shell mode by
|
||||
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/15135
|
||||
- Validate OAuth resource parameter matches MCP server URL by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15289
|
||||
- docs(cli): add System Prompt Override (GEMINI_SYSTEM_MD) by @ashmod in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9515
|
||||
- more robust command parsing logs by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15339
|
||||
- Introspection agent demo by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15232
|
||||
- fix(core): sanitize hook command expansion and prevent injection by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15343
|
||||
- fix(folder trust): add validation for trusted folder level by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12215
|
||||
- fix(cli): fix right border overflow in trust dialogs by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15350
|
||||
- fix(policy): fix bug where accepting-edits continued after it was turned off
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/15351
|
||||
- fix: prevent infinite relaunch loop when --resume fails (#14941) by @Ying-xi
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14951
|
||||
- chore/release: bump version to 0.21.0-nightly.20251220.41a1a3eed by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15352
|
||||
- feat(telemetry): add clearcut logging for hooks by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15405
|
||||
- fix(core): Add `.geminiignore` support to SearchText tool by @xyrolle in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13763
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.18.0-preview.4...v0.19.0-preview.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.22.0-preview.3...v0.23.0-preview.0
|
||||
|
||||
+741
-9
@@ -12,13 +12,745 @@ on GitHub.
|
||||
|
||||
## Current Releases
|
||||
|
||||
| Release channel | Notes |
|
||||
| :----------------------------------------- | :---------------------------------------------- |
|
||||
| Nightly | Nightly release with the most recent changes. |
|
||||
| [Preview](#release-v0190-preview0-preview) | Experimental features ready for early feedback. |
|
||||
| [Latest](#release-v0190---v0194-latest) | Stable, recommended for general use. |
|
||||
| Release channel | Notes |
|
||||
| :---------------------------------------- | :---------------------------------------------- |
|
||||
| Nightly | Nightly release with the most recent changes. |
|
||||
| [Preview](#release-v0230-preview-preview) | Experimental features ready for early feedback. |
|
||||
| [Latest](#release-v0220---v0225-latest) | Stable, recommended for general use. |
|
||||
|
||||
## Release v0.19.0 - v0.19.4 (Latest)
|
||||
## Release v0.23.0-preview (Preview)
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Code assist service metrics. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15024
|
||||
- chore/release: bump version to 0.21.0-nightly.20251216.bb0c0d8ee by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15121
|
||||
- Docs by @Roaimkhan in https://github.com/google-gemini/gemini-cli/pull/15103
|
||||
- Use official ACP SDK and support HTTP/SSE based MCP servers by @SteffenDE in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13856
|
||||
- Remove foreground for themes other than shades of purple and holiday. by
|
||||
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/14606
|
||||
- chore: remove repo specific tips by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15164
|
||||
- chore: remove user query from footer in debug mode by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15169
|
||||
- Disallow unnecessary awaits. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15172
|
||||
- Add one to the padding in settings dialog to avoid flicker. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15173
|
||||
- feat(core): introduce remote agent infrastructure and rename local executor by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15110
|
||||
- feat(cli): Add `/auth logout` command to clear credentials and auth state by
|
||||
@CN-Scars in https://github.com/google-gemini/gemini-cli/pull/13383
|
||||
- (fix) Automated pr labeler by @DaanVersavel in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14885
|
||||
- feat: launch Gemini 3 Flash in Gemini CLI ⚡️⚡️⚡️ by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15196
|
||||
- Refactor: Migrate console.error in ripGrep.ts to debugLogger by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15201
|
||||
- chore: update a2a-js to 0.3.7 by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15197
|
||||
- chore(core): remove redundant isModelAvailabilityServiceEnabled toggle and
|
||||
clean up dead code by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15207
|
||||
- feat(core): Late resolve `GenerateContentConfig`s and reduce mutation. by
|
||||
@joshualitt in https://github.com/google-gemini/gemini-cli/pull/14920
|
||||
- Respect previewFeatures value from the remote flag if undefined by @sehoon38
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15214
|
||||
- feat(ui): add Windows clipboard image support and Alt+V paste workaround by
|
||||
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/15218
|
||||
- chore(core): remove legacy fallback flags and migrate loop detection by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/15213
|
||||
- fix(ui): Prevent eager slash command completion hiding sibling commands by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15224
|
||||
- Docs: Update Changelog for Dec 17, 2025 by @jkcinouye in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15204
|
||||
- Code Assist backend telemetry for user accept/reject of suggestions by
|
||||
@gundermanc in https://github.com/google-gemini/gemini-cli/pull/15206
|
||||
- fix(cli): correct initial history length handling for chat commands by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15223
|
||||
- chore/release: bump version to 0.21.0-nightly.20251218.739c02bd6 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15231
|
||||
- Change detailed model stats to use a new shared Table class to resolve
|
||||
robustness issues. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15208
|
||||
- feat: add agent toml parser by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15112
|
||||
- Add core tool that adds all context from the core package. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15238
|
||||
- (docs): Add reference section to hooks documentation by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15159
|
||||
- feat(hooks): add support for friendly names and descriptions by @abhipatel12
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15174
|
||||
- feat: Detect background color by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15132
|
||||
- add 3.0 to allowed sensitive keywords by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15276
|
||||
- feat: Pass additional environment variables to shell execution by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15160
|
||||
- Remove unused code by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15290
|
||||
- Handle all 429 as retryableQuotaError by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15288
|
||||
- Remove unnecessary dependencies by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15291
|
||||
- fix: prevent infinite loop in prompt completion on error by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14548
|
||||
- fix(ui): show command suggestions even on perfect match and sort them by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15287
|
||||
- feat(hooks): reduce log verbosity and improve error reporting in UI by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15297
|
||||
- feat: simplify tool confirmation labels for better UX by @NTaylorMullen in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15296
|
||||
- chore/release: bump version to 0.21.0-nightly.20251219.70696e364 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15301
|
||||
- feat(core): Implement JIT context memory loading and UI sync by @SandyTao520
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14469
|
||||
- feat(ui): Put "Allow for all future sessions" behind a setting off by default.
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/15322
|
||||
- fix(cli):change the placeholder of input during the shell mode by
|
||||
@JayadityaGit in https://github.com/google-gemini/gemini-cli/pull/15135
|
||||
- Validate OAuth resource parameter matches MCP server URL by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15289
|
||||
- docs(cli): add System Prompt Override (GEMINI_SYSTEM_MD) by @ashmod in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9515
|
||||
- more robust command parsing logs by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15339
|
||||
- Introspection agent demo by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15232
|
||||
- fix(core): sanitize hook command expansion and prevent injection by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/15343
|
||||
- fix(folder trust): add validation for trusted folder level by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12215
|
||||
- fix(cli): fix right border overflow in trust dialogs by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15350
|
||||
- fix(policy): fix bug where accepting-edits continued after it was turned off
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/15351
|
||||
- fix: prevent infinite relaunch loop when --resume fails (#14941) by @Ying-xi
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14951
|
||||
- chore/release: bump version to 0.21.0-nightly.20251220.41a1a3eed by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15352
|
||||
- feat(telemetry): add clearcut logging for hooks by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15405
|
||||
- fix(core): Add `.geminiignore` support to SearchText tool by @xyrolle in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13763
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.22.0-preview.3...v0.23.0-preview.0
|
||||
|
||||
## Release v0.22.0 - v0.22.5 (Latest)
|
||||
|
||||
### Highlights
|
||||
|
||||
- **Comprehensive quota visibility:** View usage statistics for all available
|
||||
models in the `/stats` command, even those not yet used in your current
|
||||
session. ([pic](https://imgur.com/a/cKyDtYh),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/14764) by
|
||||
[@sehoon38](https://github.com/sehoon38))
|
||||
- **Polished CLI statistics:** We’ve cleaned up the `/stats` view to prioritize
|
||||
actionable quota information while providing a detailed token and
|
||||
cache-efficiency breakdown in `/stats model`
|
||||
([login with Google](https://imgur.com/a/w9xKthm),
|
||||
[api key](https://imgur.com/a/FjQPHOY),
|
||||
[model stats](https://imgur.com/a/VfWzVgw),
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/14961) by
|
||||
[@jacob314](https://github.com/jacob314))
|
||||
- **Multi-file drag & drop:** Multi-file drag & drop is now supported and
|
||||
properly translated to be prefixed with `@`.
|
||||
([pr](https://github.com/google-gemini/gemini-cli/pull/14832) by
|
||||
[@jackwotherspoon](https://github.com/jackwotherspoon))
|
||||
|
||||
### What's Changed
|
||||
|
||||
- feat(ide): fallback to GEMINI_CLI_IDE_AUTH_TOKEN env var by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14843
|
||||
- feat: display quota stats for unused models in /stats by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14764
|
||||
- feat: ensure codebase investigator uses preview model when main agent does by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14412
|
||||
- chore: add closing reason to stale bug workflow by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14861
|
||||
- Send the model and CLI version with the user agent by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14865
|
||||
- refactor(sessions): move session summary generation to startup by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/14691
|
||||
- Limit search depth in path corrector by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14869
|
||||
- Fix: Correct typo in code comment by @kuishou68 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14671
|
||||
- feat(core): Plumbing for late resolution of model configs. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14597
|
||||
- feat: attempt more error parsing by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14899
|
||||
- Add missing await. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14910
|
||||
- feat(core): Add support for transcript_path in hooks for git-ai/Gemini
|
||||
extension by @svarlamov in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14663
|
||||
- refactor: implement DelegateToAgentTool with discriminated union by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14769
|
||||
- feat: reset availabilityService on /auth by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14911
|
||||
- chore/release: bump version to 0.21.0-nightly.20251211.8c83e1ea9 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14924
|
||||
- Fix: Correctly detect MCP tool errors by @kevin-ramdass in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14937
|
||||
- increase labeler timeout by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14922
|
||||
- tool(cli): tweak the frontend tool to be aware of more core files from the cli
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/14962
|
||||
- feat(cli): polish cached token stats and simplify stats display when quota is
|
||||
present. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14961
|
||||
- feat(settings-validation): add validation for settings schema by @lifefloating
|
||||
in https://github.com/google-gemini/gemini-cli/pull/12929
|
||||
- fix(ide): Update IDE extension to write auth token in env var by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14999
|
||||
- Revert "chore(deps): bump express from 5.1.0 to 5.2.0" by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14998
|
||||
- feat(a2a): Introduce /init command for a2a server by @cocosheng-g in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13419
|
||||
- feat: support multi-file drag and drop of images by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14832
|
||||
- fix(policy): allow codebase_investigator by default in read-only policy by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15000
|
||||
- refactor(ide ext): Update port file name + switch to 1-based index for
|
||||
characters + remove truncation text by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/10501
|
||||
- fix(vscode-ide-companion): correct license generation for workspace
|
||||
dependencies by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15004
|
||||
- fix: temp fix for subagent invocation until subagent delegation is merged to
|
||||
stable by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15007
|
||||
- test: update ide detection tests to make them more robust when run in an ide
|
||||
by @kevin-ramdass in https://github.com/google-gemini/gemini-cli/pull/15008
|
||||
- Remove flex from stats display. See snapshots for diffs. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14983
|
||||
- Add license field into package.json by @jb-perez in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14473
|
||||
- feat: Persistent "Always Allow" policies with granular shell & MCP support by
|
||||
@allenhutchison in https://github.com/google-gemini/gemini-cli/pull/14737
|
||||
- chore/release: bump version to 0.21.0-nightly.20251212.54de67536 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14969
|
||||
- fix(core): commandPrefix word boundary and compound command safety by
|
||||
@allenhutchison in https://github.com/google-gemini/gemini-cli/pull/15006
|
||||
- chore(docs): add 'Maintainers only' label info to CONTRIBUTING.md by @jacob314
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14914
|
||||
- Refresh hooks when refreshing extensions. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14918
|
||||
- Add clarity to error messages by @gsehgal in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14879
|
||||
- chore : remove a redundant tip by @JayadityaGit in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14947
|
||||
- chore/release: bump version to 0.21.0-nightly.20251213.977248e09 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15029
|
||||
- Disallow redundant typecasts. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15030
|
||||
- fix(auth): prioritize GEMINI_API_KEY env var and skip unnecessary key… by
|
||||
@galz10 in https://github.com/google-gemini/gemini-cli/pull/14745
|
||||
- fix: use zod for safety check result validation by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15026
|
||||
- update(telemetry): add hashed_extension_name to field to extension events by
|
||||
@kiranani in https://github.com/google-gemini/gemini-cli/pull/15025
|
||||
- fix: similar to policy-engine, throw error in case of requiring tool execution
|
||||
confirmation for non-interactive mode by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14702
|
||||
- Clean up processes in integration tests by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15102
|
||||
- docs: update policy engine getting started and defaults by @NTaylorMullen in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15105
|
||||
- Fix tool output fragmentation by encapsulating content in functionResponse by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/13082
|
||||
- Simplify method signature. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15114
|
||||
- Show raw input token counts in json output. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15021
|
||||
- fix: Mark A2A requests as interactive by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15108
|
||||
- use previewFeatures to determine which pro model to use for A2A by @sehoon38
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15131
|
||||
- refactor(cli): fix settings merging so that settings using the new json format
|
||||
take priority over ones using the old format by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15116
|
||||
- fix(patch): cherry-pick a6d1245 to release/v0.22.0-preview.1-pr-15214 to patch
|
||||
version v0.22.0-preview.1 and create version 0.22.0-preview.2 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15226
|
||||
- fix(patch): cherry-pick 9e6914d to release/v0.22.0-preview.2-pr-15288 to patch
|
||||
version v0.22.0-preview.2 and create version 0.22.0-preview.3 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15294
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.21.3...v0.22.0
|
||||
|
||||
## Release v0.21.0 - v0.21.1
|
||||
|
||||
### Highlights
|
||||
|
||||
- **⚡️⚡️⚡️ Gemini 3 Flash + Gemini CLI:** If you are a paid user, you can now
|
||||
enable Gemini 3 Pro and Gemini 3 Flash. Go to `/settings` and set **Preview
|
||||
Features** to `true` to enable Gemini 3. For more information:
|
||||
[Gemini 3 Flash is now available in Gemini CLI](https://developers.googleblog.com/gemini-3-flash-is-now-available-in-gemini-cli/).
|
||||
|
||||
### What's Changed
|
||||
|
||||
- refactor(stdio): always patch stdout and use createWorkingStdio for clean
|
||||
output by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14159
|
||||
- chore(release): bump version to 0.21.0-nightly.20251202.2d935b379 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14409
|
||||
- implement fuzzy search inside settings by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13864
|
||||
- feat: enable message bus integration by default by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14329
|
||||
- docs: Recommend using --debug intead of --verbose for CLI debugging by @bbiggs
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14334
|
||||
- feat: consolidate remote MCP servers to use `url` in config by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/13762
|
||||
- Restrict integration tests tools by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14403
|
||||
- track github repository names in telemetry events by @IamRiddhi in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13670
|
||||
- Allow telemetry exporters to GCP to utilize user's login credentials, if
|
||||
requested by @mboshernitsan in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13778
|
||||
- refactor(editor): use const assertion for editor types with single source of
|
||||
truth by @amsminn in https://github.com/google-gemini/gemini-cli/pull/8604
|
||||
- fix(security): Fix npm audit vulnerabilities in glob and body-parser by
|
||||
@afarber in https://github.com/google-gemini/gemini-cli/pull/14090
|
||||
- Add new enterprise instructions by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/8641
|
||||
- feat(hooks): Hook Session Lifecycle & Compression Integration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14151
|
||||
- Avoid triggering refreshStatic unless there really is a banner to display. by
|
||||
@jacob314 in https://github.com/google-gemini/gemini-cli/pull/14328
|
||||
- feat(hooks): Hooks Commands Panel, Enable/Disable, and Migrate by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14225
|
||||
- fix: Bundle default policies for npx distribution by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14457
|
||||
- feat(hooks): Hook System Documentation by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14307
|
||||
- Fix tests by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14458
|
||||
- feat: add scheduled workflow to close stale issues by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14404
|
||||
- feat: Support Extension Hooks with Security Warning by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14460
|
||||
- feat: Add enableAgents experimental flag by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14371
|
||||
- docs: fix typo 'socus' to 'focus' in todos.md by @Viktor286 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14374
|
||||
- Markdown export: move the emoji to the end of the line by @mhansen in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12278
|
||||
- fix(acp): prevent unnecessary credential cache clearing on re-authent… by
|
||||
@h-michael in https://github.com/google-gemini/gemini-cli/pull/9410
|
||||
- fix(cli): Fix word navigation for CJK characters by @SandyTao520 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14475
|
||||
- Remove example extension by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14376
|
||||
- Add commands for listing and updating per-extension settings by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12664
|
||||
- chore(tests): remove obsolete test for hierarchical memory by @pareshjoshij in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13122
|
||||
- feat(cli): support /copy in remote sessions using OSC52 by @ismellpillows in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13471
|
||||
- Update setting search UX by @Adib234 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14451
|
||||
- Fix(cli): Improve Homebrew update instruction to specify gemini-cli by
|
||||
@DaanVersavel in https://github.com/google-gemini/gemini-cli/pull/14502
|
||||
- do not toggle the setting item when entering space by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14489
|
||||
- fix: improve retry logic for fetch errors and network codes by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14439
|
||||
- remove unused isSearching field by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14509
|
||||
- feat(mcp): add `--type` alias for `--transport` flag in gemini mcp add by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/14503
|
||||
- feat(cli): Move key restore logic to core by @cocosheng-g in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13013
|
||||
- feat: add auto-execute on Enter behavior to argumentless MCP prompts by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/14510
|
||||
- fix(shell): cursor visibility when using interactive mode by @aswinashok44 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14095
|
||||
- Adding session id as part of json o/p by @MJjainam in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14504
|
||||
- fix(extensions): resolve GitHub API 415 error for source tarballs by
|
||||
@jpoehnelt in https://github.com/google-gemini/gemini-cli/pull/13319
|
||||
- fix(client): Correctly latch hasFailedCompressionAttempt flag by @pareshjoshij
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13002
|
||||
- Disable flaky extension reloading test on linux by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14528
|
||||
- Add support for MCP dynamic tool update by `notifications/tools/list_changed`
|
||||
by @Adib234 in https://github.com/google-gemini/gemini-cli/pull/14375
|
||||
- Fix privacy screen for legacy tier users by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14522
|
||||
- feat: Exclude maintainer labeled issues from stale issue closer by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14532
|
||||
- Grant chained workflows proper permission. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14534
|
||||
- Make trigger_e2e manually fireable. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14547
|
||||
- Write e2e status to local repo not forked repo by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14549
|
||||
- Fixes [API Error: Cannot read properties of undefined (reading 'error')] by
|
||||
@silviojr in https://github.com/google-gemini/gemini-cli/pull/14553
|
||||
- Trigger chained e2e tests on all pull requests by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14551
|
||||
- Fix bug in the shellExecutionService resulting in both truncation and 3X bloat
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/14545
|
||||
- Fix issue where we were passing the model content reflecting terminal line
|
||||
wrapping. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14566
|
||||
- chore/release: bump version to 0.21.0-nightly.20251204.3da4fd5f7 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14476
|
||||
- feat(sessions): use 1-line generated session summary to describe sessions by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/14467
|
||||
- Use Robot PAT for chained e2e merge queue skipper by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14585
|
||||
- fix(core): improve API response error handling and retry logic by @mattKorwel
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14563
|
||||
- Docs: Model routing clarification by @jkcinouye in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14373
|
||||
- expose previewFeatures flag in a2a by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14550
|
||||
- Fix emoji width in debug console. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14593
|
||||
- Fully detach autoupgrade process by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14595
|
||||
- Docs: Update Gemini 3 on Gemini CLI documentation by @jkcinouye in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14601
|
||||
- Disallow floating promises. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14605
|
||||
- chore/release: bump version to 0.21.0-nightly.20251207.025e450ac by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14662
|
||||
- feat(modelAvailabilityService): integrate model availability service into
|
||||
backend logic by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14470
|
||||
- Add prompt_id propagation in a2a-server task by @koxkox111 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14581
|
||||
- Fix: Prevent freezing in non-interactive Gemini CLI when debug mode is enabled
|
||||
by @parthasaradhie in https://github.com/google-gemini/gemini-cli/pull/14580
|
||||
- fix(audio): improve reading of audio files by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14658
|
||||
- Update automated triage workflow to stop assigning priority labels by
|
||||
@skeshive in https://github.com/google-gemini/gemini-cli/pull/14717
|
||||
- set failed status when chained e2e fails by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14725
|
||||
- feat(github action) Triage and Label Pull Requests by Size and Comple… by
|
||||
@DaanVersavel in https://github.com/google-gemini/gemini-cli/pull/5571
|
||||
- refactor(telemetry): Improve previous PR that allows telemetry to use the CLI
|
||||
auth and add testing by @mboshernitsan in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14589
|
||||
- Always set status in chained_e2e workflow by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14730
|
||||
- feat: Add OTEL log event `gemini_cli.startup_stats` for startup stats. by
|
||||
@kevin-ramdass in https://github.com/google-gemini/gemini-cli/pull/14734
|
||||
- feat: auto-execute on slash command completion functions by @jackwotherspoon
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14584
|
||||
- Docs: Proper release notes by @jkcinouye in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14405
|
||||
- Add support for user-scoped extension settings by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13748
|
||||
- refactor(core): Improve environment variable handling in shell execution by
|
||||
@galz10 in https://github.com/google-gemini/gemini-cli/pull/14742
|
||||
- Remove old E2E Workflows by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14749
|
||||
- fix: handle missing local extension config and skip hooks when disabled by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14744
|
||||
- chore/release: bump version to 0.21.0-nightly.20251209.ec9a8c7a7 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14751
|
||||
- feat: Add support for MCP Resources by @MrLesk in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13178
|
||||
- Always set pending status in E2E tests by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14756
|
||||
- fix(lint): upgrade pip and use public pypi for yamllint by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14746
|
||||
- fix: use Gemini API supported image formats for clipboard by @jackwotherspoon
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14762
|
||||
- feat(a2a): Introduce restore command for a2a server by @cocosheng-g in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13015
|
||||
- allow final:true to be returned on a2a server edit calls. by @DavidAPierce in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14747
|
||||
- (fix) Automated pr labeler by @DaanVersavel in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14788
|
||||
- Update CODEOWNERS by @kklashtorny1 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14830
|
||||
- Docs: Fix errors preventing site rebuild. by @jkcinouye in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14842
|
||||
- chore(deps): bump express from 5.1.0 to 5.2.0 by @dependabot[bot] in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14325
|
||||
- fix(patch): cherry-pick 3f5f030 to release/v0.21.0-preview.0-pr-14843 to patch
|
||||
version v0.21.0-preview.0 and create version 0.21.0-preview.1 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14851
|
||||
- fix(patch): cherry-pick ee6556c to release/v0.21.0-preview.1-pr-14691 to patch
|
||||
version v0.21.0-preview.1 and create version 0.21.0-preview.2 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14908
|
||||
- fix(patch): cherry-pick 54de675 to release/v0.21.0-preview.2-pr-14961 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14968
|
||||
- fix(patch): cherry-pick 12cbe32 to release/v0.21.0-preview.3-pr-15000 to patch
|
||||
version v0.21.0-preview.3 and create version 0.21.0-preview.4 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15003
|
||||
- fix(patch): cherry-pick edbe548 to release/v0.21.0-preview.4-pr-15007 to patch
|
||||
version v0.21.0-preview.4 and create version 0.21.0-preview.5 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15015
|
||||
- fix(patch): cherry-pick 2995af6 to release/v0.21.0-preview.5-pr-15131 to patch
|
||||
version v0.21.0-preview.5 and create version 0.21.0-preview.6 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15153
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.20.2...v0.21.0
|
||||
|
||||
## Release v0.22.0-preview-0 (Preview)
|
||||
|
||||
### What's Changed
|
||||
|
||||
- feat(ide): fallback to GEMINI_CLI_IDE_AUTH_TOKEN env var by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14843
|
||||
- feat: display quota stats for unused models in /stats by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14764
|
||||
- feat: ensure codebase investigator uses preview model when main agent does by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14412
|
||||
- chore: add closing reason to stale bug workflow by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14861
|
||||
- Send the model and CLI version with the user agent by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14865
|
||||
- refactor(sessions): move session summary generation to startup by
|
||||
@jackwotherspoon in https://github.com/google-gemini/gemini-cli/pull/14691
|
||||
- Limit search depth in path corrector by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14869
|
||||
- Fix: Correct typo in code comment by @kuishou68 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14671
|
||||
- feat(core): Plumbing for late resolution of model configs. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14597
|
||||
- feat: attempt more error parsing by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14899
|
||||
- Add missing await. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14910
|
||||
- feat(core): Add support for transcript_path in hooks for git-ai/Gemini
|
||||
extension by @svarlamov in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14663
|
||||
- refactor: implement DelegateToAgentTool with discriminated union by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/14769
|
||||
- feat: reset availabilityService on /auth by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14911
|
||||
- chore/release: bump version to 0.21.0-nightly.20251211.8c83e1ea9 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14924
|
||||
- Fix: Correctly detect MCP tool errors by @kevin-ramdass in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14937
|
||||
- increase labeler timeout by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14922
|
||||
- tool(cli): tweak the frontend tool to be aware of more core files from the cli
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/14962
|
||||
- feat(cli): polish cached token stats and simplify stats display when quota is
|
||||
present. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14961
|
||||
- feat(settings-validation): add validation for settings schema by @lifefloating
|
||||
in https://github.com/google-gemini/gemini-cli/pull/12929
|
||||
- fix(ide): Update IDE extension to write auth token in env var by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14999
|
||||
- Revert "chore(deps): bump express from 5.1.0 to 5.2.0" by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14998
|
||||
- feat(a2a): Introduce /init command for a2a server by @cocosheng-g in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13419
|
||||
- feat: support multi-file drag and drop of images by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14832
|
||||
- fix(policy): allow codebase_investigator by default in read-only policy by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/15000
|
||||
- refactor(ide ext): Update port file name + switch to 1-based index for
|
||||
characters + remove truncation text by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/10501
|
||||
- fix(vscode-ide-companion): correct license generation for workspace
|
||||
dependencies by @skeshive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15004
|
||||
- fix: temp fix for subagent invocation until subagent delegation is merged to
|
||||
stable by @abhipatel12 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15007
|
||||
- test: update ide detection tests to make them more robust when run in an ide
|
||||
by @kevin-ramdass in https://github.com/google-gemini/gemini-cli/pull/15008
|
||||
- Remove flex from stats display. See snapshots for diffs. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14983
|
||||
- Add license field into package.json by @jb-perez in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14473
|
||||
- feat: Persistent "Always Allow" policies with granular shell & MCP support by
|
||||
@allenhutchison in https://github.com/google-gemini/gemini-cli/pull/14737
|
||||
- chore/release: bump version to 0.21.0-nightly.20251212.54de67536 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14969
|
||||
- fix(core): commandPrefix word boundary and compound command safety by
|
||||
@allenhutchison in https://github.com/google-gemini/gemini-cli/pull/15006
|
||||
- chore(docs): add 'Maintainers only' label info to CONTRIBUTING.md by @jacob314
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14914
|
||||
- Refresh hooks when refreshing extensions. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14918
|
||||
- Add clarity to error messages by @gsehgal in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14879
|
||||
- chore : remove a redundant tip by @JayadityaGit in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14947
|
||||
- chore/release: bump version to 0.21.0-nightly.20251213.977248e09 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/15029
|
||||
- Disallow redundant typecasts. by @gundermanc in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15030
|
||||
- fix(auth): prioritize GEMINI_API_KEY env var and skip unnecessary key… by
|
||||
@galz10 in https://github.com/google-gemini/gemini-cli/pull/14745
|
||||
- fix: use zod for safety check result validation by @allenhutchison in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15026
|
||||
- update(telemetry): add hashed_extension_name to field to extension events by
|
||||
@kiranani in https://github.com/google-gemini/gemini-cli/pull/15025
|
||||
- fix: similar to policy-engine, throw error in case of requiring tool execution
|
||||
confirmation for non-interactive mode by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14702
|
||||
- Clean up processes in integration tests by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15102
|
||||
- docs: update policy engine getting started and defaults by @NTaylorMullen in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15105
|
||||
- Fix tool output fragmentation by encapsulating content in functionResponse by
|
||||
@abhipatel12 in https://github.com/google-gemini/gemini-cli/pull/13082
|
||||
- Simplify method signature. by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15114
|
||||
- Show raw input token counts in json output. by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15021
|
||||
- fix: Mark A2A requests as interactive by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15108
|
||||
- use previewFeatures to determine which pro model to use for A2A by @sehoon38
|
||||
in https://github.com/google-gemini/gemini-cli/pull/15131
|
||||
- refactor(cli): fix settings merging so that settings using the new json format
|
||||
take priority over ones using the old format by @jacob314 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/15116
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.21.0-preview.6...v0.22.0-preview.0
|
||||
|
||||
## Release v0.20.0 - v0.20.2
|
||||
|
||||
### What's Changed
|
||||
|
||||
- Update error codes when process exiting the gemini cli by @megha1188 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13728
|
||||
- chore(release): bump version to 0.20.0-nightly.20251126.d2a6cff4d by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13835
|
||||
- feat(core): Improve request token calculation accuracy by @SandyTao520 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13824
|
||||
- Changes in system instruction to adapt to gemini 3.0 to ensure that the CLI
|
||||
explains its actions before calling tools by @silviojr in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13810
|
||||
- feat(hooks): Hook Tool Execution Integration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9108
|
||||
- Add support for MCP server instructions behind config option by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13432
|
||||
- Update System Instructions for interactive vs non-interactive mode. by
|
||||
@aishaneeshah in https://github.com/google-gemini/gemini-cli/pull/12315
|
||||
- Add consent flag to Link command by @kevinjwang1 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13832
|
||||
- feat(mcp): Inject GoogleCredentialProvider headers in McpClient by
|
||||
@sai-sunder-s in https://github.com/google-gemini/gemini-cli/pull/13783
|
||||
- feat(core): implement towards policy-driven model fallback mechanism by
|
||||
@adamfweidman in https://github.com/google-gemini/gemini-cli/pull/13781
|
||||
- feat(core): Add configurable inactivity timeout for shell commands by @galz10
|
||||
in https://github.com/google-gemini/gemini-cli/pull/13531
|
||||
- fix(auth): improve API key authentication flow by @galz10 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13829
|
||||
- feat(hooks): Hook LLM Request/Response Integration by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9110
|
||||
- feat(ui): Show waiting MCP servers in ConfigInitDisplay by @werdnum in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13721
|
||||
- Add usage limit remaining in /stats by @sehoon38 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13843
|
||||
- feat(shell): Standardize pager to 'cat' for shell execution by model by
|
||||
@galz10 in https://github.com/google-gemini/gemini-cli/pull/13878
|
||||
- chore/release: bump version to 0.20.0-nightly.20251127.5bed97064 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/13877
|
||||
- Revert to default LICENSE (Revert #13449) by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13876
|
||||
- update(telemetry): OTel API response event with finish reasons by @kiranani in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13849
|
||||
- feat(hooks): Hooks Comprehensive Integration Testing by @Edilmo in
|
||||
https://github.com/google-gemini/gemini-cli/pull/9112
|
||||
- chore: fix session browser test and skip hook system tests by @jackwotherspoon
|
||||
in https://github.com/google-gemini/gemini-cli/pull/14099
|
||||
- feat(telemetry): Add Semantic logging for to ApiRequestEvents by @kiranani in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13912
|
||||
- test: Add verification for $schema property in settings schema by
|
||||
@maryamariyan in https://github.com/google-gemini/gemini-cli/pull/13497
|
||||
- Fixes `/clear` command to preserve input history for up-arrow navigation while
|
||||
still clearing the context window and screen by @korade-krushna in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14182
|
||||
- fix(core): handle EPIPE error in hook runner when writing to stdin by
|
||||
@SandyTao520 in https://github.com/google-gemini/gemini-cli/pull/14231
|
||||
- fix: Exclude web-fetch tool from executing in default non-interactive mode to
|
||||
avoid CLI hang. by @MayV in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14244
|
||||
- Always use MCP server instructions by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14297
|
||||
- feat: auto-execute simple slash commands on Enter by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13985
|
||||
- chore/release: bump version to 0.20.0-nightly.20251201.2fe609cb6 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14304
|
||||
- feat: Add startup profiler to measure and record application initialization
|
||||
phases. by @kevin-ramdass in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13638
|
||||
- bug(core): Avoid stateful tool use in `executor`. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14305
|
||||
- feat(themes): add built-in holiday theme 🎁 by @jackwotherspoon in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14301
|
||||
- Updated ToC on docs intro; updated title casing to match Google style by
|
||||
@pcoet in https://github.com/google-gemini/gemini-cli/pull/13717
|
||||
- feat(a2a): Urgent fix - Process modelInfo agent message by @cocosheng-g in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14315
|
||||
- feat(core): enhance availability routing with wrapped fallback and
|
||||
single-model policies by @adamfweidman in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13874
|
||||
- chore(logging): log the problematic event for #12122 by @briandealwis in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14092
|
||||
- fix: remove invalid type key in bug_report.yml by @fancive in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13576
|
||||
- update screenshot by @Transient-Onlooker in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13976
|
||||
- docs: Fix grammar error in Release Cadence (Nightly section) by @JuanCS-Dev in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13866
|
||||
- fix(async): prevent missed async errors from bypassing catch handlers by
|
||||
@amsminn in https://github.com/google-gemini/gemini-cli/pull/13714
|
||||
- fix(zed-integration): remove extra field from acp auth request by
|
||||
@marcocondrache in https://github.com/google-gemini/gemini-cli/pull/13646
|
||||
- feat(cli): Documentation for model configs. by @joshualitt in
|
||||
https://github.com/google-gemini/gemini-cli/pull/12967
|
||||
- fix(ui): misaligned markdown table rendering by @dumbbellcode in
|
||||
https://github.com/google-gemini/gemini-cli/pull/8336
|
||||
- docs: Update 4 files by @g-samroberts in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13628
|
||||
- fix: Conditionally add set -eEuo pipefail in setup-github command by @Smetalo
|
||||
in https://github.com/google-gemini/gemini-cli/pull/8550
|
||||
- fix(cli): fix issue updating a component while rendering a different component
|
||||
by @jacob314 in https://github.com/google-gemini/gemini-cli/pull/14319
|
||||
- Increase flakey test timeout by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14377
|
||||
- Remove references to deleted kind/bug label by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14383
|
||||
- Don't fail test if we can't cleanup by @scidomino in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14389
|
||||
- feat(core): Implement JIT context manager and setting by @SandyTao520 in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14324
|
||||
- Use polling for extensions-reload integration test by @chrstnb in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14391
|
||||
- Add docs directive to GEMINI.md by @g-samroberts in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14327
|
||||
- Hide sessions that don't have user messages by @bl-ue in
|
||||
https://github.com/google-gemini/gemini-cli/pull/13994
|
||||
- chore(ci): mark GitHub release as pre-release if not on "latest" npm channel
|
||||
by @ljxfstorm in https://github.com/google-gemini/gemini-cli/pull/7386
|
||||
- fix(patch): cherry-pick d284fa6 to release/v0.20.0-preview.0-pr-14545
|
||||
[CONFLICTS] by @gemini-cli-robot in
|
||||
https://github.com/google-gemini/gemini-cli/pull/14559
|
||||
- fix(patch): cherry-pick 828afe1 to release/v0.20.0-preview.1-pr-14159 to patch
|
||||
version v0.20.0-preview.1 and create version 0.20.0-preview.2 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14733
|
||||
- fix(patch): cherry-pick 171103a to release/v0.20.0-preview.2-pr-14742 to patch
|
||||
version v0.20.0-preview.2 and create version 0.20.0-preview.5 by
|
||||
@gemini-cli-robot in https://github.com/google-gemini/gemini-cli/pull/14752
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.19.4...v0.20.0
|
||||
|
||||
## Release v0.19.0 - v0.19.4
|
||||
|
||||
## Highlights
|
||||
|
||||
@@ -26,10 +758,10 @@ on GitHub.
|
||||
integration after enabling "Preview Features" in their CLI’s `/settings`.
|
||||
- **Interactive shell:**
|
||||
- **Click-to-Focus:** Go to `/settings` and enable **Use Alternate Buffer**
|
||||
WhenUse Alternate Buffer" setting is enabled users can click within the
|
||||
When "Use Alternate Buffer" setting is enabled users can click within the
|
||||
embedded shell output to focus it for input.
|
||||
- **Loading phrase:** Clearly indicates when the interactive shell is awaiting
|
||||
user input. ([vid](https://imgur.com/a/kjK8bUK),
|
||||
user input. ([vid](https://imgur.com/a/kjK8bUK)
|
||||
[pr](https://github.com/google-gemini/gemini-cli/pull/12535) by
|
||||
[@jackwotherspoon](https://github.com/jackwotherspoon))
|
||||
|
||||
@@ -165,7 +897,7 @@ on GitHub.
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.18.4...v0.19.0
|
||||
|
||||
## Release v0.19.0-preview.0 (Preview)
|
||||
## Release v0.19.0-preview.0
|
||||
|
||||
### What's Changed
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **`/copy`**
|
||||
- **Description:** Copies the last output produced by Gemini CLI to your
|
||||
clipboard, for easy sharing or reuse.
|
||||
- **Behavior:**
|
||||
- Local sessions use system clipboard tools (pbcopy/xclip/clip).
|
||||
- Remote sessions (SSH/WSL) use OSC 52 and require terminal support.
|
||||
- **Note:** This command requires platform-specific clipboard tools to be
|
||||
installed.
|
||||
- On Linux, it requires `xclip` or `xsel`. You can typically install them
|
||||
|
||||
@@ -1,780 +0,0 @@
|
||||
# Gemini CLI configuration
|
||||
|
||||
Gemini CLI offers several ways to configure its behavior, including environment
|
||||
variables, command-line arguments, and settings files. This document outlines
|
||||
the different configuration methods and available settings.
|
||||
|
||||
## Configuration layers
|
||||
|
||||
Configuration is applied in the following order of precedence (lower numbers are
|
||||
overridden by higher numbers):
|
||||
|
||||
1. **Default values:** Hardcoded defaults within the application.
|
||||
2. **User settings file:** Global settings for the current user.
|
||||
3. **Project settings file:** Project-specific settings.
|
||||
4. **System settings file:** System-wide settings.
|
||||
5. **Environment variables:** System-wide or session-specific variables,
|
||||
potentially loaded from `.env` files.
|
||||
6. **Command-line arguments:** Values passed when launching the CLI.
|
||||
|
||||
## Settings files
|
||||
|
||||
Gemini CLI uses `settings.json` files for persistent configuration. There are
|
||||
three locations for these files:
|
||||
|
||||
- **User settings file:**
|
||||
- **Location:** `~/.gemini/settings.json` (where `~` is your home directory).
|
||||
- **Scope:** Applies to all Gemini CLI sessions for the current user.
|
||||
- **Project settings file:**
|
||||
- **Location:** `.gemini/settings.json` within your project's root directory.
|
||||
- **Scope:** Applies only when running Gemini CLI from that specific project.
|
||||
Project settings override user settings.
|
||||
- **System settings file:**
|
||||
- **Location:** `/etc/gemini-cli/settings.json` (Linux),
|
||||
`C:\ProgramData\gemini-cli\settings.json` (Windows) or
|
||||
`/Library/Application Support/GeminiCli/settings.json` (macOS). The path can
|
||||
be overridden using the `GEMINI_CLI_SYSTEM_SETTINGS_PATH` environment
|
||||
variable.
|
||||
- **Scope:** Applies to all Gemini CLI sessions on the system, for all users.
|
||||
System settings override user and project settings. May be useful for system
|
||||
administrators at enterprises to have controls over users' Gemini CLI
|
||||
setups.
|
||||
|
||||
**Note on environment variables in settings:** String values within your
|
||||
`settings.json` files can reference environment variables using either
|
||||
`$VAR_NAME` or `${VAR_NAME}` syntax. These variables will be automatically
|
||||
resolved when the settings are loaded. For example, if you have an environment
|
||||
variable `MY_API_TOKEN`, you could use it in `settings.json` like this:
|
||||
`"apiKey": "$MY_API_TOKEN"`.
|
||||
|
||||
### The `.gemini` directory in your project
|
||||
|
||||
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`).
|
||||
|
||||
### Available settings in `settings.json`:
|
||||
|
||||
- **`contextFileName`** (string or array of strings):
|
||||
- **Description:** Specifies the filename for context files (e.g.,
|
||||
`GEMINI.md`, `AGENTS.md`). Can be a single filename or a list of accepted
|
||||
filenames.
|
||||
- **Default:** `GEMINI.md`
|
||||
- **Example:** `"contextFileName": "AGENTS.md"`
|
||||
|
||||
- **`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.yml&title={title}&info={info}"`
|
||||
- **Properties:**
|
||||
- **`urlTemplate`** (string): A URL that can contain `{title}` and `{info}`
|
||||
placeholders.
|
||||
- **Example:**
|
||||
```json
|
||||
"bugCommand": {
|
||||
"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, "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,
|
||||
"enableRecursiveFileSearch": false
|
||||
}
|
||||
```
|
||||
|
||||
- **`coreTools`** (array of strings):
|
||||
- **Description:** Allows you to specify a list of core tool names that should
|
||||
be made available to the model. This can be used to restrict the set of
|
||||
built-in tools. See [Built-in Tools](../core/tools-api.md#built-in-tools)
|
||||
for a list of core tools. You can also specify command-specific restrictions
|
||||
for tools that support it, like the `ShellTool`. For example,
|
||||
`"coreTools": ["ShellTool(ls -l)"]` will only allow the `ls -l` command to
|
||||
be executed.
|
||||
- **Default:** All tools available for use by the Gemini model.
|
||||
- **Example:** `"coreTools": ["ReadFileTool", "GlobTool", "ShellTool(ls)"]`.
|
||||
|
||||
- **`excludeTools`** (array of strings):
|
||||
- **Description:** Allows you to specify a list of core tool names that should
|
||||
be excluded from the model. A tool listed in both `excludeTools` and
|
||||
`coreTools` is excluded. You can also specify command-specific restrictions
|
||||
for tools that support it, like the `ShellTool`. For example,
|
||||
`"excludeTools": ["ShellTool(rm -rf)"]` will block the `rm -rf` command.
|
||||
- **Default**: No tools excluded.
|
||||
- **Example:** `"excludeTools": ["run_shell_command", "findFiles"]`.
|
||||
- **Security Note:** Command-specific restrictions in `excludeTools` for
|
||||
`run_shell_command` are based on simple string matching and can be easily
|
||||
bypassed. This feature is **not a security mechanism** and should not be
|
||||
relied upon to safely execute untrusted code. It is recommended to use
|
||||
`coreTools` to explicitly select commands that can be executed.
|
||||
|
||||
- **`allowMCPServers`** (array of strings):
|
||||
- **Description:** Allows you to specify a list of MCP server names that
|
||||
should be made available to the model. This can be used to restrict the set
|
||||
of MCP servers to connect to. Note that this will be ignored if
|
||||
`--allowed-mcp-server-names` is set.
|
||||
- **Default:** All MCP servers are available for use by the Gemini model.
|
||||
- **Example:** `"allowMCPServers": ["myPythonServer"]`.
|
||||
- **Security Note:** This uses simple string matching on MCP server names,
|
||||
which can be modified. If you're a system administrator looking to prevent
|
||||
users from bypassing this, consider configuring the `mcpServers` at the
|
||||
system settings level such that the user will not be able to configure any
|
||||
MCP servers of their own. This should not be used as an airtight security
|
||||
mechanism.
|
||||
|
||||
- **`excludeMCPServers`** (array of strings):
|
||||
- **Description:** Allows you to specify a list of MCP server names that
|
||||
should be excluded from the model. A server listed in both
|
||||
`excludeMCPServers` and `allowMCPServers` is excluded. Note that this will
|
||||
be ignored if `--allowed-mcp-server-names` is set.
|
||||
- **Default**: No MCP servers excluded.
|
||||
- **Example:** `"excludeMCPServers": ["myNodeServer"]`.
|
||||
- **Security note:** This uses simple string matching on MCP server names,
|
||||
which can be modified. If you're a system administrator looking to prevent
|
||||
users from bypassing this, consider configuring the `mcpServers` at the
|
||||
system settings level such that the user will not be able to configure any
|
||||
MCP servers of their own. This should not be used as an airtight security
|
||||
mechanism.
|
||||
|
||||
- **`autoAccept`** (boolean):
|
||||
- **Description:** Controls whether the CLI automatically accepts and executes
|
||||
tool calls that are considered safe (e.g., read-only operations) without
|
||||
explicit user confirmation. If set to `true`, the CLI will bypass the
|
||||
confirmation prompt for tools deemed safe.
|
||||
- **Default:** `false`
|
||||
- **Example:** `"autoAccept": true`
|
||||
|
||||
- **`theme`** (string):
|
||||
- **Description:** Sets the visual [theme](./themes.md) for Gemini CLI.
|
||||
- **Default:** `"Default"`
|
||||
- **Example:** `"theme": "GitHub"`
|
||||
|
||||
- **`vimMode`** (boolean):
|
||||
- **Description:** Enables or disables vim mode for input editing. When
|
||||
enabled, the input area supports vim-style navigation and editing commands
|
||||
with NORMAL and INSERT modes. The vim mode status is displayed in the footer
|
||||
and persists between sessions.
|
||||
- **Default:** `false`
|
||||
- **Example:** `"vimMode": true`
|
||||
|
||||
- **`sandbox`** (boolean or string):
|
||||
- **Description:** Controls whether and how to use sandboxing for tool
|
||||
execution. If set to `true`, Gemini CLI uses a pre-built
|
||||
`gemini-cli-sandbox` Docker image. For more information, see
|
||||
[Sandboxing](#sandboxing).
|
||||
- **Default:** `false`
|
||||
- **Example:** `"sandbox": "docker"`
|
||||
|
||||
- **`toolDiscoveryCommand`** (string):
|
||||
- **Description:** Defines a custom shell command for discovering tools from
|
||||
your project. The shell command must return on `stdout` a JSON array of
|
||||
[function declarations](https://ai.google.dev/gemini-api/docs/function-calling#function-declarations).
|
||||
Tool wrappers are optional.
|
||||
- **Default:** Empty
|
||||
- **Example:** `"toolDiscoveryCommand": "bin/get_tools"`
|
||||
|
||||
- **`toolCallCommand`** (string):
|
||||
- **Description:** Defines a custom shell command for calling a specific tool
|
||||
that was discovered using `toolDiscoveryCommand`. The shell command must
|
||||
meet the following criteria:
|
||||
- It must take function `name` (exactly as in
|
||||
[function declaration](https://ai.google.dev/gemini-api/docs/function-calling#function-declarations))
|
||||
as first command line argument.
|
||||
- It must read function arguments as JSON on `stdin`, analogous to
|
||||
[`functionCall.args`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functioncall).
|
||||
- It must return function output as JSON on `stdout`, analogous to
|
||||
[`functionResponse.response.content`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functionresponse).
|
||||
- **Default:** Empty
|
||||
- **Example:** `"toolCallCommand": "bin/call_tool"`
|
||||
|
||||
- **`mcpServers`** (object):
|
||||
- **Description:** Configures connections to one or more Model-Context
|
||||
Protocol (MCP) servers for discovering and using custom tools. Gemini CLI
|
||||
attempts to connect to each configured MCP server to discover available
|
||||
tools. If multiple MCP servers expose a tool with the same name, the tool
|
||||
names will be prefixed with the server alias you defined in the
|
||||
configuration (e.g., `serverAlias__actualToolName`) to avoid conflicts. Note
|
||||
that the system might strip certain schema properties from MCP tool
|
||||
definitions for compatibility.
|
||||
- **Default:** Empty
|
||||
- **Properties:**
|
||||
- **`<SERVER_NAME>`** (object): The server parameters for the named server.
|
||||
- `command` (string, required): The command to execute to start the MCP
|
||||
server.
|
||||
- `args` (array of strings, optional): Arguments to pass to the command.
|
||||
- `env` (object, optional): Environment variables to set for the server
|
||||
process.
|
||||
- `cwd` (string, optional): The working directory in which to start the
|
||||
server.
|
||||
- `timeout` (number, optional): Timeout in milliseconds for requests to
|
||||
this MCP server.
|
||||
- `trust` (boolean, optional): Trust this server and bypass all tool call
|
||||
confirmations.
|
||||
- `includeTools` (array of strings, optional): List of tool names to
|
||||
include from this MCP server. When specified, only the tools listed here
|
||||
will be available from this server (whitelist behavior). If not
|
||||
specified, all tools from the server are enabled by default.
|
||||
- `excludeTools` (array of strings, optional): List of tool names to
|
||||
exclude from this MCP server. Tools listed here will not be available to
|
||||
the model, even if they are exposed by the server. **Note:**
|
||||
`excludeTools` takes precedence over `includeTools` - if a tool is in
|
||||
both lists, it will be excluded.
|
||||
- **Example:**
|
||||
```json
|
||||
"mcpServers": {
|
||||
"myPythonServer": {
|
||||
"command": "python",
|
||||
"args": ["mcp_server.py", "--port", "8080"],
|
||||
"cwd": "./mcp_tools/python",
|
||||
"timeout": 5000,
|
||||
"includeTools": ["safe_tool", "file_reader"],
|
||||
},
|
||||
"myNodeServer": {
|
||||
"command": "node",
|
||||
"args": ["mcp_server.js"],
|
||||
"cwd": "./mcp_tools/node",
|
||||
"excludeTools": ["dangerous_tool", "file_deleter"]
|
||||
},
|
||||
"myDockerServer": {
|
||||
"command": "docker",
|
||||
"args": ["run", "-i", "--rm", "-e", "API_KEY", "ghcr.io/foo/bar"],
|
||||
"env": {
|
||||
"API_KEY": "$MY_API_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`checkpointing`** (object):
|
||||
- **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
|
||||
```
|
||||
|
||||
- **`hideTips`** (boolean):
|
||||
- **Description:** Enables or disables helpful tips in the CLI interface.
|
||||
- **Default:** `false`
|
||||
- **Example:**
|
||||
|
||||
```json
|
||||
"hideTips": true
|
||||
```
|
||||
|
||||
- **`hideBanner`** (boolean):
|
||||
- **Description:** Enables or disables the startup banner (ASCII art logo) in
|
||||
the CLI interface.
|
||||
- **Default:** `false`
|
||||
- **Example:**
|
||||
|
||||
```json
|
||||
"hideBanner": true
|
||||
```
|
||||
|
||||
- **`maxSessionTurns`** (number):
|
||||
- **Description:** Sets the maximum number of turns for a session. If the
|
||||
session exceeds this limit, the CLI will stop processing and start a new
|
||||
chat.
|
||||
- **Default:** `-1` (unlimited)
|
||||
- **Example:**
|
||||
```json
|
||||
"maxSessionTurns": 10
|
||||
```
|
||||
|
||||
- **`summarizeToolOutput`** (object):
|
||||
- **Description:** Enables or disables the summarization of tool output. You
|
||||
can specify the token budget for the summarization using the `tokenBudget`
|
||||
setting.
|
||||
- Note: Currently only the `run_shell_command` tool is supported.
|
||||
- **Default:** `{}` (Disabled by default)
|
||||
- **Example:**
|
||||
```json
|
||||
"summarizeToolOutput": {
|
||||
"run_shell_command": {
|
||||
"tokenBudget": 2000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`excludedProjectEnvVars`** (array of strings):
|
||||
- **Description:** Specifies environment variables that should be excluded
|
||||
from being loaded from project `.env` files. This prevents project-specific
|
||||
environment variables (like `DEBUG=true`) from interfering with gemini-cli
|
||||
behavior. Variables from `.gemini/.env` files are never excluded.
|
||||
- **Default:** `["DEBUG", "DEBUG_MODE"]`
|
||||
- **Example:**
|
||||
```json
|
||||
"excludedProjectEnvVars": ["DEBUG", "DEBUG_MODE", "NODE_ENV"]
|
||||
```
|
||||
|
||||
- **`includeDirectories`** (array of strings):
|
||||
- **Description:** Specifies an array of additional absolute or relative paths
|
||||
to include in the workspace context. This allows you to work with files
|
||||
across multiple directories as if they were one. Paths can use `~` to refer
|
||||
to the user's home directory. This setting can be combined with the
|
||||
`--include-directories` command-line flag.
|
||||
- **Default:** `[]`
|
||||
- **Example:**
|
||||
```json
|
||||
"includeDirectories": [
|
||||
"/path/to/another/project",
|
||||
"../shared-library",
|
||||
"~/common-utils"
|
||||
]
|
||||
```
|
||||
|
||||
- **`loadMemoryFromIncludeDirectories`** (boolean):
|
||||
- **Description:** Controls the behavior of the `/memory refresh` command. If
|
||||
set to `true`, `GEMINI.md` files should be loaded from all directories that
|
||||
are added. If set to `false`, `GEMINI.md` should only be loaded from the
|
||||
current directory.
|
||||
- **Default:** `false`
|
||||
- **Example:**
|
||||
```json
|
||||
"loadMemoryFromIncludeDirectories": true
|
||||
```
|
||||
|
||||
### Example `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": "GitHub",
|
||||
"sandbox": "docker",
|
||||
"toolDiscoveryCommand": "bin/get_tools",
|
||||
"toolCallCommand": "bin/call_tool",
|
||||
"mcpServers": {
|
||||
"mainServer": {
|
||||
"command": "bin/mcp_server.py"
|
||||
},
|
||||
"anotherServer": {
|
||||
"command": "node",
|
||||
"args": ["mcp_server.js", "--verbose"]
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"target": "local",
|
||||
"otlpEndpoint": "http://localhost:4317",
|
||||
"logPrompts": true
|
||||
},
|
||||
"usageStatisticsEnabled": true,
|
||||
"hideTips": false,
|
||||
"hideBanner": false,
|
||||
"maxSessionTurns": 10,
|
||||
"summarizeToolOutput": {
|
||||
"run_shell_command": {
|
||||
"tokenBudget": 100
|
||||
}
|
||||
},
|
||||
"excludedProjectEnvVars": ["DEBUG", "DEBUG_MODE", "NODE_ENV"],
|
||||
"includeDirectories": ["path/to/dir1", "~/path/to/dir2", "../path/to/dir3"],
|
||||
"loadMemoryFromIncludeDirectories": 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 and `.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.
|
||||
|
||||
The CLI automatically loads environment variables from an `.env` file. The
|
||||
loading order is:
|
||||
|
||||
1. `.env` file in the current working directory.
|
||||
2. If not found, it searches upwards in parent directories until it finds an
|
||||
`.env` file or reaches the project root (identified by a `.git` folder) or
|
||||
the home directory.
|
||||
3. If still not found, it looks for `~/.env` (in the user's home directory).
|
||||
|
||||
**Environment variable exclusion:** Some environment variables (like `DEBUG` and
|
||||
`DEBUG_MODE`) are automatically excluded from being loaded from project `.env`
|
||||
files to prevent interference with gemini-cli behavior. Variables from
|
||||
`.gemini/.env` files are never excluded. You can customize this behavior using
|
||||
the `excludedProjectEnvVars` setting in your `settings.json` file.
|
||||
|
||||
- **`GEMINI_API_KEY`** (Required):
|
||||
- Your API key for the Gemini API.
|
||||
- **Crucial for operation.** The CLI will not function without it.
|
||||
- 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
|
||||
- Example: `export GEMINI_MODEL="gemini-2.5-flash"`
|
||||
- **`GEMINI_CLI_CUSTOM_HEADERS`**:
|
||||
- Adds extra HTTP headers to Gemini API and Code Assist requests.
|
||||
- Accepts a comma-separated list of `Name: value` pairs.
|
||||
- Example:
|
||||
`export GEMINI_CLI_CUSTOM_HEADERS="X-My-Header: foo, X-Trace-ID: abc123"`.
|
||||
- **`GEMINI_API_KEY_AUTH_MECHANISM`**:
|
||||
- Specifies how the API key should be sent for authentication when using
|
||||
`AuthType.USE_GEMINI` or `AuthType.USE_VERTEX_AI`.
|
||||
- Valid values are `x-goog-api-key` (default) or `bearer`.
|
||||
- If set to `bearer`, the API key will be sent in the
|
||||
`Authorization: Bearer <key>` header.
|
||||
- Example: `export GEMINI_API_KEY_AUTH_MECHANISM="bearer"`
|
||||
- **`GOOGLE_API_KEY`**:
|
||||
- Your Google Cloud API key.
|
||||
- Required for using Vertex AI in express mode.
|
||||
- Ensure you have the necessary permissions.
|
||||
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`.
|
||||
- **`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 in this
|
||||
project.
|
||||
- **Cloud shell note:** When running in a Cloud Shell environment, this
|
||||
variable defaults to a special project allocated for Cloud Shell users. If
|
||||
you have `GOOGLE_CLOUD_PROJECT` set in your global environment in Cloud
|
||||
Shell, it will be overridden by this default. To use a different project in
|
||||
Cloud Shell, you must define `GOOGLE_CLOUD_PROJECT` in a `.env` file.
|
||||
- Example: `export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
|
||||
- **`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"`.
|
||||
- **`GOOGLE_CLOUD_LOCATION`**:
|
||||
- Your Google Cloud Project Location (e.g., us-central1).
|
||||
- Required for using Vertex AI in non express mode.
|
||||
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`.
|
||||
- **`GEMINI_SANDBOX`**:
|
||||
- Alternative to the `sandbox` setting in `settings.json`.
|
||||
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
|
||||
- **`HTTP_PROXY` / `HTTPS_PROXY`**:
|
||||
- Specifies the proxy server to use for outgoing HTTP/HTTPS requests.
|
||||
- Example: `export HTTPS_PROXY="http://proxy.example.com:8080"`
|
||||
- **`SEATBELT_PROFILE`** (macOS specific):
|
||||
- Switches the Seatbelt (`sandbox-exec`) profile on macOS.
|
||||
- `permissive-open`: (Default) Restricts writes to the project folder (and a
|
||||
few other folders, see
|
||||
`packages/cli/src/utils/sandbox-macos-permissive-open.sb`) but allows other
|
||||
operations.
|
||||
- `strict`: Uses a strict profile that declines operations by default.
|
||||
- `<profile_name>`: Uses a custom profile. To define a custom profile, create
|
||||
a file named `sandbox-macos-<profile_name>.sb` in your project's `.gemini/`
|
||||
directory (e.g., `my-project/.gemini/sandbox-macos-custom.sb`).
|
||||
- **`DEBUG` or `DEBUG_MODE`** (often used by underlying libraries or the CLI
|
||||
itself):
|
||||
- Set to `true` or `1` to enable verbose debug logging, which can be helpful
|
||||
for troubleshooting.
|
||||
- **Note:** These variables are automatically excluded from project `.env`
|
||||
files by default to prevent interference with gemini-cli behavior. Use
|
||||
`.gemini/.env` files if you need to set these for gemini-cli specifically.
|
||||
- **`NO_COLOR`**:
|
||||
- Set to any value to disable all color output in the CLI.
|
||||
- **`CLI_TITLE`**:
|
||||
- Set to a string to customize the title of the CLI.
|
||||
- **`CODE_ASSIST_ENDPOINT`**:
|
||||
- Specifies the endpoint for the code assist server.
|
||||
- This is useful for development and testing.
|
||||
- **`GEMINI_SYSTEM_MD`**:
|
||||
- Overrides the base system prompt with the contents of a Markdown file.
|
||||
- If set to `1` or `true`, it uses the file at `.gemini/system.md`.
|
||||
- If set to a file path, it uses that file. The path can be absolute or
|
||||
relative. `~` is supported for the home directory.
|
||||
- The specified file must exist.
|
||||
- **`GEMINI_WRITE_SYSTEM_MD`**:
|
||||
- Writes the default system prompt to a file. This is useful for getting a
|
||||
template to customize.
|
||||
- If set to `1` or `true`, it writes to `.gemini/system.md`.
|
||||
- If set to a file path, it writes to that path. The path can be absolute or
|
||||
relative. `~` is supported for the home directory. **Note: This will
|
||||
overwrite the file if it already exists.**
|
||||
|
||||
## Command-line arguments
|
||||
|
||||
Arguments passed directly when running the CLI can override other configurations
|
||||
for that specific session.
|
||||
|
||||
- **`--model <model_name>`** (**`-m <model_name>`**):
|
||||
- Specifies the Gemini model to use for this session.
|
||||
- Example: `npm start -- --model gemini-1.5-pro-latest`
|
||||
- **`--prompt <your_prompt>`** (**`-p <your_prompt>`**):
|
||||
- Used to pass a prompt directly to the command. This invokes Gemini CLI in a
|
||||
non-interactive mode.
|
||||
- **`--prompt-interactive <your_prompt>`** (**`-i <your_prompt>`**):
|
||||
- Starts an interactive session with the provided prompt as the initial input.
|
||||
- The prompt is processed within the interactive session, not before it.
|
||||
- Cannot be used when piping input from stdin.
|
||||
- Example: `gemini -i "explain this code"`
|
||||
- **`--sandbox`** (**`-s`**):
|
||||
- Enables sandbox mode for this session.
|
||||
- **`--sandbox-image`**:
|
||||
- Sets the sandbox image URI.
|
||||
- **`--debug`** (**`-d`**):
|
||||
- Enables debug mode for this session, providing more verbose output.
|
||||
- **`--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.
|
||||
- **`--extensions <extension_name ...>`** (**`-e <extension_name ...>`**):
|
||||
- Specifies a list of extensions to use for the session. If not provided, all
|
||||
available extensions are used.
|
||||
- Use the special term `gemini -e none` to disable all extensions.
|
||||
- Example: `gemini -e my-extension -e my-other-extension`
|
||||
- **`--list-extensions`** (**`-l`**):
|
||||
- Lists all available extensions and exits.
|
||||
- **`--include-directories <dir1,dir2,...>`**:
|
||||
- Includes additional directories in the workspace for multi-directory
|
||||
support.
|
||||
- Can be specified multiple times or as comma-separated values.
|
||||
- 5 directories can be added at maximum.
|
||||
- Example: `--include-directories /path/to/project1,/path/to/project2` or
|
||||
`--include-directories /path/to/project1 --include-directories /path/to/project2`
|
||||
- **`--version`**:
|
||||
- Displays the version of the CLI.
|
||||
|
||||
## Context files (hierarchical instructional context)
|
||||
|
||||
While not strictly configuration for the CLI's _behavior_, context files
|
||||
(defaulting to `GEMINI.md` but configurable via the `contextFileName` setting)
|
||||
are crucial for configuring the _instructional context_ (also referred to as
|
||||
"memory") provided to the Gemini model. This powerful feature allows you to give
|
||||
project-specific instructions, coding style guides, or any relevant background
|
||||
information to the AI, making its responses more tailored and accurate to your
|
||||
needs. The CLI includes UI elements, such as an indicator in the footer showing
|
||||
the number of loaded context files, to keep you informed about the active
|
||||
context.
|
||||
|
||||
- **Purpose:** These Markdown files contain instructions, guidelines, or context
|
||||
that you want the Gemini model to be aware of during your interactions. The
|
||||
system is designed to manage this instructional context hierarchically.
|
||||
|
||||
### Example context file content (e.g., `GEMINI.md`)
|
||||
|
||||
Here's a conceptual example of what a context file at the root of a TypeScript
|
||||
project might contain:
|
||||
|
||||
```markdown
|
||||
# Project: My Awesome TypeScript Library
|
||||
|
||||
## General Instructions:
|
||||
|
||||
- When generating new TypeScript code, please follow the existing coding style.
|
||||
- Ensure all new functions and classes have JSDoc comments.
|
||||
- Prefer functional programming paradigms where appropriate.
|
||||
- All code should be compatible with TypeScript 5.0 and Node.js 20+.
|
||||
|
||||
## Coding Style:
|
||||
|
||||
- Use 2 spaces for indentation.
|
||||
- Interface names should be prefixed with `I` (e.g., `IUserService`).
|
||||
- Private class members should be prefixed with an underscore (`_`).
|
||||
- Always use strict equality (`===` and `!==`).
|
||||
|
||||
## Specific Component: `src/api/client.ts`
|
||||
|
||||
- This file handles all outbound API requests.
|
||||
- When adding new API call functions, ensure they include robust error handling
|
||||
and logging.
|
||||
- Use the existing `fetchWithRetry` utility for all GET requests.
|
||||
|
||||
## Regarding Dependencies:
|
||||
|
||||
- Avoid introducing new external dependencies unless absolutely necessary.
|
||||
- If a new dependency is required, please state the reason.
|
||||
```
|
||||
|
||||
This example demonstrates how you can provide general project context, specific
|
||||
coding conventions, and even notes about particular files or components. The
|
||||
more relevant and precise your context files are, the better the AI can assist
|
||||
you. Project-specific context files are highly encouraged to establish
|
||||
conventions and context.
|
||||
|
||||
- **Hierarchical loading and precedence:** The CLI implements a sophisticated
|
||||
hierarchical memory system by loading context files (e.g., `GEMINI.md`) from
|
||||
several locations. Content from files lower in this list (more specific)
|
||||
typically overrides or supplements content from files higher up (more
|
||||
general). The exact concatenation order and final context can be inspected
|
||||
using the `/memory show` command. The typical loading order is:
|
||||
1. **Global context file:**
|
||||
- Location: `~/.gemini/<contextFileName>` (e.g., `~/.gemini/GEMINI.md` in
|
||||
your user home directory).
|
||||
- Scope: Provides default instructions for all your projects.
|
||||
2. **Project root and ancestors context files:**
|
||||
- Location: The CLI searches for the configured context file in the
|
||||
current working directory and then in each parent directory up to either
|
||||
the project root (identified by a `.git` folder) or your home directory.
|
||||
- Scope: Provides context relevant to the entire project or a significant
|
||||
portion of it.
|
||||
3. **Sub-directory context files (contextual/local):**
|
||||
- Location: The CLI also scans for the configured context file in
|
||||
subdirectories _below_ the current working directory (respecting common
|
||||
ignore patterns like `node_modules`, `.git`, etc.). The breadth of this
|
||||
search is limited to 200 directories by default, but can be configured
|
||||
with a `memoryDiscoveryMaxDirs` field in your `settings.json` file.
|
||||
- Scope: Allows for highly specific instructions relevant to a particular
|
||||
component, module, or subsection of your project.
|
||||
- **Concatenation and UI indication:** The contents of all found context files
|
||||
are concatenated (with separators indicating their origin and path) and
|
||||
provided as part of the system prompt to the Gemini model. The CLI footer
|
||||
displays the count of loaded context files, giving you a quick visual cue
|
||||
about the active instructional context.
|
||||
- **Importing content:** You can modularize your context files by importing
|
||||
other Markdown files using the `@path/to/file.md` syntax. For more details,
|
||||
see the [Memory Import Processor documentation](../core/memport.md).
|
||||
- **Commands for memory management:**
|
||||
- Use `/memory refresh` to force a re-scan and reload of all context files
|
||||
from all configured locations. This updates the AI's instructional context.
|
||||
- Use `/memory show` to display the combined instructional context currently
|
||||
loaded, allowing you to verify the hierarchy and content being used by the
|
||||
AI.
|
||||
- See the [Commands documentation](./commands.md#memory) for full details on
|
||||
the `/memory` command and its sub-commands (`show` and `refresh`).
|
||||
|
||||
By understanding and utilizing these configuration layers and the hierarchical
|
||||
nature of context files, you can effectively manage the AI's memory and tailor
|
||||
the Gemini CLI's responses to your specific needs and projects.
|
||||
|
||||
## Sandboxing
|
||||
|
||||
The Gemini CLI can execute potentially unsafe operations (like shell commands
|
||||
and file modifications) within a sandboxed environment to protect your system.
|
||||
|
||||
Sandboxing is disabled by default, but you can enable it in a few ways:
|
||||
|
||||
- Using `--sandbox` or `-s` flag.
|
||||
- Setting `GEMINI_SANDBOX` environment variable.
|
||||
- Sandbox is enabled in `--yolo` mode by default.
|
||||
|
||||
By default, it uses a pre-built `gemini-cli-sandbox` Docker image.
|
||||
|
||||
For project-specific sandboxing needs, you can create a custom Dockerfile at
|
||||
`.gemini/sandbox.Dockerfile` in your project's root directory. This Dockerfile
|
||||
can be based on the base sandbox image:
|
||||
|
||||
```dockerfile
|
||||
FROM gemini-cli-sandbox
|
||||
|
||||
# Add your custom dependencies or configurations here
|
||||
# For example:
|
||||
# RUN apt-get update && apt-get install -y some-package
|
||||
# COPY ./my-config /app/my-config
|
||||
```
|
||||
|
||||
When `.gemini/sandbox.Dockerfile` exists, you can use `BUILD_SANDBOX`
|
||||
environment variable when running Gemini CLI to automatically build the custom
|
||||
sandbox image:
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
@@ -38,6 +38,8 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
|
||||
files and directories from being accessed by tools.
|
||||
- **[Context files (GEMINI.md)](./gemini-md.md):** Provide persistent,
|
||||
hierarchical context to the model.
|
||||
- **[System prompt override](./system-prompt.md):** Replace the built‑in system
|
||||
instructions using `GEMINI_SYSTEM_MD`.
|
||||
|
||||
## Non-interactive mode
|
||||
|
||||
|
||||
@@ -15,19 +15,28 @@ available combinations.
|
||||
|
||||
#### Cursor Movement
|
||||
|
||||
| Action | Keys |
|
||||
| ----------------------------------------- | ---------------------- |
|
||||
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` |
|
||||
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End` |
|
||||
| Action | Keys |
|
||||
| ------------------------------------------- | ------------------------------------------------------------ |
|
||||
| Move the cursor to the start of the line. | `Ctrl + A`<br />`Home` |
|
||||
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End` |
|
||||
| Move the cursor one character to the left. | `Left Arrow (no Ctrl, no Cmd)`<br />`Ctrl + B` |
|
||||
| Move the cursor one character to the right. | `Right Arrow (no Ctrl, no Cmd)`<br />`Ctrl + F` |
|
||||
| Move the cursor one word to the left. | `Ctrl + Left Arrow`<br />`Cmd + Left Arrow`<br />`Cmd + B` |
|
||||
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Cmd + Right Arrow`<br />`Cmd + F` |
|
||||
|
||||
#### Editing
|
||||
|
||||
| Action | Keys |
|
||||
| ------------------------------------------------ | ----------------------------------------- |
|
||||
| Delete from the cursor to the end of the line. | `Ctrl + K` |
|
||||
| Delete from the cursor to the start of the line. | `Ctrl + U` |
|
||||
| Clear all text in the input field. | `Ctrl + C` |
|
||||
| Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace` |
|
||||
| Action | Keys |
|
||||
| ------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
||||
| Delete from the cursor to the end of the line. | `Ctrl + K` |
|
||||
| Delete from the cursor to the start of the line. | `Ctrl + U` |
|
||||
| Clear all text in the input field. | `Ctrl + C` |
|
||||
| Delete the previous word. | `Ctrl + Backspace`<br />`Cmd + Backspace`<br />`Ctrl + ""`<br />`Cmd + ""`<br />`Ctrl + W` |
|
||||
| Delete the next word. | `Ctrl + Delete`<br />`Cmd + Delete` |
|
||||
| Delete the character to the left. | `Backspace`<br />`""`<br />`Ctrl + H` |
|
||||
| Delete the character to the right. | `Delete`<br />`Ctrl + D` |
|
||||
| Undo the most recent text edit. | `Ctrl + Z (no Shift)` |
|
||||
| Redo the most recent undone text edit. | `Ctrl + Shift + Z` |
|
||||
|
||||
#### Screen Control
|
||||
|
||||
@@ -53,7 +62,7 @@ available combinations.
|
||||
| Show the previous entry in history. | `Ctrl + P (no Shift)` |
|
||||
| Show the next entry in history. | `Ctrl + N (no Shift)` |
|
||||
| Start reverse search through history. | `Ctrl + R` |
|
||||
| Insert the selected reverse-search match. | `Enter (no Ctrl)` |
|
||||
| Submit the selected reverse-search match. | `Enter (no Ctrl)` |
|
||||
| Accept a suggestion while reverse searching. | `Tab` |
|
||||
|
||||
#### Navigation
|
||||
@@ -84,22 +93,25 @@ available combinations.
|
||||
|
||||
#### External Tools
|
||||
|
||||
| Action | Keys |
|
||||
| ---------------------------------------------- | ---------- |
|
||||
| Open the current prompt in an external editor. | `Ctrl + X` |
|
||||
| Paste from the clipboard. | `Ctrl + V` |
|
||||
| Action | Keys |
|
||||
| ---------------------------------------------- | ------------------------- |
|
||||
| Open the current prompt in an external editor. | `Ctrl + X` |
|
||||
| Paste from the clipboard. | `Ctrl + V`<br />`Cmd + V` |
|
||||
|
||||
#### App Controls
|
||||
|
||||
| Action | Keys |
|
||||
| ----------------------------------------------------------------- | ---------- |
|
||||
| Toggle detailed error information. | `F12` |
|
||||
| Toggle the full TODO list. | `Ctrl + T` |
|
||||
| Toggle IDE context details. | `Ctrl + G` |
|
||||
| Toggle Markdown rendering. | `Cmd + M` |
|
||||
| Toggle copy mode when the terminal is using the alternate buffer. | `Ctrl + S` |
|
||||
| Expand a height-constrained response to show additional lines. | `Ctrl + S` |
|
||||
| Toggle focus between the shell and Gemini input. | `Ctrl + F` |
|
||||
| Action | Keys |
|
||||
| ----------------------------------------------------------------- | ---------------- |
|
||||
| Toggle detailed error information. | `F12` |
|
||||
| Toggle the full TODO list. | `Ctrl + T` |
|
||||
| Show IDE context details. | `Ctrl + G` |
|
||||
| Toggle Markdown rendering. | `Cmd + M` |
|
||||
| Toggle copy mode when the terminal is using the alternate buffer. | `Ctrl + S` |
|
||||
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` |
|
||||
| Toggle Auto Edit (auto-accept edits) mode. | `Shift + Tab` |
|
||||
| Expand a height-constrained response to show additional lines. | `Ctrl + S` |
|
||||
| Focus the shell input from the gemini input. | `Tab (no Shift)` |
|
||||
| Focus the Gemini input from the shell input. | `Tab` |
|
||||
|
||||
#### Session Control
|
||||
|
||||
@@ -112,30 +124,11 @@ available combinations.
|
||||
|
||||
## Additional context-specific shortcuts
|
||||
|
||||
- `Ctrl+Y`: Toggle YOLO (auto-approval) mode for tool calls.
|
||||
- `Shift+Tab`: Toggle Auto Edit (auto-accept edits) mode.
|
||||
- `Option+M` (macOS): Entering `µ` with Option+M also toggles Markdown
|
||||
rendering, matching `Cmd+M`.
|
||||
- `Option+B/F/M` (macOS only): Are interpreted as `Cmd+B/F/M` even if your
|
||||
terminal isn't configured to send Meta with Option.
|
||||
- `!` on an empty prompt: Enter or exit shell mode.
|
||||
- `\` (at end of a line) + `Enter`: Insert a newline without leaving single-line
|
||||
mode.
|
||||
- `Ctrl+Delete` / `Meta+Delete`: Delete the word to the right of the cursor.
|
||||
- `Ctrl+B` or `Left Arrow`: Move the cursor one character to the left while
|
||||
editing text.
|
||||
- `Ctrl+F` or `Right Arrow`: Move the cursor one character to the right; with an
|
||||
embedded shell attached, `Ctrl+F` still toggles focus.
|
||||
- `Ctrl+D` or `Delete`: Remove the character immediately to the right of the
|
||||
cursor.
|
||||
- `Ctrl+H` or `Backspace`: Remove the character immediately to the left of the
|
||||
cursor.
|
||||
- `Ctrl+Left Arrow` / `Meta+Left Arrow` / `Meta+B`: Move one word to the left.
|
||||
- `Ctrl+Right Arrow` / `Meta+Right Arrow` / `Meta+F`: Move one word to the
|
||||
right.
|
||||
- `Ctrl+W`: Delete the word to the left of the cursor (in addition to
|
||||
`Ctrl+Backspace` / `Cmd+Backspace`).
|
||||
- `Ctrl+Z` / `Ctrl+Shift+Z`: Undo or redo the most recent text edit.
|
||||
- `Meta+Enter`: Open the current input in an external editor (alias for
|
||||
`Ctrl+X`).
|
||||
- `Esc` pressed twice quickly: Clear the current input buffer.
|
||||
- `Up Arrow` / `Down Arrow`: When the cursor is at the top or bottom of a
|
||||
single-line input, navigate backward or forward through prompt history.
|
||||
|
||||
+12
-16
@@ -1,4 +1,4 @@
|
||||
## Model routing
|
||||
# Model routing
|
||||
|
||||
Gemini CLI includes a model routing feature that automatically switches to a
|
||||
fallback model in case of a model failure. This feature is enabled by default
|
||||
@@ -6,24 +6,20 @@ and provides resilience when the primary model is unavailable.
|
||||
|
||||
## How it works
|
||||
|
||||
Model routing is not based on prompt complexity, but is a fallback mechanism.
|
||||
Here's how it works:
|
||||
Model routing is managed by the `ModelAvailabilityService`, which monitors model
|
||||
health and automatically routes requests to available models based on defined
|
||||
policies.
|
||||
|
||||
1. **Model failure:** If the currently selected model fails to respond (for
|
||||
example, due to a server error or other issue), the CLI will initiate the
|
||||
fallback process.
|
||||
1. **Model failure:** If the currently selected model fails (e.g., due to quota
|
||||
or server errors), the CLI will iniate the fallback process.
|
||||
|
||||
2. **User consent:** The CLI will prompt you to ask if you want to switch to
|
||||
the fallback model. This is handled by the `fallbackModelHandler`.
|
||||
2. **User consent:** Depending on the failure and the model's policy, the CLI
|
||||
may prompt you to switch to a fallback model (by default always prompts
|
||||
you).
|
||||
|
||||
3. **Fallback activation:** If you consent, the CLI will activate the fallback
|
||||
mode by calling `config.setFallbackMode(true)`.
|
||||
|
||||
4. **Model switch:** On the next request, the CLI will use the
|
||||
`DEFAULT_GEMINI_FLASH_MODEL` as the fallback model. This is handled by the
|
||||
`resolveModel` function in
|
||||
`packages/cli/src/zed-integration/zedIntegration.ts` which checks if
|
||||
`isInFallbackMode()` is true.
|
||||
3. **Model switch:** If approved, or if the policy allows for silent fallback,
|
||||
the CLI will use an available fallback model for the current turn or the
|
||||
remainder of the session.
|
||||
|
||||
### Model selection precedence
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ To enable Gemini 3 Pro and Gemini 3 Flash (if available), enable
|
||||
|
||||
You can also use the `--model` flag to specify a particular Gemini model on
|
||||
startup. For more details, refer to the
|
||||
[configuration documentation](./configuration.md).
|
||||
[configuration documentation](../get-started/configuration.md).
|
||||
|
||||
Changes to these settings will be applied to all subsequent interactions with
|
||||
Gemini CLI.
|
||||
|
||||
+69
-51
@@ -18,45 +18,50 @@ Note: Workspace settings override user settings.
|
||||
Here is a list of all the available settings, grouped by category and ordered as
|
||||
they appear in the UI.
|
||||
|
||||
<!-- SETTINGS-AUTOGEN:START -->
|
||||
|
||||
### General
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- | ----------- |
|
||||
| Preview Features (e.g., models) | `general.previewFeatures` | Enable preview features (e.g., preview models). | `false` |
|
||||
| Vim Mode | `general.vimMode` | Enable Vim keybindings. | `false` |
|
||||
| Disable Auto Update | `general.disableAutoUpdate` | Disable automatic updates. | `false` |
|
||||
| Enable Prompt Completion | `general.enablePromptCompletion` | Enable AI-powered prompt completion suggestions while typing. | `false` |
|
||||
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
|
||||
| Session Retention | `general.sessionRetention` | Settings for automatic session cleanup. This feature is disabled by default. | `undefined` |
|
||||
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup. | `false` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------- | ---------------------------------- | ------------------------------------------------------------- | ------- |
|
||||
| Preview Features (e.g., models) | `general.previewFeatures` | Enable preview features (e.g., preview models). | `false` |
|
||||
| Vim Mode | `general.vimMode` | Enable Vim keybindings | `false` |
|
||||
| Disable Auto Update | `general.disableAutoUpdate` | Disable automatic updates | `false` |
|
||||
| Enable Prompt Completion | `general.enablePromptCompletion` | Enable AI-powered prompt completion suggestions while typing. | `false` |
|
||||
| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` |
|
||||
| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup | `false` |
|
||||
|
||||
### Output
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------- | --------------- | ------------------------------------------------------ | ------- |
|
||||
| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `text` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------- | --------------- | ------------------------------------------------------ | -------- |
|
||||
| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `"text"` |
|
||||
|
||||
### UI
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------ | ---------------------------------------- | -------------------------------------------------------------------- | ------- |
|
||||
| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar. | `false` |
|
||||
| Show Status in Title | `ui.showStatusInTitle` | Show Gemini CLI status and thoughts in the terminal window title. | `false` |
|
||||
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI. | `false` |
|
||||
| Hide Banner | `ui.hideBanner` | Hide the application banner. | `false` |
|
||||
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
|
||||
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` |
|
||||
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
|
||||
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
|
||||
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
|
||||
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI. | `false` |
|
||||
| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI. | `false` |
|
||||
| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `false` |
|
||||
| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` |
|
||||
| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` |
|
||||
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `true` |
|
||||
| Disable Loading Phrases | `ui.accessibility.disableLoadingPhrases` | Disable loading phrases for accessibility. | `false` |
|
||||
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible. | `false` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar | `false` |
|
||||
| Show Thoughts in Title | `ui.showStatusInTitle` | Show Gemini CLI model thoughts in the terminal window title during the working phase | `false` |
|
||||
| Dynamic Window Title | `ui.dynamicWindowTitle` | Update the terminal window title with current status icons (Ready: ◇, Action Required: ✋, Working: ✦) | `true` |
|
||||
| Show Home Directory Warning | `ui.showHomeDirectoryWarning` | Show a warning when running Gemini CLI in the home directory. | `true` |
|
||||
| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI | `false` |
|
||||
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
|
||||
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
|
||||
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` |
|
||||
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
|
||||
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
|
||||
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` |
|
||||
| Hide Footer | `ui.hideFooter` | Hide the footer from the UI | `false` |
|
||||
| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI | `false` |
|
||||
| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `true` |
|
||||
| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` |
|
||||
| Show Model Info In Chat | `ui.showModelInfoInChat` | Show the model name in the chat for each model turn. | `false` |
|
||||
| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` |
|
||||
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
|
||||
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
||||
| Disable Loading Phrases | `ui.accessibility.disableLoadingPhrases` | Disable loading phrases for accessibility | `false` |
|
||||
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |
|
||||
|
||||
### IDE
|
||||
|
||||
@@ -69,7 +74,7 @@ they appear in the UI.
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ------- |
|
||||
| Max Session Turns | `model.maxSessionTurns` | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` |
|
||||
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.2` |
|
||||
| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.5` |
|
||||
| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` |
|
||||
|
||||
### Context
|
||||
@@ -85,28 +90,41 @@ they appear in the UI.
|
||||
|
||||
### Tools
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` |
|
||||
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `10000` |
|
||||
| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `100` |
|
||||
| Enable Message Bus Integration | `tools.enableMessageBusIntegration` | Enable policy-based tool confirmation via message bus integration. | `true` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` |
|
||||
| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `4000000` |
|
||||
| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `1000` |
|
||||
| Disable LLM Correction | `tools.disableLLMCorrection` | Disable LLM-based error correction for edit tools. When enabled, tools will fail immediately if exact string matches are not found, instead of attempting to self-correct. | `false` |
|
||||
|
||||
### Security
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------- | ------------------------------ | -------------------------------------------------- | ------- |
|
||||
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
||||
| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` |
|
||||
| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `false` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------- | ------- |
|
||||
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
||||
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
|
||||
| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` |
|
||||
| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `false` |
|
||||
| Enable Environment Variable Redaction | `security.environmentVariableRedaction.enabled` | Enable redaction of environment variables that may contain secrets. | `false` |
|
||||
|
||||
### Experimental
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ----------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | ------- |
|
||||
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
|
||||
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------- |
|
||||
| Agent Skills | `experimental.skills` | Enable Agent Skills (experimental). | `false` |
|
||||
| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` |
|
||||
| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` |
|
||||
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions). | `false` |
|
||||
| Enable CLI Help Agent | `experimental.cliHelpAgentSettings.enabled` | Enable the CLI Help Agent. | `true` |
|
||||
|
||||
### Hooks
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------ | --------------------- | ------------------------------------------------ | ------- |
|
||||
| Hook Notifications | `hooks.notifications` | Show visual indicators when hooks are executing. | `true` |
|
||||
|
||||
<!-- SETTINGS-AUTOGEN:END -->
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# Agent Skills
|
||||
|
||||
_Note: This is an experimental feature enabled via `experimental.skills`. You
|
||||
can also search for "Skills" within the `/settings` interactive UI to toggle
|
||||
this and manage other skill-related settings._
|
||||
|
||||
Agent Skills allow you to extend Gemini CLI with specialized expertise,
|
||||
procedural workflows, and task-specific resources. Based on the
|
||||
[Agent Skills](https://agentskills.io) open standard, a "skill" is a
|
||||
self-contained directory that packages instructions and assets into a
|
||||
discoverable capability.
|
||||
|
||||
## Overview
|
||||
|
||||
Unlike general context files ([`GEMINI.md`](./gemini-md.md)), which provide
|
||||
persistent project-wide background, Skills represent **on-demand expertise**.
|
||||
This allows Gemini to maintain a vast library of specialized capabilities—such
|
||||
as security auditing, cloud deployments, or codebase migrations—without
|
||||
cluttering the model's immediate context window.
|
||||
|
||||
Gemini autonomously decides when to employ a skill based on your request and the
|
||||
skill's description. When a relevant skill is identified, the model "pulls in"
|
||||
the full instructions and resources required to complete the task using the
|
||||
`activate_skill` tool.
|
||||
|
||||
## Key Benefits
|
||||
|
||||
- **Shared Expertise:** Package complex workflows (like a specific team's PR
|
||||
review process) into a folder that anyone can use.
|
||||
- **Repeatable Workflows:** Ensure complex multi-step tasks are performed
|
||||
consistently by providing a procedural framework.
|
||||
- **Resource Bundling:** Include scripts, templates, or example data alongside
|
||||
instructions so the agent has everything it needs.
|
||||
- **Progressive Disclosure:** Only skill metadata (name and description) is
|
||||
loaded initially. Detailed instructions and resources are only disclosed when
|
||||
the model explicitly activates the skill, saving context tokens.
|
||||
|
||||
## Skill Discovery Tiers
|
||||
|
||||
Gemini CLI discovers skills from three primary locations:
|
||||
|
||||
1. **Project Skills** (`.gemini/skills/`): Project-specific skills that are
|
||||
typically committed to version control and shared with the team.
|
||||
2. **User Skills** (`~/.gemini/skills/`): Personal skills available across all
|
||||
your projects.
|
||||
3. **Extension Skills**: Skills bundled within installed
|
||||
[extensions](../extensions/index.md).
|
||||
|
||||
**Precedence:** If multiple skills share the same name, higher-precedence
|
||||
locations override lower ones: **Project > User > Extension**.
|
||||
|
||||
## Managing Skills
|
||||
|
||||
### In an Interactive Session
|
||||
|
||||
Use the `/skills` slash command to view and manage available expertise:
|
||||
|
||||
- `/skills list` (default): Shows all discovered skills and their status.
|
||||
- `/skills disable <name>`: Prevents a specific skill from being used.
|
||||
- `/skills enable <name>`: Re-enables a disabled skill.
|
||||
- `/skills reload`: Refreshes the list of discovered skills from all tiers.
|
||||
|
||||
_Note: `/skills disable` and `/skills enable` default to the `user` scope. Use
|
||||
`--scope project` to manage project-specific settings._
|
||||
|
||||
### From the Terminal
|
||||
|
||||
The `gemini skills` command provides management utilities:
|
||||
|
||||
```bash
|
||||
# List all discovered skills
|
||||
gemini skills list
|
||||
|
||||
# Install a skill from a Git repository, local directory, or zipped skill file (.skill)
|
||||
# Uses the user scope by default (~/.gemini/skills)
|
||||
gemini skills install https://github.com/user/repo.git
|
||||
gemini skills install /path/to/local/skill
|
||||
gemini skills install /path/to/local/my-expertise.skill
|
||||
|
||||
# Install a specific skill from a monorepo or subdirectory using --path
|
||||
gemini skills install https://github.com/my-org/my-skills.git --path skills/frontend-design
|
||||
|
||||
# Install to the workspace scope (.gemini/skills)
|
||||
gemini skills install /path/to/skill --scope workspace
|
||||
|
||||
# Uninstall a skill by name
|
||||
gemini skills uninstall my-expertise --scope workspace
|
||||
|
||||
# Enable a skill (globally)
|
||||
gemini skills enable my-expertise
|
||||
|
||||
# Disable a skill. Can use --scope to specify project or user (defaults to project)
|
||||
gemini skills disable my-expertise --scope project
|
||||
```
|
||||
|
||||
## Creating a Skill
|
||||
|
||||
A skill is a directory containing a `SKILL.md` file at its root. This file uses
|
||||
YAML frontmatter for metadata and Markdown for instructions.
|
||||
|
||||
### Folder Structure
|
||||
|
||||
Skills are self-contained directories. At a minimum, a skill requires a
|
||||
`SKILL.md` file, but can include other resources:
|
||||
|
||||
```text
|
||||
my-skill/
|
||||
├── SKILL.md (Required) Instructions and metadata
|
||||
├── scripts/ (Optional) Executable scripts/tools
|
||||
├── references/ (Optional) Static documentation and examples
|
||||
└── assets/ (Optional) Templates and binary resources
|
||||
```
|
||||
|
||||
### Basic Structure (SKILL.md)
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: <unique-name>
|
||||
description: <what the skill does and when Gemini should use it>
|
||||
---
|
||||
|
||||
<your instructions for how the agent should behave / use the skill>
|
||||
```
|
||||
|
||||
- **`name`**: A unique identifier (lowercase, alphanumeric, and dashes).
|
||||
- **`description`**: The most critical field. Gemini uses this to decide when
|
||||
the skill is relevant. Be specific about the expertise provided.
|
||||
- **Body**: Everything below the second `---` is injected as expert procedural
|
||||
guidance for the model.
|
||||
|
||||
### Example: Team Code Reviewer
|
||||
|
||||
Create `~/.gemini/skills/code-reviewer/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: code-reviewer
|
||||
description:
|
||||
Expertise in reviewing code for style, security, and performance. Use when the
|
||||
user asks for "feedback," a "review," or to "check" their changes.
|
||||
---
|
||||
|
||||
# Code Reviewer
|
||||
|
||||
You are an expert code reviewer. When reviewing code, follow this workflow:
|
||||
|
||||
1. **Analyze**: Review the staged changes or specific files provided. Ensure
|
||||
that the changes are scoped properly and represent minimal changes required
|
||||
to address the issue.
|
||||
2. **Style**: Ensure code follows the project's conventions and idiomatic
|
||||
patterns as described in the `GEMINI.md` file.
|
||||
3. **Security**: Flag any potential security vulnerabilities.
|
||||
4. **Tests**: Verify that new logic has corresponding test coverage and that
|
||||
the test coverage adequately validates the changes.
|
||||
|
||||
Provide your feedback as a concise bulleted list of "Strengths" and
|
||||
"Opportunities."
|
||||
```
|
||||
|
||||
### Resource Conventions
|
||||
|
||||
While you can structure your skill directory however you like, the Agent Skills
|
||||
standard encourages these conventions:
|
||||
|
||||
- **`scripts/`**: Executable scripts (bash, python, node) the agent can run.
|
||||
- **`references/`**: Static documentation, schemas, or example data for the
|
||||
agent to consult.
|
||||
- **`assets/`**: Code templates, boilerplate, or binary resources.
|
||||
|
||||
When a skill is activated, Gemini CLI provides the model with a tree view of the
|
||||
entire skill directory, allowing it to discover and utilize these assets.
|
||||
|
||||
## How it Works (Security & Privacy)
|
||||
|
||||
1. **Discovery**: At the start of a session, Gemini CLI scans the discovery
|
||||
tiers and injects the name and description of all enabled skills into the
|
||||
system prompt.
|
||||
2. **Activation**: When Gemini identifies a task matching a skill's
|
||||
description, it calls the `activate_skill` tool.
|
||||
3. **Consent**: You will see a confirmation prompt in the UI detailing the
|
||||
skill's name, purpose, and the directory path it will gain access to.
|
||||
4. **Injection**: Upon your approval:
|
||||
- The `SKILL.md` body and folder structure is added to the conversation
|
||||
history.
|
||||
- The skill's directory is added to the agent's allowed file paths, granting
|
||||
it permission to read any bundled assets.
|
||||
5. **Execution**: The model proceeds with the specialized expertise active. It
|
||||
is instructed to prioritize the skill's procedural guidance within reason.
|
||||
@@ -0,0 +1,93 @@
|
||||
# System Prompt Override (GEMINI_SYSTEM_MD)
|
||||
|
||||
The core system instructions that guide Gemini CLI can be completely replaced
|
||||
with your own Markdown file. This feature is controlled via the
|
||||
`GEMINI_SYSTEM_MD` environment variable.
|
||||
|
||||
## Overview
|
||||
|
||||
The `GEMINI_SYSTEM_MD` variable instructs the CLI to use an external Markdown
|
||||
file for its system prompt, completely overriding the built-in default. This is
|
||||
a full replacement, not a merge. If you use a custom file, none of the original
|
||||
core instructions will apply unless you include them yourself.
|
||||
|
||||
This feature is intended for advanced users who need to enforce strict,
|
||||
project-specific behavior or create a customized persona.
|
||||
|
||||
> Tip: You can export the current default system prompt to a file first, review
|
||||
> it, and then selectively modify or replace it (see
|
||||
> [“Export the default prompt”](#export-the-default-prompt-recommended)).
|
||||
|
||||
## How to enable
|
||||
|
||||
You can set the environment variable temporarily in your shell, or persist it
|
||||
via a `.gemini/.env` file. See
|
||||
[Persisting Environment Variables](../get-started/authentication.md#persisting-environment-variables).
|
||||
|
||||
- Use the project default path (`.gemini/system.md`):
|
||||
- `GEMINI_SYSTEM_MD=true` or `GEMINI_SYSTEM_MD=1`
|
||||
- The CLI reads `./.gemini/system.md` (relative to your current project
|
||||
directory).
|
||||
|
||||
- Use a custom file path:
|
||||
- `GEMINI_SYSTEM_MD=/absolute/path/to/my-system.md`
|
||||
- Relative paths are supported and resolved from the current working
|
||||
directory.
|
||||
- Tilde expansion is supported (e.g., `~/my-system.md`).
|
||||
|
||||
- Disable the override (use built‑in prompt):
|
||||
- `GEMINI_SYSTEM_MD=false` or `GEMINI_SYSTEM_MD=0` or unset the variable.
|
||||
|
||||
If the override is enabled but the target file does not exist, the CLI will
|
||||
error with: `missing system prompt file '<path>'`.
|
||||
|
||||
## Quick examples
|
||||
|
||||
- One‑off session using a project file:
|
||||
- `GEMINI_SYSTEM_MD=1 gemini`
|
||||
- Persist for a project using `.gemini/.env`:
|
||||
- Create `.gemini/system.md`, then add to `.gemini/.env`:
|
||||
- `GEMINI_SYSTEM_MD=1`
|
||||
- Use a custom file under your home directory:
|
||||
- `GEMINI_SYSTEM_MD=~/prompts/SYSTEM.md gemini`
|
||||
|
||||
## UI indicator
|
||||
|
||||
When `GEMINI_SYSTEM_MD` is active, the CLI shows a `|⌐■_■|` indicator in the UI
|
||||
to signal custom system‑prompt mode.
|
||||
|
||||
## Export the default prompt (recommended)
|
||||
|
||||
Before overriding, export the current default prompt so you can review required
|
||||
safety and workflow rules.
|
||||
|
||||
- Write the built‑in prompt to the project default path:
|
||||
- `GEMINI_WRITE_SYSTEM_MD=1 gemini`
|
||||
- Or write to a custom path:
|
||||
- `GEMINI_WRITE_SYSTEM_MD=~/prompts/DEFAULT_SYSTEM.md gemini`
|
||||
|
||||
This creates the file and writes the current built‑in system prompt to it.
|
||||
|
||||
## Best practices: SYSTEM.md vs GEMINI.md
|
||||
|
||||
- SYSTEM.md (firmware):
|
||||
- Non‑negotiable operational rules: safety, tool‑use protocols, approvals, and
|
||||
mechanics that keep the CLI reliable.
|
||||
- Stable across tasks and projects (or per project when needed).
|
||||
- GEMINI.md (strategy):
|
||||
- Persona, goals, methodologies, and project/domain context.
|
||||
- Evolves per task; relies on SYSTEM.md for safe execution.
|
||||
|
||||
Keep SYSTEM.md minimal but complete for safety and tool operation. Keep
|
||||
GEMINI.md focused on high‑level guidance and project specifics.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Error: `missing system prompt file '…'`
|
||||
- Ensure the referenced path exists and is readable.
|
||||
- For `GEMINI_SYSTEM_MD=1|true`, create `./.gemini/system.md` in your project.
|
||||
- Override not taking effect
|
||||
- Confirm the variable is loaded (use `.gemini/.env` or export in your shell).
|
||||
- Paths are resolved from the current working directory; try an absolute path.
|
||||
- Restore defaults
|
||||
- Unset `GEMINI_SYSTEM_MD` or set it to `0`/`false`.
|
||||
+23
-3
@@ -8,8 +8,10 @@ Learn how to enable and setup OpenTelemetry for Gemini CLI.
|
||||
- [Configuration](#configuration)
|
||||
- [Google Cloud telemetry](#google-cloud-telemetry)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Authenticating with CLI Credentials](#authenticating-with-cli-credentials)
|
||||
- [Direct export (recommended)](#direct-export-recommended)
|
||||
- [Collector-based export (advanced)](#collector-based-export-advanced)
|
||||
- [Monitoring Dashboards](#monitoring-dashboards)
|
||||
- [Local telemetry](#local-telemetry)
|
||||
- [File-based output (recommended)](#file-based-output-recommended)
|
||||
- [Collector-based export (advanced)](#collector-based-export-advanced-1)
|
||||
@@ -213,6 +215,24 @@ forward data to Google Cloud.
|
||||
- Open `~/.gemini/tmp/<projectHash>/otel/collector-gcp.log` to view local
|
||||
collector logs.
|
||||
|
||||
### Monitoring Dashboards
|
||||
|
||||
Gemini CLI provides a pre-configured
|
||||
[Google Cloud Monitoring](https://cloud.google.com/monitoring) dashboard to
|
||||
visualize your telemetry.
|
||||
|
||||
This dashboard can be found under **Google Cloud Monitoring Dashboard
|
||||
Templates** as "**Gemini CLI Monitoring**".
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
To learn more, check out this blog post:
|
||||
[Instant insights: Gemini CLI’s new pre-configured monitoring dashboards](https://cloud.google.com/blog/topics/developers-practitioners/instant-insights-gemini-clis-new-pre-configured-monitoring-dashboards/).
|
||||
|
||||
## Local telemetry
|
||||
|
||||
For local development and debugging, you can capture telemetry data locally:
|
||||
@@ -297,7 +317,7 @@ Captures startup configuration and user prompt submissions.
|
||||
|
||||
#### Tools
|
||||
|
||||
Captures tool executions, output truncation, and Smart Edit behavior.
|
||||
Captures tool executions, output truncation, and Edit behavior.
|
||||
|
||||
- `gemini_cli.tool_call`: Emitted for each tool (function) call.
|
||||
- **Attributes**:
|
||||
@@ -325,11 +345,11 @@ Captures tool executions, output truncation, and Smart Edit behavior.
|
||||
- `lines` (int)
|
||||
- `prompt_id` (string)
|
||||
|
||||
- `gemini_cli.smart_edit_strategy`: Smart Edit strategy chosen.
|
||||
- `gemini_cli.edit_strategy`: Edit strategy chosen.
|
||||
- **Attributes**:
|
||||
- `strategy` (string)
|
||||
|
||||
- `gemini_cli.smart_edit_correction`: Smart Edit correction result.
|
||||
- `gemini_cli.edit_correction`: Edit correction result.
|
||||
- **Attributes**:
|
||||
- `correction` ("success" | "failure")
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
This page contains tutorials for interacting with Gemini CLI.
|
||||
|
||||
## Agent Skills
|
||||
|
||||
- [Getting Started with Agent Skills](./tutorials/skills-getting-started.md)
|
||||
|
||||
## Setting up a Model Context Protocol (MCP) server
|
||||
|
||||
> [!CAUTION] Before using a third-party MCP server, ensure you trust its source
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# Getting Started with Agent Skills
|
||||
|
||||
Agent Skills allow you to extend Gemini CLI with specialized expertise. This
|
||||
tutorial will guide you through creating your first skill, enabling it, and
|
||||
using it in a session.
|
||||
|
||||
## 1. Enable Agent Skills
|
||||
|
||||
Agent Skills are currently an experimental feature and must be enabled in your
|
||||
settings.
|
||||
|
||||
### Via the interactive UI
|
||||
|
||||
1. Start a Gemini CLI session by running `gemini`.
|
||||
2. Type `/settings` to open the interactive settings dialog.
|
||||
3. Search for "Skills".
|
||||
4. Toggle **Agent Skills** to `true`.
|
||||
5. Press `Esc` to save and exit. You may need to restart the CLI for the
|
||||
changes to take effect.
|
||||
|
||||
### Via `settings.json`
|
||||
|
||||
Alternatively, you can manually edit your global settings file at
|
||||
`~/.gemini/settings.json` (create it if it doesn't exist):
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"skills": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Create Your First Skill
|
||||
|
||||
A skill is a directory containing a `SKILL.md` file. Let's create an **API
|
||||
Auditor** skill that helps you verify if local or remote endpoints are
|
||||
responding correctly.
|
||||
|
||||
1. **Create the skill directory structure:**
|
||||
|
||||
```bash
|
||||
mkdir -p .gemini/skills/api-auditor/scripts
|
||||
```
|
||||
|
||||
2. **Create the `SKILL.md` file:** Create a file at
|
||||
`.gemini/skills/api-auditor/SKILL.md` with the following content:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: api-auditor
|
||||
description:
|
||||
Expertise in auditing and testing API endpoints. Use when the user asks to
|
||||
"check", "test", or "audit" a URL or API.
|
||||
---
|
||||
|
||||
# API Auditor Instructions
|
||||
|
||||
You act as a QA engineer specialized in API reliability. When this skill is
|
||||
active, you MUST:
|
||||
|
||||
1. **Audit**: Use the bundled `scripts/audit.js` utility to check the
|
||||
status of the provided URL.
|
||||
2. **Report**: Analyze the output (status codes, latency) and explain any
|
||||
failures in plain English.
|
||||
3. **Secure**: Remind the user if they are testing a sensitive endpoint
|
||||
without an `https://` protocol.
|
||||
```
|
||||
|
||||
3. **Create the bundled Node.js script:** Create a file at
|
||||
`.gemini/skills/api-auditor/scripts/audit.js`. This script will be used by
|
||||
the agent to perform the actual check:
|
||||
|
||||
```javascript
|
||||
// .gemini/skills/api-auditor/scripts/audit.js
|
||||
const url = process.argv[2];
|
||||
|
||||
if (!url) {
|
||||
console.error('Usage: node audit.js <url>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Auditing ${url}...`);
|
||||
fetch(url, { method: 'HEAD' })
|
||||
.then((r) => console.log(`Result: Success (Status ${r.status})`))
|
||||
.catch((e) => console.error(`Result: Failed (${e.message})`));
|
||||
```
|
||||
|
||||
## 3. Verify the Skill is Discovered
|
||||
|
||||
Use the `/skills` slash command (or `gemini skills list` from your terminal) to
|
||||
see if Gemini CLI has found your new skill.
|
||||
|
||||
In a Gemini CLI session:
|
||||
|
||||
```
|
||||
/skills list
|
||||
```
|
||||
|
||||
You should see `api-auditor` in the list of available skills.
|
||||
|
||||
## 4. Use the Skill in a Chat
|
||||
|
||||
Now, let's see the skill in action. Start a new session and ask a question about
|
||||
an endpoint.
|
||||
|
||||
**User:** "Can you audit http://geminili.com"
|
||||
|
||||
Gemini will recognize the request matches the `api-auditor` description and will
|
||||
ask for your permission to activate it.
|
||||
|
||||
**Model:** (After calling `activate_skill`) "I've activated the **api-auditor**
|
||||
skill. I'll run the audit script now..."
|
||||
|
||||
Gemini will then use the `run_shell_command` tool to execute your bundled Node
|
||||
script:
|
||||
|
||||
`node .gemini/skills/api-auditor/scripts/audit.js http://geminili.com`
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Explore [Agent Skills Authoring Guide](../skills.md#creating-a-skill) to learn
|
||||
about more advanced skill features.
|
||||
- Learn how to share skills via [Extensions](../../extensions/index.md).
|
||||
@@ -83,7 +83,9 @@ The processor automatically detects and prevents circular imports:
|
||||
# file-a.md
|
||||
|
||||
@./file-b.md
|
||||
```
|
||||
|
||||
```markdown
|
||||
# file-b.md
|
||||
|
||||
@./file-a.md <!-- This will be detected and prevented -->
|
||||
|
||||
@@ -258,8 +258,9 @@ The Gemini CLI ships with a set of default policies to provide a safe
|
||||
out-of-the-box experience.
|
||||
|
||||
- **Read-only tools** (like `read_file`, `glob`) are generally **allowed**.
|
||||
- **Agent delegation** (like `delegate_to_agent`) is **allowed** (sub-agent
|
||||
actions are checked individually).
|
||||
- **Agent delegation** (like `delegate_to_agent`) defaults to **`ask_user`** to
|
||||
ensure remote agents can prompt for confirmation, but local sub-agent actions
|
||||
are executed silently and checked individually.
|
||||
- **Write tools** (like `write_file`, `run_shell_command`) default to
|
||||
**`ask_user`**.
|
||||
- In **`yolo`** mode, a high-priority rule allows all tools.
|
||||
|
||||
@@ -165,7 +165,7 @@ The file has the following structure:
|
||||
- `version`: The version of the extension.
|
||||
- `mcpServers`: A map of MCP servers to settings. The key is the name of the
|
||||
server, and the value is the server configuration. These servers will be
|
||||
loaded on startup just like MCP servers settingsd in a
|
||||
loaded on startup just like MCP servers settings in a
|
||||
[`settings.json` file](../get-started/configuration.md). If both an extension
|
||||
and a `settings.json` file settings an MCP server with the same name, the
|
||||
server defined in the `settings.json` file takes precedence.
|
||||
@@ -226,13 +226,13 @@ key. The value will be saved to a `.env` file in the extension's directory
|
||||
You can view a list of an extension's settings by running:
|
||||
|
||||
```
|
||||
gemini extensions settings list <extension name>
|
||||
gemini extensions list
|
||||
```
|
||||
|
||||
and you can update a given setting using:
|
||||
|
||||
```
|
||||
gemini extensions settings set <extension name> <setting name> [--scope <scope>]
|
||||
gemini extensions config <extension name> [setting name] [--scope <scope>]
|
||||
```
|
||||
|
||||
- `--scope`: The scope to set the setting in (`user` or `workspace`). This is
|
||||
@@ -263,6 +263,54 @@ Would provide these commands:
|
||||
- `/deploy` - Shows as `[gcp] Custom command from deploy.toml` in help
|
||||
- `/gcs:sync` - Shows as `[gcp] Custom command from sync.toml` in help
|
||||
|
||||
### Hooks
|
||||
|
||||
Extensions can provide [hooks](../hooks/index.md) to intercept and customize
|
||||
Gemini CLI behavior at specific lifecycle events. Hooks provided by an extension
|
||||
must be defined in a `hooks/hooks.json` file within the extension directory.
|
||||
|
||||
> [!IMPORTANT] Hooks are not defined directly in `gemini-extension.json`. The
|
||||
> CLI specifically looks for the `hooks/hooks.json` file.
|
||||
|
||||
#### Directory structure
|
||||
|
||||
```
|
||||
.gemini/extensions/my-extension/
|
||||
├── gemini-extension.json
|
||||
└── hooks/
|
||||
└── hooks.json
|
||||
```
|
||||
|
||||
#### `hooks/hooks.json` format
|
||||
|
||||
The `hooks.json` file contains a `hooks` object where keys are
|
||||
[event names](../hooks/reference.md#supported-events) and values are arrays of
|
||||
[hook definitions](../hooks/reference.md#hook-definition).
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"before_agent": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${extensionPath}/scripts/setup.js",
|
||||
"name": "Extension Setup"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported variables
|
||||
|
||||
Just like `gemini-extension.json`, the `hooks/hooks.json` file supports
|
||||
[variable substitution](#variables). This is particularly useful for referencing
|
||||
scripts within the extension directory using `${extensionPath}`.
|
||||
|
||||
### Conflict resolution
|
||||
|
||||
Extension commands have the lowest precedence. When a conflict occurs with user
|
||||
@@ -278,11 +326,12 @@ For example, if both a user and the `gcp` extension define a `deploy` command:
|
||||
- `/gcp.deploy` - Executes the extension's deploy command (marked with `[gcp]`
|
||||
tag)
|
||||
|
||||
## Variables
|
||||
### Variables
|
||||
|
||||
Gemini CLI extensions allow variable substitution in `gemini-extension.json`.
|
||||
This can be useful if e.g., you need the current directory to run an MCP server
|
||||
using `"cwd": "${extensionPath}${/}run.ts"`.
|
||||
Gemini CLI extensions allow variable substitution in both
|
||||
`gemini-extension.json` and `hooks/hooks.json`. This can be useful if e.g., you
|
||||
need the current directory to run an MCP server or hook script using
|
||||
`"cwd": "${extensionPath}${/}run.ts"`.
|
||||
|
||||
**Supported variables:**
|
||||
|
||||
@@ -291,3 +340,4 @@ using `"cwd": "${extensionPath}${/}run.ts"`.
|
||||
| `${extensionPath}` | The fully-qualified path of the extension in the user's filesystem e.g., '/Users/username/.gemini/extensions/example-extension'. This will not unwrap symlinks. |
|
||||
| `${workspacePath}` | The fully-qualified path of the current workspace. |
|
||||
| `${/} or ${pathSeparator}` | The path separator (differs per OS). |
|
||||
| `${process.execPath}` | The path to the Node.js binary executing the CLI. |
|
||||
|
||||
@@ -159,7 +159,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
#### `output`
|
||||
|
||||
- **`output.format`** (enum):
|
||||
- **Description:** The format of the CLI output.
|
||||
- **Description:** The format of the CLI output. Can be `text` or `json`.
|
||||
- **Default:** `"text"`
|
||||
- **Values:** `"text"`, `"json"`
|
||||
|
||||
@@ -180,10 +180,21 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.showStatusInTitle`** (boolean):
|
||||
- **Description:** Show Gemini CLI status and thoughts in the terminal window
|
||||
title
|
||||
- **Description:** Show Gemini CLI model thoughts in the terminal window title
|
||||
during the working phase
|
||||
- **Default:** `false`
|
||||
|
||||
- **`ui.dynamicWindowTitle`** (boolean):
|
||||
- **Description:** Update the terminal window title with current status icons
|
||||
(Ready: ◇, Action Required: ✋, Working: ✦)
|
||||
- **Default:** `true`
|
||||
|
||||
- **`ui.showHomeDirectoryWarning`** (boolean):
|
||||
- **Description:** Show a warning when running Gemini CLI in the home
|
||||
directory.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.hideTips`** (boolean):
|
||||
- **Description:** Hide helpful tips in the UI
|
||||
- **Default:** `false`
|
||||
@@ -269,7 +280,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
#### `ide`
|
||||
|
||||
- **`ide.enabled`** (boolean):
|
||||
- **Description:** Enable IDE integration mode
|
||||
- **Description:** Enable IDE integration mode.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
@@ -546,6 +557,14 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
used.
|
||||
- **Default:** `[]`
|
||||
|
||||
#### `agents`
|
||||
|
||||
- **`agents.overrides`** (object):
|
||||
- **Description:** Override settings for specific agents, e.g. to disable the
|
||||
agent, set a custom model config, or run config.
|
||||
- **Default:** `{}`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `context`
|
||||
|
||||
- **`context.fileName`** (string | string[]):
|
||||
@@ -573,12 +592,12 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`context.fileFiltering.respectGitIgnore`** (boolean):
|
||||
- **Description:** Respect .gitignore files when searching
|
||||
- **Description:** Respect .gitignore files when searching.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`context.fileFiltering.respectGeminiIgnore`** (boolean):
|
||||
- **Description:** Respect .geminiignore files when searching
|
||||
- **Description:** Respect .geminiignore files when searching.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
@@ -621,6 +640,11 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
shell command. Defaults to 5 minutes.
|
||||
- **Default:** `300`
|
||||
|
||||
- **`tools.shell.enableShellOutputEfficiency`** (boolean):
|
||||
- **Description:** Enable shell output efficiency optimizations for better
|
||||
performance.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`tools.autoAccept`** (boolean):
|
||||
- **Description:** Automatically accept and execute tool calls that are
|
||||
considered safe (e.g., read-only operations).
|
||||
@@ -679,20 +703,17 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `1000`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.enableMessageBusIntegration`** (boolean):
|
||||
- **Description:** Enable policy-based tool confirmation via message bus
|
||||
integration. When enabled, tools automatically respect policy engine
|
||||
decisions (ALLOW/DENY/ASK_USER) without requiring individual tool
|
||||
implementations.
|
||||
- **Default:** `true`
|
||||
- **`tools.disableLLMCorrection`** (boolean):
|
||||
- **Description:** Disable LLM-based error correction for edit tools. When
|
||||
enabled, tools will fail immediately if exact string matches are not found,
|
||||
instead of attempting to self-correct.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.enableHooks`** (boolean):
|
||||
- **Description:** Enable the hooks system for intercepting and customizing
|
||||
Gemini CLI behavior. When enabled, hooks configured in settings will execute
|
||||
at appropriate lifecycle events (BeforeTool, AfterTool, BeforeModel, etc.).
|
||||
Requires MessageBus integration.
|
||||
- **Default:** `false`
|
||||
- **Description:** Enables the hooks system experiment. When disabled, the
|
||||
hooks system is completely deactivated regardless of other settings.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `mcp`
|
||||
@@ -712,12 +733,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `useSmartEdit`
|
||||
|
||||
- **`useSmartEdit`** (boolean):
|
||||
- **Description:** Enable the smart-edit tool instead of the replace tool.
|
||||
- **Default:** `true`
|
||||
|
||||
#### `useWriteTodos`
|
||||
|
||||
- **`useWriteTodos`** (boolean):
|
||||
@@ -731,6 +746,11 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.enablePermanentToolApproval`** (boolean):
|
||||
- **Description:** Enable the "Allow for all future sessions" option in tool
|
||||
confirmation dialogs.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`security.blockGitExtensions`** (boolean):
|
||||
- **Description:** Blocks installing and loading extensions from Git.
|
||||
- **Default:** `false`
|
||||
@@ -741,6 +761,22 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.environmentVariableRedaction.allowed`** (array):
|
||||
- **Description:** Environment variables to always allow (bypass redaction).
|
||||
- **Default:** `[]`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.environmentVariableRedaction.blocked`** (array):
|
||||
- **Description:** Environment variables to always redact.
|
||||
- **Default:** `[]`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.environmentVariableRedaction.enabled`** (boolean):
|
||||
- **Description:** Enable redaction of environment variables that may contain
|
||||
secrets.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.auth.selectedType`** (string):
|
||||
- **Description:** The currently selected authentication type.
|
||||
- **Default:** `undefined`
|
||||
@@ -784,7 +820,8 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
#### `experimental`
|
||||
|
||||
- **`experimental.enableAgents`** (boolean):
|
||||
- **Description:** Enable local and remote subagents.
|
||||
- **Description:** Enable local and remote subagents. Warning: Experimental
|
||||
feature, uses YOLO mode for subagents
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
@@ -793,18 +830,23 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.extensionConfig`** (boolean):
|
||||
- **Description:** Enable requesting and fetching of extension settings.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.extensionReloading`** (boolean):
|
||||
- **Description:** Enables extension loading/unloading within the CLI session.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.isModelAvailabilityServiceEnabled`** (boolean):
|
||||
- **Description:** Enable model routing using new availability service.
|
||||
- **`experimental.jitContext`** (boolean):
|
||||
- **Description:** Enable Just-In-Time (JIT) context loading.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.jitContext`** (boolean):
|
||||
- **Description:** Enable Just-In-Time (JIT) context loading.
|
||||
- **`experimental.skills`** (boolean):
|
||||
- **Description:** Enable Agent Skills (experimental).
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
@@ -835,13 +877,39 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `"auto"`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.useOSC52Paste`** (boolean):
|
||||
- **Description:** Use OSC 52 sequence for pasting instead of clipboardy
|
||||
(useful for remote sessions).
|
||||
- **Default:** `false`
|
||||
|
||||
- **`experimental.cliHelpAgentSettings.enabled`** (boolean):
|
||||
- **Description:** Enable the CLI Help Agent.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `skills`
|
||||
|
||||
- **`skills.disabled`** (array):
|
||||
- **Description:** List of disabled skills.
|
||||
- **Default:** `[]`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `hooks`
|
||||
|
||||
- **`hooks.enabled`** (boolean):
|
||||
- **Description:** Canonical toggle for the hooks system. When disabled, no
|
||||
hooks will be executed.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`hooks.disabled`** (array):
|
||||
- **Description:** List of hook names (commands) that should be disabled.
|
||||
Hooks in this list will not execute even if configured.
|
||||
- **Default:** `[]`
|
||||
|
||||
- **`hooks.notifications`** (boolean):
|
||||
- **Description:** Show visual indicators when hooks are executing.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`hooks.BeforeTool`** (array):
|
||||
- **Description:** Hooks that execute before tool execution. Can intercept,
|
||||
validate, or modify tool calls.
|
||||
@@ -896,6 +964,25 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Description:** Hooks that execute before tool selection. Can filter or
|
||||
prioritize available tools dynamically.
|
||||
- **Default:** `[]`
|
||||
|
||||
#### `admin`
|
||||
|
||||
- **`admin.secureModeEnabled`** (boolean):
|
||||
- **Description:** If true, disallows yolo mode from being used.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`admin.extensions.enabled`** (boolean):
|
||||
- **Description:** If false, disallows extensions from being installed or
|
||||
used.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`admin.mcp.enabled`** (boolean):
|
||||
- **Description:** If false, disallows MCP servers from being used.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`admin.skills.enabled`** (boolean):
|
||||
- **Description:** If false, disallows agent skills from being used.
|
||||
- **Default:** `true`
|
||||
<!-- SETTINGS-AUTOGEN:END -->
|
||||
|
||||
#### `mcpServers`
|
||||
@@ -1128,6 +1215,18 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- **`GEMINI_SANDBOX`**:
|
||||
- Alternative to the `sandbox` setting in `settings.json`.
|
||||
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
|
||||
- **`GEMINI_SYSTEM_MD`**:
|
||||
- Replaces the built‑in system prompt with content from a Markdown file.
|
||||
- `true`/`1`: Use project default path `./.gemini/system.md`.
|
||||
- Any other string: Treat as a path (relative/absolute supported, `~`
|
||||
expands).
|
||||
- `false`/`0` or unset: Use the built‑in prompt. See
|
||||
[System Prompt Override](../cli/system-prompt.md).
|
||||
- **`GEMINI_WRITE_SYSTEM_MD`**:
|
||||
- Writes the current built‑in system prompt to a file for review.
|
||||
- `true`/`1`: Write to `./.gemini/system.md`. Otherwise treat the value as a
|
||||
path.
|
||||
- Run the CLI once with this set to generate the file.
|
||||
- **`SEATBELT_PROFILE`** (macOS specific):
|
||||
- Switches the Seatbelt (`sandbox-exec`) profile on macOS.
|
||||
- `permissive-open`: (Default) Restricts writes to the project folder (and a
|
||||
@@ -1153,6 +1252,52 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- Specifies the endpoint for the code assist server.
|
||||
- This is useful for development and testing.
|
||||
|
||||
### Environment variable redaction
|
||||
|
||||
To prevent accidental leakage of sensitive information, Gemini CLI automatically
|
||||
redacts potential secrets from environment variables when executing tools (such
|
||||
as shell commands). This "best effort" redaction applies to variables inherited
|
||||
from the system or loaded from `.env` files.
|
||||
|
||||
**Default Redaction Rules:**
|
||||
|
||||
- **By Name:** Variables are redacted if their names contain sensitive terms
|
||||
like `TOKEN`, `SECRET`, `PASSWORD`, `KEY`, `AUTH`, `CREDENTIAL`, `PRIVATE`, or
|
||||
`CERT`.
|
||||
- **By Value:** Variables are redacted if their values match known secret
|
||||
patterns, such as:
|
||||
- Private keys (RSA, OpenSSH, PGP, etc.)
|
||||
- Certificates
|
||||
- URLs containing credentials
|
||||
- API keys and tokens (GitHub, Google, AWS, Stripe, Slack, etc.)
|
||||
- **Specific Blocklist:** Certain variables like `CLIENT_ID`, `DB_URI`,
|
||||
`DATABASE_URL`, and `CONNECTION_STRING` are always redacted by default.
|
||||
|
||||
**Allowlist (Never Redacted):**
|
||||
|
||||
- Common system variables (e.g., `PATH`, `HOME`, `USER`, `SHELL`, `TERM`,
|
||||
`LANG`).
|
||||
- Variables starting with `GEMINI_CLI_`.
|
||||
- GitHub Action specific variables.
|
||||
|
||||
**Configuration:**
|
||||
|
||||
You can customize this behavior in your `settings.json` file:
|
||||
|
||||
- **`security.allowedEnvironmentVariables`**: A list of variable names to
|
||||
_never_ redact, even if they match sensitive patterns.
|
||||
- **`security.blockedEnvironmentVariables`**: A list of variable names to
|
||||
_always_ redact, even if they don't match sensitive patterns.
|
||||
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"allowedEnvironmentVariables": ["MY_PUBLIC_KEY", "NOT_A_SECRET_TOKEN"],
|
||||
"blockedEnvironmentVariables": ["INTERNAL_IP_ADDRESS"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Command-line arguments
|
||||
|
||||
Arguments passed directly when running the CLI can override other configurations
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
Note: This page will be replaced by [installation.md](installation.md).
|
||||
|
||||
# Gemini CLI installation, execution, and deployment
|
||||
|
||||
Install and run Gemini CLI. This document provides an overview of Gemini CLI's
|
||||
installation methods and deployment architecture.
|
||||
|
||||
## How to install and/or run Gemini CLI
|
||||
|
||||
There are several ways to run Gemini CLI. The recommended option depends on how
|
||||
you intend to use Gemini CLI.
|
||||
|
||||
- As a standard installation. This is the most straightforward method of using
|
||||
Gemini CLI.
|
||||
- In a sandbox. This method offers increased security and isolation.
|
||||
- From the source. This is recommended for contributors to the project.
|
||||
|
||||
### 1. Standard installation (recommended for standard users)
|
||||
|
||||
This is the recommended way for end-users to install Gemini CLI. It involves
|
||||
downloading the Gemini CLI package from the NPM registry.
|
||||
|
||||
- **Global install:**
|
||||
|
||||
```bash
|
||||
npm install -g @google/gemini-cli
|
||||
```
|
||||
|
||||
Then, run the CLI from anywhere:
|
||||
|
||||
```bash
|
||||
gemini
|
||||
```
|
||||
|
||||
- **NPX execution:**
|
||||
|
||||
```bash
|
||||
# Execute the latest version from NPM without a global install
|
||||
npx @google/gemini-cli
|
||||
```
|
||||
|
||||
### 2. Run in a sandbox (Docker/Podman)
|
||||
|
||||
For security and isolation, Gemini CLI can be run inside a container. This is
|
||||
the default way that the CLI executes tools that might have side effects.
|
||||
|
||||
- **Directly from the registry:** 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.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.
|
||||
```bash
|
||||
gemini --sandbox -y -p "your prompt here"
|
||||
```
|
||||
|
||||
### 3. Run from source (recommended for Gemini CLI contributors)
|
||||
|
||||
Contributors to the project will want to run the CLI directly from the source
|
||||
code.
|
||||
|
||||
- **Development mode:** This method provides hot-reloading and is useful for
|
||||
active development.
|
||||
```bash
|
||||
# From the root of the repository
|
||||
npm run start
|
||||
```
|
||||
- **Production-like mode (Linked package):** This method simulates a global
|
||||
installation by linking your local package. It's useful for testing a local
|
||||
build in a production workflow.
|
||||
|
||||
```bash
|
||||
# Link the local cli package to your global node_modules
|
||||
npm link packages/cli
|
||||
|
||||
# Now you can run your local version using the `gemini` command
|
||||
gemini
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 development.
|
||||
|
||||
```bash
|
||||
# Execute the CLI directly from the main branch on GitHub
|
||||
npx https://github.com/google-gemini/gemini-cli
|
||||
```
|
||||
|
||||
## Deployment architecture
|
||||
|
||||
The execution methods described above are made possible by the following
|
||||
architectural components and processes:
|
||||
|
||||
**NPM packages**
|
||||
|
||||
Gemini CLI project is a monorepo that publishes two core packages to the NPM
|
||||
registry:
|
||||
|
||||
- `@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.
|
||||
|
||||
**Build and packaging processes**
|
||||
|
||||
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 `@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.
|
||||
|
||||
**Docker sandbox image**
|
||||
|
||||
The Docker-based execution method is supported by the `gemini-cli-sandbox`
|
||||
container image. This image is published to a container registry and contains a
|
||||
pre-installed, global version of Gemini CLI.
|
||||
|
||||
## Release process
|
||||
|
||||
The release process is automated through GitHub Actions. The release workflow
|
||||
performs the following actions:
|
||||
|
||||
1. Build the NPM packages using `tsc`.
|
||||
2. Publish the NPM packages to the artifact registry.
|
||||
3. Create GitHub releases with bundled assets.
|
||||
@@ -24,7 +24,7 @@ Rename the photos in my "photos" directory based on their contents.
|
||||
|
||||
Result: Gemini will ask for permission to rename your files.
|
||||
|
||||
Select **Yes, allow once.** and your files will be renamed:
|
||||
Select **Allow once** and your files will be renamed:
|
||||
|
||||
```bash
|
||||
photos/yellow_flowers.png
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
# Gemini 3 Pro and Gemini 3 Flash on Gemini CLI
|
||||
|
||||
Gemini 3 Pro and Gemini 3 Flash are now available on Gemini CLI! Currently, most
|
||||
paid customers of Gemini CLI will have access to both Gemini 3 Pro and Gemini 3
|
||||
Flash, including the following subscribers:
|
||||
|
||||
- Google AI Pro and Google AI Ultra (excluding business customers).
|
||||
- Gemini Code Assist Standard and Enterprise (requires
|
||||
[administrative enablement](#administrator-instructions)).
|
||||
- Paid Gemini API and Vertex API key holders.
|
||||
|
||||
For free tier users:
|
||||
|
||||
- If you signed up for the waitlist, please check your email for details. We’ve
|
||||
onboarded everyone who signed up to the previously available waitlist.
|
||||
- If you were not on our waitlist, we’re rolling out additional access gradually
|
||||
to ensure the experience remains fast and reliable. Stay tuned for more
|
||||
details.
|
||||
Gemini 3 Pro and Gemini 3 Flash are available on Gemini CLI for all users!
|
||||
|
||||
## How to get started with Gemini 3 on Gemini CLI
|
||||
|
||||
Get started by upgrading Gemini CLI to the latest version (0.21.1):
|
||||
Get started by upgrading Gemini CLI to the latest version:
|
||||
|
||||
```bash
|
||||
npm install -g @google/gemini-cli@latest
|
||||
|
||||
+184
-134
@@ -4,128 +4,6 @@ This guide covers security considerations, performance optimization, debugging
|
||||
techniques, and privacy considerations for developing and deploying hooks in
|
||||
Gemini CLI.
|
||||
|
||||
## Security considerations
|
||||
|
||||
### Validate all inputs
|
||||
|
||||
Never trust data from hooks without validation. Hook inputs may contain
|
||||
user-provided data that could be malicious:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Validate JSON structure
|
||||
if ! echo "$input" | jq empty 2>/dev/null; then
|
||||
echo "Invalid JSON input" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate required fields
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
||||
if [ -z "$tool_name" ]; then
|
||||
echo "Missing tool_name field" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Use timeouts
|
||||
|
||||
Set reasonable timeouts to prevent hooks from hanging indefinitely:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "slow-validator",
|
||||
"type": "command",
|
||||
"command": "./hooks/validate.sh",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Recommended timeouts:**
|
||||
|
||||
- Fast validation: 1000-5000ms
|
||||
- Network requests: 10000-30000ms
|
||||
- Heavy computation: 30000-60000ms
|
||||
|
||||
### Limit permissions
|
||||
|
||||
Run hooks with minimal required permissions:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Don't run as root
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "Hook should not run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check file permissions before writing
|
||||
if [ -w "$file_path" ]; then
|
||||
# Safe to write
|
||||
else
|
||||
echo "Insufficient permissions" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Scan for secrets
|
||||
|
||||
Use `BeforeTool` hooks to prevent committing sensitive data:
|
||||
|
||||
```javascript
|
||||
const SECRET_PATTERNS = [
|
||||
/api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
|
||||
/password\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
|
||||
/secret\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
|
||||
/AKIA[0-9A-Z]{16}/, // AWS access key
|
||||
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
|
||||
/sk-[a-zA-Z0-9]{48}/, // OpenAI API key
|
||||
];
|
||||
|
||||
function containsSecret(content) {
|
||||
return SECRET_PATTERNS.some((pattern) => pattern.test(content));
|
||||
}
|
||||
```
|
||||
|
||||
### Review external scripts
|
||||
|
||||
Always review hook scripts from untrusted sources before enabling them:
|
||||
|
||||
```bash
|
||||
# Review before installing
|
||||
cat third-party-hook.sh | less
|
||||
|
||||
# Check for suspicious patterns
|
||||
grep -E 'curl|wget|ssh|eval' third-party-hook.sh
|
||||
|
||||
# Verify hook source
|
||||
ls -la third-party-hook.sh
|
||||
```
|
||||
|
||||
### Sandbox untrusted hooks
|
||||
|
||||
For maximum security, consider running untrusted hooks in isolated environments:
|
||||
|
||||
```bash
|
||||
# Run hook in Docker container
|
||||
docker run --rm \
|
||||
-v "$GEMINI_PROJECT_DIR:/workspace:ro" \
|
||||
-i untrusted-hook-image \
|
||||
/hook-script.sh < input.json
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Keep hooks fast
|
||||
@@ -140,11 +18,13 @@ const data2 = await fetch(url2).then((r) => r.json());
|
||||
const data3 = await fetch(url3).then((r) => r.json());
|
||||
|
||||
// Prefer parallel operations for better performance
|
||||
const [data1, data2, data3] = await Promise.all([
|
||||
fetch(url1).then((r) => r.json()),
|
||||
fetch(url2).then((r) => r.json()),
|
||||
fetch(url3).then((r) => r.json()),
|
||||
]);
|
||||
// Start requests concurrently
|
||||
const p1 = fetch(url1).then((r) => r.json());
|
||||
const p2 = fetch(url2).then((r) => r.json());
|
||||
const p3 = fetch(url3).then((r) => r.json());
|
||||
|
||||
// Wait for all results
|
||||
const [data1, data2, data3] = await Promise.all([p1, p2, p3]);
|
||||
```
|
||||
|
||||
### Cache expensive operations
|
||||
@@ -219,7 +99,7 @@ all tools with `*`, specify only the tools you need:
|
||||
|
||||
```json
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "validate-writes",
|
||||
@@ -298,7 +178,7 @@ cat > test-input.json << 'EOF'
|
||||
"session_id": "test-123",
|
||||
"cwd": "/tmp/test",
|
||||
"hook_event_name": "BeforeTool",
|
||||
"tool_name": "WriteFile",
|
||||
"tool_name": "write_file",
|
||||
"tool_input": {
|
||||
"file_path": "test.txt",
|
||||
"content": "Test content"
|
||||
@@ -437,7 +317,7 @@ Add descriptions to help others understand your hooks:
|
||||
"hooks": {
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "secret-scanner",
|
||||
@@ -484,7 +364,7 @@ Verify the hook appears in the list and is enabled.
|
||||
|
||||
```bash
|
||||
# Test regex pattern
|
||||
echo "WriteFile" | grep -E "Write.*|Edit"
|
||||
echo "write_file|replace" | grep -E "write_.*|replace"
|
||||
```
|
||||
|
||||
**Check disabled list:**
|
||||
@@ -559,7 +439,7 @@ async function getCachedData(key) {
|
||||
"hooks": {
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "WriteFile",
|
||||
"matcher": "write_file",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "quick-check",
|
||||
@@ -569,7 +449,7 @@ async function getCachedData(key) {
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "WriteFile",
|
||||
"matcher": "write_file",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "deep-check",
|
||||
@@ -714,6 +594,176 @@ if [ -f "$GEMINI_PROJECT_DIR/.env" ]; then
|
||||
fi
|
||||
```
|
||||
|
||||
## Using Hooks Securely
|
||||
|
||||
### Threat Model
|
||||
|
||||
Understanding where hooks come from and what they can do is critical for secure
|
||||
usage.
|
||||
|
||||
| Hook Source | Description |
|
||||
| :---------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **System** | Configured by system administrators (e.g., `/etc/gemini-cli/settings.json`, `/Library/...`). Assumed to be the **safest**. |
|
||||
| **User** (`~/.gemini/...`) | Configured by you. You are responsible for ensuring they are safe. |
|
||||
| **Extensions** | You explicitly approve and install these. Security depends on the extension source (integrity). |
|
||||
| **Project** (`./.gemini/...`) | **Untrusted by default.** Safest in trusted internal repos; higher risk in third-party/public repos. |
|
||||
|
||||
#### Project Hook Security
|
||||
|
||||
When you open a project with hooks defined in `.gemini/settings.json`:
|
||||
|
||||
1. **Detection**: Gemini CLI detects the hooks.
|
||||
2. **Identification**: A unique identity is generated for each hook based on its
|
||||
`name` and `command`.
|
||||
3. **Warning**: If this specific hook identity has not been seen before, a
|
||||
**warning** is displayed.
|
||||
4. **Execution**: The hook is executed (unless specific security settings block
|
||||
it).
|
||||
5. **Trust**: The hook is marked as "trusted" for this project.
|
||||
|
||||
> [!IMPORTANT] **Modification Detection**: If the `command` string of a project
|
||||
> hook is changed (e.g., by a `git pull`), its identity changes. Gemini CLI will
|
||||
> treat it as a **new, untrusted hook** and warn you again. This prevents
|
||||
> malicious actors from silently swapping a verified command for a malicious
|
||||
> one.
|
||||
|
||||
### Risks
|
||||
|
||||
| Risk | Description |
|
||||
| :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Arbitrary Code Execution** | Hooks run as your user. They can do anything you can do (delete files, install software). |
|
||||
| **Data Exfiltration** | A hook could read your input (prompts), output (code), or environment variables (`GEMINI_API_KEY`) and send them to a remote server. |
|
||||
| **Prompt Injection** | Malicious content in a file or web page could trick an LLM into running a tool that triggers a hook in an unexpected way. |
|
||||
|
||||
### Mitigation Strategies
|
||||
|
||||
#### Verify the source
|
||||
|
||||
**Verify the source** of any project hooks or extensions before enabling them.
|
||||
|
||||
- For open-source projects, a quick review of the hook scripts is recommended.
|
||||
- For extensions, ensure you trust the author or publisher (e.g., verified
|
||||
publishers, well-known community members).
|
||||
- Be cautious with obfuscated scripts or compiled binaries from unknown sources.
|
||||
|
||||
#### Sanitize Environment
|
||||
|
||||
Hooks inherit the environment of the Gemini CLI process, which may include
|
||||
sensitive API keys. Gemini CLI attempts to sanitize sensitive variables, but you
|
||||
should be cautious.
|
||||
|
||||
- **Avoid printing environment variables** to stdout/stderr unless necessary.
|
||||
- **Use `.env` files** to securely manage sensitive variables, ensuring they are
|
||||
excluded from version control.
|
||||
|
||||
**System Administrators:** You can enforce environment variable redaction by
|
||||
default in the system configuration (e.g., `/etc/gemini-cli/settings.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"environmentVariableRedaction": {
|
||||
"enabled": true,
|
||||
"blocked": ["MY_SECRET_KEY"],
|
||||
"allowed": ["SAFE_VAR"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authoring Secure Hooks
|
||||
|
||||
When writing your own hooks, follow these practices to ensure they are robust
|
||||
and secure.
|
||||
|
||||
### Validate all inputs
|
||||
|
||||
Never trust data from hooks without validation. Hook inputs often come from the
|
||||
LLM or user prompts, which can be manipulated.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Validate JSON structure
|
||||
if ! echo "$input" | jq empty 2>/dev/null; then
|
||||
echo "Invalid JSON input" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate tool_name explicitly
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
||||
if [[ "$tool_name" != "write_file" && "$tool_name" != "read_file" ]]; then
|
||||
echo "Unexpected tool: $tool_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Use timeouts
|
||||
|
||||
Prevent denial-of-service (hanging agents) by enforcing timeouts. Gemini CLI
|
||||
defaults to 60 seconds, but you should set stricter limits for fast hooks.
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "fast-validator",
|
||||
"command": "./hooks/validate.sh",
|
||||
"timeout": 5000 // 5 seconds
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Limit permissions
|
||||
|
||||
Run hooks with minimal required permissions:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Don't run as root
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "Hook should not run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check file permissions before writing
|
||||
if [ -w "$file_path" ]; then
|
||||
# Safe to write
|
||||
else
|
||||
echo "Insufficient permissions" >&2
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Example: Secret Scanner
|
||||
|
||||
Use `BeforeTool` hooks to prevent committing sensitive data. This is a powerful
|
||||
pattern for enhancing security in your workflow.
|
||||
|
||||
```javascript
|
||||
const SECRET_PATTERNS = [
|
||||
/api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
|
||||
/password\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
|
||||
/secret\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
|
||||
/AKIA[0-9A-Z]{16}/, // AWS access key
|
||||
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
|
||||
/sk-[a-zA-Z0-9]{48}/, // OpenAI API key
|
||||
];
|
||||
|
||||
function containsSecret(content) {
|
||||
return SECRET_PATTERNS.some((pattern) => pattern.test(content));
|
||||
}
|
||||
```
|
||||
|
||||
## Privacy considerations
|
||||
|
||||
Hook inputs and outputs may contain sensitive information. Gemini CLI respects
|
||||
@@ -802,5 +852,5 @@ console.log(JSON.stringify(sanitizeOutput(hookOutput)));
|
||||
|
||||
- [Hooks Reference](index.md) - Complete API reference
|
||||
- [Writing Hooks](writing-hooks.md) - Tutorial and examples
|
||||
- [Configuration](../cli/configuration.md) - Gemini CLI settings
|
||||
- [Configuration](../get-started/configuration.md) - Gemini CLI settings
|
||||
- [Hooks Design Document](../hooks-design.md) - Technical architecture
|
||||
|
||||
+191
-28
@@ -4,9 +4,25 @@ Hooks are scripts or programs that Gemini CLI executes at specific points in the
|
||||
agentic loop, allowing you to intercept and customize behavior without modifying
|
||||
the CLI's source code.
|
||||
|
||||
> **Note: Hooks are currently an experimental feature.**
|
||||
>
|
||||
> To use hooks, you must explicitly enable them in your `settings.json`:
|
||||
>
|
||||
> ```json
|
||||
> {
|
||||
> "tools": { "enableHooks": true },
|
||||
> "hooks": { "enabled": true }
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> Both of these are needed in this experimental phase.
|
||||
|
||||
See [writing hooks guide](writing-hooks.md) for a tutorial on creating your
|
||||
first hook and a comprehensive example.
|
||||
|
||||
See [hooks reference](reference.md) for the technical specification of the I/O
|
||||
schemas.
|
||||
|
||||
See [best practices](best-practices.md) for guidelines on security, performance,
|
||||
and debugging.
|
||||
|
||||
@@ -24,6 +40,33 @@ With hooks, you can:
|
||||
Hooks run synchronously as part of the agent loop—when a hook event fires,
|
||||
Gemini CLI waits for all matching hooks to complete before continuing.
|
||||
|
||||
## Security and Risks
|
||||
|
||||
> **Warning: Hooks execute arbitrary code with your user privileges.**
|
||||
>
|
||||
> By configuring hooks, you are explicitly allowing Gemini CLI to run shell
|
||||
> commands on your machine. Malicious or poorly configured hooks can:
|
||||
|
||||
- **Exfiltrate data**: Read sensitive files (`.env`, ssh keys) and send them to
|
||||
remote servers.
|
||||
- **Modify system**: Delete files, install malware, or change system settings.
|
||||
- **Consume resources**: Run infinite loops or crash your system.
|
||||
|
||||
**Project-level hooks** (in `.gemini/settings.json`) and **Extension hooks** are
|
||||
particularly risky when opening third-party projects or extensions from
|
||||
untrusted authors. Gemini CLI will **warn you** the first time it detects a new
|
||||
project hook (identified by its name and command), but it is **your
|
||||
responsibility** to review these hooks (and any installed extensions) before
|
||||
trusting them.
|
||||
|
||||
> **Note:** Extension hooks are subject to a mandatory security warning and
|
||||
> consent flow during extension installation or update if hooks are detected.
|
||||
> You must explicitly approve the installation or update of any extension that
|
||||
> contains hooks.
|
||||
|
||||
See [Security Considerations](best-practices.md#using-hooks-securely) for a
|
||||
detailed threat model and mitigation strategies.
|
||||
|
||||
## Core concepts
|
||||
|
||||
### Hook events
|
||||
@@ -70,7 +113,7 @@ trigger the hook:
|
||||
"hooks": {
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
/* hooks for write operations */
|
||||
]
|
||||
@@ -82,8 +125,8 @@ trigger the hook:
|
||||
|
||||
**Matcher patterns:**
|
||||
|
||||
- **Exact match:** `"ReadFile"` matches only `ReadFile`
|
||||
- **Regex:** `"Write.*|Edit"` matches `WriteFile`, `WriteBinary`, `Edit`
|
||||
- **Exact match:** `"read_file"` matches only `read_file`
|
||||
- **Regex:** `"write_.*|replace"` matches `write_file`, `replace`
|
||||
- **Wildcard:** `"*"` or `""` matches all tools
|
||||
|
||||
**Session event matchers:**
|
||||
@@ -115,6 +158,7 @@ Every hook receives these base fields:
|
||||
```json
|
||||
{
|
||||
"session_id": "abc123",
|
||||
"transcript_path": "/path/to/transcript.jsonl",
|
||||
"cwd": "/path/to/project",
|
||||
"hook_event_name": "BeforeTool",
|
||||
"timestamp": "2025-12-01T10:30:00Z"
|
||||
@@ -130,7 +174,7 @@ Every hook receives these base fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "WriteFile",
|
||||
"tool_name": "write_file",
|
||||
"tool_input": {
|
||||
"file_path": "/path/to/file.ts",
|
||||
"content": "..."
|
||||
@@ -159,7 +203,7 @@ Or simple exit codes:
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "ReadFile",
|
||||
"tool_name": "read_file",
|
||||
"tool_input": { "file_path": "..." },
|
||||
"tool_response": "file contents..."
|
||||
}
|
||||
@@ -212,7 +256,7 @@ Or simple exit codes:
|
||||
"toolConfig": {
|
||||
"functionCallingConfig": {
|
||||
"mode": "AUTO",
|
||||
"allowedFunctionNames": ["ReadFile", "WriteFile"]
|
||||
"allowedFunctionNames": ["read_file", "write_file"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,7 +360,7 @@ Or simple exit codes:
|
||||
"toolConfig": {
|
||||
"functionCallingConfig": {
|
||||
"mode": "ANY",
|
||||
"allowedFunctionNames": ["ReadFile", "WriteFile", "Edit"]
|
||||
"allowedFunctionNames": ["read_file", "write_file", "replace"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +370,7 @@ Or simple exit codes:
|
||||
Or simple output (comma-separated tool names sets mode to ANY):
|
||||
|
||||
```bash
|
||||
echo "ReadFile,WriteFile,Edit"
|
||||
echo "read_file,write_file,replace"
|
||||
```
|
||||
|
||||
#### SessionStart
|
||||
@@ -410,13 +454,23 @@ precedence rules.
|
||||
|
||||
### Configuration layers
|
||||
|
||||
Hook configurations are applied in the following order of precedence (higher
|
||||
numbers override lower numbers):
|
||||
Hook configurations are applied in the following order of execution (lower
|
||||
numbers run first):
|
||||
|
||||
1. **System defaults:** Built-in default settings (lowest precedence)
|
||||
2. **User settings:** `~/.gemini/settings.json`
|
||||
3. **Project settings:** `.gemini/settings.json` in your project directory
|
||||
4. **System settings:** `/etc/gemini-cli/settings.json` (highest precedence)
|
||||
1. **Project settings:** `.gemini/settings.json` in your project directory
|
||||
(highest priority)
|
||||
2. **User settings:** `~/.gemini/settings.json`
|
||||
3. **System settings:** `/etc/gemini-cli/settings.json`
|
||||
4. **Extensions:** Internal hooks defined by installed extensions (lowest
|
||||
priority). See [Extensions documentation](../extensions/index.md#hooks) for
|
||||
details on how extensions define and configure hooks.
|
||||
|
||||
#### Deduplication and shadowing
|
||||
|
||||
If multiple hooks with the identical **name** and **command** are discovered
|
||||
across different configuration layers, Gemini CLI deduplicates them. The hook
|
||||
from the higher-priority layer (e.g., Project) will be kept, and others will be
|
||||
ignored.
|
||||
|
||||
Within each level, hooks run in the order they are declared in the
|
||||
configuration.
|
||||
@@ -446,8 +500,9 @@ configuration.
|
||||
|
||||
**Configuration properties:**
|
||||
|
||||
- **`name`** (string, required): Unique identifier for the hook used in
|
||||
`/hooks enable/disable` commands
|
||||
- **`name`** (string, recommended): Unique identifier for the hook used in
|
||||
`/hooks enable/disable` commands. If omitted, the `command` path is used as
|
||||
the identifier.
|
||||
- **`type`** (string, required): Hook type - currently only `"command"` is
|
||||
supported
|
||||
- **`command`** (string, required): Path to the script or command to execute
|
||||
@@ -478,14 +533,29 @@ Use the `/hooks panel` command to view all registered hooks:
|
||||
|
||||
This command displays:
|
||||
|
||||
- All active hooks organized by event
|
||||
- All configured hooks organized by event
|
||||
- Hook source (user, project, system)
|
||||
- Hook type (command or plugin)
|
||||
- Execution status and recent output
|
||||
- Individual hook status (enabled/disabled)
|
||||
|
||||
### Enable and disable hooks
|
||||
### Enable and disable all hooks at once
|
||||
|
||||
You can temporarily enable or disable individual hooks using commands:
|
||||
You can enable or disable all hooks at once using commands:
|
||||
|
||||
```bash
|
||||
/hooks enable-all
|
||||
/hooks disable-all
|
||||
```
|
||||
|
||||
These commands provide a shortcut to enable or disable all configured hooks
|
||||
without managing them individually. The `enable-all` command removes all hooks
|
||||
from the `hooks.disabled` array, while `disable-all` adds all configured hooks
|
||||
to the disabled list. Changes take effect immediately without requiring a
|
||||
restart.
|
||||
|
||||
### Enable and disable individual hooks
|
||||
|
||||
You can enable or disable individual hooks using commands:
|
||||
|
||||
```bash
|
||||
/hooks enable hook-name
|
||||
@@ -494,6 +564,9 @@ You can temporarily enable or disable individual hooks using commands:
|
||||
|
||||
These commands allow you to control hook execution without editing configuration
|
||||
files. The hook name should match the `name` field in your hook configuration.
|
||||
Changes made via these commands are persisted to your settings. The settings are
|
||||
saved to workspace scope if available, otherwise to your global user settings
|
||||
(`~/.gemini/settings.json`).
|
||||
|
||||
### Disabled hooks configuration
|
||||
|
||||
@@ -524,7 +597,7 @@ This command:
|
||||
|
||||
- Reads `.claude/settings.json`
|
||||
- Converts event names (`PreToolUse` → `BeforeTool`, etc.)
|
||||
- Translates tool names (`Bash` → `RunShellCommand`, `Edit` → `Edit`)
|
||||
- Translates tool names (`Bash` → `run_shell_command`, `replace` → `replace`)
|
||||
- Updates matcher patterns
|
||||
- Writes to `.gemini/settings.json`
|
||||
|
||||
@@ -543,12 +616,101 @@ This command:
|
||||
|
||||
### Tool name mapping
|
||||
|
||||
| Claude Code | Gemini CLI |
|
||||
| ----------- | ----------------- |
|
||||
| `Bash` | `RunShellCommand` |
|
||||
| `Edit` | `Edit` |
|
||||
| `Read` | `ReadFile` |
|
||||
| `Write` | `WriteFile` |
|
||||
| Claude Code | Gemini CLI |
|
||||
| ----------- | --------------------- |
|
||||
| `Bash` | `run_shell_command` |
|
||||
| `Edit` | `replace` |
|
||||
| `Read` | `read_file` |
|
||||
| `Write` | `write_file` |
|
||||
| `Glob` | `glob` |
|
||||
| `Grep` | `search_file_content` |
|
||||
| `LS` | `list_directory` |
|
||||
|
||||
## Tool and Event Matchers Reference
|
||||
|
||||
### Available tool names for matchers
|
||||
|
||||
The following built-in tools can be used in `BeforeTool` and `AfterTool` hook
|
||||
matchers:
|
||||
|
||||
#### File operations
|
||||
|
||||
- `read_file` - Read a single file
|
||||
- `read_many_files` - Read multiple files at once
|
||||
- `write_file` - Create or overwrite a file
|
||||
- `replace` - Edit file content with find/replace
|
||||
|
||||
#### File system
|
||||
|
||||
- `list_directory` - List directory contents
|
||||
- `glob` - Find files matching a pattern
|
||||
- `search_file_content` - Search within file contents
|
||||
|
||||
#### Execution
|
||||
|
||||
- `run_shell_command` - Execute shell commands
|
||||
|
||||
#### Web and external
|
||||
|
||||
- `google_web_search` - Google Search with grounding
|
||||
- `web_fetch` - Fetch web page content
|
||||
|
||||
#### Agent features
|
||||
|
||||
- `write_todos` - Manage TODO items
|
||||
- `save_memory` - Save information to memory
|
||||
- `delegate_to_agent` - Delegate tasks to sub-agents
|
||||
|
||||
#### Example matchers
|
||||
|
||||
```json
|
||||
{
|
||||
"matcher": "write_file|replace" // File editing tools
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"matcher": "read_.*" // All read operations
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"matcher": "run_shell_command" // Only shell commands
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"matcher": "*" // All tools
|
||||
}
|
||||
```
|
||||
|
||||
### Event-specific matchers
|
||||
|
||||
#### SessionStart event matchers
|
||||
|
||||
- `startup` - Fresh session start
|
||||
- `resume` - Resuming a previous session
|
||||
- `clear` - Session cleared
|
||||
|
||||
#### SessionEnd event matchers
|
||||
|
||||
- `exit` - Normal exit
|
||||
- `clear` - Session cleared
|
||||
- `logout` - User logged out
|
||||
- `prompt_input_exit` - Exit from prompt input
|
||||
- `other` - Other reasons
|
||||
|
||||
#### PreCompress event matchers
|
||||
|
||||
- `manual` - Manually triggered compression
|
||||
- `auto` - Automatically triggered compression
|
||||
|
||||
#### Notification event matchers
|
||||
|
||||
- `ToolPermission` - Tool permission notifications
|
||||
|
||||
## Learn more
|
||||
|
||||
@@ -556,5 +718,6 @@ This command:
|
||||
- [Best Practices](best-practices.md) - Security, performance, and debugging
|
||||
- [Custom Commands](../cli/custom-commands.md) - Create reusable prompt
|
||||
shortcuts
|
||||
- [Configuration](../cli/configuration.md) - Gemini CLI configuration options
|
||||
- [Configuration](../get-started/configuration.md) - Gemini CLI configuration
|
||||
options
|
||||
- [Hooks Design Document](../hooks-design.md) - Technical architecture details
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
# Hooks Reference
|
||||
|
||||
This document provides the technical specification for Gemini CLI hooks,
|
||||
including the JSON schemas for input and output, exit code behaviors, and the
|
||||
stable model API.
|
||||
|
||||
## Communication Protocol
|
||||
|
||||
Hooks communicate with Gemini CLI via standard streams and exit codes:
|
||||
|
||||
- **Input**: Gemini CLI sends a JSON object to the hook's `stdin`.
|
||||
- **Output**: The hook sends a JSON object (or plain text) to `stdout`.
|
||||
- **Exit Codes**: Used to signal success or blocking errors.
|
||||
|
||||
### Exit Code Behavior
|
||||
|
||||
| Exit Code | Meaning | Behavior |
|
||||
| :-------- | :----------------- | :---------------------------------------------------------------------------------------------- |
|
||||
| `0` | **Success** | `stdout` is parsed as JSON. If parsing fails, it's treated as a `systemMessage`. |
|
||||
| `2` | **Blocking Error** | Interrupts the current operation. `stderr` is shown to the agent (for tool events) or the user. |
|
||||
| Other | **Warning** | Execution continues. `stderr` is logged as a non-blocking warning. |
|
||||
|
||||
---
|
||||
|
||||
## Input Schema (`stdin`)
|
||||
|
||||
Every hook receives a base JSON object. Extra fields are added depending on the
|
||||
specific event.
|
||||
|
||||
### Base Fields (All Events)
|
||||
|
||||
| Field | Type | Description |
|
||||
| :---------------- | :------- | :---------------------------------------------------- |
|
||||
| `session_id` | `string` | Unique identifier for the current CLI session. |
|
||||
| `transcript_path` | `string` | Path to the session's JSON transcript (if available). |
|
||||
| `cwd` | `string` | The current working directory. |
|
||||
| `hook_event_name` | `string` | The name of the firing event (e.g., `BeforeTool`). |
|
||||
| `timestamp` | `string` | ISO 8601 timestamp of the event. |
|
||||
|
||||
### Event-Specific Fields
|
||||
|
||||
#### Tool Events (`BeforeTool`, `AfterTool`)
|
||||
|
||||
- `tool_name`: (`string`) The internal name of the tool (e.g., `write_file`,
|
||||
`run_shell_command`).
|
||||
- `tool_input`: (`object`) The arguments passed to the tool.
|
||||
- `tool_response`: (`object`, **AfterTool only**) The raw output from the tool
|
||||
execution.
|
||||
- `mcp_context`: (`object`, **optional**) Present only for MCP tool invocations.
|
||||
Contains server identity information:
|
||||
- `server_name`: (`string`) The configured name of the MCP server.
|
||||
- `tool_name`: (`string`) The original tool name from the MCP server.
|
||||
- `command`: (`string`, optional) For stdio transport, the command used to
|
||||
start the server.
|
||||
- `args`: (`string[]`, optional) For stdio transport, the command arguments.
|
||||
- `cwd`: (`string`, optional) For stdio transport, the working directory.
|
||||
- `url`: (`string`, optional) For SSE/HTTP transport, the server URL.
|
||||
- `tcp`: (`string`, optional) For WebSocket transport, the TCP address.
|
||||
|
||||
#### Agent Events (`BeforeAgent`, `AfterAgent`)
|
||||
|
||||
- `prompt`: (`string`) The user's submitted prompt.
|
||||
- `prompt_response`: (`string`, **AfterAgent only**) The final response text
|
||||
from the model.
|
||||
- `stop_hook_active`: (`boolean`, **AfterAgent only**) Indicates if a stop hook
|
||||
is already handling a continuation.
|
||||
|
||||
#### Model Events (`BeforeModel`, `AfterModel`, `BeforeToolSelection`)
|
||||
|
||||
- `llm_request`: (`LLMRequest`) A stable representation of the outgoing request.
|
||||
See [Stable Model API](#stable-model-api).
|
||||
- `llm_response`: (`LLMResponse`, **AfterModel only**) A stable representation
|
||||
of the incoming response.
|
||||
|
||||
#### Session & Notification Events
|
||||
|
||||
- `source`: (`startup` | `resume` | `clear`, **SessionStart only**) The trigger
|
||||
source.
|
||||
- `reason`: (`exit` | `clear` | `logout` | `prompt_input_exit` | `other`,
|
||||
**SessionEnd only**) The reason for session end.
|
||||
- `trigger`: (`manual` | `auto`, **PreCompress only**) What triggered the
|
||||
compression event.
|
||||
- `notification_type`: (`ToolPermission`, **Notification only**) The type of
|
||||
notification being fired.
|
||||
- `message`: (`string`, **Notification only**) The notification message.
|
||||
- `details`: (`object`, **Notification only**) Payload-specific details for the
|
||||
notification.
|
||||
|
||||
---
|
||||
|
||||
## Output Schema (`stdout`)
|
||||
|
||||
If the hook exits with `0`, the CLI attempts to parse `stdout` as JSON.
|
||||
|
||||
### Common Output Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
| :------------------- | :-------- | :------------------------------------------------------------------------------------- |
|
||||
| `decision` | `string` | One of: `allow`, `deny`, `block`, `ask`, `approve`. |
|
||||
| `reason` | `string` | Explanation shown to the **agent** when a decision is `deny` or `block`. |
|
||||
| `systemMessage` | `string` | Message displayed in Gemini CLI terminal to provide warning or context to the **user** |
|
||||
| `continue` | `boolean` | If `false`, immediately terminates the agent loop for this turn. |
|
||||
| `stopReason` | `string` | Message shown to the user when `continue` is `false`. |
|
||||
| `suppressOutput` | `boolean` | If `true`, the hook execution is hidden from the CLI transcript. |
|
||||
| `hookSpecificOutput` | `object` | Container for event-specific data (see below). |
|
||||
|
||||
### `hookSpecificOutput` Reference
|
||||
|
||||
| Field | Supported Events | Description |
|
||||
| :------------------ | :----------------------------------------- | :-------------------------------------------------------------------------------- |
|
||||
| `additionalContext` | `SessionStart`, `BeforeAgent`, `AfterTool` | Appends text directly to the agent's context. |
|
||||
| `llm_request` | `BeforeModel` | A `Partial<LLMRequest>` to override parameters of the outgoing call. |
|
||||
| `llm_response` | `BeforeModel` | A **full** `LLMResponse` to bypass the model and provide a synthetic result. |
|
||||
| `llm_response` | `AfterModel` | A `Partial<LLMResponse>` to modify the model's response before the agent sees it. |
|
||||
| `toolConfig` | `BeforeToolSelection` | Object containing `mode` (`AUTO`/`ANY`/`NONE`) and `allowedFunctionNames`. |
|
||||
|
||||
---
|
||||
|
||||
## Stable Model API
|
||||
|
||||
Gemini CLI uses a decoupled format for model interactions to ensure hooks remain
|
||||
stable even if the underlying Gemini SDK changes.
|
||||
|
||||
### `LLMRequest` Object
|
||||
|
||||
Used in `BeforeModel` and `BeforeToolSelection`.
|
||||
|
||||
> 💡 **Note**: In v1, model hooks are primarily text-focused. Non-text parts
|
||||
> (like images or function calls) provided in the `content` array will be
|
||||
> simplified to their string representation by the translator.
|
||||
|
||||
```typescript
|
||||
{
|
||||
"model": string,
|
||||
"messages": Array<{
|
||||
"role": "user" | "model" | "system",
|
||||
"content": string | Array<{ "type": string, [key: string]: any }>
|
||||
}>,
|
||||
"config"?: {
|
||||
"temperature"?: number,
|
||||
"maxOutputTokens"?: number,
|
||||
"topP"?: number,
|
||||
"topK"?: number
|
||||
},
|
||||
"toolConfig"?: {
|
||||
"mode"?: "AUTO" | "ANY" | "NONE",
|
||||
"allowedFunctionNames"?: string[]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `LLMResponse` Object
|
||||
|
||||
Used in `AfterModel` and as a synthetic response in `BeforeModel`.
|
||||
|
||||
```typescript
|
||||
{
|
||||
"text"?: string,
|
||||
"candidates": Array<{
|
||||
"content": {
|
||||
"role": "model",
|
||||
"parts": string[]
|
||||
},
|
||||
"finishReason"?: "STOP" | "MAX_TOKENS" | "SAFETY" | "RECITATION" | "OTHER",
|
||||
"index"?: number,
|
||||
"safetyRatings"?: Array<{
|
||||
"category": string,
|
||||
"probability": string,
|
||||
"blocked"?: boolean
|
||||
}>
|
||||
}>,
|
||||
"usageMetadata"?: {
|
||||
"promptTokenCount"?: number,
|
||||
"candidatesTokenCount"?: number,
|
||||
"totalTokenCount"?: number
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -72,9 +72,9 @@ Run Gemini CLI and execute any command that uses tools:
|
||||
```
|
||||
> Read the README.md file
|
||||
|
||||
[Agent uses ReadFile tool]
|
||||
[Agent uses read_file tool]
|
||||
|
||||
Logged: ReadFile
|
||||
Logged: read_file
|
||||
```
|
||||
|
||||
Check `.gemini/tool-log.txt` to see the logged tool executions.
|
||||
@@ -110,7 +110,7 @@ exit 0
|
||||
"hooks": {
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "secret-scanner",
|
||||
@@ -167,7 +167,7 @@ exit 0
|
||||
"hooks": {
|
||||
"AfterTool": [
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "auto-test",
|
||||
@@ -379,7 +379,7 @@ chmod +x .gemini/hooks/*.js
|
||||
],
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "security-check",
|
||||
@@ -392,7 +392,7 @@ chmod +x .gemini/hooks/*.js
|
||||
],
|
||||
"AfterTool": [
|
||||
{
|
||||
"matcher": "WriteFile|Edit",
|
||||
"matcher": "write_file|replace",
|
||||
"hooks": [
|
||||
{
|
||||
"name": "auto-test",
|
||||
@@ -601,7 +601,7 @@ async function main() {
|
||||
.map((k) => k.trim());
|
||||
|
||||
// Simple keyword-based filtering + core tools
|
||||
const coreTools = ['ReadFile', 'WriteFile', 'Edit', 'RunShellCommand'];
|
||||
const coreTools = ['read_file', 'write_file', 'replace', 'run_shell_command'];
|
||||
const filtered = candidateTools.filter((tool) => {
|
||||
if (coreTools.includes(tool)) return true;
|
||||
const toolLower = tool.toLowerCase();
|
||||
@@ -1018,9 +1018,27 @@ const SECRET_PATTERNS = [
|
||||
];
|
||||
```
|
||||
|
||||
## Packaging as an extension
|
||||
|
||||
While project-level hooks are great for specific repositories, you might want to
|
||||
share your hooks across multiple projects or with other users. You can do this
|
||||
by packaging your hooks as a [Gemini CLI extension](../extensions/index.md).
|
||||
|
||||
Packaging as an extension provides:
|
||||
|
||||
- **Easy distribution:** Share hooks via a git repository or GitHub release.
|
||||
- **Centralized management:** Install, update, and disable hooks using
|
||||
`gemini extensions` commands.
|
||||
- **Version control:** Manage hook versions separately from your project code.
|
||||
- **Variable substitution:** Use `${extensionPath}` and `${process.execPath}`
|
||||
for portable, cross-platform scripts.
|
||||
|
||||
To package hooks as an extension, follow the
|
||||
[extensions hook documentation](../extensions/index.md#hooks).
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Hooks Reference](index.md) - Complete API reference and configuration
|
||||
- [Best Practices](best-practices.md) - Security, performance, and debugging
|
||||
- [Configuration](../cli/configuration.md) - Gemini CLI settings
|
||||
- [Configuration](../get-started/configuration.md) - Gemini CLI settings
|
||||
- [Custom Commands](../cli/custom-commands.md) - Create custom commands
|
||||
|
||||
@@ -128,8 +128,8 @@ editor.
|
||||
You can also **modify the suggested changes** directly in the diff view before
|
||||
accepting them.
|
||||
|
||||
If you select ‘Yes, allow always’ in the CLI, changes will no longer show up in
|
||||
the IDE as they will be auto-accepted.
|
||||
If you select ‘Allow for this session’ in the CLI, changes will no longer show
|
||||
up in the IDE as they will be auto-accepted.
|
||||
|
||||
## Using with sandboxing
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ This documentation is organized into the following sections:
|
||||
- **[Memport](./core/memport.md):** Using the Memory Import Processor.
|
||||
- **[Tools API](./core/tools-api.md):** Information on how the core manages and
|
||||
exposes tools.
|
||||
- **[System Prompt Override](./cli/system-prompt.md):** Replace built-in system
|
||||
instructions using `GEMINI_SYSTEM_MD`.
|
||||
|
||||
- **[Policy Engine](./core/policy-engine.md):** Use the Policy Engine for
|
||||
fine-grained control over tool execution.
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ Dressing Room, which is Google's system for managing NPM packages in the
|
||||
`@google/**` namespace. The packages are all named `@google/**`.
|
||||
|
||||
More information can be found about these systems in the
|
||||
[maintainer repo guide](https://github.com/google-gemini/maintainers-gemini-cli/blob/main/npm.md)
|
||||
[NPM Package Overview](npm.md)
|
||||
|
||||
### Package scopes
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@
|
||||
"label": "Session Management",
|
||||
"slug": "docs/cli/session-management"
|
||||
},
|
||||
{
|
||||
"label": "Agent Skills",
|
||||
"slug": "docs/cli/skills"
|
||||
},
|
||||
{
|
||||
"label": "Settings",
|
||||
"slug": "docs/cli/settings"
|
||||
@@ -115,6 +119,10 @@
|
||||
{
|
||||
"label": "Uninstall",
|
||||
"slug": "docs/cli/uninstall"
|
||||
},
|
||||
{
|
||||
"label": "System prompt override",
|
||||
"slug": "docs/cli/system-prompt"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -204,6 +212,10 @@
|
||||
"label": "Writing hooks",
|
||||
"slug": "docs/hooks/writing-hooks"
|
||||
},
|
||||
{
|
||||
"label": "Hooks reference",
|
||||
"slug": "docs/hooks/reference"
|
||||
},
|
||||
{
|
||||
"label": "Best practices",
|
||||
"slug": "docs/hooks/best-practices"
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ user input, such as text editors (`vim`, `nano`), terminal-based UIs (`htop`),
|
||||
and interactive version control operations (`git rebase -i`).
|
||||
|
||||
When an interactive command is running, you can send input to it from the Gemini
|
||||
CLI. To focus on the interactive shell, press `ctrl+f`. The terminal output,
|
||||
CLI. To focus on the interactive shell, press `Tab`. The terminal output,
|
||||
including complex TUIs, will be rendered correctly.
|
||||
|
||||
## Important notes
|
||||
|
||||
@@ -43,9 +43,15 @@ topics on:
|
||||
- **Cause:** You may be on a corporate network with a firewall that intercepts
|
||||
and inspects SSL/TLS traffic. This often requires a custom root CA
|
||||
certificate to be trusted by Node.js.
|
||||
- **Solution:** Set the `NODE_EXTRA_CA_CERTS` environment variable to the
|
||||
absolute path of your corporate root CA certificate file.
|
||||
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
|
||||
- **Solution:** First try setting `NODE_USE_SYSTEM_CA`; if that does not
|
||||
resolve the issue, set `NODE_EXTRA_CA_CERTS`.
|
||||
- Set the `NODE_USE_SYSTEM_CA=1` environment variable to tell Node.js to use
|
||||
the operating system's native certificate store (where corporate
|
||||
certificates are typically already installed).
|
||||
- Example: `export NODE_USE_SYSTEM_CA=1`
|
||||
- Set the `NODE_EXTRA_CA_CERTS` environment variable to the absolute path of
|
||||
your corporate root CA certificate file.
|
||||
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
|
||||
|
||||
## Common error messages and solutions
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ const external = [
|
||||
'@lydell/node-pty-linux-x64',
|
||||
'@lydell/node-pty-win32-arm64',
|
||||
'@lydell/node-pty-win32-x64',
|
||||
'keytar',
|
||||
];
|
||||
|
||||
const baseConfig = {
|
||||
|
||||
+53
-10
@@ -12,7 +12,7 @@ import prettierConfig from 'eslint-config-prettier';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import vitest from '@vitest/eslint-plugin';
|
||||
import globals from 'globals';
|
||||
import licenseHeader from 'eslint-plugin-license-header';
|
||||
import headers from 'eslint-plugin-headers';
|
||||
import path from 'node:path';
|
||||
import url from 'node:url';
|
||||
|
||||
@@ -35,6 +35,8 @@ export default tseslint.config(
|
||||
'package/bundle/**',
|
||||
'.integration-tests/**',
|
||||
'dist/**',
|
||||
'evals/**',
|
||||
'packages/test-utils/**',
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
@@ -164,9 +166,43 @@ export default tseslint.config(
|
||||
'prefer-arrow-callback': 'error',
|
||||
'prefer-const': ['error', { destructuring: 'all' }],
|
||||
radix: 'error',
|
||||
'no-console': 'error',
|
||||
'default-case': 'error',
|
||||
'@typescript-eslint/await-thenable': ['error'],
|
||||
'@typescript-eslint/no-floating-promises': ['error'],
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': ['error'],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'node:os',
|
||||
importNames: ['homedir', 'tmpdir'],
|
||||
message:
|
||||
'Please use the helpers from @google/gemini-cli-core instead of node:os homedir()/tmpdir() to ensure strict environment isolation.',
|
||||
},
|
||||
{
|
||||
name: 'os',
|
||||
importNames: ['homedir', 'tmpdir'],
|
||||
message:
|
||||
'Please use the helpers from @google/gemini-cli-core instead of os homedir()/tmpdir() to ensure strict environment isolation.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Allow os.homedir() in tests and paths.ts where it is used to implement the helper
|
||||
files: [
|
||||
'**/*.test.ts',
|
||||
'**/*.test.tsx',
|
||||
'packages/core/src/utils/paths.ts',
|
||||
'packages/test-utils/src/**/*.ts',
|
||||
'scripts/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-imports': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -208,19 +244,26 @@ export default tseslint.config(
|
||||
{
|
||||
files: ['./**/*.{tsx,ts,js}'],
|
||||
plugins: {
|
||||
'license-header': licenseHeader,
|
||||
headers,
|
||||
import: importPlugin,
|
||||
},
|
||||
rules: {
|
||||
'license-header/header': [
|
||||
'headers/header-format': [
|
||||
'error',
|
||||
[
|
||||
'/**',
|
||||
' * @license',
|
||||
' * Copyright 2025 Google LLC',
|
||||
' * SPDX-License-Identifier: Apache-2.0',
|
||||
' */',
|
||||
],
|
||||
{
|
||||
source: 'string',
|
||||
content: [
|
||||
'@license',
|
||||
'Copyright (year) Google LLC',
|
||||
'SPDX-License-Identifier: Apache-2.0',
|
||||
].join('\n'),
|
||||
patterns: {
|
||||
year: {
|
||||
pattern: '202[5-6]',
|
||||
defaultValue: '2026',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
'import/enforce-node-protocol-usage': ['error', 'always'],
|
||||
},
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
# Behavioral Evals
|
||||
|
||||
Behavioral evaluations (evals) are tests designed to validate the agent's
|
||||
behavior in response to specific prompts. They serve as a critical feedback loop
|
||||
for changes to system prompts, tool definitions, and other model-steering
|
||||
mechanisms.
|
||||
|
||||
## Why Behavioral Evals?
|
||||
|
||||
Unlike traditional **integration tests** which verify that the system functions
|
||||
correctly (e.g., "does the file writer actually write to disk?"), behavioral
|
||||
evals verify that the model _chooses_ to take the correct action (e.g., "does
|
||||
the model decide to write to disk when asked to save code?").
|
||||
|
||||
They are also distinct from broad **industry benchmarks** (like SWE-bench).
|
||||
While benchmarks measure general capabilities across complex challenges, our
|
||||
behavioral evals focus on specific, granular behaviors relevant to the Gemini
|
||||
CLI's features.
|
||||
|
||||
### Key Characteristics
|
||||
|
||||
- **Feedback Loop**: They help us understand how changes to prompts or tools
|
||||
affect the model's decision-making.
|
||||
- _Did a change to the system prompt make the model less likely to use tool
|
||||
X?_
|
||||
- _Did a new tool definition confuse the model?_
|
||||
- **Regression Testing**: They prevent regressions in model steering.
|
||||
- **Non-Determinism**: Unlike unit tests, LLM behavior can be non-deterministic.
|
||||
We distinguish between behaviors that should be robust (`ALWAYS_PASSES`) and
|
||||
those that are generally reliable but might occasionally vary
|
||||
(`USUALLY_PASSES`).
|
||||
|
||||
## Creating an Evaluation
|
||||
|
||||
Evaluations are located in the `evals` directory. Each evaluation is a Vitest
|
||||
test file that uses the `evalTest` function from `evals/test-helper.ts`.
|
||||
|
||||
### `evalTest`
|
||||
|
||||
The `evalTest` function is a helper that runs a single evaluation case. It takes
|
||||
two arguments:
|
||||
|
||||
1. `policy`: The consistency expectation for this test (`'ALWAYS_PASSES'` or
|
||||
`'USUALLY_PASSES'`).
|
||||
2. `evalCase`: An object defining the test case.
|
||||
|
||||
#### Policies
|
||||
|
||||
Policies control how strictly a test is validated. Tests should generally use
|
||||
the ALWAYS_PASSES policy to offer the strictest guarantees.
|
||||
|
||||
USUALLY_PASSES exists to enable assertion of less consistent or aspirational
|
||||
behaviors.
|
||||
|
||||
- `ALWAYS_PASSES`: Tests expected to pass 100% of the time. These are typically
|
||||
trivial and test basic functionality. These run in every CI.
|
||||
- `USUALLY_PASSES`: Tests expected to pass most of the time but may have some
|
||||
flakiness due to non-deterministic behaviors. These are run nightly and used
|
||||
to track the health of the product from build to build.
|
||||
|
||||
#### `EvalCase` Properties
|
||||
|
||||
- `name`: The name of the evaluation case.
|
||||
- `prompt`: The prompt to send to the model.
|
||||
- `params`: An optional object with parameters to pass to the test rig (e.g.,
|
||||
settings).
|
||||
- `assert`: An async function that takes the test rig and the result of the run
|
||||
and asserts that the result is correct.
|
||||
- `log`: An optional boolean that, if set to `true`, will log the tool calls to
|
||||
a file in the `evals/logs` directory.
|
||||
|
||||
### Example
|
||||
|
||||
```typescript
|
||||
import { describe, expect } from 'vitest';
|
||||
import { evalTest } from './test-helper.js';
|
||||
|
||||
describe('my_feature', () => {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should do something',
|
||||
prompt: 'do it',
|
||||
assert: async (rig, result) => {
|
||||
// assertions
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Running Evaluations
|
||||
|
||||
### Always Passing Evals
|
||||
|
||||
To run the evaluations that are expected to always pass (CI safe):
|
||||
|
||||
```bash
|
||||
npm run test:always_passing_evals
|
||||
```
|
||||
|
||||
### All Evals
|
||||
|
||||
To run all evaluations, including those that may be flaky ("usually passes"):
|
||||
|
||||
```bash
|
||||
npm run test:all_evals
|
||||
```
|
||||
|
||||
This command sets the `RUN_EVALS` environment variable to `1`, which enables the
|
||||
`USUALLY_PASSES` tests.
|
||||
|
||||
## Reporting
|
||||
|
||||
Results for evaluations are available on GitHub Actions:
|
||||
|
||||
- **CI Evals**: Included in the
|
||||
[E2E (Chained)](https://github.com/google-gemini/gemini-cli/actions/workflows/chained_e2e.yml)
|
||||
workflow. These must pass 100% for every PR.
|
||||
- **Nightly Evals**: Run daily via the
|
||||
[Evals: Nightly](https://github.com/google-gemini/gemini-cli/actions/workflows/evals-nightly.yml)
|
||||
workflow. These track the long-term health and stability of model steering.
|
||||
|
||||
### Nightly Report Format
|
||||
|
||||
The nightly workflow executes the full evaluation suite multiple times
|
||||
(currently 3 attempts) to account for non-determinism. These results are
|
||||
aggregated into a **Nightly Summary** attached to the workflow run.
|
||||
|
||||
#### How to interpret the report:
|
||||
|
||||
- **Pass Rate (%)**: Each cell represents the percentage of successful runs for
|
||||
a specific test in that workflow instance.
|
||||
- **History**: The table shows the pass rates for the last 10 nightly runs,
|
||||
allowing you to identify if a model's behavior is trending towards
|
||||
instability.
|
||||
- **Total Pass Rate**: An aggregate metric of all evaluations run in that batch.
|
||||
|
||||
A significant drop in the pass rate for a `USUALLY_PASSES` test—even if it
|
||||
doesn't drop to 0%—often indicates that a recent change to a system prompt or
|
||||
tool definition has made the model's behavior less reliable.
|
||||
|
||||
You may be able to investigate the regression using Gemini CLI by giving it the
|
||||
link to the runs before and after the change and the name of the test and asking
|
||||
it to investigate what changes may have impacted the test.
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, expect } from 'vitest';
|
||||
import { evalTest } from './test-helper.js';
|
||||
import { validateModelOutput } from '../integration-tests/test-helper.js';
|
||||
|
||||
describe('save_memory', () => {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should be able to save to memory',
|
||||
params: {
|
||||
settings: { tools: { core: ['save_memory'] } },
|
||||
},
|
||||
prompt: `remember that my favorite color is blue.
|
||||
|
||||
what is my favorite color? tell me that and surround it with $ symbol`,
|
||||
assert: async (rig, result) => {
|
||||
const foundToolCall = await rig.waitForToolCall('save_memory');
|
||||
expect(
|
||||
foundToolCall,
|
||||
'Expected to find a save_memory tool call',
|
||||
).toBeTruthy();
|
||||
|
||||
validateModelOutput(result, 'blue', 'Save memory test');
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import { TestRig } from '@google/gemini-cli-test-utils';
|
||||
|
||||
export * from '@google/gemini-cli-test-utils';
|
||||
|
||||
// Indicates the consistency expectation for this test.
|
||||
// - ALWAYS_PASSES - Means that the test is expected to pass 100% of the time. These
|
||||
// These tests are typically trivial and test basic functionality with unambiguous
|
||||
// prompts. For example: "call save_memory to remember foo" should be fairly reliable.
|
||||
// These are the first line of defense against regressions in key behaviors and run in
|
||||
// every CI. You can run these locally with 'npm run test:always_passing_evals'.
|
||||
//
|
||||
// - USUALLY_PASSES - Means that the test is expected to pass most of the time but
|
||||
// may have some flakiness as a result of relying on non-deterministic prompted
|
||||
// behaviors and/or ambiguous prompts or complex tasks.
|
||||
// For example: "Please do build changes until the very end" --> ambiguous whether
|
||||
// the agent should add to memory without more explicit system prompt or user
|
||||
// instructions. There are many more of these tests and they may pass less consistently.
|
||||
// The pass/fail trendline of this set of tests can be used as a general measure
|
||||
// of product quality. You can run these locally with 'npm run test:all_evals'.
|
||||
// This may take a really long time and is not recommended.
|
||||
export type EvalPolicy = 'ALWAYS_PASSES' | 'USUALLY_PASSES';
|
||||
|
||||
export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
|
||||
const fn = async () => {
|
||||
const rig = new TestRig();
|
||||
try {
|
||||
await rig.setup(evalCase.name, evalCase.params);
|
||||
const result = await rig.run({ args: evalCase.prompt });
|
||||
await evalCase.assert(rig, result);
|
||||
} finally {
|
||||
await logToFile(
|
||||
evalCase.name,
|
||||
JSON.stringify(rig.readToolLogs(), null, 2),
|
||||
);
|
||||
await rig.cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
if (policy === 'USUALLY_PASSES' && !process.env.RUN_EVALS) {
|
||||
it.skip(evalCase.name, fn);
|
||||
} else {
|
||||
it(evalCase.name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvalCase {
|
||||
name: string;
|
||||
params?: Record<string, any>;
|
||||
prompt: string;
|
||||
assert: (rig: TestRig, result: string) => Promise<void>;
|
||||
}
|
||||
|
||||
async function logToFile(name: string, content: string) {
|
||||
const logDir = 'evals/logs';
|
||||
await fs.promises.mkdir(logDir, { recursive: true });
|
||||
const sanitizedName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
const logFile = `${logDir}/${sanitizedName}.log`;
|
||||
await fs.promises.writeFile(logFile, content);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 300000, // 5 minutes
|
||||
reporters: ['default', 'json'],
|
||||
outputFile: {
|
||||
json: 'evals/logs/report.json',
|
||||
},
|
||||
include: ['**/*.eval.ts'],
|
||||
},
|
||||
});
|
||||
@@ -32,7 +32,7 @@ describe('Interactive Mode', () => {
|
||||
await run.sendKeys(
|
||||
'Write a 200 word story about a robot. The story MUST end with the text THE_END followed by a period.',
|
||||
);
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
// Wait for the specific end marker.
|
||||
await run.expectText('THE_END.', 30000);
|
||||
|
||||
@@ -95,7 +95,10 @@ describe('extension reloading', () => {
|
||||
|
||||
// Poll for the updated list
|
||||
await rig.pollCommand(
|
||||
() => run.sendKeys('\u0015/mcp list\r'),
|
||||
async () => {
|
||||
await run.sendText('/mcp list');
|
||||
await run.type('\r');
|
||||
},
|
||||
() => {
|
||||
const output = stripAnsi(run.output);
|
||||
return (
|
||||
@@ -110,9 +113,9 @@ describe('extension reloading', () => {
|
||||
// Update the extension, expect the list to update, and mcp servers as well.
|
||||
await run.sendKeys('\u0015/extensions update test-extension');
|
||||
await run.expectText('/extensions update test-extension');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
await run.expectText(
|
||||
` * test-server (remote): http://localhost:${portB}/mcp`,
|
||||
);
|
||||
@@ -123,7 +126,10 @@ describe('extension reloading', () => {
|
||||
|
||||
// Poll for the updated extension version
|
||||
await rig.pollCommand(
|
||||
() => run.sendKeys('\u0015/extensions list\r'),
|
||||
async () => {
|
||||
await run.sendText('/extensions list');
|
||||
await run.type('\r');
|
||||
},
|
||||
() =>
|
||||
stripAnsi(run.output).includes(
|
||||
'test-extension (v0.0.2) - active (updated)',
|
||||
@@ -133,7 +139,10 @@ describe('extension reloading', () => {
|
||||
|
||||
// Poll for the updated mcp tool
|
||||
await rig.pollCommand(
|
||||
() => run.sendKeys('\u0015/mcp list\r'),
|
||||
async () => {
|
||||
await run.sendText('/mcp list');
|
||||
await run.type('\r');
|
||||
},
|
||||
() => {
|
||||
const output = stripAnsi(run.output);
|
||||
return (
|
||||
@@ -146,7 +155,7 @@ describe('extension reloading', () => {
|
||||
);
|
||||
|
||||
await run.sendText('/quit');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
// Clean things up.
|
||||
await serverA.stop();
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('Interactive file system', () => {
|
||||
// Step 1: Read the file
|
||||
const readPrompt = `Read the version from ${fileName}`;
|
||||
await run.type(readPrompt);
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
const readCall = await rig.waitForToolCall('read_file', 30000);
|
||||
expect(readCall, 'Expected to find a read_file tool call').toBe(true);
|
||||
@@ -45,7 +45,7 @@ describe('Interactive file system', () => {
|
||||
// Step 2: Write the file
|
||||
const writePrompt = `now change the version to 1.0.1 in the file`;
|
||||
await run.type(writePrompt);
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
// Check tool calls made with right args
|
||||
await rig.expectToolCallSuccess(
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"list_dir","args":{"path":"."}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Final Answer"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Responding**\n\nI will respond to the user's request.\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":100,"totalTokenCount":120,"promptTokensDetails":[{"modality":"TEXT","tokenCount":100}],"thoughtsTokenCount":20}},{"candidates":[{"content":{"parts":[{"text":"Response to: "}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":5,"totalTokenCount":125,"promptTokensDetails":[{"modality":"TEXT","tokenCount":100}],"thoughtsTokenCount":20}},{"candidates":[{"content":{"parts":[{"text":"Hello World"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":7,"totalTokenCount":127,"promptTokensDetails":[{"modality":"TEXT","tokenCount":100}],"thoughtsTokenCount":20}}]}
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import { join } from 'node:path';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
describe('Hooks Agent Flow', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (rig) {
|
||||
await rig.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
describe('BeforeAgent Hooks', () => {
|
||||
it('should inject additional context via BeforeAgent hook', async () => {
|
||||
await rig.setup('should inject additional context via BeforeAgent hook', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-agent-flow.responses',
|
||||
),
|
||||
});
|
||||
|
||||
const hookScript = `
|
||||
try {
|
||||
const output = {
|
||||
decision: "allow",
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "BeforeAgent",
|
||||
additionalContext: "SYSTEM INSTRUCTION: This is injected context."
|
||||
}
|
||||
};
|
||||
process.stdout.write(JSON.stringify(output));
|
||||
} catch (e) {
|
||||
console.error('Failed to write stdout:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
console.error('DEBUG: BeforeAgent hook executed');
|
||||
`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'before_agent_context.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
await rig.setup('should inject additional context via BeforeAgent hook', {
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeAgent: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await rig.run({ args: 'Hello test' });
|
||||
|
||||
// Verify hook execution and telemetry
|
||||
const hookTelemetryFound = await rig.waitForTelemetryEvent('hook_call');
|
||||
expect(hookTelemetryFound).toBeTruthy();
|
||||
|
||||
const hookLogs = rig.readHookLogs();
|
||||
const beforeAgentLog = hookLogs.find(
|
||||
(log) => log.hookCall.hook_event_name === 'BeforeAgent',
|
||||
);
|
||||
|
||||
expect(beforeAgentLog).toBeDefined();
|
||||
expect(beforeAgentLog?.hookCall.stdout).toContain('injected context');
|
||||
expect(beforeAgentLog?.hookCall.stdout).toContain('"decision":"allow"');
|
||||
expect(beforeAgentLog?.hookCall.stdout).toContain(
|
||||
'SYSTEM INSTRUCTION: This is injected context.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AfterAgent Hooks', () => {
|
||||
it('should receive prompt and response in AfterAgent hook', async () => {
|
||||
await rig.setup('should receive prompt and response in AfterAgent hook', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-agent-flow.responses',
|
||||
),
|
||||
});
|
||||
|
||||
const hookScript = `
|
||||
const fs = require('fs');
|
||||
try {
|
||||
const input = fs.readFileSync(0, 'utf-8');
|
||||
console.error('DEBUG: AfterAgent hook input received');
|
||||
process.stdout.write("Received Input: " + input);
|
||||
} catch (err) {
|
||||
console.error('Hook Failed:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'after_agent_verify.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
await rig.setup('should receive prompt and response in AfterAgent hook', {
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
AfterAgent: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await rig.run({ args: 'Hello validation' });
|
||||
|
||||
const hookTelemetryFound = await rig.waitForTelemetryEvent('hook_call');
|
||||
expect(hookTelemetryFound).toBeTruthy();
|
||||
|
||||
const hookLogs = rig.readHookLogs();
|
||||
const afterAgentLog = hookLogs.find(
|
||||
(log) => log.hookCall.hook_event_name === 'AfterAgent',
|
||||
);
|
||||
|
||||
expect(afterAgentLog).toBeDefined();
|
||||
// Verify the hook stdout contains the input we echoed which proves the
|
||||
// hook received the prompt and response
|
||||
expect(afterAgentLog?.hookCall.stdout).toContain('Received Input');
|
||||
expect(afterAgentLog?.hookCall.stdout).toContain('Hello validation');
|
||||
// The fake response contains "Hello World"
|
||||
expect(afterAgentLog?.hookCall.stdout).toContain('Hello World');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multi-step Loops', () => {
|
||||
it('should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls', async () => {
|
||||
await rig.setup(
|
||||
'should fire BeforeAgent and AfterAgent exactly once per turn despite tool calls',
|
||||
{
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-agent-flow-multistep.responses',
|
||||
),
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeAgent: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node -e "console.log('BeforeAgent Fired')"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
AfterAgent: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node -e "console.log('AfterAgent Fired')"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await rig.run({ args: 'Do a multi-step task' });
|
||||
|
||||
const hookLogs = rig.readHookLogs();
|
||||
const beforeAgentLogs = hookLogs.filter(
|
||||
(log) => log.hookCall.hook_event_name === 'BeforeAgent',
|
||||
);
|
||||
const afterAgentLogs = hookLogs.filter(
|
||||
(log) => log.hookCall.hook_event_name === 'AfterAgent',
|
||||
);
|
||||
|
||||
expect(beforeAgentLogs).toHaveLength(1);
|
||||
|
||||
expect(afterAgentLogs).toHaveLength(1);
|
||||
|
||||
const afterAgentLog = afterAgentLogs[0];
|
||||
expect(afterAgentLog).toBeDefined();
|
||||
expect(afterAgentLog?.hookCall.stdout).toContain('AfterAgent Fired');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hi there!"}],"role":"model"},"finishReason":"STOP","index":0}]}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Clarification: I am a bot."}],"role":"model"},"finishReason":"STOP","index":0}]}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"**Initializing File Creation**\n\nI'm starting to think about how to make a new file called `test.txt`. My plan is to use a `write_file` tool. I'll need to specify the location and what the file should contain. For now, it will be empty.\n\n\n","thought":true}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":13216,"totalTokenCount":13269,"promptTokensDetails":[{"modality":"TEXT","tokenCount":13216}],"thoughtsTokenCount":53}},{"candidates":[{"content":{"parts":[{"functionCall":{"name":"write_file","args":{"file_path":"test.txt","content":""}},"thoughtSignature":"CiQBcsjafJ20Qbx0YvING6aZ0wYoGWJh3eqornOG4E4AfBLiVsQKXwFyyNp8UlwYs/pv9IRQQGhDlrmlOJF2hfQijryyUYLI+qjDYTpZ6KKIfZF4+vS0soL2BJ3eTXA6gaadFEfNQem3WQVeQoKLFoW4Hv4mbasXqQc0K3p15DuSAtZZENTbCnsBcsjafGK+BJyF/Npnd7gyU0TL5PXePT0nuDFjhJDxlSRUJHDP315TewD3PUYsXd10oWsfhy4B5AngyUiBPUoajdsxg8WxaxnOZYqcp8EIuwtGZrCTev6IihT5nE5jj7u0P9vtnCmkAc6p+4O7Q7Jku1uVGqeJChgzI4YKSAFyyNp8EXSdbttV4xzX+NLKkc276L8Y63tnKU6/Y7fc9/58tU29DSdrgwfe9qmvwtTsO0piFXSLazqHJt8h2bgR7A7GnKDiIA=="}],"role":"model"},"index":0}],"usageMetadata":{"promptTokenCount":13216,"candidatesTokenCount":21,"totalTokenCount":13290,"promptTokensDetails":[{"modality":"TEXT","tokenCount":13216}],"thoughtsTokenCount":53}},{"candidates":[{"content":{"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":13216,"candidatesTokenCount":21,"totalTokenCount":13290,"promptTokensDetails":[{"modality":"TEXT","tokenCount":13216}],"thoughtsTokenCount":53}}]}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"write_file","args":{"content":"original content","file_path":"original.txt"}}}],"role":"model"},"finishReason":"STOP","index":0}]}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I have created the file."}],"role":"model"},"finishReason":"STOP","index":0}]}]}
|
||||
@@ -32,10 +32,8 @@ describe('Hooks System Integration', () => {
|
||||
'hooks-system.block-tool.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
matcher: 'write_file',
|
||||
@@ -44,7 +42,7 @@ describe('Hooks System Integration', () => {
|
||||
{
|
||||
type: 'command',
|
||||
command:
|
||||
'echo "{\\"decision\\": \\"block\\", \\"reason\\": \\"File writing blocked by security policy\\"}"',
|
||||
"node -e \"console.log(JSON.stringify({decision: 'block', reason: 'File writing blocked by security policy'}))\"",
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -86,10 +84,8 @@ describe('Hooks System Integration', () => {
|
||||
'hooks-system.allow-tool.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
matcher: 'write_file',
|
||||
@@ -97,7 +93,7 @@ describe('Hooks System Integration', () => {
|
||||
{
|
||||
type: 'command',
|
||||
command:
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"reason\\": \\"File writing approved\\"}"',
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'File writing approved'}))\"",
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -129,17 +125,15 @@ describe('Hooks System Integration', () => {
|
||||
describe('Command Hooks - Additional Context', () => {
|
||||
it('should add additional context from AfterTool hooks', async () => {
|
||||
const command =
|
||||
'echo "{\\"hookSpecificOutput\\": {\\"hookEventName\\": \\"AfterTool\\", \\"additionalContext\\": \\"Security scan: File content appears safe\\"}}"';
|
||||
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'Security scan: File content appears safe'}}))\"";
|
||||
await rig.setup('should add additional context from AfterTool hooks', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-system.after-tool-context.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
AfterTool: [
|
||||
{
|
||||
matcher: 'read_file',
|
||||
@@ -190,40 +184,35 @@ describe('Hooks System Integration', () => {
|
||||
'hooks-system.before-model.responses',
|
||||
),
|
||||
});
|
||||
const hookScript = `#!/bin/bash
|
||||
echo '{
|
||||
"decision": "allow",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "BeforeModel",
|
||||
"llm_request": {
|
||||
"messages": [
|
||||
const hookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({
|
||||
decision: "allow",
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "BeforeModel",
|
||||
llm_request: {
|
||||
messages: [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Please respond with exactly: The security hook modified this request successfully."
|
||||
role: "user",
|
||||
content: "Please respond with exactly: The security hook modified this request successfully."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}'`;
|
||||
}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'before_model_hook.sh');
|
||||
const scriptPath = join(rig.testDir!, 'before_model_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
// Make executable
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync(`chmod +x "${scriptPath}"`);
|
||||
|
||||
await rig.setup('should modify LLM requests with BeforeModel hooks', {
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeModel: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: scriptPath,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -250,7 +239,9 @@ echo '{
|
||||
expect(hookTelemetryFound[0].hookCall.hook_event_name).toBe(
|
||||
'BeforeModel',
|
||||
);
|
||||
expect(hookTelemetryFound[0].hookCall.hook_name).toBe(scriptPath);
|
||||
expect(hookTelemetryFound[0].hookCall.hook_name).toBe(
|
||||
`node "${scriptPath}"`,
|
||||
);
|
||||
expect(hookTelemetryFound[0].hookCall.hook_input).toBeDefined();
|
||||
expect(hookTelemetryFound[0].hookCall.hook_output).toBeDefined();
|
||||
expect(hookTelemetryFound[0].hookCall.exit_code).toBe(0);
|
||||
@@ -270,43 +261,39 @@ echo '{
|
||||
),
|
||||
});
|
||||
// Create a hook script that modifies the LLM response
|
||||
const hookScript = `#!/bin/bash
|
||||
echo '{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "AfterModel",
|
||||
"llm_response": {
|
||||
"candidates": [
|
||||
const hookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "AfterModel",
|
||||
llm_response: {
|
||||
candidates: [
|
||||
{
|
||||
"content": {
|
||||
"role": "model",
|
||||
"parts": [
|
||||
content: {
|
||||
role: "model",
|
||||
parts: [
|
||||
"[FILTERED] Response has been filtered for security compliance."
|
||||
]
|
||||
},
|
||||
"finishReason": "STOP"
|
||||
finishReason: "STOP"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}'`;
|
||||
}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'after_model_hook.sh');
|
||||
const scriptPath = join(rig.testDir!, 'after_model_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync(`chmod +x "${scriptPath}"`);
|
||||
|
||||
await rig.setup('should modify LLM responses with AfterModel hooks', {
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
AfterModel: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: scriptPath,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -343,17 +330,15 @@ echo '{
|
||||
);
|
||||
// Create inline hook command (works on both Unix and Windows)
|
||||
const hookCommand =
|
||||
'echo "{\\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeToolSelection\\", \\"toolConfig\\": {\\"mode\\": \\"ANY\\", \\"allowedFunctionNames\\": [\\"read_file\\", \\"run_shell_command\\"]}}}"';
|
||||
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'BeforeToolSelection', toolConfig: {mode: 'ANY', allowedFunctionNames: ['read_file', 'run_shell_command']}}}))\"";
|
||||
|
||||
await rig.setup(
|
||||
'should modify tool selection with BeforeToolSelection hooks',
|
||||
{
|
||||
settings: {
|
||||
debugMode: true,
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeToolSelection: [
|
||||
{
|
||||
hooks: [
|
||||
@@ -404,32 +389,28 @@ echo '{
|
||||
),
|
||||
});
|
||||
// Create a hook script that adds context to the prompt
|
||||
const hookScript = `#!/bin/bash
|
||||
echo '{
|
||||
"decision": "allow",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "BeforeAgent",
|
||||
"additionalContext": "SYSTEM INSTRUCTION: You are in a secure environment. Always mention security compliance in your responses."
|
||||
const hookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({
|
||||
decision: "allow",
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "BeforeAgent",
|
||||
additionalContext: "SYSTEM INSTRUCTION: You are in a secure environment. Always mention security compliance in your responses."
|
||||
}
|
||||
}'`;
|
||||
}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'before_agent_hook.sh');
|
||||
const scriptPath = join(rig.testDir!, 'before_agent_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync(`chmod +x "${scriptPath}"`);
|
||||
|
||||
await rig.setup('should augment prompts with BeforeAgent hooks', {
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeAgent: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: scriptPath,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -452,9 +433,10 @@ echo '{
|
||||
|
||||
describe('Notification Hooks - Permission Handling', () => {
|
||||
it('should handle notification hooks for tool permissions', async () => {
|
||||
// Create inline hook command (works on both Unix and Windows)
|
||||
// Create inline hook command (works on both Unix and Windows)
|
||||
const hookCommand =
|
||||
'echo "{\\"suppressOutput\\": false, \\"systemMessage\\": \\"Permission request logged by security hook\\"}"';
|
||||
'node -e "console.log(JSON.stringify({suppressOutput: false, systemMessage: \'Permission request logged by security hook\'}))"';
|
||||
|
||||
await rig.setup('should handle notification hooks for tool permissions', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -464,11 +446,11 @@ echo '{
|
||||
settings: {
|
||||
// Configure tools to enable hooks and require confirmation to trigger notifications
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
approval: 'ASK', // Disable YOLO mode to show permission prompts
|
||||
confirmationRequired: ['run_shell_command'],
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
Notification: [
|
||||
{
|
||||
matcher: 'ToolPermission',
|
||||
@@ -548,9 +530,9 @@ echo '{
|
||||
it('should execute hooks sequentially when configured', async () => {
|
||||
// Create inline hook commands (works on both Unix and Windows)
|
||||
const hook1Command =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeAgent\\", \\"additionalContext\\": \\"Step 1: Initial validation passed.\\"}}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 1: Initial validation passed.'}}))\"";
|
||||
const hook2Command =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeAgent\\", \\"additionalContext\\": \\"Step 2: Security check completed.\\"}}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'Step 2: Security check completed.'}}))\"";
|
||||
|
||||
await rig.setup('should execute hooks sequentially when configured', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -558,10 +540,8 @@ echo '{
|
||||
'hooks-system.sequential-execution.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeAgent: [
|
||||
{
|
||||
sequential: true,
|
||||
@@ -621,36 +601,33 @@ echo '{
|
||||
),
|
||||
});
|
||||
// Create a hook script that validates the input format
|
||||
const hookScript = `#!/bin/bash
|
||||
# Read JSON input from stdin
|
||||
input=$(cat)
|
||||
const hookScript = `const fs = require('fs');
|
||||
const input = fs.readFileSync(0, 'utf-8');
|
||||
try {
|
||||
const json = JSON.parse(input);
|
||||
// Check fields
|
||||
if (json.session_id && json.cwd && json.hook_event_name && json.timestamp && json.tool_name && json.tool_input) {
|
||||
console.log(JSON.stringify({decision: "allow", reason: "Input format is correct"}));
|
||||
} else {
|
||||
console.log(JSON.stringify({decision: "block", reason: "Input format is invalid"}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(JSON.stringify({decision: "block", reason: "Invalid JSON"}));
|
||||
}`;
|
||||
|
||||
# Check for required fields
|
||||
if echo "$input" | jq -e '.session_id and .cwd and .hook_event_name and .timestamp and .tool_name and .tool_input' > /dev/null 2>&1; then
|
||||
echo '{"decision": "allow", "reason": "Input format is correct"}'
|
||||
exit 0
|
||||
else
|
||||
echo '{"decision": "block", "reason": "Input format is invalid"}'
|
||||
exit 0
|
||||
fi`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'input_validation_hook.sh');
|
||||
const scriptPath = join(rig.testDir!, 'input_validation_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync(`chmod +x "${scriptPath}"`);
|
||||
|
||||
await rig.setup('should provide correct input format to hooks', {
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: scriptPath,
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
@@ -682,11 +659,11 @@ fi`;
|
||||
it('should handle hooks for all major event types', async () => {
|
||||
// Create inline hook commands (works on both Unix and Windows)
|
||||
const beforeToolCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"BeforeTool: File operation logged\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'BeforeTool: File operation logged'}))\"";
|
||||
const afterToolCommand =
|
||||
'echo "{\\"hookSpecificOutput\\": {\\"hookEventName\\": \\"AfterTool\\", \\"additionalContext\\": \\"AfterTool: Operation completed successfully\\"}}"';
|
||||
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'AfterTool', additionalContext: 'AfterTool: Operation completed successfully'}}))\"";
|
||||
const beforeAgentCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"hookSpecificOutput\\": {\\"hookEventName\\": \\"BeforeAgent\\", \\"additionalContext\\": \\"BeforeAgent: User request processed\\"}}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', hookSpecificOutput: {hookEventName: 'BeforeAgent', additionalContext: 'BeforeAgent: User request processed'}}))\"";
|
||||
|
||||
await rig.setup('should handle hooks for all major event types', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -694,10 +671,8 @@ fi`;
|
||||
'hooks-system.multiple-events.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeAgent: [
|
||||
{
|
||||
hooks: [
|
||||
@@ -802,17 +777,15 @@ fi`;
|
||||
// Create a hook script that fails
|
||||
// Create inline hook commands (works on both Unix and Windows)
|
||||
// Failing hook: exits with non-zero code
|
||||
const failingCommand = 'exit 1';
|
||||
const failingCommand = 'node -e "process.exit(1)"';
|
||||
// Working hook: returns success with JSON
|
||||
const workingCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"reason\\": \\"Working hook succeeded\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'Working hook succeeded'}))\"";
|
||||
|
||||
await rig.setup('should handle hook failures gracefully', {
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
hooks: [
|
||||
@@ -855,7 +828,7 @@ fi`;
|
||||
it('should generate telemetry events for hook executions', async () => {
|
||||
// Create inline hook command (works on both Unix and Windows)
|
||||
const hookCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"reason\\": \\"Telemetry test hook\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', reason: 'Telemetry test hook'}))\"";
|
||||
|
||||
await rig.setup('should generate telemetry events for hook executions', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -863,10 +836,8 @@ fi`;
|
||||
'hooks-system.telemetry.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
hooks: [
|
||||
@@ -898,7 +869,7 @@ fi`;
|
||||
it('should fire SessionStart hook on app startup', async () => {
|
||||
// Create inline hook command that outputs JSON
|
||||
const sessionStartCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"Session starting on startup\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting on startup'}))\"";
|
||||
|
||||
await rig.setup('should fire SessionStart hook on app startup', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -906,10 +877,8 @@ fi`;
|
||||
'hooks-system.session-startup.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
SessionStart: [
|
||||
{
|
||||
matcher: 'startup',
|
||||
@@ -955,12 +924,172 @@ fi`;
|
||||
}
|
||||
});
|
||||
|
||||
it('should fire SessionStart hook and inject context', async () => {
|
||||
// Create hook script that outputs JSON with additionalContext
|
||||
const hookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({
|
||||
decision: 'allow',
|
||||
systemMessage: 'Context injected via SessionStart hook',
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext: 'If asked about your identity, you must respond with the following: I am a protocol droid that speaks the bacchi.'
|
||||
}
|
||||
}));`;
|
||||
|
||||
await rig.setup('should fire SessionStart hook and inject context', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-system.session-startup.responses',
|
||||
),
|
||||
});
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'session_start_context_hook.cjs');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
await rig.setup('should fire SessionStart hook and inject context', {
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
SessionStart: [
|
||||
{
|
||||
matcher: 'startup',
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Run a query - the SessionStart hook will fire during app initialization
|
||||
const result = await rig.run({ args: 'Who are you?' });
|
||||
|
||||
// Check if systemMessage was displayed (in stderr, which rig.run captures)
|
||||
expect(result).toContain('Context injected via SessionStart hook');
|
||||
|
||||
// Check if additionalContext influenced the model response
|
||||
// Note: We use fake responses, but the rig records interactions.
|
||||
// If we are using fake responses, the model won't actually respond unless we provide a fake response for the injected context.
|
||||
// But the test rig setup uses 'hooks-system.session-startup.responses'.
|
||||
// If I'm adding a new test, I might need to generate new fake responses or expect the context to be sent to the model (verify API logs).
|
||||
|
||||
// Verify hook executed
|
||||
const hookLogs = rig.readHookLogs();
|
||||
const sessionStartLog = hookLogs.find(
|
||||
(log) => log.hookCall.hook_event_name === 'SessionStart',
|
||||
);
|
||||
|
||||
expect(sessionStartLog).toBeDefined();
|
||||
|
||||
// Verify the API request contained the injected context
|
||||
// rig.readAllApiRequest() gives us telemetry on API requests.
|
||||
const apiRequests = rig.readAllApiRequest();
|
||||
// We expect at least one API request
|
||||
expect(apiRequests.length).toBeGreaterThan(0);
|
||||
|
||||
// The injected context should be in the request text
|
||||
// For non-interactive mode, I prepended it to input: "context\n\ninput"
|
||||
// The telemetry `request_text` should contain it.
|
||||
const requestText = apiRequests[0].attributes?.request_text || '';
|
||||
expect(requestText).toContain('protocol droid');
|
||||
});
|
||||
|
||||
it('should fire SessionStart hook and display systemMessage in interactive mode', async () => {
|
||||
// Create hook script that outputs JSON with systemMessage and additionalContext
|
||||
const hookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({
|
||||
decision: 'allow',
|
||||
systemMessage: 'Interactive Session Start Message',
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext: 'The user is a Jedi Master.'
|
||||
}
|
||||
}));`;
|
||||
|
||||
await rig.setup(
|
||||
'should fire SessionStart hook and display systemMessage in interactive mode',
|
||||
{
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-system.session-startup.responses',
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const scriptPath = join(
|
||||
rig.testDir!,
|
||||
'session_start_interactive_hook.cjs',
|
||||
);
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
await rig.setup(
|
||||
'should fire SessionStart hook and display systemMessage in interactive mode',
|
||||
{
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
SessionStart: [
|
||||
{
|
||||
matcher: 'startup',
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${scriptPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const run = await rig.runInteractive();
|
||||
|
||||
// Verify systemMessage is displayed
|
||||
await run.expectText('Interactive Session Start Message', 10000);
|
||||
|
||||
// Send a prompt to establish a session and trigger an API call
|
||||
await run.sendKeys('Hello');
|
||||
await run.type('\r');
|
||||
|
||||
// Wait for response to ensure API call happened
|
||||
await run.expectText('Hello', 15000);
|
||||
|
||||
// Wait for telemetry to be written to disk
|
||||
await rig.waitForTelemetryReady();
|
||||
|
||||
// Verify the API request contained the injected context
|
||||
// We may need to poll for API requests as they are written asynchronously
|
||||
const pollResult = await poll(
|
||||
() => {
|
||||
const apiRequests = rig.readAllApiRequest();
|
||||
return apiRequests.length > 0;
|
||||
},
|
||||
15000,
|
||||
500,
|
||||
);
|
||||
|
||||
expect(pollResult).toBe(true);
|
||||
|
||||
const apiRequests = rig.readAllApiRequest();
|
||||
// The injected context should be in the request_text of the API request
|
||||
const requestText = apiRequests[0].attributes?.request_text || '';
|
||||
expect(requestText).toContain('Jedi Master');
|
||||
});
|
||||
|
||||
it('should fire SessionEnd and SessionStart hooks on /clear command', async () => {
|
||||
// Create inline hook commands for both SessionEnd and SessionStart
|
||||
const sessionEndCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"Session ending due to clear\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session ending due to clear'}))\"";
|
||||
const sessionStartCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"Session starting after clear\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'Session starting after clear'}))\"";
|
||||
|
||||
await rig.setup(
|
||||
'should fire SessionEnd and SessionStart hooks on /clear command',
|
||||
@@ -970,10 +1099,8 @@ fi`;
|
||||
'hooks-system.session-clear.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
SessionEnd: [
|
||||
{
|
||||
matcher: '*',
|
||||
@@ -1007,7 +1134,7 @@ fi`;
|
||||
|
||||
// Send an initial prompt to establish a session
|
||||
await run.sendKeys('Say hello');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
// Wait for the response
|
||||
await run.expectText('Hello', 10000);
|
||||
@@ -1017,14 +1144,14 @@ fi`;
|
||||
const numClears = 3;
|
||||
for (let i = 0; i < numClears; i++) {
|
||||
await run.sendKeys('/clear');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
// Wait a bit for clear to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
// Send a prompt to establish an active session before next clear
|
||||
await run.sendKeys('Say hello');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
// Wait for response
|
||||
await run.expectText('Hello', 10000);
|
||||
@@ -1136,7 +1263,7 @@ fi`;
|
||||
it('should fire PreCompress hook on automatic compression', async () => {
|
||||
// Create inline hook command that outputs JSON
|
||||
const preCompressCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"PreCompress hook executed for automatic compression\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'PreCompress hook executed for automatic compression'}))\"";
|
||||
|
||||
await rig.setup('should fire PreCompress hook on automatic compression', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -1144,10 +1271,8 @@ fi`;
|
||||
'hooks-system.compress-auto.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
PreCompress: [
|
||||
{
|
||||
matcher: 'auto',
|
||||
@@ -1203,7 +1328,7 @@ fi`;
|
||||
describe('SessionEnd on Exit', () => {
|
||||
it('should fire SessionEnd hook on graceful exit in non-interactive mode', async () => {
|
||||
const sessionEndCommand =
|
||||
'echo "{\\"decision\\": \\"allow\\", \\"systemMessage\\": \\"SessionEnd hook executed on exit\\"}"';
|
||||
"node -e \"console.log(JSON.stringify({decision: 'allow', systemMessage: 'SessionEnd hook executed on exit'}))\"";
|
||||
|
||||
await rig.setup('should fire SessionEnd hook on graceful exit', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -1211,10 +1336,8 @@ fi`;
|
||||
'hooks-system.session-startup.responses',
|
||||
),
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
SessionEnd: [
|
||||
{
|
||||
matcher: 'exit',
|
||||
@@ -1297,44 +1420,39 @@ fi`;
|
||||
});
|
||||
|
||||
// Create two hook scripts - one enabled, one disabled
|
||||
const enabledHookScript = `#!/bin/bash
|
||||
echo '{"decision": "allow", "systemMessage": "Enabled hook executed"}'`;
|
||||
const enabledHookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({decision: "allow", systemMessage: "Enabled hook executed"}));`;
|
||||
|
||||
const disabledHookScript = `#!/bin/bash
|
||||
echo '{"decision": "block", "systemMessage": "Disabled hook should not execute", "reason": "This hook should be disabled"}'`;
|
||||
const disabledHookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook should not execute", reason: "This hook should be disabled"}));`;
|
||||
|
||||
const enabledPath = join(rig.testDir!, 'enabled_hook.sh');
|
||||
const disabledPath = join(rig.testDir!, 'disabled_hook.sh');
|
||||
const enabledPath = join(rig.testDir!, 'enabled_hook.cjs');
|
||||
const disabledPath = join(rig.testDir!, 'disabled_hook.cjs');
|
||||
|
||||
writeFileSync(enabledPath, enabledHookScript);
|
||||
writeFileSync(disabledPath, disabledHookScript);
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync(`chmod +x "${enabledPath}"`);
|
||||
execSync(`chmod +x "${disabledPath}"`);
|
||||
|
||||
await rig.setup('should not execute hooks disabled in settings file', {
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: enabledPath,
|
||||
command: `node "${enabledPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
{
|
||||
type: 'command',
|
||||
command: disabledPath,
|
||||
command: `node "${disabledPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
disabled: [disabledPath], // Disable the second hook
|
||||
disabled: [`node "${disabledPath}"`], // Disable the second hook
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1358,10 +1476,10 @@ echo '{"decision": "block", "systemMessage": "Disabled hook should not execute",
|
||||
// Check hook telemetry - only enabled hook should have executed
|
||||
const hookLogs = rig.readHookLogs();
|
||||
const enabledHookLog = hookLogs.find(
|
||||
(log) => log.hookCall.hook_name === enabledPath,
|
||||
(log) => log.hookCall.hook_name === `node "${enabledPath}"`,
|
||||
);
|
||||
const disabledHookLog = hookLogs.find(
|
||||
(log) => log.hookCall.hook_name === disabledPath,
|
||||
(log) => log.hookCall.hook_name === `node "${disabledPath}"`,
|
||||
);
|
||||
|
||||
expect(enabledHookLog).toBeDefined();
|
||||
@@ -1380,46 +1498,41 @@ echo '{"decision": "block", "systemMessage": "Disabled hook should not execute",
|
||||
);
|
||||
|
||||
// Create two hook scripts - one that will be disabled, one that won't
|
||||
const activeHookScript = `#!/bin/bash
|
||||
echo '{"decision": "allow", "systemMessage": "Active hook executed"}'`;
|
||||
const activeHookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({decision: "allow", systemMessage: "Active hook executed"}));`;
|
||||
|
||||
const disabledHookScript = `#!/bin/bash
|
||||
echo '{"decision": "block", "systemMessage": "Disabled hook should not execute", "reason": "This hook is disabled"}'`;
|
||||
const disabledHookScript = `const fs = require('fs');
|
||||
console.log(JSON.stringify({decision: "block", systemMessage: "Disabled hook should not execute", reason: "This hook is disabled"}));`;
|
||||
|
||||
const activePath = join(rig.testDir!, 'active_hook.sh');
|
||||
const disabledPath = join(rig.testDir!, 'disabled_hook.sh');
|
||||
const activePath = join(rig.testDir!, 'active_hook.cjs');
|
||||
const disabledPath = join(rig.testDir!, 'disabled_hook.cjs');
|
||||
|
||||
writeFileSync(activePath, activeHookScript);
|
||||
writeFileSync(disabledPath, disabledHookScript);
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync(`chmod +x "${activePath}"`);
|
||||
execSync(`chmod +x "${disabledPath}"`);
|
||||
|
||||
await rig.setup(
|
||||
'should respect disabled hooks across multiple operations',
|
||||
{
|
||||
settings: {
|
||||
tools: {
|
||||
enableHooks: true,
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: activePath,
|
||||
command: `node "${activePath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
{
|
||||
type: 'command',
|
||||
command: disabledPath,
|
||||
command: `node "${disabledPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
disabled: [disabledPath], // Disable the second hook
|
||||
disabled: [`node "${disabledPath}"`], // Disable the second hook
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1441,10 +1554,10 @@ echo '{"decision": "block", "systemMessage": "Disabled hook should not execute",
|
||||
// Check hook telemetry
|
||||
const hookLogs1 = rig.readHookLogs();
|
||||
const activeHookLog1 = hookLogs1.find(
|
||||
(log) => log.hookCall.hook_name === activePath,
|
||||
(log) => log.hookCall.hook_name === `node "${activePath}"`,
|
||||
);
|
||||
const disabledHookLog1 = hookLogs1.find(
|
||||
(log) => log.hookCall.hook_name === disabledPath,
|
||||
(log) => log.hookCall.hook_name === `node "${disabledPath}"`,
|
||||
);
|
||||
|
||||
expect(activeHookLog1).toBeDefined();
|
||||
@@ -1465,9 +1578,171 @@ echo '{"decision": "block", "systemMessage": "Disabled hook should not execute",
|
||||
// Verify disabled hook still hasn't executed
|
||||
const hookLogs2 = rig.readHookLogs();
|
||||
const disabledHookCalls = hookLogs2.filter(
|
||||
(log) => log.hookCall.hook_name === disabledPath,
|
||||
(log) => log.hookCall.hook_name === `node "${disabledPath}"`,
|
||||
);
|
||||
expect(disabledHookCalls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BeforeTool Hooks - Input Override', () => {
|
||||
it('should override tool input parameters via BeforeTool hook', async () => {
|
||||
// 1. First setup to get the test directory and prepare the hook script
|
||||
await rig.setup(
|
||||
'should override tool input parameters via BeforeTool hook',
|
||||
);
|
||||
|
||||
// Create a hook script that overrides the tool input
|
||||
const hookOutput = {
|
||||
decision: 'allow',
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'BeforeTool',
|
||||
tool_input: {
|
||||
file_path: 'modified.txt',
|
||||
content: 'modified content',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const hookScript = `process.stdout.write(JSON.stringify(${JSON.stringify(
|
||||
hookOutput,
|
||||
)}));`;
|
||||
|
||||
const scriptPath = join(rig.testDir!, 'input_override_hook.js');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
|
||||
// Ensure path is properly escaped for command line usage on all platforms
|
||||
// On Windows, backslashes in the command string need to be handled carefully
|
||||
// Using forward slashes works well with Node.js on all platforms
|
||||
const commandPath = scriptPath.replace(/\\/g, '/');
|
||||
|
||||
// 2. Full setup with settings and fake responses
|
||||
await rig.setup(
|
||||
'should override tool input parameters via BeforeTool hook',
|
||||
{
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-system.input-modification.responses',
|
||||
),
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
matcher: 'write_file',
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${commandPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Run the agent. The fake response will attempt to call write_file with
|
||||
// file_path="original.txt" and content="original content"
|
||||
await rig.run({
|
||||
args: 'Create a file called original.txt with content "original content"',
|
||||
});
|
||||
|
||||
// 1. Verify that 'modified.txt' was created with 'modified content' (Override successful)
|
||||
const modifiedContent = rig.readFile('modified.txt');
|
||||
expect(modifiedContent).toBe('modified content');
|
||||
|
||||
// 2. Verify that 'original.txt' was NOT created (Override replaced original)
|
||||
let originalExists = false;
|
||||
try {
|
||||
rig.readFile('original.txt');
|
||||
originalExists = true;
|
||||
} catch {
|
||||
originalExists = false;
|
||||
}
|
||||
expect(originalExists).toBe(false);
|
||||
|
||||
// 3. Verify hook telemetry
|
||||
const hookTelemetryFound = await rig.waitForTelemetryEvent('hook_call');
|
||||
expect(hookTelemetryFound).toBeTruthy();
|
||||
|
||||
const hookLogs = rig.readHookLogs();
|
||||
expect(hookLogs.length).toBe(1);
|
||||
expect(hookLogs[0].hookCall.hook_name).toContain(
|
||||
'input_override_hook.js',
|
||||
);
|
||||
|
||||
// 4. Verify that the agent didn't try to work-around the hook input change
|
||||
const toolLogs = rig.readToolLogs();
|
||||
expect(toolLogs.length).toBe(1);
|
||||
expect(toolLogs[0].toolRequest.name).toBe('write_file');
|
||||
expect(JSON.parse(toolLogs[0].toolRequest.args).file_path).toBe(
|
||||
'modified.txt',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BeforeTool Hooks - Stop Execution', () => {
|
||||
it('should stop agent execution via BeforeTool hook', async () => {
|
||||
// Create a hook script that stops execution
|
||||
const hookOutput = {
|
||||
continue: false,
|
||||
reason: 'Emergency Stop triggered by hook',
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'BeforeTool',
|
||||
},
|
||||
};
|
||||
|
||||
const hookScript = `console.log(JSON.stringify(${JSON.stringify(
|
||||
hookOutput,
|
||||
)}));`;
|
||||
|
||||
await rig.setup('should stop agent execution via BeforeTool hook');
|
||||
const scriptPath = join(rig.testDir!, 'before_tool_stop_hook.js');
|
||||
writeFileSync(scriptPath, hookScript);
|
||||
const commandPath = scriptPath.replace(/\\/g, '/');
|
||||
|
||||
await rig.setup('should stop agent execution via BeforeTool hook', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'hooks-system.before-tool-stop.responses',
|
||||
),
|
||||
settings: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
BeforeTool: [
|
||||
{
|
||||
matcher: 'write_file',
|
||||
hooks: [
|
||||
{
|
||||
type: 'command',
|
||||
command: `node "${commandPath}"`,
|
||||
timeout: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Use write_file to create test.txt',
|
||||
});
|
||||
|
||||
// The hook should have stopped execution message (returned from tool)
|
||||
expect(result).toContain(
|
||||
'Agent execution stopped: Emergency Stop triggered by hook',
|
||||
);
|
||||
|
||||
// Tool should NOT be called successfully (it was blocked/stopped)
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const writeFileCalls = toolLogs.filter(
|
||||
(t) =>
|
||||
t.toolRequest.name === 'write_file' && t.toolRequest.success === true,
|
||||
);
|
||||
expect(writeFileCalls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The capital of France is Paris."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7,"candidatesTokenCount":7,"totalTokenCount":14,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! How can I help you today?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":9,"totalTokenCount":14,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
|
||||
@@ -14,7 +14,6 @@ describe('JSON output', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('json-output-test');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -22,6 +21,12 @@ describe('JSON output', () => {
|
||||
});
|
||||
|
||||
it('should return a valid JSON with response and stats', async () => {
|
||||
await rig.setup('json-output-france', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'json-output.france.responses',
|
||||
),
|
||||
});
|
||||
const result = await rig.run({
|
||||
args: ['What is the capital of France?', '--output-format', 'json'],
|
||||
});
|
||||
@@ -36,6 +41,12 @@ describe('JSON output', () => {
|
||||
});
|
||||
|
||||
it('should return a valid JSON with a session ID', async () => {
|
||||
await rig.setup('json-output-session-id', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'json-output.session-id.responses',
|
||||
),
|
||||
});
|
||||
const result = await rig.run({
|
||||
args: ['Hello', '--output-format', 'json'],
|
||||
});
|
||||
@@ -47,7 +58,6 @@ describe('JSON output', () => {
|
||||
});
|
||||
|
||||
it('should return a JSON error for sd auth mismatch before running', async () => {
|
||||
process.env['GOOGLE_GENAI_USE_GCA'] = 'true';
|
||||
await rig.setup('json-output-auth-mismatch', {
|
||||
settings: {
|
||||
security: {
|
||||
@@ -58,12 +68,13 @@ describe('JSON output', () => {
|
||||
|
||||
let thrown: Error | undefined;
|
||||
try {
|
||||
await rig.run({ args: ['Hello', '--output-format', 'json'] });
|
||||
await rig.run({
|
||||
args: ['Hello', '--output-format', 'json'],
|
||||
env: { GOOGLE_GENAI_USE_GCA: 'true' },
|
||||
});
|
||||
expect.fail('Expected process to exit with error');
|
||||
} catch (e) {
|
||||
thrown = e as Error;
|
||||
} finally {
|
||||
delete process.env['GOOGLE_GENAI_USE_GCA'];
|
||||
}
|
||||
|
||||
expect(thrown).toBeDefined();
|
||||
@@ -102,7 +113,7 @@ describe('JSON output', () => {
|
||||
});
|
||||
|
||||
it('should not exit on tool errors and allow model to self-correct in JSON mode', async () => {
|
||||
rig.setup('json-output-error', {
|
||||
await rig.setup('json-output-error', {
|
||||
fakeResponsesPath: join(
|
||||
import.meta.dirname,
|
||||
'json-output.error.responses',
|
||||
|
||||
@@ -200,7 +200,7 @@ describe('mcp server with cyclic tool schema is detected', () => {
|
||||
const run = await rig.runInteractive();
|
||||
|
||||
await run.type('/mcp list');
|
||||
await run.sendKeys('\r');
|
||||
await run.type('\r');
|
||||
|
||||
await run.expectText('tool_with_cyclic_schema');
|
||||
});
|
||||
|
||||
@@ -27,6 +27,10 @@ class MockConfig {
|
||||
getDebugMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getFileFilteringRespectGeminiIgnore() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ripgrep-real-direct', () => {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig, printDebugInfo, validateModelOutput } from './test-helper.js';
|
||||
|
||||
describe('save_memory', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => await rig.cleanup());
|
||||
|
||||
it('should be able to save to memory', async () => {
|
||||
await rig.setup('should be able to save to memory', {
|
||||
settings: { tools: { core: ['save_memory'] } },
|
||||
});
|
||||
|
||||
const prompt = `remember that my favorite color is blue.
|
||||
|
||||
what is my favorite color? tell me that and surround it with $ symbol`;
|
||||
const result = await rig.run({ args: prompt });
|
||||
|
||||
const foundToolCall = await rig.waitForToolCall('save_memory');
|
||||
|
||||
// Add debugging information
|
||||
if (!foundToolCall || !result.toLowerCase().includes('blue')) {
|
||||
const allTools = printDebugInfo(rig, result, {
|
||||
'Found tool call': foundToolCall,
|
||||
'Contains blue': result.toLowerCase().includes('blue'),
|
||||
});
|
||||
|
||||
console.error(
|
||||
'Memory tool calls:',
|
||||
allTools
|
||||
.filter((t) => t.toolRequest.name === 'save_memory')
|
||||
.map((t) => t.toolRequest.args),
|
||||
);
|
||||
}
|
||||
|
||||
expect(
|
||||
foundToolCall,
|
||||
'Expected to find a save_memory tool call',
|
||||
).toBeTruthy();
|
||||
|
||||
// Validate model output - will throw if no output, warn if missing expected content
|
||||
validateModelOutput(result, 'blue', 'Save memory test');
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+945
-213
File diff suppressed because it is too large
Load Diff
+12
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.22.0",
|
||||
"version": "0.25.1",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/google-gemini/gemini-cli.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.22.0"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.25.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
@@ -41,18 +41,20 @@
|
||||
"test": "npm run test --workspaces --if-present",
|
||||
"test:ci": "npm run test:ci --workspaces --if-present && npm run test:scripts",
|
||||
"test:scripts": "vitest run --config ./scripts/tests/vitest.config.ts",
|
||||
"test:always_passing_evals": "vitest run --config evals/vitest.config.ts",
|
||||
"test:all_evals": "cross-env RUN_EVALS=1 vitest run --config evals/vitest.config.ts",
|
||||
"test:e2e": "cross-env VERBOSE=true KEEP_OUTPUT=true npm run test:integration:sandbox:none",
|
||||
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
|
||||
"test:integration:sandbox:none": "cross-env GEMINI_SANDBOX=false vitest run --root ./integration-tests",
|
||||
"test:integration:sandbox:docker": "cross-env GEMINI_SANDBOX=docker npm run build:sandbox && cross-env GEMINI_SANDBOX=docker vitest run --root ./integration-tests",
|
||||
"test:integration:sandbox:podman": "cross-env GEMINI_SANDBOX=podman vitest run --root ./integration-tests",
|
||||
"lint": "eslint . --ext .ts,.tsx && eslint integration-tests && eslint scripts",
|
||||
"lint": "eslint . --cache",
|
||||
"lint:fix": "eslint . --fix --ext .ts,.tsx && eslint integration-tests --fix && eslint scripts --fix && npm run format",
|
||||
"lint:ci": "npm run lint:all",
|
||||
"lint:all": "node scripts/lint.js",
|
||||
"format": "prettier --experimental-cli --write .",
|
||||
"typecheck": "npm run typecheck --workspaces --if-present",
|
||||
"preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci",
|
||||
"preflight": "npm run clean && npm ci && npm run format && npm run build && npm run lint:ci && npm run typecheck && npm run test:ci",
|
||||
"prepare": "husky && npm run bundle",
|
||||
"prepare:package": "node scripts/prepare-package.js",
|
||||
"release:version": "node scripts/version.js",
|
||||
@@ -62,7 +64,7 @@
|
||||
"pre-commit": "node scripts/pre-commit.js"
|
||||
},
|
||||
"overrides": {
|
||||
"ink": "npm:@jrichman/ink@6.4.6",
|
||||
"ink": "npm:@jrichman/ink@6.4.7",
|
||||
"wrap-ansi": "9.0.2",
|
||||
"cliui": {
|
||||
"wrap-ansi": "7.0.0"
|
||||
@@ -89,12 +91,13 @@
|
||||
"@vitest/coverage-v8": "^3.1.1",
|
||||
"@vitest/eslint-plugin": "^1.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"depcheck": "^1.4.7",
|
||||
"esbuild": "^0.25.0",
|
||||
"esbuild-plugin-wasm": "^1.1.0",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-headers": "^1.3.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-license-header": "^0.8.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"glob": "^12.0.0",
|
||||
@@ -113,13 +116,14 @@
|
||||
"react-devtools-core": "^6.1.2",
|
||||
"semver": "^7.7.2",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"ts-prune": "^0.10.3",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vitest": "^3.2.4",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ink": "npm:@jrichman/ink@6.4.6",
|
||||
"ink": "npm:@jrichman/ink@6.4.7",
|
||||
"latest-version": "^9.0.0",
|
||||
"simple-git": "^3.28.0"
|
||||
},
|
||||
@@ -130,6 +134,7 @@
|
||||
"@lydell/node-pty-linux-x64": "1.1.0",
|
||||
"@lydell/node-pty-win32-arm64": "1.1.0",
|
||||
"@lydell/node-pty-win32-x64": "1.1.0",
|
||||
"keytar": "^7.9.0",
|
||||
"node-pty": "^1.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.22.0",
|
||||
"version": "0.25.1",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,13 +25,13 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.2",
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"express": "^5.1.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"tar": "^7.5.2",
|
||||
"uuid": "^11.1.0",
|
||||
"uuid": "^13.0.0",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -123,7 +123,7 @@ export class Task {
|
||||
// process. This is not scoped to the individual task but reflects the global connection
|
||||
// state managed within the @gemini-cli/core module.
|
||||
async getMetadata(): Promise<TaskMetadata> {
|
||||
const toolRegistry = await this.config.getToolRegistry();
|
||||
const toolRegistry = this.config.getToolRegistry();
|
||||
const mcpServers = this.config.getMcpClientManager()?.getMcpServers() || {};
|
||||
const serverStatuses = getAllMCPServerStatuses();
|
||||
const servers = Object.keys(mcpServers).map((serverName) => ({
|
||||
|
||||
@@ -7,53 +7,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import type { Command } from './types.js';
|
||||
|
||||
describe('CommandRegistry', () => {
|
||||
const mockListExtensionsCommandInstance: Command = {
|
||||
const {
|
||||
mockExtensionsCommand,
|
||||
mockListExtensionsCommand,
|
||||
mockExtensionsCommandInstance,
|
||||
mockListExtensionsCommandInstance,
|
||||
} = vi.hoisted(() => {
|
||||
const listInstance: Command = {
|
||||
name: 'extensions list',
|
||||
description: 'Lists all installed extensions.',
|
||||
execute: vi.fn(),
|
||||
};
|
||||
const mockListExtensionsCommand = vi.fn(
|
||||
() => mockListExtensionsCommandInstance,
|
||||
);
|
||||
|
||||
const mockExtensionsCommandInstance: Command = {
|
||||
const extInstance: Command = {
|
||||
name: 'extensions',
|
||||
description: 'Manage extensions.',
|
||||
execute: vi.fn(),
|
||||
subCommands: [mockListExtensionsCommandInstance],
|
||||
subCommands: [listInstance],
|
||||
};
|
||||
const mockExtensionsCommand = vi.fn(() => mockExtensionsCommandInstance);
|
||||
|
||||
return {
|
||||
mockListExtensionsCommandInstance: listInstance,
|
||||
mockExtensionsCommandInstance: extInstance,
|
||||
mockExtensionsCommand: vi.fn(() => extInstance),
|
||||
mockListExtensionsCommand: vi.fn(() => listInstance),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('./extensions.js', () => ({
|
||||
ExtensionsCommand: mockExtensionsCommand,
|
||||
ListExtensionsCommand: mockListExtensionsCommand,
|
||||
}));
|
||||
|
||||
vi.mock('./init.js', () => ({
|
||||
InitCommand: vi.fn(() => ({
|
||||
name: 'init',
|
||||
description: 'Initializes the server.',
|
||||
execute: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('./restore.js', () => ({
|
||||
RestoreCommand: vi.fn(() => ({
|
||||
name: 'restore',
|
||||
description: 'Restores the server.',
|
||||
execute: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
import { commandRegistry } from './command-registry.js';
|
||||
|
||||
describe('CommandRegistry', () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock('./extensions.js', () => ({
|
||||
ExtensionsCommand: mockExtensionsCommand,
|
||||
ListExtensionsCommand: mockListExtensionsCommand,
|
||||
}));
|
||||
vi.clearAllMocks();
|
||||
commandRegistry.initialize();
|
||||
});
|
||||
|
||||
it('should register ExtensionsCommand on initialization', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
expect(mockExtensionsCommand).toHaveBeenCalled();
|
||||
const command = commandRegistry.get('extensions');
|
||||
expect(command).toBe(mockExtensionsCommandInstance);
|
||||
});
|
||||
}, 20000);
|
||||
|
||||
it('should register sub commands on initialization', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const command = commandRegistry.get('extensions list');
|
||||
expect(command).toBe(mockListExtensionsCommandInstance);
|
||||
});
|
||||
|
||||
it('get() should return undefined for a non-existent command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const command = commandRegistry.get('non-existent');
|
||||
expect(command).toBeUndefined();
|
||||
});
|
||||
|
||||
it('register() should register a new command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockCommand: Command = {
|
||||
name: 'test-command',
|
||||
description: '',
|
||||
@@ -65,7 +91,6 @@ describe('CommandRegistry', () => {
|
||||
});
|
||||
|
||||
it('register() should register a nested command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockSubSubCommand: Command = {
|
||||
name: 'test-command-sub-sub',
|
||||
description: '',
|
||||
@@ -95,8 +120,8 @@ describe('CommandRegistry', () => {
|
||||
});
|
||||
|
||||
it('register() should not enter an infinite loop with a cyclic command', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const { debugLogger } = await import('@google/gemini-cli-core');
|
||||
const warnSpy = vi.spyOn(debugLogger, 'warn').mockImplementation(() => {});
|
||||
const mockCommand: Command = {
|
||||
name: 'cyclic-command',
|
||||
description: '',
|
||||
@@ -112,7 +137,6 @@ describe('CommandRegistry', () => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'Command cyclic-command already registered. Skipping.',
|
||||
);
|
||||
// If the test finishes, it means we didn't get into an infinite loop.
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,23 +4,31 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { MemoryCommand } from './memory.js';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
import { ExtensionsCommand } from './extensions.js';
|
||||
import { InitCommand } from './init.js';
|
||||
import { RestoreCommand } from './restore.js';
|
||||
import type { Command } from './types.js';
|
||||
|
||||
class CommandRegistry {
|
||||
export class CommandRegistry {
|
||||
private readonly commands = new Map<string, Command>();
|
||||
|
||||
constructor() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.commands.clear();
|
||||
this.register(new ExtensionsCommand());
|
||||
this.register(new RestoreCommand());
|
||||
this.register(new InitCommand());
|
||||
this.register(new MemoryCommand());
|
||||
}
|
||||
|
||||
register(command: Command) {
|
||||
if (this.commands.has(command.name)) {
|
||||
console.warn(`Command ${command.name} already registered. Skipping.`);
|
||||
debugLogger.warn(`Command ${command.name} already registered. Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
addMemory,
|
||||
listMemoryFiles,
|
||||
refreshMemory,
|
||||
showMemory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
AddMemoryCommand,
|
||||
ListMemoryCommand,
|
||||
MemoryCommand,
|
||||
RefreshMemoryCommand,
|
||||
ShowMemoryCommand,
|
||||
} from './memory.js';
|
||||
import type { CommandContext } from './types.js';
|
||||
import type {
|
||||
AnyDeclarativeTool,
|
||||
Config,
|
||||
ToolRegistry,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
// Mock the core functions
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
showMemory: vi.fn(),
|
||||
refreshMemory: vi.fn(),
|
||||
listMemoryFiles: vi.fn(),
|
||||
addMemory: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockShowMemory = vi.mocked(showMemory);
|
||||
const mockRefreshMemory = vi.mocked(refreshMemory);
|
||||
const mockListMemoryFiles = vi.mocked(listMemoryFiles);
|
||||
const mockAddMemory = vi.mocked(addMemory);
|
||||
|
||||
describe('a2a-server memory commands', () => {
|
||||
let mockContext: CommandContext;
|
||||
let mockConfig: Config;
|
||||
let mockToolRegistry: ToolRegistry;
|
||||
let mockSaveMemoryTool: AnyDeclarativeTool;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSaveMemoryTool = {
|
||||
name: 'save_memory',
|
||||
description: 'Saves memory',
|
||||
buildAndExecute: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as AnyDeclarativeTool;
|
||||
|
||||
mockToolRegistry = {
|
||||
getTool: vi.fn(),
|
||||
} as unknown as ToolRegistry;
|
||||
|
||||
mockConfig = {
|
||||
getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
|
||||
} as unknown as Config;
|
||||
|
||||
mockContext = {
|
||||
config: mockConfig,
|
||||
};
|
||||
|
||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockSaveMemoryTool);
|
||||
});
|
||||
|
||||
describe('MemoryCommand', () => {
|
||||
it('delegates to ShowMemoryCommand', async () => {
|
||||
const command = new MemoryCommand();
|
||||
mockShowMemory.mockReturnValue({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'showing memory',
|
||||
});
|
||||
const response = await command.execute(mockContext, []);
|
||||
expect(response.data).toBe('showing memory');
|
||||
expect(mockShowMemory).toHaveBeenCalledWith(mockContext.config);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ShowMemoryCommand', () => {
|
||||
it('executes showMemory and returns the content', async () => {
|
||||
const command = new ShowMemoryCommand();
|
||||
mockShowMemory.mockReturnValue({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'test memory content',
|
||||
});
|
||||
|
||||
const response = await command.execute(mockContext, []);
|
||||
|
||||
expect(mockShowMemory).toHaveBeenCalledWith(mockContext.config);
|
||||
expect(response.name).toBe('memory show');
|
||||
expect(response.data).toBe('test memory content');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RefreshMemoryCommand', () => {
|
||||
it('executes refreshMemory and returns the content', async () => {
|
||||
const command = new RefreshMemoryCommand();
|
||||
mockRefreshMemory.mockResolvedValue({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'memory refreshed',
|
||||
});
|
||||
|
||||
const response = await command.execute(mockContext, []);
|
||||
|
||||
expect(mockRefreshMemory).toHaveBeenCalledWith(mockContext.config);
|
||||
expect(response.name).toBe('memory refresh');
|
||||
expect(response.data).toBe('memory refreshed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ListMemoryCommand', () => {
|
||||
it('executes listMemoryFiles and returns the content', async () => {
|
||||
const command = new ListMemoryCommand();
|
||||
mockListMemoryFiles.mockReturnValue({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'file1.md\nfile2.md',
|
||||
});
|
||||
|
||||
const response = await command.execute(mockContext, []);
|
||||
|
||||
expect(mockListMemoryFiles).toHaveBeenCalledWith(mockContext.config);
|
||||
expect(response.name).toBe('memory list');
|
||||
expect(response.data).toBe('file1.md\nfile2.md');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AddMemoryCommand', () => {
|
||||
it('returns message content if addMemory returns a message', async () => {
|
||||
const command = new AddMemoryCommand();
|
||||
mockAddMemory.mockReturnValue({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'error message',
|
||||
});
|
||||
|
||||
const response = await command.execute(mockContext, []);
|
||||
|
||||
expect(mockAddMemory).toHaveBeenCalledWith('');
|
||||
expect(response.name).toBe('memory add');
|
||||
expect(response.data).toBe('error message');
|
||||
});
|
||||
|
||||
it('executes the save_memory tool if found', async () => {
|
||||
const command = new AddMemoryCommand();
|
||||
const fact = 'this is a new fact';
|
||||
mockAddMemory.mockReturnValue({
|
||||
type: 'tool',
|
||||
toolName: 'save_memory',
|
||||
toolArgs: { fact },
|
||||
});
|
||||
|
||||
const response = await command.execute(mockContext, [
|
||||
'this',
|
||||
'is',
|
||||
'a',
|
||||
'new',
|
||||
'fact',
|
||||
]);
|
||||
|
||||
expect(mockAddMemory).toHaveBeenCalledWith(fact);
|
||||
expect(mockConfig.getToolRegistry).toHaveBeenCalled();
|
||||
expect(mockToolRegistry.getTool).toHaveBeenCalledWith('save_memory');
|
||||
expect(mockSaveMemoryTool.buildAndExecute).toHaveBeenCalledWith(
|
||||
{ fact },
|
||||
expect.any(AbortSignal),
|
||||
undefined,
|
||||
{
|
||||
sanitizationConfig: {
|
||||
allowedEnvironmentVariables: [],
|
||||
blockedEnvironmentVariables: [],
|
||||
enableEnvironmentVariableRedaction: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(mockRefreshMemory).toHaveBeenCalledWith(mockContext.config);
|
||||
expect(response.name).toBe('memory add');
|
||||
expect(response.data).toBe(`Added memory: "${fact}"`);
|
||||
});
|
||||
|
||||
it('returns an error if the tool is not found', async () => {
|
||||
const command = new AddMemoryCommand();
|
||||
const fact = 'another fact';
|
||||
mockAddMemory.mockReturnValue({
|
||||
type: 'tool',
|
||||
toolName: 'save_memory',
|
||||
toolArgs: { fact },
|
||||
});
|
||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(undefined);
|
||||
|
||||
const response = await command.execute(mockContext, ['another', 'fact']);
|
||||
|
||||
expect(response.name).toBe('memory add');
|
||||
expect(response.data).toBe('Error: Tool save_memory not found.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
addMemory,
|
||||
listMemoryFiles,
|
||||
refreshMemory,
|
||||
showMemory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type {
|
||||
Command,
|
||||
CommandContext,
|
||||
CommandExecutionResponse,
|
||||
} from './types.js';
|
||||
|
||||
const DEFAULT_SANITIZATION_CONFIG = {
|
||||
allowedEnvironmentVariables: [],
|
||||
blockedEnvironmentVariables: [],
|
||||
enableEnvironmentVariableRedaction: false,
|
||||
};
|
||||
|
||||
export class MemoryCommand implements Command {
|
||||
readonly name = 'memory';
|
||||
readonly description = 'Manage memory.';
|
||||
readonly subCommands = [
|
||||
new ShowMemoryCommand(),
|
||||
new RefreshMemoryCommand(),
|
||||
new ListMemoryCommand(),
|
||||
new AddMemoryCommand(),
|
||||
];
|
||||
readonly topLevel = true;
|
||||
readonly requiresWorkspace = true;
|
||||
|
||||
async execute(
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
return new ShowMemoryCommand().execute(context, _);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowMemoryCommand implements Command {
|
||||
readonly name = 'memory show';
|
||||
readonly description = 'Shows the current memory contents.';
|
||||
|
||||
async execute(
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const result = showMemory(context.config);
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
}
|
||||
|
||||
export class RefreshMemoryCommand implements Command {
|
||||
readonly name = 'memory refresh';
|
||||
readonly description = 'Refreshes the memory from the source.';
|
||||
|
||||
async execute(
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const result = await refreshMemory(context.config);
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
}
|
||||
|
||||
export class ListMemoryCommand implements Command {
|
||||
readonly name = 'memory list';
|
||||
readonly description = 'Lists the paths of the GEMINI.md files in use.';
|
||||
|
||||
async execute(
|
||||
context: CommandContext,
|
||||
_: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const result = listMemoryFiles(context.config);
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
}
|
||||
|
||||
export class AddMemoryCommand implements Command {
|
||||
readonly name = 'memory add';
|
||||
readonly description = 'Add content to the memory.';
|
||||
|
||||
async execute(
|
||||
context: CommandContext,
|
||||
args: string[],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const textToAdd = args.join(' ').trim();
|
||||
const result = addMemory(textToAdd);
|
||||
if (result.type === 'message') {
|
||||
return { name: this.name, data: result.content };
|
||||
}
|
||||
|
||||
const toolRegistry = context.config.getToolRegistry();
|
||||
const tool = toolRegistry.getTool(result.toolName);
|
||||
if (tool) {
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
await tool.buildAndExecute(result.toolArgs, signal, undefined, {
|
||||
sanitizationConfig: DEFAULT_SANITIZATION_CONFIG,
|
||||
});
|
||||
await refreshMemory(context.config);
|
||||
return {
|
||||
name: this.name,
|
||||
data: `Added memory: "${textToAdd}"`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: this.name,
|
||||
data: `Error: Tool ${result.toolName} not found.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
import type { TelemetryTarget } from '@google/gemini-cli-core';
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
type ExtensionLoader,
|
||||
startupProfiler,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
homedir,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
@@ -37,6 +37,10 @@ export async function loadConfig(
|
||||
const workspaceDir = process.cwd();
|
||||
const adcFilePath = process.env['GOOGLE_APPLICATION_CREDENTIALS'];
|
||||
|
||||
const folderTrust =
|
||||
settings.folderTrust === true ||
|
||||
process.env['GEMINI_FOLDER_TRUST'] === 'true';
|
||||
|
||||
const configParams: ConfigParameters = {
|
||||
sessionId: taskId,
|
||||
model: settings.general?.previewFeatures
|
||||
@@ -72,26 +76,30 @@ export async function loadConfig(
|
||||
settings.fileFiltering?.enableRecursiveFileSearch,
|
||||
},
|
||||
ideMode: false,
|
||||
folderTrust: settings.folderTrust === true,
|
||||
folderTrust,
|
||||
trustedFolder: true,
|
||||
extensionLoader,
|
||||
checkpointing: process.env['CHECKPOINTING']
|
||||
? process.env['CHECKPOINTING'] === 'true'
|
||||
: settings.checkpointing?.enabled,
|
||||
previewFeatures: settings.general?.previewFeatures,
|
||||
interactive: true,
|
||||
enableInteractiveShell: true,
|
||||
};
|
||||
|
||||
const fileService = new FileDiscoveryService(workspaceDir);
|
||||
const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
|
||||
workspaceDir,
|
||||
[workspaceDir],
|
||||
false,
|
||||
fileService,
|
||||
extensionLoader,
|
||||
settings.folderTrust === true,
|
||||
);
|
||||
const { memoryContent, fileCount, filePaths } =
|
||||
await loadServerHierarchicalMemory(
|
||||
workspaceDir,
|
||||
[workspaceDir],
|
||||
false,
|
||||
fileService,
|
||||
extensionLoader,
|
||||
folderTrust,
|
||||
);
|
||||
configParams.userMemory = memoryContent;
|
||||
configParams.geminiMdFileCount = fileCount;
|
||||
configParams.geminiMdFilePaths = filePaths;
|
||||
const config = new Config({
|
||||
...configParams,
|
||||
});
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
type MCPServerConfig,
|
||||
type ExtensionInstallMetadata,
|
||||
type GeminiCLIExtension,
|
||||
homedir,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export const EXTENSIONS_DIRECTORY_NAME = path.join(GEMINI_DIR, 'extensions');
|
||||
@@ -39,7 +39,7 @@ interface ExtensionConfig {
|
||||
export function loadExtensions(workspaceDir: string): GeminiCLIExtension[] {
|
||||
const allExtensions = [
|
||||
...loadExtensionsFromDir(workspaceDir),
|
||||
...loadExtensionsFromDir(os.homedir()),
|
||||
...loadExtensionsFromDir(homedir()),
|
||||
];
|
||||
|
||||
const uniqueExtensions: GeminiCLIExtension[] = [];
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import { loadSettings, USER_SETTINGS_PATH } from './settings.js';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const suffix = Math.random().toString(36).slice(2);
|
||||
@@ -26,13 +27,21 @@ vi.mock('node:os', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@google/gemini-cli-core', () => ({
|
||||
GEMINI_DIR: '.gemini',
|
||||
debugLogger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
getErrorMessage: (error: unknown) => String(error),
|
||||
}));
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
const path = await import('node:path');
|
||||
const os = await import('node:os');
|
||||
return {
|
||||
...actual,
|
||||
GEMINI_DIR: '.gemini',
|
||||
debugLogger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
getErrorMessage: (error: unknown) => String(error),
|
||||
homedir: () => path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`),
|
||||
};
|
||||
});
|
||||
|
||||
describe('loadSettings', () => {
|
||||
const mockHomeDir = path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`);
|
||||
@@ -75,7 +84,7 @@ describe('loadSettings', () => {
|
||||
fs.rmSync(mockWorkspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to cleanup temp dirs', e);
|
||||
debugLogger.error('Failed to cleanup temp dirs', e);
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
GEMINI_DIR,
|
||||
getErrorMessage,
|
||||
type TelemetrySettings,
|
||||
homedir,
|
||||
} from '@google/gemini-cli-core';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ import {
|
||||
createStreamMessageRequest,
|
||||
createMockConfig,
|
||||
} from '../utils/testing_utils.js';
|
||||
import { MockTool } from '@google/gemini-cli-core';
|
||||
// Import MockTool from specific path to avoid vitest dependency in main core bundle
|
||||
import { MockTool } from '@google/gemini-cli-core/src/test-utils/mock-tool.js';
|
||||
import type { Command, CommandContext } from '../commands/types.js';
|
||||
|
||||
const mockToolConfirmationFn = async () =>
|
||||
|
||||
@@ -25,7 +25,7 @@ import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js';
|
||||
import { loadSettings } from '../config/settings.js';
|
||||
import { loadExtensions } from '../config/extension.js';
|
||||
import { commandRegistry } from '../commands/command-registry.js';
|
||||
import { SimpleExtensionLoader } from '@google/gemini-cli-core';
|
||||
import { debugLogger, SimpleExtensionLoader } from '@google/gemini-cli-core';
|
||||
import type { Command, CommandArgument } from '../commands/types.js';
|
||||
import { GitService } from '@google/gemini-cli-core';
|
||||
|
||||
@@ -239,7 +239,7 @@ export async function createApp() {
|
||||
): CommandResponse | undefined => {
|
||||
const commandName = command.name;
|
||||
if (visited.includes(commandName)) {
|
||||
console.warn(
|
||||
debugLogger.warn(
|
||||
`Command ${commandName} already inserted in the response, skipping`,
|
||||
);
|
||||
return undefined;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { gzipSync, gunzipSync } from 'node:zlib';
|
||||
import * as tar from 'tar';
|
||||
import * as fse from 'fs-extra';
|
||||
import { promises as fsPromises, createReadStream } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { tmpdir } from '@google/gemini-cli-core';
|
||||
import { join } from 'node:path';
|
||||
import type { Task as SDKTask } from '@a2a-js/sdk';
|
||||
import type { TaskStore } from '@a2a-js/sdk/server';
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
||||
GeminiClient,
|
||||
HookSystem,
|
||||
PolicyDecision,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
|
||||
import type { Config, Storage } from '@google/gemini-cli-core';
|
||||
@@ -59,7 +60,6 @@ export function createMockConfig(
|
||||
getEmbeddingModel: vi.fn().mockReturnValue('text-embedding-004'),
|
||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||
getUserTier: vi.fn(),
|
||||
getEnableMessageBusIntegration: vi.fn().mockReturnValue(false),
|
||||
getMessageBus: vi.fn(),
|
||||
getPolicyEngine: vi.fn(),
|
||||
getEnableExtensionReloading: vi.fn().mockReturnValue(false),
|
||||
@@ -78,6 +78,17 @@ export function createMockConfig(
|
||||
mockConfig.getGeminiClient = vi
|
||||
.fn()
|
||||
.mockReturnValue(new GeminiClient(mockConfig));
|
||||
|
||||
mockConfig.getPolicyEngine = vi.fn().mockReturnValue({
|
||||
check: async () => {
|
||||
const mode = mockConfig.getApprovalMode();
|
||||
if (mode === ApprovalMode.YOLO) {
|
||||
return { decision: PolicyDecision.ALLOW };
|
||||
}
|
||||
return { decision: PolicyDecision.ASK_USER };
|
||||
},
|
||||
});
|
||||
|
||||
return mockConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.22.0",
|
||||
"version": "0.25.1",
|
||||
"description": "Gemini CLI",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -26,9 +26,10 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.22.0"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.25.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.11.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@google/genai": "1.30.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
@@ -44,7 +45,7 @@
|
||||
"fzf": "^0.5.2",
|
||||
"glob": "^12.0.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"ink": "npm:@jrichman/ink@6.4.6",
|
||||
"ink": "npm:@jrichman/ink@6.4.7",
|
||||
"ink-gradient": "^3.0.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"latest-version": "^9.0.0",
|
||||
|
||||
@@ -14,7 +14,7 @@ import { enableCommand } from './extensions/enable.js';
|
||||
import { linkCommand } from './extensions/link.js';
|
||||
import { newCommand } from './extensions/new.js';
|
||||
import { validateCommand } from './extensions/validate.js';
|
||||
import { settingsCommand } from './extensions/settings.js';
|
||||
import { configureCommand } from './extensions/configure.js';
|
||||
import { initializeOutputListenersAndFlush } from '../gemini.js';
|
||||
|
||||
export const extensionsCommand: CommandModule = {
|
||||
@@ -33,7 +33,7 @@ export const extensionsCommand: CommandModule = {
|
||||
.command(linkCommand)
|
||||
.command(newCommand)
|
||||
.command(validateCommand)
|
||||
.command(settingsCommand)
|
||||
.command(configureCommand)
|
||||
.demandCommand(1, 'You need at least one command before continuing.')
|
||||
.version(false),
|
||||
handler: () => {
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
vi,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { configureCommand } from './configure.js';
|
||||
import yargs from 'yargs';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
import {
|
||||
updateSetting,
|
||||
promptForSetting,
|
||||
getScopedEnvContents,
|
||||
type ExtensionSetting,
|
||||
} from '../../config/extensions/extensionSettings.js';
|
||||
import prompts from 'prompts';
|
||||
|
||||
const {
|
||||
mockExtensionManager,
|
||||
mockGetExtensionAndManager,
|
||||
mockGetExtensionManager,
|
||||
mockLoadSettings,
|
||||
} = vi.hoisted(() => {
|
||||
const extensionManager = {
|
||||
loadExtensionConfig: vi.fn(),
|
||||
getExtensions: vi.fn(),
|
||||
loadExtensions: vi.fn(),
|
||||
getSettings: vi.fn(),
|
||||
};
|
||||
return {
|
||||
mockExtensionManager: extensionManager,
|
||||
mockGetExtensionAndManager: vi.fn(),
|
||||
mockGetExtensionManager: vi.fn(),
|
||||
mockLoadSettings: vi.fn().mockReturnValue({ merged: {} }),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/extension-manager.js', () => ({
|
||||
ExtensionManager: vi.fn().mockImplementation(() => mockExtensionManager),
|
||||
}));
|
||||
|
||||
vi.mock('../../config/extensions/extensionSettings.js', () => ({
|
||||
updateSetting: vi.fn(),
|
||||
promptForSetting: vi.fn(),
|
||||
getScopedEnvContents: vi.fn(),
|
||||
ExtensionSettingScope: {
|
||||
USER: 'user',
|
||||
WORKSPACE: 'workspace',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../utils.js', () => ({
|
||||
exitCli: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./utils.js', () => ({
|
||||
getExtensionAndManager: mockGetExtensionAndManager,
|
||||
getExtensionManager: mockGetExtensionManager,
|
||||
}));
|
||||
|
||||
vi.mock('prompts');
|
||||
|
||||
vi.mock('../../config/extensions/consent.js', () => ({
|
||||
requestConsentNonInteractive: vi.fn(),
|
||||
}));
|
||||
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
|
||||
vi.mock('../../config/settings.js', () => ({
|
||||
loadSettings: mockLoadSettings,
|
||||
}));
|
||||
|
||||
describe('extensions configure command', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(debugLogger, 'log');
|
||||
vi.spyOn(debugLogger, 'error');
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Default behaviors
|
||||
mockLoadSettings.mockReturnValue({ merged: {} });
|
||||
mockGetExtensionAndManager.mockResolvedValue({
|
||||
extension: null,
|
||||
extensionManager: null,
|
||||
});
|
||||
mockGetExtensionManager.mockResolvedValue(mockExtensionManager);
|
||||
(ExtensionManager as unknown as Mock).mockImplementation(
|
||||
() => mockExtensionManager,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
const runCommand = async (command: string) => {
|
||||
const parser = yargs().command(configureCommand).help(false).version(false);
|
||||
await parser.parse(command);
|
||||
};
|
||||
|
||||
const setupExtension = (
|
||||
name: string,
|
||||
settings: Array<Partial<ExtensionSetting>> = [],
|
||||
id = 'test-id',
|
||||
path = '/test/path',
|
||||
) => {
|
||||
const extension = { name, path, id };
|
||||
mockGetExtensionAndManager.mockImplementation(async (n) => {
|
||||
if (n === name)
|
||||
return { extension, extensionManager: mockExtensionManager };
|
||||
return { extension: null, extensionManager: null };
|
||||
});
|
||||
|
||||
mockExtensionManager.getExtensions.mockReturnValue([extension]);
|
||||
mockExtensionManager.loadExtensionConfig.mockResolvedValue({
|
||||
name,
|
||||
settings,
|
||||
});
|
||||
return extension;
|
||||
};
|
||||
|
||||
describe('Specific setting configuration', () => {
|
||||
it('should configure a specific setting', async () => {
|
||||
setupExtension('test-ext', [
|
||||
{ name: 'Test Setting', envVar: 'TEST_VAR' },
|
||||
]);
|
||||
(updateSetting as Mock).mockResolvedValue(undefined);
|
||||
|
||||
await runCommand('config test-ext TEST_VAR');
|
||||
|
||||
expect(updateSetting).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 'test-ext' }),
|
||||
'test-id',
|
||||
'TEST_VAR',
|
||||
promptForSetting,
|
||||
'user',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing extension', async () => {
|
||||
mockGetExtensionAndManager.mockResolvedValue({
|
||||
extension: null,
|
||||
extensionManager: null,
|
||||
});
|
||||
|
||||
await runCommand('config missing-ext TEST_VAR');
|
||||
|
||||
expect(updateSetting).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject invalid extension names', async () => {
|
||||
await runCommand('config ../invalid TEST_VAR');
|
||||
expect(debugLogger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Invalid extension name'),
|
||||
);
|
||||
|
||||
await runCommand('config ext/with/slash TEST_VAR');
|
||||
expect(debugLogger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Invalid extension name'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Extension configuration (all settings)', () => {
|
||||
it('should configure all settings for an extension', async () => {
|
||||
const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
|
||||
setupExtension('test-ext', settings);
|
||||
(getScopedEnvContents as Mock).mockResolvedValue({});
|
||||
(updateSetting as Mock).mockResolvedValue(undefined);
|
||||
|
||||
await runCommand('config test-ext');
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
'Configuring settings for "test-ext"...',
|
||||
);
|
||||
expect(updateSetting).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 'test-ext' }),
|
||||
'test-id',
|
||||
'VAR_1',
|
||||
promptForSetting,
|
||||
'user',
|
||||
);
|
||||
});
|
||||
|
||||
it('should verify overwrite if setting is already set', async () => {
|
||||
const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
|
||||
setupExtension('test-ext', settings);
|
||||
(getScopedEnvContents as Mock).mockImplementation(
|
||||
async (_config, _id, scope) => {
|
||||
if (scope === 'user') return { VAR_1: 'existing' };
|
||||
return {};
|
||||
},
|
||||
);
|
||||
(prompts as unknown as Mock).mockResolvedValue({ overwrite: true });
|
||||
(updateSetting as Mock).mockResolvedValue(undefined);
|
||||
|
||||
await runCommand('config test-ext');
|
||||
|
||||
expect(prompts).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'confirm',
|
||||
message: expect.stringContaining('is already set. Overwrite?'),
|
||||
}),
|
||||
);
|
||||
expect(updateSetting).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should note if setting is configured in workspace', async () => {
|
||||
const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
|
||||
setupExtension('test-ext', settings);
|
||||
(getScopedEnvContents as Mock).mockImplementation(
|
||||
async (_config, _id, scope) => {
|
||||
if (scope === 'workspace') return { VAR_1: 'workspace_value' };
|
||||
return {};
|
||||
},
|
||||
);
|
||||
(updateSetting as Mock).mockResolvedValue(undefined);
|
||||
|
||||
await runCommand('config test-ext');
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('is already configured in the workspace scope'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip update if user denies overwrite', async () => {
|
||||
const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
|
||||
setupExtension('test-ext', settings);
|
||||
(getScopedEnvContents as Mock).mockResolvedValue({ VAR_1: 'existing' });
|
||||
(prompts as unknown as Mock).mockResolvedValue({ overwrite: false });
|
||||
|
||||
await runCommand('config test-ext');
|
||||
|
||||
expect(prompts).toHaveBeenCalled();
|
||||
expect(updateSetting).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configure all extensions', () => {
|
||||
it('should configure settings for all installed extensions', async () => {
|
||||
const ext1 = {
|
||||
name: 'ext1',
|
||||
path: '/p1',
|
||||
id: 'id1',
|
||||
settings: [{ envVar: 'V1' }],
|
||||
};
|
||||
const ext2 = {
|
||||
name: 'ext2',
|
||||
path: '/p2',
|
||||
id: 'id2',
|
||||
settings: [{ envVar: 'V2' }],
|
||||
};
|
||||
mockExtensionManager.getExtensions.mockReturnValue([ext1, ext2]);
|
||||
|
||||
mockExtensionManager.loadExtensionConfig.mockImplementation(
|
||||
async (path) => {
|
||||
if (path === '/p1')
|
||||
return { name: 'ext1', settings: [{ name: 'S1', envVar: 'V1' }] };
|
||||
if (path === '/p2')
|
||||
return { name: 'ext2', settings: [{ name: 'S2', envVar: 'V2' }] };
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
(getScopedEnvContents as Mock).mockResolvedValue({});
|
||||
(updateSetting as Mock).mockResolvedValue(undefined);
|
||||
|
||||
await runCommand('config');
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Configuring settings for "ext1"'),
|
||||
);
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Configuring settings for "ext2"'),
|
||||
);
|
||||
expect(updateSetting).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should log if no extensions installed', async () => {
|
||||
mockExtensionManager.getExtensions.mockReturnValue([]);
|
||||
await runCommand('config');
|
||||
expect(debugLogger.log).toHaveBeenCalledWith('No extensions installed.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { CommandModule } from 'yargs';
|
||||
import {
|
||||
updateSetting,
|
||||
promptForSetting,
|
||||
ExtensionSettingScope,
|
||||
getScopedEnvContents,
|
||||
} from '../../config/extensions/extensionSettings.js';
|
||||
import { getExtensionAndManager, getExtensionManager } from './utils.js';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
import { debugLogger, coreEvents } from '@google/gemini-cli-core';
|
||||
import { exitCli } from '../utils.js';
|
||||
import prompts from 'prompts';
|
||||
import type { ExtensionConfig } from '../../config/extension.js';
|
||||
interface ConfigureArgs {
|
||||
name?: string;
|
||||
setting?: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export const configureCommand: CommandModule<object, ConfigureArgs> = {
|
||||
command: 'config [name] [setting]',
|
||||
describe: 'Configure extension settings.',
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional('name', {
|
||||
describe: 'Name of the extension to configure.',
|
||||
type: 'string',
|
||||
})
|
||||
.positional('setting', {
|
||||
describe: 'The specific setting to configure (name or env var).',
|
||||
type: 'string',
|
||||
})
|
||||
.option('scope', {
|
||||
describe: 'The scope to set the setting in.',
|
||||
type: 'string',
|
||||
choices: ['user', 'workspace'],
|
||||
default: 'user',
|
||||
}),
|
||||
handler: async (args) => {
|
||||
const { name, setting, scope } = args;
|
||||
const settings = loadSettings(process.cwd()).merged;
|
||||
|
||||
if (!(settings.experimental?.extensionConfig ?? true)) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'Extension configuration is currently disabled. Enable it by setting "experimental.extensionConfig" to true.',
|
||||
);
|
||||
await exitCli();
|
||||
return;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
||||
debugLogger.error(
|
||||
'Invalid extension name. Names cannot contain path separators or "..".',
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 1: Configure specific setting for an extension
|
||||
if (name && setting) {
|
||||
await configureSpecificSetting(
|
||||
name,
|
||||
setting,
|
||||
scope as ExtensionSettingScope,
|
||||
);
|
||||
}
|
||||
// Case 2: Configure all settings for an extension
|
||||
else if (name) {
|
||||
await configureExtension(name, scope as ExtensionSettingScope);
|
||||
}
|
||||
// Case 3: Configure all extensions
|
||||
else {
|
||||
await configureAllExtensions(scope as ExtensionSettingScope);
|
||||
}
|
||||
|
||||
await exitCli();
|
||||
},
|
||||
};
|
||||
|
||||
async function configureSpecificSetting(
|
||||
extensionName: string,
|
||||
settingKey: string,
|
||||
scope: ExtensionSettingScope,
|
||||
) {
|
||||
const { extension, extensionManager } =
|
||||
await getExtensionAndManager(extensionName);
|
||||
if (!extension || !extensionManager) {
|
||||
return;
|
||||
}
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (!extensionConfig) {
|
||||
debugLogger.error(
|
||||
`Could not find configuration for extension "${extensionName}".`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await updateSetting(
|
||||
extensionConfig,
|
||||
extension.id,
|
||||
settingKey,
|
||||
promptForSetting,
|
||||
scope,
|
||||
);
|
||||
}
|
||||
|
||||
async function configureExtension(
|
||||
extensionName: string,
|
||||
scope: ExtensionSettingScope,
|
||||
) {
|
||||
const { extension, extensionManager } =
|
||||
await getExtensionAndManager(extensionName);
|
||||
if (!extension || !extensionManager) {
|
||||
return;
|
||||
}
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (
|
||||
!extensionConfig ||
|
||||
!extensionConfig.settings ||
|
||||
extensionConfig.settings.length === 0
|
||||
) {
|
||||
debugLogger.log(
|
||||
`Extension "${extensionName}" has no settings to configure.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogger.log(`Configuring settings for "${extensionName}"...`);
|
||||
await configureExtensionSettings(extensionConfig, extension.id, scope);
|
||||
}
|
||||
|
||||
async function configureAllExtensions(scope: ExtensionSettingScope) {
|
||||
const extensionManager = await getExtensionManager();
|
||||
const extensions = extensionManager.getExtensions();
|
||||
|
||||
if (extensions.length === 0) {
|
||||
debugLogger.log('No extensions installed.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const extension of extensions) {
|
||||
const extensionConfig = await extensionManager.loadExtensionConfig(
|
||||
extension.path,
|
||||
);
|
||||
if (
|
||||
extensionConfig &&
|
||||
extensionConfig.settings &&
|
||||
extensionConfig.settings.length > 0
|
||||
) {
|
||||
debugLogger.log(`\nConfiguring settings for "${extension.name}"...`);
|
||||
await configureExtensionSettings(extensionConfig, extension.id, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function configureExtensionSettings(
|
||||
extensionConfig: ExtensionConfig,
|
||||
extensionId: string,
|
||||
scope: ExtensionSettingScope,
|
||||
) {
|
||||
const currentScopedSettings = await getScopedEnvContents(
|
||||
extensionConfig,
|
||||
extensionId,
|
||||
scope,
|
||||
);
|
||||
|
||||
let workspaceSettings: Record<string, string> = {};
|
||||
if (scope === ExtensionSettingScope.USER) {
|
||||
workspaceSettings = await getScopedEnvContents(
|
||||
extensionConfig,
|
||||
extensionId,
|
||||
ExtensionSettingScope.WORKSPACE,
|
||||
);
|
||||
}
|
||||
|
||||
if (!extensionConfig.settings) return;
|
||||
|
||||
for (const setting of extensionConfig.settings) {
|
||||
const currentValue = currentScopedSettings[setting.envVar];
|
||||
const workspaceValue = workspaceSettings[setting.envVar];
|
||||
|
||||
if (workspaceValue !== undefined) {
|
||||
debugLogger.log(
|
||||
`Note: Setting "${setting.name}" is already configured in the workspace scope.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (currentValue !== undefined) {
|
||||
const response = await prompts({
|
||||
type: 'confirm',
|
||||
name: 'overwrite',
|
||||
message: `Setting "${setting.name}" (${setting.envVar}) is already set. Overwrite?`,
|
||||
initial: false,
|
||||
});
|
||||
|
||||
if (!response.overwrite) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await updateSetting(
|
||||
extensionConfig,
|
||||
extensionId,
|
||||
setting.envVar,
|
||||
promptForSetting,
|
||||
scope,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,9 @@ describe('extensions disable command', () => {
|
||||
_: [],
|
||||
$0: '',
|
||||
};
|
||||
await (command.handler as unknown as (args: TestArgv) => void)(argv);
|
||||
await (command.handler as unknown as (args: TestArgv) => Promise<void>)(
|
||||
argv,
|
||||
);
|
||||
expect(mockExtensionManager).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: '/test/dir',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user