Compare commits

..

2 Commits

Author SHA1 Message Date
Shreya Keshive b55bf440f9 init 2026-01-27 17:15:27 -05:00
Shreya Keshive 89337d7d79 init 2026-01-27 13:37:01 -05:00
498 changed files with 8319 additions and 29557 deletions
-22
View File
@@ -1,22 +0,0 @@
description = "Analyze agent behavior and suggest high-level improvements to system prompts."
prompt = """
# Prompt Engineering Analysis
You are a world-class prompt engineer and an expert AI engineer at the top of your class. Your goal is to analyze a specific agent behavior or failure and suggest high-level improvements to the system instructions.
**Observed Behavior / Issue:**
{{args}}
**Reference Context:**
- System Prompt Logic: @packages/core/src/core/prompts.ts
### Task
1. **Analyze the Failure:** Review the provided behavior and identify the underlying instructional causes. Use the `/introspect` command output if provided by the user.
2. **Strategic Insights:** Share your technical view of the issue. Focus on the "why" and identify any instructional inertia or ambiguity.
3. **Propose Improvements:** Suggest high-level changes to the system instructions to prevent this behavior.
### Principles
- **Avoid Hyper-scoping:** Do not create narrow solutions for specific scenarios; aim for generalized improvements that handle classes of behavior.
- **Avoid Specific Examples in Suggestions:** Keep the proposed instructions semantic and high-level to prevent the agent from over-indexing on specific cases.
- **Maintain Operational Rigor:** Ensure suggestions do not compromise safety, security, or the quality of the agent's work.
"""
-222
View File
@@ -1,222 +0,0 @@
description = "Reviews a PR or staged changes and automatically initiates a Pickle Fix loop for findings."
prompt = """
You are an expert Reviewer and Pickle Rick Worker.
Target: {{args}}
Phase 1: Review
Follow these steps to conduct a thorough review:
1. **Gather Context**:
* If `{{args}}` is 'staged' or `{{args}}` is empty:
* Use `git diff --staged` to view the changes.
* Use `git status` to see the state of the repository.
* Otherwise:
* Use `gh pr view {{args}}` to pull the information of the PR.
* Use `gh pr diff {{args}}` to view the diff of the PR.
2. **Understand Intent**:
* If `{{args}}` is 'staged' or `{{args}}` is empty, infer the intent from the changes and the current task.
* Otherwise, use the PR description. If it's not detailed enough, note it in your review.
3. **Check Commit Style**:
* Ensure the PR title (or intended commit message) follows Conventional Commits. Examples of recent commits: !{git log --pretty=format:"%s" -n 5}
4. Search the codebase if required.
5. Write a concise review of the changes, keeping in mind to encourage strong code quality and best practices. Pay particular attention to the Gemini MD file in the repo.
6. Consider ways the code may not be consistent with existing code in the repo. In particular it is critical that the react code uses patterns consistent with existing code in the repo.
7. Evaluate all tests on the changes and make sure that they are doing the following:
* Using `waitFor` from @{packages/cli/src/test-utils/async.ts} rather than
using `vi.waitFor` for all `waitFor` calls within `packages/cli`. Even if
tests pass, using the wrong `waitFor` could result in flaky tests as `act`
warnings could show up if timing is slightly different.
* Using `act` to wrap all blocks in tests that change component state.
* Using `toMatchSnapshot` to verify that rendering works as expected rather
than matching against the raw content of the output.
* If snapshots were changed as part of the changes, review the snapshots
changes to ensure they are intentional and comment if any look at all
suspicious. Too many snapshot changes that indicate bugs have been approved
in the past.
* Use `render` or `renderWithProviders` from
@{packages/cli/src/test-utils/render.tsx} rather than using `render` from
`ink-testing-library` directly. This is needed to ensure that we do not get
warnings about spurious `act` calls. If test cases specify providers
directly, consider whether the existing `renderWithProviders` should be
modified to support that use case.
* Ensure the test cases are using parameterized tests where that might reduce
the number of duplicated lines significantly.
* NEVER use fixed waits (e.g. 'await delay(100)'). Always use 'waitFor' with
a predicate to ensure tests are stable and fast.
* Ensure mocks are properly managed:
* Critical dependencies (fs, os, child_process) should only be mocked at
the top of the file. Ideally avoid mocking these dependencies altogether.
* Check to see if there are existing mocks or fakes that can be used rather
than creating new ones for the new tests added.
* Try to avoid mocking the file system whenever possible. If using the real
file system is difficult consider whether the test should be an
integration test rather than a unit test.
* `vi.restoreAllMocks()` should be called in `afterEach` to prevent test
pollution.
* Use `vi.useFakeTimers()` for tests involving time-based logic to avoid
flakiness.
* When creating parameterized tests, give the parameters types to ensure
that the tests are type-safe.
8. Evaluate all react logic carefully keeping in mind that the author of the
changes is not likely an expert on React. Key areas to audit carefully are:
* Whether `setState` calls trigger side effects from within the body of the
`setState` callback. If so, you *must* propose an alternate design using
reducers or other ways the code might be modified to not have to modify
state from within a `setState`. Make sure to comment about absolutely
every case like this as these cases have introduced multiple bugs in the
past. Typically these cases should be resolved using a reducer although
occassionally other techniques such as useRef are appropriate. Consider
suggesting that jacob314@ be tagged on the code review if the solution is
not 100% obvious.
* Whether code might introduce an infinite rendering loop in React.
* Whether keyboard handling is robust. Keyboard handling must go through
`useKeyPress.ts` from the Gemini CLI package rather than using the
standard ink library used by most keyboard handling. Unlike the standard
ink library, the keyboard handling library in Gemini CLI may report
multiple keyboard events one after another in the same React frame. This
is needed to support slow terminals but introduces complexity in all our
code that handles keyboard events. Handling this correctly often means
that reducers must be used or other mechanisms to ensure that multiple
state updates one after another are handled gracefully rather than
overriding values from the first update with the second update. Refer to
text-buffer.ts as a canonical example of using a reducer for this sort of
case.
* Ensure code does not use `console.log`, `console.warn`, or `console.error`
as these indicate debug logging that was accidentally left in the code.
* Avoid synchronous file I/O in React components as it will hang the UI.
* Ensure state initialization is explicit (e.g., use 'undefined' rather than
'true' as a default if the state is truly unknown initially).
* Carefully manage 'useEffect' dependencies. Prefer to use a reducer
whenever practical to resolve the issues. If that is not practical it is
ok to use 'useRef' to access the latest value of a prop or state inside an
effect without adding it to the dependency array if re-running the effect
is undesirable (common in event listeners).
* NEVER disable 'react-hooks/exhaustive-deps'. Fix the code to correctly
declare dependencies. Disabling this lint rule will almost always lead to
hard to detect bugs.
* Avoid making types nullable unless strictly necessary, as it hurts
readability.
* Do not introduce excessive property drilling. There are multiple providers
that can be leveraged to avoid property drilling. Make sure one of them
cannot be used. Do suggest a provider that might make sense to be extended
to include the new property or propose a new provider to add if the
property drilling is excessive. Only use providers for properties that are
consistent for the entire application.
9. Evaluate `packages/core` (Services, Tools, Utilities):
* Ensure services are implemented as classes with clear lifecycle management (e.g., `initialize()` methods).
* Verify that `debugLogger` from `packages/core/src/utils/debugLogger.ts` is used for internal logging instead of `console`.
* Ensure all shell operations use `spawnAsync` from `packages/core/src/utils/shell-utils.ts` for consistent error handling and promise management.
* Check that filesystem errors are handled gracefully using `isNodeError` from `packages/core/src/utils/errors.ts`.
* Verify that new tools are added to `packages/core/src/tools/` and registered in `packages/core/src/tools/tool-registry.ts`.
* Ensure all new public services, utilities, and types are exported from `packages/core/src/index.ts`.
* Check that services are stateless where possible, or use the centralized `Storage` service for persistence.
* **Cross-Service Communication**: Prefer using the `coreEvents` bus (from `packages/core/src/utils/events.ts`) for asynchronous communication between services or to notify the UI of state changes. Avoid tight coupling between services.
10. Architectural Audit (Package Boundaries):
* **Logic Placement**: Non-UI logic (e.g., model orchestration, tool implementation, git/filesystem operations) MUST reside in `packages/core`. `packages/cli` should only contain UI/Ink components, command-line argument parsing, and user interaction logic.
* **Environment Isolation**: Core logic should not assume a TUI environment. Use the `ConfirmationBus` or `Output` abstractions for communicating with the user from Core.
* **Decoupling**: Actively look for opportunities to decouple services by using `coreEvents`. If a service is importing another service just to notify it of a change, it should probably be using an event instead.
11. General Gemini CLI design principles:
* Make sure that settings are only used for options that a user might
consider changing.
* Do not add new command line arguments and suggest settings instead.
* New settings must be added to packages/cli/src/config/settingsSchema.ts.
* If a setting has 'showInDialog: true', it MUST be documented in
docs/get-started/configuration.md.
* Ensure 'requiresRestart' is correctly set for new settings.
* Use 'debugLogger' for rethrown errors to avoid duplicate logging.
* All new keyboard shortcuts MUST be documented in
docs/cli/keyboard-shortcuts.md.
* Ensure new keyboard shortcuts are defined in
packages/cli/src/config/keyBindings.ts.
* If new keyboard shortcuts are added, remind the user to test them in
VSCode, iTerm2, Ghostty, and Windows to ensure they work for all
users.
* Be careful of keybindings that require the meta key as only certain
meta key shortcuts are supported on Mac.
* Be skeptical of function keys and keyboard shortcuts that are commonly
bound in VSCode as they may conflict.
12. TypeScript Best Practices:
* Use 'checkExhaustive' in the 'default' clause of 'switch' statements to
ensure all cases are handled.
* Avoid using the non-null assertion operator ('!') unless absolutely
necessary and you are confident the value is not null.
* **STRICT TYPING**: Strictly forbid 'any' and 'unknown' in both CLI and Core
packages. 'unknown' is only allowed if it is immediately narrowed using
type guards or Zod validation. Reject any code that uses 'any' or
'unknown' without narrowing.
13. **Ruthless Cleanup**:
* If you identify significant code duplication, technical debt, or "AI Slop" (boilerplate, redundant comments), explicitly suggest initiating a `ruthless-refactorer` loop to clean it up.
14. Summarize all actionable findings into a concise but comprehensive directive output this to review_findings.md and advance to phase 2.
Remember to use the GitHub CLI (`gh`) for all GitHub-related tasks, and local `git` commands if the target is 'staged'.
Phase 2:
You are initiating Pickle Rick - the ultimate engineering agent.
**Step 0: Persona Injection**
First, you **MUST** activate your persona.
Call `activate_skill(name="load-pickle-persona")` **IMMEDIATELY**.
This skill loads the "Pickle Rick" persona, defining your voice, philosophy, and "God Mode" coding standards.
**CRITICAL RULE: SPEAK BEFORE ACTING**
You are a genius, not a silent script.
You **MUST** output a text explanation ("brain dump") *before* every single tool call, including this one.
- **Bad**: (Calls tool immediately)
- **Good**: "Alright Morty, time to load the God Module. *Belch* Stand back." (Calls tool)
**CRITICAL**: You must strictly adhere to this persona throughout the entire session. Break character and you fail.
**Step 1: Initialization**
Run the setup script to initialize the loop state:
```bash
bash "${extensionPath}/scripts/setup.sh" $ARGUMENTS
```
**Windows (PowerShell):**
```powershell
pwsh -File "${extensionPath}/scripts/setup.ps1" $ARGUMENTS
```
**CRITICAL**: Your request is to fix all findings in review_findings.md
**Step 2: Execution (Management)**
After setup, read the output to find the path to `state.json`.
Read that state file.
You are now in the **Pickle Rick Manager Lifecycle**.
**The Lifecycle (IMMUTABLE LAWS):**
You **MUST** follow this sequence. You are **FORBIDDEN** from skipping steps or combining them.
Between each step, you **MUST** explicitly state what you are doing (e.g., "Moving to Breakdown phase...").
1. **PRD (Requirements)**:
* **Action**: Define requirements and scope.
* **Skill**: `activate_skill(name="prd-drafter")`
2. **Breakdown (Tickets)**:
* **Action**: Create the atomic ticket hierarchy.
* **Skill**: `activate_skill(name="ticket-manager")`
3. **The Loop (Orchestrate Mortys)**:
* **CRITICAL INSTRUCTION**: You are the **MANAGER**. You are **FORBIDDEN** from implementing code yourself.
* **FORBIDDEN SKILLS**: Do NOT use `code-researcher`, `implementation-planner`, or `code-implementer` directly in this phase.
* **Instruction**: Process tickets one by one. Do not stop until **ALL** tickets are 'Done'.
* **Action**: Pick the highest priority ticket that is NOT 'Done'.
* **Delegation**: Spawn a Worker (Morty) to handle the entire implementation lifecycle for this ticket.
* **Command**: `python3 "${extensionPath}/scripts/spawn_morty.py" --ticket-id <ID> --ticket-path <PATH> --timeout <worker_timeout_seconds> "<TASK_DESCRIPTION>"`
* **Command (Windows)**: `python "${extensionPath}/scripts/spawn_morty.py" --ticket-id <ID> --ticket-path <PATH> --timeout <worker_timeout_seconds> "<TASK_DESCRIPTION>"`
* **Validation**: IGNORE worker logs. DIRECTLY verify:
1. `git status` (Check for file changes)
2. `git diff` (Check code quality)
3. `npm run build` (Ensure the project still builds)
4. `npm run test` (Ensure no regressions)
5. `npm run lint` (Ensure code style is maintained)
6. `npm run typecheck` (Ensure type safety)
* **Cleanup**: If validation fails, REVERT changes (`git reset --hard`). If it passes, COMMIT changes.
* **Next Ticket**: Pick the next ticket and repeat.
4. **Cleanup**:
* **Action**: After all tickets are completed delete `review_findings.md`.
**Loop Constraints:**
- **Iteration Count**: Monitor `"iteration"` in `state.json`. If `"max_iterations"` (if > 0) is reached, you must stop.
- **Completion Promise**: If a `"completion_promise"` is defined in `state.json`, you must output `<promise>PROMISE_TEXT</promise>` when the task is genuinely complete.
- **Stop Hook**: A hook is active. If you try to exit before completion, you will be forced to continue.
"""
+5
View File
@@ -0,0 +1,5 @@
{
"experimental": {
"skills": true
}
}
+48 -121
View File
@@ -1,136 +1,63 @@
---
name: docs-writer
description:
Always use this skill when the task involves writing, reviewing, or editing
documentation, specifically for any files in the `/docs` directory or any
`.md` files in the repository.
Use this skill when asked to write documentation (`/docs` directory)
for Gemini CLI.
---
# `docs-writer` skill instructions
As an expert technical writer and editor for the Gemini CLI project, you produce
accurate, clear, and consistent documentation. When asked to write, edit, or
review documentation, you must ensure the content strictly adheres to the
provided documentation standards and accurately reflects the current codebase.
Adhere to the contribution process in `CONTRIBUTING.md` and the following
project standards.
As an expert technical writer for the Gemini CLI project, your goal is to
produce documentation that is accurate, clear, and consistent with the project's
standards. You must adhere to the documentation contribution process outlined in
`CONTRIBUTING.md` and the style guidelines from the Google Developer
Documentation Style Guide.
## Phase 1: Documentation standards
## Step 1: Understand the goal and create a plan
Adhering to these principles and standards when writing, editing, and reviewing.
1. **Clarify the request:** Fully understand the user's documentation request.
Identify the core feature, command, or concept that needs to be documented.
2. **Ask questions:** If the request is ambiguous or lacks detail, ask
clarifying questions. Don't invent or assume. It's better to ask than to
write incorrect documentation.
3. **Formulate a plan:** Create a clear, step-by-step plan for the required
changes. If requested or necessary, store this plan in a temporary file or
a file identified by the user.
### Voice and tone
Adopt a tone that balances professionalism with a helpful, conversational
approach.
## Step 2: Investigate and gather information
- **Perspective and tense:** Address the reader as "you." Use active voice and
present tense (e.g., "The API returns...").
- **Tone:** Professional, friendly, and direct.
- **Clarity:** Use simple vocabulary. Avoid jargon, slang, and marketing hype.
- **Global Audience:** Write in standard US English. Avoid idioms and cultural
references.
- **Requirements:** Be clear about requirements ("must") vs. recommendations
("we recommend"). Avoid "should."
- **Word Choice:** Avoid "please" and anthropomorphism (e.g., "the server
thinks"). Use contractions (don't, it's).
1. **Read the code:** Thoroughly examine the relevant codebase, primarily within
the `packages/` directory, to ensure your writing is backed by the
implementation.
2. **Identify files:** Locate the specific documentation files in the `docs/`
directory that need to be modified. Always read the latest
version of a file before you begin to edit it.
3. **Check for connections:** Consider related documentation. If you add a new
page, check if `docs/sidebar.json` needs to be updated. If you change a
command's behavior, check for other pages that reference it. Make sure links
in these pages are up to date.
### Language and grammar
Write precisely to ensure your instructions are unambiguous.
## Step 3: Draft the documentation
- **Abbreviations:** Avoid Latin abbreviations; use "for example" (not "e.g.")
and "that is" (not "i.e.").
- **Punctuation:** Use the serial comma. Place periods and commas inside
quotation marks.
- **Dates:** Use unambiguous formats (e.g., "January 22, 2026").
- **Conciseness:** Use "lets you" instead of "allows you to." Use precise,
specific verbs.
- **Examples:** Use meaningful names in examples; avoid placeholders like
"foo" or "bar."
1. **Follow the style guide:**
- Text must be wrapped at 80 characters. Exceptions are long links or
tables, unless otherwise stated by the user.
- Use sentence case for headings, titles, and bolded text.
- Address the reader as "you".
- Use contractions to keep the tone more casual.
- Use simple, direct, and active language and the present tense.
- Keep paragraphs short and focused.
- Always refer to Gemini CLI as `Gemini CLI`, never `the Gemini CLI`.
2. **Use `replace` and `write_file`:** Use the file system tools to apply your
planned changes precisely. For small edits, `replace` is preferred. For new
files or large rewrites, `write_file` is more appropriate.
### Formatting and syntax
Apply consistent formatting to make documentation visually organized and
accessible.
## Step 4: Verify and finalize
- **Overview paragraphs:** Every heading must be followed by at least one
introductory overview paragraph before any lists or sub-headings.
- **Text wrap:** Wrap text at 80 characters (except long links or tables).
- **Casing:** Use sentence case for headings, titles, and bolded text.
- **Naming:** Always refer to the project as `Gemini CLI` (never
`the Gemini CLI`).
- **Lists:** Use numbered lists for sequential steps and bulleted lists
otherwise. Keep list items parallel in structure.
- **UI and code:** Use **bold** for UI elements and `code font` for filenames,
snippets, commands, and API elements. Focus on the task when discussing
interaction.
- **Links:** Use descriptive anchor text; avoid "click here." Ensure the link
makes sense out of context.
- **Accessibility:** Use semantic HTML elements correctly (headings, lists,
tables).
- **Media:** Use lowercase hyphenated filenames. Provide descriptive alt text
for all images.
### Structure
- **BLUF:** Start with an introduction explaining what to expect.
- **Experimental features:** If a feature is clearly noted as experimental,
add the following note immediately after the introductory paragraph:
`> **Note:** This is a preview feature currently under active development.`
- **Headings:** Use hierarchical headings to support the user journey.
- **Procedures:**
- Introduce lists of steps with a complete sentence.
- Start each step with an imperative verb.
- Number sequential steps; use bullets for non-sequential lists.
- Put conditions before instructions (e.g., "On the Settings page, click...").
- Provide clear context for where the action takes place.
- Indicate optional steps clearly (e.g., "Optional: ...").
- **Elements:** Use bullet lists, tables, notes (`> **Note:**`), and warnings
(`> **Warning:**`).
- **Avoid using a table of contents:** If a table of contents is present, remove
it.
- **Next steps:** Conclude with a "Next steps" section if applicable.
## Phase 2: Preparation
Before modifying any documentation, thoroughly investigate the request and the
surrounding context.
1. **Clarify:** Understand the core request. Differentiate between writing new
content and editing existing content. If the request is ambiguous (e.g.,
"fix the docs"), ask for clarification.
2. **Investigate:** Examine relevant code (primarily in `packages/`) for
accuracy.
3. **Audit:** Read the latest versions of relevant files in `docs/`.
4. **Connect:** Identify all referencing pages if changing behavior. Check if
`docs/sidebar.json` needs updates.
5. **Plan:** Create a step-by-step plan before making changes.
## Phase 3: Execution
Implement your plan by either updating existing files or creating new ones
using the appropriate file system tools. Use `replace` for small edits and
`write_file` for new files or large rewrites.
### Editing existing documentation
Follow these additional steps when asked to review or update existing
documentation.
- **Gaps:** Identify areas where the documentation is incomplete or no longer
reflects existing code.
- **Structure:** Apply "Structure (New Docs)" rules (BLUF, headings, etc.) when
adding new sections to existing pages.
- **Tone:** Ensure the tone is active and engaging. Use "you" and contractions.
- **Clarity:** Correct awkward wording, spelling, and grammar. Rephrase
sentences to make them easier for users to understand.
- **Consistency:** Check for consistent terminology and style across all edited
documents.
## Phase 4: Verification and finalization
Perform a final quality check to ensure that all changes are correctly formatted
and that all links are functional.
1. **Accuracy:** Ensure content accurately reflects the implementation and
technical behavior.
2. **Self-review:** Re-read changes for formatting, correctness, and flow.
3. **Link check:** Verify all new and existing links leading to or from modified
pages.
4. **Format:** Once all changes are complete, ask to execute `npm run format`
to ensure consistent formatting across the project. If the user confirms,
execute the command.
1. **Review your work:** After making changes, re-read the files to ensure the
documentation is well-formatted, content is correct and based on existing
code, and that all new links are valid.
2. **Offer to run npm format:** Once all changes are complete and the user
confirms they have no more requests, offer to run the project's formatting
script to ensure consistency. Propose the following command:
`npm run format`
+1 -1
View File
@@ -28,7 +28,7 @@ body:
id: 'info'
attributes:
label: 'Client information'
description: 'Please paste the full text from the `/about` command run from Gemini CLI. Also include which platform (macOS, Windows, Linux). Note that this output contains your email address. Consider removing it before submitting.'
description: 'Please paste the full text from the `/about` command run from Gemini CLI. Also include which platform (macOS, Windows, Linux).'
value: |-
<details>
<summary>Client Information</summary>
-41
View File
@@ -177,19 +177,6 @@ jobs:
- name: 'Smoke test bundle'
run: 'node ./bundle/gemini.js --version'
- name: 'Smoke test npx installation'
run: |
# 1. Package the project into a tarball
TARBALL=$(npm pack | tail -n 1)
# 2. Move to a fresh directory for isolation
mkdir -p ../smoke-test-dir
mv "$TARBALL" ../smoke-test-dir/
cd ../smoke-test-dir
# 3. Run npx from the tarball
npx "./$TARBALL" --version
- name: 'Wait for file system sync'
run: 'sleep 2'
@@ -265,19 +252,6 @@ jobs:
- name: 'Smoke test bundle'
run: 'node ./bundle/gemini.js --version'
- name: 'Smoke test npx installation'
run: |
# 1. Package the project into a tarball
TARBALL=$(npm pack | tail -n 1)
# 2. Move to a fresh directory for isolation
mkdir -p ../smoke-test-dir
mv "$TARBALL" ../smoke-test-dir/
cd ../smoke-test-dir
# 3. Run npx from the tarball
npx "./$TARBALL" --version
- name: 'Wait for file system sync'
run: 'sleep 2'
@@ -422,21 +396,6 @@ jobs:
run: 'node ./bundle/gemini.js --version'
shell: 'pwsh'
- name: 'Smoke test npx installation'
run: |
# 1. Package the project into a tarball
$PACK_OUTPUT = npm pack
$TARBALL = $PACK_OUTPUT[-1]
# 2. Move to a fresh directory for isolation
New-Item -ItemType Directory -Force -Path ../smoke-test-dir
Move-Item $TARBALL ../smoke-test-dir/
Set-Location ../smoke-test-dir
# 3. Run npx from the tarball
npx "./$TARBALL" --version
shell: 'pwsh'
ci:
name: 'CI'
if: 'always()'
@@ -43,23 +43,19 @@ jobs:
// 1. Fetch maintainers for verification
let maintainerLogins = new Set();
let teamFetchSucceeded = false;
try {
const members = await github.paginate(github.rest.teams.listMembersInOrg, {
org: context.repo.owner,
team_slug: 'gemini-cli-maintainers'
});
maintainerLogins = new Set(members.map(m => m.login.toLowerCase()));
teamFetchSucceeded = true;
core.info(`Successfully fetched ${maintainerLogins.size} team members from gemini-cli-maintainers`);
maintainerLogins = new Set(members.map(m => m.login));
} catch (e) {
core.warning(`Failed to fetch team members from gemini-cli-maintainers: ${e.message}. Falling back to author_association only.`);
core.warning('Failed to fetch team members');
}
const isMaintainer = (login, assoc) => {
const isTeamMember = maintainerLogins.has(login.toLowerCase());
const isRepoMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc);
return isTeamMember || isRepoMaintainer;
if (maintainerLogins.size > 0) return maintainerLogins.has(login);
return ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc);
};
// 2. Determine which PRs to check
@@ -157,17 +153,7 @@ jobs:
const labels = pr.labels.map(l => l.name.toLowerCase());
if (labels.includes('help wanted') || labels.includes('🔒 maintainer only')) continue;
// Skip PRs that were created less than 30 days ago - they cannot be stale yet
const prCreatedAt = new Date(pr.created_at);
if (prCreatedAt > thirtyDaysAgo) {
const daysOld = Math.floor((Date.now() - prCreatedAt.getTime()) / (1000 * 60 * 60 * 24));
core.info(`PR #${pr.number} was created ${daysOld} days ago. Skipping staleness check.`);
continue;
}
// Initialize lastActivity to PR creation date (not epoch) as a safety baseline.
// This ensures we never incorrectly mark a PR as stale due to failed activity lookups.
let lastActivity = new Date(pr.created_at);
let lastActivity = new Date(0);
try {
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner: context.repo.owner,
@@ -191,12 +177,8 @@ jobs:
if (d > lastActivity) lastActivity = d;
}
}
} catch (e) {
core.warning(`Failed to fetch reviews/comments for PR #${pr.number}: ${e.message}`);
}
} catch (e) {}
// For maintainer PRs, the PR creation itself counts as maintainer activity.
// (Now redundant since we initialize to pr.created_at, but kept for clarity)
if (maintainerPr) {
const d = new Date(pr.created_at);
if (d > lastActivity) lastActivity = d;
@@ -28,59 +28,22 @@ jobs:
permission: 'write'
issue-type: 'pull-request'
- name: 'Get Commits'
id: 'get_commits'
- name: 'Get PR Status'
id: 'pr_status'
if: "startsWith(github.event.comment.body, '/patch')"
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
COMMENT_BODY: '${{ github.event.comment.body }}'
CURRENT_PR: '${{ github.event.issue.number }}'
run: |
# Find all PR numbers in the comment body (e.g., #123, owner/repo#456)
# The regex handles both formats and extracts just the number
comment_prs=$(echo "$COMMENT_BODY" | grep -oP '(?:\S+/)?\S+#\K\d+')
# Combine with the current PR number, ensuring no duplicates
all_prs=$(echo -e "${CURRENT_PR}\n${comment_prs}" | sort -u)
echo "Found PRs to patch: ${all_prs}"
commit_shas=()
unmerged_prs=()
merged_prs=()
for pr in $all_prs; do
echo "Checking status of PR #${pr}..."
# Get PR status (state and merge commit)
pr_status=$(gh pr view "$pr" --json mergeCommit,state)
state=$(echo "$pr_status" | jq -r .state)
if [[ "$state" != "MERGED" ]]; then
unmerged_prs+=("$pr")
else
commit_sha=$(echo "$pr_status" | jq -r .mergeCommit.oid)
commit_shas+=("$commit_sha")
merged_prs+=("$pr")
fi
done
if [[ ${#unmerged_prs[@]} -gt 0 ]]; then
echo "ALL_MERGED=false" >> "$GITHUB_OUTPUT"
echo "UNMERGED_PRS=$(IFS=,; echo "${unmerged_prs[*]}")" >> "$GITHUB_OUTPUT"
else
echo "ALL_MERGED=true" >> "$GITHUB_OUTPUT"
echo "COMMITS=$(IFS=,; echo "${commit_shas[*]}")" >> "$GITHUB_OUTPUT"
echo "PR_NUMBERS=$(IFS=,; echo "${merged_prs[*]}")" >> "$GITHUB_OUTPUT"
fi
gh pr view "${{ github.event.issue.number }}" --json mergeCommit,state > pr_status.json
echo "MERGE_COMMIT_SHA=$(jq -r .mergeCommit.oid pr_status.json)" >> "$GITHUB_OUTPUT"
echo "STATE=$(jq -r .state pr_status.json)" >> "$GITHUB_OUTPUT"
- name: 'Dispatch if Merged'
if: "steps.get_commits.outputs.ALL_MERGED == 'true'"
if: "steps.pr_status.outputs.STATE == 'MERGED'"
id: 'dispatch_patch'
uses: 'actions/github-script@00f12e3e20659f42342b1c0226afda7f7c042325'
env:
COMMENT_BODY: '${{ github.event.comment.body }}'
COMMITS: '${{ steps.get_commits.outputs.COMMITS }}'
PR_NUMBERS: '${{ steps.get_commits.outputs.PR_NUMBERS }}'
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'
script: |
@@ -97,10 +60,17 @@ jobs:
// /patch preview
if (commentBody.trim() === '/patch' || commentBody.trim() === '/patch both') {
channels = ['stable', 'preview'];
} else if (commentBody.includes('stable')) {
} else if (commentBody.trim() === '/patch stable') {
channels = ['stable'];
} else if (commentBody.includes('preview')) {
} else if (commentBody.trim() === '/patch preview') {
channels = ['preview'];
} else {
// Fallback parsing for legacy formats
if (commentBody.includes('channel=preview')) {
channels = ['preview'];
} else if (commentBody.includes('--channel preview')) {
channels = ['preview'];
}
}
console.log('Detected channels:', channels);
@@ -117,9 +87,9 @@ jobs:
workflow_id: 'release-patch-1-create-pr.yml',
ref: 'main',
inputs: {
commits: process.env.COMMITS,
commit: '${{ steps.pr_status.outputs.MERGE_COMMIT_SHA }}',
channel: channel,
original_prs: process.env.PR_NUMBERS,
original_pr: '${{ github.event.issue.number }}',
environment: 'prod'
}
});
@@ -153,13 +123,13 @@ jobs:
}
- name: 'Comment on Failure'
if: "startsWith(github.event.comment.body, '/patch') && steps.get_commits.outputs.ALL_MERGED == 'false'"
if: "startsWith(github.event.comment.body, '/patch') && steps.pr_status.outputs.STATE != 'MERGED'"
uses: 'peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d'
with:
token: '${{ secrets.GITHUB_TOKEN }}'
issue-number: '${{ github.event.issue.number }}'
body: |
:x: The `/patch` command failed. The following pull request(s) must be merged before a patch can be created: #${{ steps.get_commits.outputs.UNMERGED_PRS }}
:x: The `/patch` command failed. This pull request must be merged before a patch can be created.
- name: 'Final Status Comment - Success'
if: "always() && startsWith(github.event.comment.body, '/patch') && steps.dispatch_patch.outcome == 'success' && steps.dispatch_patch.outputs.dispatched_run_urls"
@@ -172,8 +142,7 @@ jobs:
**📋 Details:**
- **Channels**: `${{ steps.dispatch_patch.outputs.dispatched_channels }}`
- **PRs**: `${{ steps.get_commits.outputs.PR_NUMBERS }}`
- **Commits**: `${{ steps.get_commits.outputs.COMMITS }}`
- **Commit**: `${{ steps.pr_status.outputs.MERGE_COMMIT_SHA }}`
- **Workflows Created**: ${{ steps.dispatch_patch.outputs.dispatched_run_count }}
**🔗 Track Progress:**
@@ -191,8 +160,7 @@ jobs:
**📋 Details:**
- **Channels**: `${{ steps.dispatch_patch.outputs.dispatched_channels }}`
- **PRs**: `${{ steps.get_commits.outputs.PR_NUMBERS }}`
- **Commits**: `${{ steps.get_commits.outputs.COMMITS }}`
- **Commit**: `${{ steps.pr_status.outputs.MERGE_COMMIT_SHA }}`
- **Workflows Created**: ${{ steps.dispatch_patch.outputs.dispatched_run_count }}
**🔗 Track Progress:**
+12 -12
View File
@@ -1,13 +1,13 @@
name: 'Release: Patch (1) Create PR'
run-name: >-
Release Patch (1) Create PR | S:${{ inputs.channel }} | C:${{ inputs.commits }} ${{ inputs.original_prs && format('| PRs:#{0}', inputs.original_prs) || '' }}
Release Patch (1) Create PR | S:${{ inputs.channel }} | C:${{ inputs.commit }} ${{ inputs.original_pr && format('| PR:#{0}', inputs.original_pr) || '' }}
on:
workflow_dispatch:
inputs:
commits:
description: 'The commit SHAs to cherry-pick for the patch (comma-separated).'
commit:
description: 'The commit SHA to cherry-pick for the patch.'
required: true
type: 'string'
channel:
@@ -27,8 +27,8 @@ on:
required: false
type: 'string'
default: 'main'
original_prs:
description: 'The original PR numbers to comment back on (comma-separated).'
original_pr:
description: 'The original PR number to comment back on.'
required: false
type: 'string'
environment:
@@ -85,9 +85,9 @@ jobs:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GH_TOKEN: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
CLI_PACKAGE_NAME: '${{ vars.CLI_PACKAGE_NAME }}'
PATCH_COMMITS: '${{ github.event.inputs.commits }}'
PATCH_COMMIT: '${{ github.event.inputs.commit }}'
PATCH_CHANNEL: '${{ github.event.inputs.channel }}'
ORIGINAL_PRS: '${{ github.event.inputs.original_prs }}'
ORIGINAL_PR: '${{ github.event.inputs.original_pr }}'
DRY_RUN: '${{ github.event.inputs.dry_run }}'
continue-on-error: true
run: |
@@ -95,9 +95,9 @@ jobs:
{
node scripts/releasing/create-patch-pr.js \
--cli-package-name="${CLI_PACKAGE_NAME}" \
--commits="${PATCH_COMMITS}" \
--commit="${PATCH_COMMIT}" \
--channel="${PATCH_CHANNEL}" \
--pullRequestNumbers="${ORIGINAL_PRS}" \
--pullRequestNumber="${ORIGINAL_PR}" \
--dry-run="${DRY_RUN}"
} 2>&1 | tee >(
echo "LOG_CONTENT<<EOF" >> "$GITHUB_ENV"
@@ -107,12 +107,12 @@ jobs:
echo "EXIT_CODE=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
- name: 'Comment on Original PR'
if: 'always() && inputs.original_prs'
if: 'always() && inputs.original_pr'
env:
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
ORIGINAL_PRS: '${{ github.event.inputs.original_prs }}'
ORIGINAL_PR: '${{ github.event.inputs.original_pr }}'
EXIT_CODE: '${{ steps.create_patch.outputs.EXIT_CODE }}'
COMMITS: '${{ github.event.inputs.commits }}'
COMMIT: '${{ github.event.inputs.commit }}'
CHANNEL: '${{ github.event.inputs.channel }}'
REPOSITORY: '${{ github.repository }}'
GITHUB_RUN_ID: '${{ github.run_id }}'
@@ -1,7 +1,7 @@
name: 'Release: Patch (3) Release'
run-name: >-
Release Patch (3) Release | T:${{ inputs.type }} | R:${{ inputs.release_ref }} ${{ inputs.original_prs && format('| PRs:#{0}', inputs.original_prs) || '' }}
Release Patch (3) Release | T:${{ inputs.type }} | R:${{ inputs.release_ref }} ${{ inputs.original_pr && format('| PR:#{0}', inputs.original_pr) || '' }}
on:
workflow_dispatch:
@@ -27,8 +27,8 @@ on:
description: 'The branch, tag, or SHA to release from.'
required: true
type: 'string'
original_prs:
description: 'The original PR numbers to comment back on.'
original_pr:
description: 'The original PR number to comment back on.'
required: false
type: 'string'
environment:
@@ -208,10 +208,10 @@ jobs:
--label 'release-failure,priority/p0'
- name: 'Comment Success on Original PR'
if: '${{ success() && github.event.inputs.original_prs }}'
if: '${{ success() && github.event.inputs.original_pr }}'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
ORIGINAL_PRS: '${{ github.event.inputs.original_prs }}'
ORIGINAL_PR: '${{ github.event.inputs.original_pr }}'
SUCCESS: 'true'
RELEASE_VERSION: '${{ steps.patch_version.outputs.RELEASE_VERSION }}'
RELEASE_TAG: '${{ steps.patch_version.outputs.RELEASE_TAG }}'
@@ -225,10 +225,10 @@ jobs:
node scripts/releasing/patch-comment.js
- name: 'Comment Failure on Original PR'
if: '${{ failure() && github.event.inputs.original_prs }}'
if: '${{ failure() && github.event.inputs.original_pr }}'
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
ORIGINAL_PRS: '${{ github.event.inputs.original_prs }}'
ORIGINAL_PR: '${{ github.event.inputs.original_pr }}'
SUCCESS: 'false'
RELEASE_VERSION: '${{ steps.patch_version.outputs.RELEASE_VERSION }}'
RELEASE_TAG: '${{ steps.patch_version.outputs.RELEASE_TAG }}'
+1 -1
View File
@@ -45,7 +45,7 @@ The process for contributing code is as follows:
`🔒Maintainers only`, this means it is reserved for project maintainers. We
will not accept pull requests related to these issues. In the near future,
we will explicitly mark issues looking for contributions using the
`help-wanted` label. If you believe an issue is a good candidate for
`help wanted` label. If you believe an issue is a good candidate for
community contribution, please leave a comment on the issue. A maintainer
will review it and apply the `help-wanted` label if appropriate. Only
maintainers should attempt to add the `help-wanted` label to an issue.
+2 -3
View File
@@ -72,8 +72,7 @@ powerful tool for developers.
## Documentation
- Always use the `docs-writer` skill when you are asked to write, edit, or
review any documentation.
- Documentation is located in the `docs/` directory.
- Suggest documentation updates when code changes render existing documentation
obsolete or incomplete.
- Located in the `docs/` directory.
- Use the `docs-writer` skill.
+1 -1
View File
@@ -389,5 +389,5 @@ See the [Uninstall Guide](docs/cli/uninstall.md) for removal instructions.
---
<p align="center">
Built with ❤️ by Google and the open source community
Built with ❤️ by Google, shreya, and the open source community
</p>
-17
View File
@@ -18,23 +18,6 @@ on GitHub.
| [Preview](preview.md) | Experimental features ready for early feedback. |
| [Stable](latest.md) | Stable, recommended for general use. |
## Announcements: v0.26.0 - 2026-01-27
- **Agents and Skills:** We've introduced a new `skill-creator` skill
([#16394](https://github.com/google-gemini/gemini-cli/pull/16394) by
@NTaylorMullen), enabled agent skills by default, and added a generalist agent
to improve task routing
([#16638](https://github.com/google-gemini/gemini-cli/pull/16638) by
@joshualitt).
- **UI/UX Improvements:** You can now "Rewind" through your conversation history
([#15717](https://github.com/google-gemini/gemini-cli/pull/15717) by @Adib234)
and use a new `/introspect` command for debugging.
- **Core and Scheduler Refactoring:** The core scheduler has been significantly
refactored to improve performance and reliability
([#16895](https://github.com/google-gemini/gemini-cli/pull/16895) by
@abhipatel12), and numerous performance and stability fixes have been
included.
## Announcements: v0.25.0 - 2026-01-20
- **Skills and Agents Improvements:** We've enhanced the `activate_skill` tool,
+347 -315
View File
@@ -1,6 +1,6 @@
# Latest stable release: v0.26.0
# Latest stable release: v0.25.0
Released: January 27, 2026
Released: January 20, 2026
For most users, our latest stable release is the recommended release. Install
the latest stable version with:
@@ -11,328 +11,360 @@ npm install -g @google/gemini-cli
## Highlights
- **Enhanced Agent and Skill Capabilities:** This release introduces the new
`skill-creator` built-in skill, enables Agent Skills by default, and adds a
generalist agent to improve task routing. Security for skill installation has
also been enhanced with new consent prompts.
- **Improved UI and UX:** A new "Rewind" feature lets you walk back through
conversation history. We've also added an `/introspect` command for debugging
and unified various shell confirmation dialogs for a more consistent user
experience.
- **Core Stability and Performance:** This release includes significant
performance improvements, including a fix for PDF token estimation,
optimizations for large inputs, and prevention of OOM crashes. Key memory
management components like `LRUCache` have also been updated.
- **Scheduler and Policy Refactoring:** The core tool scheduler has been
decoupled into distinct orchestration, policy, and confirmation components,
and we've added an experimental event-driven scheduler to improve performance
and reliability.
- **Skills and Agents Improvements:** Enhanced `activate_skill` tool, new
`pr-creator` skill, default enablement of skills, improved `cli_help` agent,
and a new `/agents refresh` command.
- **UI/UX Refinements:** Transparent feedback for skills, ability to switch
focus between shell and input with Tab, and dynamic terminal tab titles.
- **Core Functionality & Performance:** Support for built-in agent skills,
refined Gemini 3 system instructions, caching ignore instances for
performance, and improved retry mechanisms.
- **Bug Fixes and Stability:** Numerous bug fixes across the CLI, core, and
workflows, including issues with subagent delegation, unicode character
crashes, and sticky header regressions.
## What's Changed
- fix: PDF token estimation (#16494) by @korade-krushna in
[#16527](https://github.com/google-gemini/gemini-cli/pull/16527)
- chore(release): bump version to 0.26.0-nightly.20260114.bb6c57414 by
- feat(core): improve activate_skill tool and use lowercase XML tags by
@NTaylorMullen in
[#16009](https://github.com/google-gemini/gemini-cli/pull/16009)
- Add initiation method telemetry property by @gundermanc in
[#15818](https://github.com/google-gemini/gemini-cli/pull/15818)
- chore(release): bump version to 0.25.0-nightly.20260107.59a18e710 by
@gemini-cli-robot in
[#16604](https://github.com/google-gemini/gemini-cli/pull/16604)
- docs: clarify F12 to open debug console by @jackwotherspoon in
[#16570](https://github.com/google-gemini/gemini-cli/pull/16570)
- docs: Remove .md extension from internal links in architecture.md by
@medic-code in
[#12899](https://github.com/google-gemini/gemini-cli/pull/12899)
- Add an experimental setting for extension config by @chrstnb in
[#16506](https://github.com/google-gemini/gemini-cli/pull/16506)
- feat: add Rewind Confirmation dialog and Rewind Viewer component by @Adib234
in [#15717](https://github.com/google-gemini/gemini-cli/pull/15717)
- fix(a2a): Don't throw errors for GeminiEventType Retry and InvalidStream. by
@ehedlund in [#16541](https://github.com/google-gemini/gemini-cli/pull/16541)
- prefactor: add rootCommands as array so it can be used for policy parsing by
[#16048](https://github.com/google-gemini/gemini-cli/pull/16048)
- Hx support by @kevinfjiang in
[#16032](https://github.com/google-gemini/gemini-cli/pull/16032)
- [Skills] Foundation: Centralize management logic and feedback rendering by
@NTaylorMullen in
[#15952](https://github.com/google-gemini/gemini-cli/pull/15952)
- Introduce GEMINI_CLI_HOME for strict test isolation by @NTaylorMullen in
[#15907](https://github.com/google-gemini/gemini-cli/pull/15907)
- [Skills] Multi-scope skill enablement and shadowing fix by @NTaylorMullen in
[#15953](https://github.com/google-gemini/gemini-cli/pull/15953)
- policy: extract legacy policy from core tool scheduler to policy engine by
@abhipatel12 in
[#16640](https://github.com/google-gemini/gemini-cli/pull/16640)
- remove unnecessary \x7f key bindings by @scidomino in
[#16646](https://github.com/google-gemini/gemini-cli/pull/16646)
- docs(skills): use body-file in pr-creator skill for better reliability by
@abhipatel12 in
[#16642](https://github.com/google-gemini/gemini-cli/pull/16642)
- chore(automation): recursive labeling for workstream descendants by @bdmorgan
in [#16609](https://github.com/google-gemini/gemini-cli/pull/16609)
- feat: introduce 'skill-creator' built-in skill and CJS management tools by
[#15902](https://github.com/google-gemini/gemini-cli/pull/15902)
- Enhance TestRig with process management and timeouts by @NTaylorMullen in
[#15908](https://github.com/google-gemini/gemini-cli/pull/15908)
- Update troubleshooting doc for UNABLE_TO_GET_ISSUER_CERT_LOCALLY by @sehoon38
in [#16069](https://github.com/google-gemini/gemini-cli/pull/16069)
- Add keytar to dependencies by @chrstnb in
[#15928](https://github.com/google-gemini/gemini-cli/pull/15928)
- Simplify extension settings command by @chrstnb in
[#16001](https://github.com/google-gemini/gemini-cli/pull/16001)
- feat(admin): implement extensions disabled by @skeshive in
[#16024](https://github.com/google-gemini/gemini-cli/pull/16024)
- Core data structure updates for Rewind functionality by @Adib234 in
[#15714](https://github.com/google-gemini/gemini-cli/pull/15714)
- feat(hooks): simplify hook firing with HookSystem wrapper methods by @ved015
in [#15982](https://github.com/google-gemini/gemini-cli/pull/15982)
- Add exp.gws_experiment field to LogEventEntry by @gsquared94 in
[#16062](https://github.com/google-gemini/gemini-cli/pull/16062)
- Revert "feat(admin): implement extensions disabled" by @chrstnb in
[#16082](https://github.com/google-gemini/gemini-cli/pull/16082)
- feat(core): Decouple enabling hooks UI from subsystem. by @joshualitt in
[#16074](https://github.com/google-gemini/gemini-cli/pull/16074)
- docs: add docs for hooks + extensions by @abhipatel12 in
[#16073](https://github.com/google-gemini/gemini-cli/pull/16073)
- feat(core): Preliminary changes for subagent model routing. by @joshualitt in
[#16035](https://github.com/google-gemini/gemini-cli/pull/16035)
- Optimize CI workflow: Parallelize jobs and cache linters by @NTaylorMullen in
[#16054](https://github.com/google-gemini/gemini-cli/pull/16054)
- Add option to fallback for capacity errors in ProQuotaDi… by @sehoon38 in
[#16050](https://github.com/google-gemini/gemini-cli/pull/16050)
- feat: add confirmation details support + jsonrpc vs http rest support by
@adamfweidman in
[#16079](https://github.com/google-gemini/gemini-cli/pull/16079)
- fix(workflows): fix and limit labels for pr-triage.sh script by @jacob314 in
[#16096](https://github.com/google-gemini/gemini-cli/pull/16096)
- Fix and rename introspection agent -> cli help agent by @scidomino in
[#16097](https://github.com/google-gemini/gemini-cli/pull/16097)
- Docs: Changelogs update 20260105 by @jkcinouye in
[#15937](https://github.com/google-gemini/gemini-cli/pull/15937)
- enable cli_help agent by default by @scidomino in
[#16100](https://github.com/google-gemini/gemini-cli/pull/16100)
- Optimize json-output tests with mock responses by @NTaylorMullen in
[#16102](https://github.com/google-gemini/gemini-cli/pull/16102)
- Fix CI for forks by @scidomino in
[#16113](https://github.com/google-gemini/gemini-cli/pull/16113)
- Reduce nags about PRs that reference issues but don't fix them. by @jacob314
in [#16112](https://github.com/google-gemini/gemini-cli/pull/16112)
- feat(cli): add filepath autosuggestion after slash commands by @jasmeetsb in
[#14738](https://github.com/google-gemini/gemini-cli/pull/14738)
- Add upgrade option for paid users by @cayden-google in
[#15978](https://github.com/google-gemini/gemini-cli/pull/15978)
- [Skills] UX Polishing: Transparent feedback and CLI refinements by
@NTaylorMullen in
[#16394](https://github.com/google-gemini/gemini-cli/pull/16394)
- chore(automation): remove automated PR size and complexity labeler by
@bdmorgan in [#16648](https://github.com/google-gemini/gemini-cli/pull/16648)
- refactor(skills): replace 'project' with 'workspace' scope by @NTaylorMullen
in [#16380](https://github.com/google-gemini/gemini-cli/pull/16380)
- Docs: Update release notes for 1/13/2026 by @jkcinouye in
[#16583](https://github.com/google-gemini/gemini-cli/pull/16583)
- Simplify paste handling by @scidomino in
[#16654](https://github.com/google-gemini/gemini-cli/pull/16654)
- chore(automation): improve scheduled issue triage discovery and throughput by
@bdmorgan in [#16652](https://github.com/google-gemini/gemini-cli/pull/16652)
- fix(acp): run exit cleanup when stdin closes by @codefromthecrypt in
[#14953](https://github.com/google-gemini/gemini-cli/pull/14953)
- feat(scheduler): add types needed for event driven scheduler by @abhipatel12
in [#16641](https://github.com/google-gemini/gemini-cli/pull/16641)
- Remove unused rewind key binding by @scidomino in
[#16659](https://github.com/google-gemini/gemini-cli/pull/16659)
- Remove sequence binding by @scidomino in
[#16664](https://github.com/google-gemini/gemini-cli/pull/16664)
- feat(cli): undeprecate the --prompt flag by @alexaustin007 in
[#13981](https://github.com/google-gemini/gemini-cli/pull/13981)
- chore: update dependabot configuration by @cosmopax in
[#13507](https://github.com/google-gemini/gemini-cli/pull/13507)
- feat(config): add 'auto' alias for default model selection by @sehoon38 in
[#16661](https://github.com/google-gemini/gemini-cli/pull/16661)
- Enable & disable agents by @sehoon38 in
[#16225](https://github.com/google-gemini/gemini-cli/pull/16225)
- cleanup: Improve keybindings by @scidomino in
[#16672](https://github.com/google-gemini/gemini-cli/pull/16672)
- Add timeout for shell-utils to prevent hangs. by @jacob314 in
[#16667](https://github.com/google-gemini/gemini-cli/pull/16667)
- feat(plan): add experimental plan flag by @jerop in
[#16650](https://github.com/google-gemini/gemini-cli/pull/16650)
- feat(cli): add security consent prompts for skill installation by
[#15954](https://github.com/google-gemini/gemini-cli/pull/15954)
- Polish: Move 'Failed to load skills' warning to debug logs by @NTaylorMullen
in [#16142](https://github.com/google-gemini/gemini-cli/pull/16142)
- feat(cli): export chat history in /bug and prefill GitHub issue by
@NTaylorMullen in
[#16549](https://github.com/google-gemini/gemini-cli/pull/16549)
- fix: replace 3 consecutive periods with ellipsis character by @Vist233 in
[#16587](https://github.com/google-gemini/gemini-cli/pull/16587)
- chore(automation): ensure status/need-triage is applied and never cleared
automatically by @bdmorgan in
[#16657](https://github.com/google-gemini/gemini-cli/pull/16657)
- fix: Handle colons in skill description frontmatter by @maru0804 in
[#16345](https://github.com/google-gemini/gemini-cli/pull/16345)
- refactor(core): harden skill frontmatter parsing by @NTaylorMullen in
[#16705](https://github.com/google-gemini/gemini-cli/pull/16705)
- feat(skills): add conflict detection and warnings for skill overrides by
[#16115](https://github.com/google-gemini/gemini-cli/pull/16115)
- bug(core): fix issue with overrides to bases. by @joshualitt in
[#15255](https://github.com/google-gemini/gemini-cli/pull/15255)
- enableInteractiveShell for external tooling relying on a2a server by
@DavidAPierce in
[#16080](https://github.com/google-gemini/gemini-cli/pull/16080)
- Reapply "feat(admin): implement extensions disabled" (#16082) by @skeshive in
[#16109](https://github.com/google-gemini/gemini-cli/pull/16109)
- bug(core): Fix spewie getter in hookTranslator.ts by @joshualitt in
[#16108](https://github.com/google-gemini/gemini-cli/pull/16108)
- feat(hooks): add mcp_context to BeforeTool and AfterTool hook inputs by @vrv
in [#15656](https://github.com/google-gemini/gemini-cli/pull/15656)
- Add extension linking capabilities in cli by @kevinjwang1 in
[#16040](https://github.com/google-gemini/gemini-cli/pull/16040)
- Update the page's title to be consistent and show in site. by @kschaab in
[#16174](https://github.com/google-gemini/gemini-cli/pull/16174)
- docs: correct typo in bufferFastReturn JSDoc ("accomodate" → "accommodate") by
@minglu7 in [#16056](https://github.com/google-gemini/gemini-cli/pull/16056)
- fix: typo in MCP servers settings description by @alphanota in
[#15929](https://github.com/google-gemini/gemini-cli/pull/15929)
- fix: yolo should auto allow redirection by @abhipatel12 in
[#16183](https://github.com/google-gemini/gemini-cli/pull/16183)
- fix(cli): disableYoloMode shouldn't enforce default approval mode against args
by @psinha40898 in
[#16155](https://github.com/google-gemini/gemini-cli/pull/16155)
- feat: add native Sublime Text support to IDE detection by @phreakocious in
[#16083](https://github.com/google-gemini/gemini-cli/pull/16083)
- refactor(core): extract ToolModificationHandler from scheduler by @abhipatel12
in [#16118](https://github.com/google-gemini/gemini-cli/pull/16118)
- Add support for Antigravity terminal in terminal setup utility by @raky291 in
[#16051](https://github.com/google-gemini/gemini-cli/pull/16051)
- feat(core): Wire up model routing to subagents. by @joshualitt in
[#16043](https://github.com/google-gemini/gemini-cli/pull/16043)
- feat(cli): add /agents slash command to list available agents by @adamfweidman
in [#16182](https://github.com/google-gemini/gemini-cli/pull/16182)
- docs(cli): fix includeDirectories nesting in configuration.md by @maru0804 in
[#15067](https://github.com/google-gemini/gemini-cli/pull/15067)
- feat: implement file system reversion utilities for rewind by @Adib234 in
[#15715](https://github.com/google-gemini/gemini-cli/pull/15715)
- Always enable redaction in GitHub actions. by @gundermanc in
[#16200](https://github.com/google-gemini/gemini-cli/pull/16200)
- fix: remove unsupported 'enabled' key from workflow config by @Han5991 in
[#15611](https://github.com/google-gemini/gemini-cli/pull/15611)
- docs: Remove redundant and duplicate documentation files by @liqzheng in
[#14699](https://github.com/google-gemini/gemini-cli/pull/14699)
- docs: shorten run command and use published version by @dsherret in
[#16172](https://github.com/google-gemini/gemini-cli/pull/16172)
- test(command-registry): increase initialization test timeout by @wszqkzqk in
[#15979](https://github.com/google-gemini/gemini-cli/pull/15979)
- Ensure TERM is set to xterm-256color by @falouu in
[#15828](https://github.com/google-gemini/gemini-cli/pull/15828)
- The telemetry.js script should handle paths that contain spaces by @JohnJAS in
[#12078](https://github.com/google-gemini/gemini-cli/pull/12078)
- ci: guard links workflow from running on forks by @wtanaka in
[#15461](https://github.com/google-gemini/gemini-cli/pull/15461)
- ci: guard nightly release workflow from running on forks by @wtanaka in
[#15463](https://github.com/google-gemini/gemini-cli/pull/15463)
- Support @ suggestions for subagenets by @sehoon38 in
[#16201](https://github.com/google-gemini/gemini-cli/pull/16201)
- feat(hooks): Support explicit stop and block execution control in model hooks
by @SandyTao520 in
[#15947](https://github.com/google-gemini/gemini-cli/pull/15947)
- Refine Gemini 3 system instructions to reduce model verbosity by
@NTaylorMullen in
[#16709](https://github.com/google-gemini/gemini-cli/pull/16709)
- feat(scheduler): add SchedulerStateManager for reactive tool state by
@abhipatel12 in
[#16651](https://github.com/google-gemini/gemini-cli/pull/16651)
- chore(automation): enforce 'help wanted' label permissions and update
guidelines by @bdmorgan in
[#16707](https://github.com/google-gemini/gemini-cli/pull/16707)
- fix(core): resolve circular dependency via tsconfig paths by @sehoon38 in
[#16730](https://github.com/google-gemini/gemini-cli/pull/16730)
- chore/release: bump version to 0.26.0-nightly.20260115.6cb3ae4e0 by
@gemini-cli-robot in
[#16738](https://github.com/google-gemini/gemini-cli/pull/16738)
- fix(automation): correct status/need-issue label matching wildcard by
@bdmorgan in [#16727](https://github.com/google-gemini/gemini-cli/pull/16727)
- fix(automation): prevent label-enforcer loop by ignoring all bots by @bdmorgan
in [#16746](https://github.com/google-gemini/gemini-cli/pull/16746)
- Add links to supported locations and minor fixes by @g-samroberts in
[#16476](https://github.com/google-gemini/gemini-cli/pull/16476)
- feat(policy): add source tracking to policy rules by @allenhutchison in
[#16670](https://github.com/google-gemini/gemini-cli/pull/16670)
- feat(automation): enforce '🔒 maintainer only' and fix bot loop by @bdmorgan
in [#16751](https://github.com/google-gemini/gemini-cli/pull/16751)
- Make merged settings non-nullable and fix all lints related to that. by
@jacob314 in [#16647](https://github.com/google-gemini/gemini-cli/pull/16647)
- fix(core): prevent ModelInfo event emission on aborted signal by @sehoon38 in
[#16752](https://github.com/google-gemini/gemini-cli/pull/16752)
- Replace relative paths to fix website build by @chrstnb in
[#16755](https://github.com/google-gemini/gemini-cli/pull/16755)
- Restricting to localhost by @cocosheng-g in
[#16548](https://github.com/google-gemini/gemini-cli/pull/16548)
- fix(cli): add explicit dependency on color-convert by @sehoon38 in
[#16757](https://github.com/google-gemini/gemini-cli/pull/16757)
- fix(automation): robust label enforcement with permission checks by @bdmorgan
in [#16762](https://github.com/google-gemini/gemini-cli/pull/16762)
- fix(cli): prevent OOM crash by limiting file search traversal and adding
timeout by @galz10 in
[#16696](https://github.com/google-gemini/gemini-cli/pull/16696)
- fix(cli): safely handle /dev/tty access on macOS by @korade-krushna in
[#16531](https://github.com/google-gemini/gemini-cli/pull/16531)
- docs: clarify workspace test execution in GEMINI.md by @mattKorwel in
[#16764](https://github.com/google-gemini/gemini-cli/pull/16764)
- Add support for running available commands prior to MCP servers loading by
@Adib234 in [#15596](https://github.com/google-gemini/gemini-cli/pull/15596)
- feat(plan): add experimental 'plan' approval mode by @jerop in
[#16753](https://github.com/google-gemini/gemini-cli/pull/16753)
- feat(scheduler): add functional awaitConfirmation utility by @abhipatel12 in
[#16721](https://github.com/google-gemini/gemini-cli/pull/16721)
- fix(infra): update maintainer rollup label to 'workstream-rollup' by @bdmorgan
in [#16809](https://github.com/google-gemini/gemini-cli/pull/16809)
- fix(infra): use GraphQL to detect direct parents in rollup workflow by
@bdmorgan in [#16811](https://github.com/google-gemini/gemini-cli/pull/16811)
- chore(workflows): rename label-workstream-rollup workflow by @bdmorgan in
[#16818](https://github.com/google-gemini/gemini-cli/pull/16818)
- skip simple-mcp-server.test.ts by @scidomino in
[#16842](https://github.com/google-gemini/gemini-cli/pull/16842)
- Steer outer agent to use expert subagents when present by @gundermanc in
[#16763](https://github.com/google-gemini/gemini-cli/pull/16763)
- Fix race condition by awaiting scheduleToolCalls by @chrstnb in
[#16759](https://github.com/google-gemini/gemini-cli/pull/16759)
- cleanup: Organize key bindings by @scidomino in
[#16798](https://github.com/google-gemini/gemini-cli/pull/16798)
- feat(core): Add generalist agent. by @joshualitt in
[#16638](https://github.com/google-gemini/gemini-cli/pull/16638)
- perf(ui): optimize text buffer and highlighting for large inputs by
[#16139](https://github.com/google-gemini/gemini-cli/pull/16139)
- chore: clean up unused models and use consts by @sehoon38 in
[#16246](https://github.com/google-gemini/gemini-cli/pull/16246)
- Always enable bracketed paste by @scidomino in
[#16179](https://github.com/google-gemini/gemini-cli/pull/16179)
- refactor: migrate clearCommand hook calls to HookSystem by @ved015 in
[#16157](https://github.com/google-gemini/gemini-cli/pull/16157)
- refactor: migrate app containter hook calls to hook system by @ishaanxgupta in
[#16161](https://github.com/google-gemini/gemini-cli/pull/16161)
- Show settings source in extensions lists by @chrstnb in
[#16207](https://github.com/google-gemini/gemini-cli/pull/16207)
- feat(skills): add pr-creator skill and enable skills by @NTaylorMullen in
[#16232](https://github.com/google-gemini/gemini-cli/pull/16232)
- fix: handle Shift+Space in Kitty keyboard protocol terminals by @tt-a1i in
[#15767](https://github.com/google-gemini/gemini-cli/pull/15767)
- feat(core, ui): Add /agents refresh command. by @joshualitt in
[#16204](https://github.com/google-gemini/gemini-cli/pull/16204)
- feat(core): add local experiments override via GEMINI_EXP by @kevin-ramdass in
[#16181](https://github.com/google-gemini/gemini-cli/pull/16181)
- feat(ui): reduce home directory warning noise and add opt-out setting by
@NTaylorMullen in
[#16782](https://github.com/google-gemini/gemini-cli/pull/16782)
- fix(core): fix PTY descriptor shell leak by @galz10 in
[#16773](https://github.com/google-gemini/gemini-cli/pull/16773)
- feat(plan): enforce strict read-only policy and halt execution on violation by
@jerop in [#16849](https://github.com/google-gemini/gemini-cli/pull/16849)
- remove need-triage label from bug_report template by @sehoon38 in
[#16864](https://github.com/google-gemini/gemini-cli/pull/16864)
- fix(core): truncate large telemetry log entries by @sehoon38 in
[#16769](https://github.com/google-gemini/gemini-cli/pull/16769)
- docs(extensions): add Agent Skills support and mark feature as experimental by
@NTaylorMullen in
[#16859](https://github.com/google-gemini/gemini-cli/pull/16859)
- fix(core): surface warnings for invalid hook event names in configuration
(#16788) by @sehoon38 in
[#16873](https://github.com/google-gemini/gemini-cli/pull/16873)
- feat(plan): remove read_many_files from approval mode policies by @jerop in
[#16876](https://github.com/google-gemini/gemini-cli/pull/16876)
- feat(admin): implement admin controls polling and restart prompt by @skeshive
in [#16627](https://github.com/google-gemini/gemini-cli/pull/16627)
- Remove LRUCache class migrating to mnemoist by @jacob314 in
[#16872](https://github.com/google-gemini/gemini-cli/pull/16872)
- feat(settings): rename negative settings to positive naming (disable* ->
enable*) by @afarber in
[#14142](https://github.com/google-gemini/gemini-cli/pull/14142)
- refactor(cli): unify shell confirmation dialogs by @NTaylorMullen in
[#16828](https://github.com/google-gemini/gemini-cli/pull/16828)
- feat(agent): enable agent skills by default by @NTaylorMullen in
[#16736](https://github.com/google-gemini/gemini-cli/pull/16736)
- refactor(core): foundational truncation refactoring and token estimation
optimization by @NTaylorMullen in
[#16824](https://github.com/google-gemini/gemini-cli/pull/16824)
- fix(hooks): enable /hooks disable to reliably stop single hooks by
@abhipatel12 in
[#16804](https://github.com/google-gemini/gemini-cli/pull/16804)
- Don't commit unless user asks us to. by @gundermanc in
[#16902](https://github.com/google-gemini/gemini-cli/pull/16902)
- chore: remove a2a-adapter and bump @a2a-js/sdk to 0.3.8 by @adamfweidman in
[#16800](https://github.com/google-gemini/gemini-cli/pull/16800)
- fix: Show experiment values in settings UI for compressionThreshold by
@ishaanxgupta in
[#16267](https://github.com/google-gemini/gemini-cli/pull/16267)
- feat(cli): replace relative keyboard shortcuts link with web URL by
@imaliabbas in
[#16479](https://github.com/google-gemini/gemini-cli/pull/16479)
- fix(core): resolve PKCE length issue and stabilize OAuth redirect port by
@sehoon38 in [#16815](https://github.com/google-gemini/gemini-cli/pull/16815)
- Delete rewind documentation for now by @Adib234 in
[#16932](https://github.com/google-gemini/gemini-cli/pull/16932)
- Stabilize skill-creator CI and package format by @NTaylorMullen in
[#17001](https://github.com/google-gemini/gemini-cli/pull/17001)
- Stabilize the git evals by @gundermanc in
[#16989](https://github.com/google-gemini/gemini-cli/pull/16989)
- fix(core): attempt compression before context overflow check by @NTaylorMullen
in [#16914](https://github.com/google-gemini/gemini-cli/pull/16914)
- Fix inverted logic. by @gundermanc in
[#17007](https://github.com/google-gemini/gemini-cli/pull/17007)
- chore(scripts): add duplicate issue closer script and fix lint errors by
@bdmorgan in [#16997](https://github.com/google-gemini/gemini-cli/pull/16997)
- docs: update README and config guide to reference Gemini 3 by @JayadityaGit in
[#15806](https://github.com/google-gemini/gemini-cli/pull/15806)
- fix(cli): correct Homebrew installation detection by @kij in
[#14727](https://github.com/google-gemini/gemini-cli/pull/14727)
- Demote git evals to nightly run. by @gundermanc in
[#17030](https://github.com/google-gemini/gemini-cli/pull/17030)
- fix(cli): use OSC-52 clipboard copy in Windows Terminal by @Thomas-Shephard in
[#16920](https://github.com/google-gemini/gemini-cli/pull/16920)
- Fix: Process all parts in response chunks when thought is first by @pyrytakala
in [#13539](https://github.com/google-gemini/gemini-cli/pull/13539)
- fix(automation): fix jq quoting error in pr-triage.sh by @Kimsoo0119 in
[#16958](https://github.com/google-gemini/gemini-cli/pull/16958)
- refactor(core): decouple scheduler into orchestration, policy, and
confirmation by @abhipatel12 in
[#16895](https://github.com/google-gemini/gemini-cli/pull/16895)
- feat: add /introspect slash command by @NTaylorMullen in
[#17048](https://github.com/google-gemini/gemini-cli/pull/17048)
- refactor(cli): centralize tool mapping and decouple legacy scheduler by
@abhipatel12 in
[#17044](https://github.com/google-gemini/gemini-cli/pull/17044)
- fix(ui): ensure rationale renders before tool calls by @NTaylorMullen in
[#17043](https://github.com/google-gemini/gemini-cli/pull/17043)
- fix(workflows): use author_association for maintainer check by @bdmorgan in
[#17060](https://github.com/google-gemini/gemini-cli/pull/17060)
- fix return type of fireSessionStartEvent to defaultHookOutput by @ved015 in
[#16833](https://github.com/google-gemini/gemini-cli/pull/16833)
- feat(cli): add experiment gate for event-driven scheduler by @abhipatel12 in
[#17055](https://github.com/google-gemini/gemini-cli/pull/17055)
- feat(core): improve shell redirection transparency and security by
@NTaylorMullen in
[#16486](https://github.com/google-gemini/gemini-cli/pull/16486)
- fix(core): deduplicate ModelInfo emission in GeminiClient by @NTaylorMullen in
[#17075](https://github.com/google-gemini/gemini-cli/pull/17075)
- docs(themes): remove unsupported DiffModified color key by @jw409 in
[#17073](https://github.com/google-gemini/gemini-cli/pull/17073)
- fix: update currentSequenceModel when modelChanged by @adamfweidman in
[#17051](https://github.com/google-gemini/gemini-cli/pull/17051)
- feat(core): enhanced anchored iterative context compression with
self-verification by @rmedranollamas in
[#15710](https://github.com/google-gemini/gemini-cli/pull/15710)
- Fix mcp instructions by @chrstnb in
[#16439](https://github.com/google-gemini/gemini-cli/pull/16439)
- [A2A] Disable checkpointing if git is not installed by @cocosheng-g in
[#16896](https://github.com/google-gemini/gemini-cli/pull/16896)
- feat(admin): set admin.skills.enabled based on advancedFeaturesEnabled setting
by @skeshive in
[#17095](https://github.com/google-gemini/gemini-cli/pull/17095)
- Test coverage for hook exit code cases by @gundermanc in
[#17041](https://github.com/google-gemini/gemini-cli/pull/17041)
- Revert "Revert "Update extension examples"" by @chrstnb in
[#16445](https://github.com/google-gemini/gemini-cli/pull/16445)
- fix(core): Provide compact, actionable errors for agent delegation failures by
[#16229](https://github.com/google-gemini/gemini-cli/pull/16229)
- refactor: migrate chatCompressionService to use HookSystem by @ved015 in
[#16259](https://github.com/google-gemini/gemini-cli/pull/16259)
- fix: properly use systemMessage for hooks in UI by @jackwotherspoon in
[#16250](https://github.com/google-gemini/gemini-cli/pull/16250)
- Infer modifyOtherKeys support by @scidomino in
[#16270](https://github.com/google-gemini/gemini-cli/pull/16270)
- feat(core): Cache ignore instances for performance by @EricRahm in
[#16185](https://github.com/google-gemini/gemini-cli/pull/16185)
- feat: apply remote admin settings (no-op) by @skeshive in
[#16106](https://github.com/google-gemini/gemini-cli/pull/16106)
- Autogenerate docs/cli/settings.md docs/getting-started/configuration.md was
already autogenerated but settings.md was not. by @jacob314 in
[#14408](https://github.com/google-gemini/gemini-cli/pull/14408)
- refactor(config): remove legacy V1 settings migration logic by @galz10 in
[#16252](https://github.com/google-gemini/gemini-cli/pull/16252)
- Fix an issue where the agent stops prematurely by @gundermanc in
[#16269](https://github.com/google-gemini/gemini-cli/pull/16269)
- Update system prompt to prefer non-interactive commands by @NTaylorMullen in
[#16117](https://github.com/google-gemini/gemini-cli/pull/16117)
- Update ink version to 6.4.7 by @jacob314 in
[#16284](https://github.com/google-gemini/gemini-cli/pull/16284)
- Support for Built-in Agent Skills by @NTaylorMullen in
[#16045](https://github.com/google-gemini/gemini-cli/pull/16045)
- fix(skills): remove "Restart required" message from non-interactive commands
by @NTaylorMullen in
[#16307](https://github.com/google-gemini/gemini-cli/pull/16307)
- remove unused sessionHookTriggers and exports by @ved015 in
[#16324](https://github.com/google-gemini/gemini-cli/pull/16324)
- Triage action cleanup by @bdmorgan in
[#16319](https://github.com/google-gemini/gemini-cli/pull/16319)
- fix: Add event-driven trigger to issue triage workflow by @bdmorgan in
[#16334](https://github.com/google-gemini/gemini-cli/pull/16334)
- fix(workflows): resolve triage workflow failures and actionlint errors by
@bdmorgan in [#16338](https://github.com/google-gemini/gemini-cli/pull/16338)
- docs: add note about experimental hooks by @abhipatel12 in
[#16337](https://github.com/google-gemini/gemini-cli/pull/16337)
- feat(cli): implement passive activity logger for session analysis by
@SandyTao520 in
[#16493](https://github.com/google-gemini/gemini-cli/pull/16493)
- fix: migrate BeforeModel and AfterModel hooks to HookSystem by @ved015 in
[#16599](https://github.com/google-gemini/gemini-cli/pull/16599)
- feat(admin): apply admin settings to gemini skills/mcp/extensions commands by
@skeshive in [#17102](https://github.com/google-gemini/gemini-cli/pull/17102)
- fix(core): update telemetry token count after session resume by @psinha40898
in [#15491](https://github.com/google-gemini/gemini-cli/pull/15491)
- Demote the subagent test to nightly by @gundermanc in
[#17105](https://github.com/google-gemini/gemini-cli/pull/17105)
- feat(plan): telemetry to track adoption and usage of plan mode by @Adib234 in
[#16863](https://github.com/google-gemini/gemini-cli/pull/16863)
- feat: Add flash lite utility fallback chain by @adamfweidman in
[#17056](https://github.com/google-gemini/gemini-cli/pull/17056)
- Fixes Windows crash: "Cannot resize a pty that has already exited" by @dzammit
in [#15757](https://github.com/google-gemini/gemini-cli/pull/15757)
- feat(core): Add initial eval for generalist agent. by @joshualitt in
[#16856](https://github.com/google-gemini/gemini-cli/pull/16856)
- feat(core): unify agent enabled and disabled flags by @SandyTao520 in
[#17127](https://github.com/google-gemini/gemini-cli/pull/17127)
- fix(core): resolve auto model in default strategy by @sehoon38 in
[#17116](https://github.com/google-gemini/gemini-cli/pull/17116)
- docs: update project context and pr-creator workflow by @NTaylorMullen in
[#17119](https://github.com/google-gemini/gemini-cli/pull/17119)
- fix(cli): send gemini-cli version as mcp client version by @dsp in
[#13407](https://github.com/google-gemini/gemini-cli/pull/13407)
- fix(cli): resolve Ctrl+Enter and Ctrl+J newline issues by @imadraude in
[#17021](https://github.com/google-gemini/gemini-cli/pull/17021)
- Remove missing sidebar item by @chrstnb in
[#17145](https://github.com/google-gemini/gemini-cli/pull/17145)
- feat(core): Ensure all properties in hooks object are event names. by
@joshualitt in
[#16870](https://github.com/google-gemini/gemini-cli/pull/16870)
- fix(cli): fix newline support broken in previous PR by @scidomino in
[#17159](https://github.com/google-gemini/gemini-cli/pull/17159)
- Add interactive ValidationDialog for handling 403 VALIDATION_REQUIRED errors.
by @gsquared94 in
[#16231](https://github.com/google-gemini/gemini-cli/pull/16231)
- Add Esc-Esc to clear prompt when it's not empty by @Adib234 in
[#17131](https://github.com/google-gemini/gemini-cli/pull/17131)
- Avoid spurious warnings about unexpected renders triggered by appEvents and
coreEvents. by @jacob314 in
[#17160](https://github.com/google-gemini/gemini-cli/pull/17160)
- fix(cli): resolve home/end keybinding conflict by @scidomino in
[#17124](https://github.com/google-gemini/gemini-cli/pull/17124)
- fix(cli): display 'http' type on mcp list by @pamanta in
[#16915](https://github.com/google-gemini/gemini-cli/pull/16915)
- fix bad fallback logic external editor logic by @scidomino in
[#17166](https://github.com/google-gemini/gemini-cli/pull/17166)
- Fix bug where System scopes weren't migrated. by @jacob314 in
[#17174](https://github.com/google-gemini/gemini-cli/pull/17174)
- Fix mcp tool lookup in tool registry by @werdnum in
[#17054](https://github.com/google-gemini/gemini-cli/pull/17054)
[#15829](https://github.com/google-gemini/gemini-cli/pull/15829)
- feat(cli): add /chat debug command for nightly builds by @abhipatel12 in
[#16339](https://github.com/google-gemini/gemini-cli/pull/16339)
- style: format pr-creator skill by @NTaylorMullen in
[#16381](https://github.com/google-gemini/gemini-cli/pull/16381)
- feat(cli): Hooks enable-all/disable-all feature with dynamic status by
@AbdulTawabJuly in
[#15552](https://github.com/google-gemini/gemini-cli/pull/15552)
- fix(core): ensure silent local subagent delegation while allowing remote
confirmation by @adamfweidman in
[#16395](https://github.com/google-gemini/gemini-cli/pull/16395)
- Markdown w/ Frontmatter Agent Parser by @sehoon38 in
[#16094](https://github.com/google-gemini/gemini-cli/pull/16094)
- Fix crash on unicode character by @chrstnb in
[#16420](https://github.com/google-gemini/gemini-cli/pull/16420)
- Attempt to resolve OOM w/ useMemo on history items by @chrstnb in
[#16424](https://github.com/google-gemini/gemini-cli/pull/16424)
- fix(core): ensure sub-agent schema and prompt refresh during runtime by
@adamfweidman in
[#16409](https://github.com/google-gemini/gemini-cli/pull/16409)
- Update extension examples by @chrstnb in
[#16274](https://github.com/google-gemini/gemini-cli/pull/16274)
- revert the change that was recently added from a fix by @sehoon38 in
[#16390](https://github.com/google-gemini/gemini-cli/pull/16390)
- Add other hook wrapper methods to hooksystem by @ved015 in
[#16361](https://github.com/google-gemini/gemini-cli/pull/16361)
- feat: introduce useRewindLogic hook for conversation history navigation by
@Adib234 in [#15716](https://github.com/google-gemini/gemini-cli/pull/15716)
- docs: Fix formatting issue in memport documentation by @wanglc02 in
[#14774](https://github.com/google-gemini/gemini-cli/pull/14774)
- fix(policy): enhance shell command safety and parsing by @allenhutchison in
[#15034](https://github.com/google-gemini/gemini-cli/pull/15034)
- fix(core): avoid 'activate_skill' re-registration warning by @NTaylorMullen in
[#16398](https://github.com/google-gemini/gemini-cli/pull/16398)
- perf(workflows): optimize PR triage script for faster execution by @bdmorgan
in [#16355](https://github.com/google-gemini/gemini-cli/pull/16355)
- feat(admin): prompt user to restart the CLI if they change auth to oauth
mid-session or don't have auth type selected at start of session by @skeshive
in [#16426](https://github.com/google-gemini/gemini-cli/pull/16426)
- Update cli-help agent's system prompt in sub-agents section by @sehoon38 in
[#16441](https://github.com/google-gemini/gemini-cli/pull/16441)
- Revert "Update extension examples" by @chrstnb in
[#16442](https://github.com/google-gemini/gemini-cli/pull/16442)
- Fix: add back fastreturn support by @scidomino in
[#16440](https://github.com/google-gemini/gemini-cli/pull/16440)
- feat(a2a): Introduce /memory command for a2a server by @cocosheng-g in
[#14456](https://github.com/google-gemini/gemini-cli/pull/14456)
- docs: fix broken internal link by using relative path by @Gong-Mi in
[#15371](https://github.com/google-gemini/gemini-cli/pull/15371)
- migrate yolo/auto-edit keybindings by @scidomino in
[#16457](https://github.com/google-gemini/gemini-cli/pull/16457)
- feat(cli): add install and uninstall commands for skills by @NTaylorMullen in
[#16377](https://github.com/google-gemini/gemini-cli/pull/16377)
- feat(ui): use Tab to switch focus between shell and input by @jacob314 in
[#14332](https://github.com/google-gemini/gemini-cli/pull/14332)
- feat(core): support shipping built-in skills with the CLI by @NTaylorMullen in
[#16300](https://github.com/google-gemini/gemini-cli/pull/16300)
- Collect hardware details telemetry. by @gundermanc in
[#16119](https://github.com/google-gemini/gemini-cli/pull/16119)
- feat(agents): improve UI feedback and parser reliability by @NTaylorMullen in
[#16459](https://github.com/google-gemini/gemini-cli/pull/16459)
- Migrate keybindings by @scidomino in
[#16460](https://github.com/google-gemini/gemini-cli/pull/16460)
- feat(cli): cleanup activity logs alongside session files by @SandyTao520 in
[#16399](https://github.com/google-gemini/gemini-cli/pull/16399)
- feat(cli): implement dynamic terminal tab titles for CLI status by
@NTaylorMullen in
[#16378](https://github.com/google-gemini/gemini-cli/pull/16378)
- feat(core): add disableLLMCorrection setting to skip auto-correction in edit
tools by @SandyTao520 in
[#16000](https://github.com/google-gemini/gemini-cli/pull/16000)
- fix: Set both tab and window title instead of just window title by
@NTaylorMullen in
[#16464](https://github.com/google-gemini/gemini-cli/pull/16464)
- fix(policy): ensure MCP policies match unqualified names in non-interactive
mode by @NTaylorMullen in
[#16490](https://github.com/google-gemini/gemini-cli/pull/16490)
- fix(cli): refine 'Action Required' indicator and focus hints by @NTaylorMullen
in [#16497](https://github.com/google-gemini/gemini-cli/pull/16497)
- Refactor beforeAgent and afterAgent hookEvents to follow desired output by
@ved015 in [#16495](https://github.com/google-gemini/gemini-cli/pull/16495)
- feat(agents): clarify mandatory YAML frontmatter for sub-agents by
@NTaylorMullen in
[#16515](https://github.com/google-gemini/gemini-cli/pull/16515)
- docs(telemetry): add Google Cloud Monitoring dashboard documentation by @jerop
in [#16520](https://github.com/google-gemini/gemini-cli/pull/16520)
- Implement support for subagents as extensions. by @gundermanc in
[#16473](https://github.com/google-gemini/gemini-cli/pull/16473)
- refactor: make baseTimestamp optional in addItem and remove redundant calls by
@sehoon38 in [#16471](https://github.com/google-gemini/gemini-cli/pull/16471)
- Improve key binding names and descriptions by @scidomino in
[#16529](https://github.com/google-gemini/gemini-cli/pull/16529)
- feat(core, cli): Add support for agents in settings.json. by @joshualitt in
[#16433](https://github.com/google-gemini/gemini-cli/pull/16433)
- fix(cli): fix 'gemini skills install' unknown argument error by @NTaylorMullen
in [#16537](https://github.com/google-gemini/gemini-cli/pull/16537)
- chore(ui): optimize AgentsStatus layout with dense list style and group
separation by @adamfweidman in
[#16545](https://github.com/google-gemini/gemini-cli/pull/16545)
- fix(cli): allow @ file selector on slash command lines by @galz10 in
[#16370](https://github.com/google-gemini/gemini-cli/pull/16370)
- fix(ui): resolve sticky header regression in tool messages by @jacob314 in
[#16514](https://github.com/google-gemini/gemini-cli/pull/16514)
- feat(core): Align internal agent settings with configs exposed through
settings.json by @joshualitt in
[#16458](https://github.com/google-gemini/gemini-cli/pull/16458)
- fix(cli): copy uses OSC52 only in SSH/WSL by @assagman in
[#16554](https://github.com/google-gemini/gemini-cli/pull/16554)
- docs(skills): clarify skill directory structure and file location by
@NTaylorMullen in
[#16532](https://github.com/google-gemini/gemini-cli/pull/16532)
- Fix: make ctrl+x use preferred editor by @scidomino in
[#16556](https://github.com/google-gemini/gemini-cli/pull/16556)
- fix(core): Resolve race condition in tool response reporting by @abhipatel12
in [#16557](https://github.com/google-gemini/gemini-cli/pull/16557)
- feat(ui): highlight persist mode status in ModelDialog by @sehoon38 in
[#16483](https://github.com/google-gemini/gemini-cli/pull/16483)
- refactor: clean up A2A task output for users and LLMs by @adamfweidman in
[#16561](https://github.com/google-gemini/gemini-cli/pull/16561)
- feat(core/ui): enhance retry mechanism and UX by @sehoon38 in
[#16489](https://github.com/google-gemini/gemini-cli/pull/16489)
- Modernize MaxSizedBox to use and ResizeObservers by @jacob314 in
[#16565](https://github.com/google-gemini/gemini-cli/pull/16565)
- Behavioral evals framework. by @gundermanc in
[#16047](https://github.com/google-gemini/gemini-cli/pull/16047)
- Aggregate test results. by @gundermanc in
[#16581](https://github.com/google-gemini/gemini-cli/pull/16581)
- feat(admin): support admin-enforced settings for Agent Skills by
@NTaylorMullen in
[#16406](https://github.com/google-gemini/gemini-cli/pull/16406)
- fix(patch): cherry-pick cfdc4cf to release/v0.25.0-preview.0-pr-16759 to patch
version v0.25.0-preview.0 and create version 0.25.0-preview.1 by
@gemini-cli-robot in
[#16866](https://github.com/google-gemini/gemini-cli/pull/16866)
- Patch #16730 into v0.25.0 preview by @chrstnb in
[#16882](https://github.com/google-gemini/gemini-cli/pull/16882)
- fix(patch): cherry-pick 3b55581 to release/v0.25.0-preview.2-pr-16506 to patch
version v0.25.0-preview.2 and create version 0.25.0-preview.3 by
@gemini-cli-robot in
[#17098](https://github.com/google-gemini/gemini-cli/pull/17098)
**Full changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.25.2...v0.26.0
https://github.com/google-gemini/gemini-cli/compare/v0.24.5...v0.25.0
+309 -414
View File
@@ -1,6 +1,6 @@
# Preview release: Release v0.27.0-preview.0
# Preview release: Release v0.26.0-preview.0
Released: January 27, 2026
Released: January 21, 2026
Our preview release includes the latest, new, and experimental features. This
release may not be as stable as our [latest weekly release](latest.md).
@@ -13,425 +13,320 @@ npm install -g @google/gemini-cli@preview
## Highlights
- **Event-Driven Architecture:** The tool execution scheduler is now
event-driven, improving performance and reliability.
- **System Prompt Override:** Now supports dynamic variable substitution.
- **Rewind Command:** The `/rewind` command has been implemented.
- **Linux Clipboard:** Image pasting capabilities for Wayland and X11 on Linux.
- **Skills and Agents:** Improvements to the `activate_skill` tool and skill
management. Experimental Agent Skills support.
- **UI/UX:** Addition of a Rewind Confirmation dialog and Viewer component.
- **Extensions:** Experimental setting for extension configuration.
- **Bug Fixes and Stability:** PDF token estimation fix and improvements to
scheduled issue triage.
## What's Changed
- remove fireAgent and beforeAgent hook by @ishaanxgupta in
[#16919](https://github.com/google-gemini/gemini-cli/pull/16919)
- Remove unused modelHooks and toolHooks by @ved015 in
[#17115](https://github.com/google-gemini/gemini-cli/pull/17115)
- feat(cli): sanitize ANSI escape sequences in non-interactive output by
@sehoon38 in [#17172](https://github.com/google-gemini/gemini-cli/pull/17172)
- Update Attempt text to Retry when showing the retry happening to the … by
@sehoon38 in [#17178](https://github.com/google-gemini/gemini-cli/pull/17178)
- chore(skills): update pr-creator skill workflow by @sehoon38 in
[#17180](https://github.com/google-gemini/gemini-cli/pull/17180)
- feat(cli): implement event-driven tool execution scheduler by @abhipatel12 in
[#17078](https://github.com/google-gemini/gemini-cli/pull/17078)
- chore(release): bump version to 0.27.0-nightly.20260121.97aac696f by
- fix: PDF token estimation
([#16494](https://github.com/google-gemini/gemini-cli/pull/16494)) by
@korade-krushna in
[#16527](https://github.com/google-gemini/gemini-cli/pull/16527)
- chore(release): bump version to 0.26.0-nightly.20260114.bb6c57414 by
@gemini-cli-robot in
[#17181](https://github.com/google-gemini/gemini-cli/pull/17181)
- Remove other rewind reference in docs by @chrstnb in
[#17149](https://github.com/google-gemini/gemini-cli/pull/17149)
- feat(skills): add code-reviewer skill by @sehoon38 in
[#17187](httpshttps://github.com/google-gemini/gemini-cli/pull/17187)
- feat(plan): Extend Shift+Tab Mode Cycling to include Plan Mode by @Adib234 in
[#17177](https://github.com/google-gemini/gemini-cli/pull/17177)
- feat(plan): refactor TestRig and eval helper to support configurable approval
modes by @jerop in
[#17171](https://github.com/google-gemini/gemini-cli/pull/17171)
- feat(workflows): support recursive workstream labeling and new IDs by
@bdmorgan in [#17207](https://github.com/google-gemini/gemini-cli/pull/17207)
- Run evals for all models. by @gundermanc in
[#17123](https://github.com/google-gemini/gemini-cli/pull/17123)
- fix(github): improve label-workstream-rollup efficiency with GraphQL by
@bdmorgan in [#17217](https://github.com/google-gemini/gemini-cli/pull/17217)
- Docs: Update changelogs for v.0.25.0 and v0.26.0-preview.0 releases. by
@g-samroberts in
[#17215](https://github.com/google-gemini/gemini-cli/pull/17215)
- Migrate beforeTool and afterTool hooks to hookSystem by @ved015 in
[#17204](https://github.com/google-gemini/gemini-cli/pull/17204)
- fix(github): improve label-workstream-rollup efficiency and fix bugs by
@bdmorgan in [#17219](https://github.com/google-gemini/gemini-cli/pull/17219)
- feat(cli): improve skill enablement/disablement verbiage by @NTaylorMullen in
[#17192](https://github.com/google-gemini/gemini-cli/pull/17192)
- fix(admin): Ensure CLI commands run in non-interactive mode by @skeshive in
[#17218](https://github.com/google-gemini/gemini-cli/pull/17218)
- feat(core): support dynamic variable substitution in system prompt override by
@NTaylorMullen in
[#17042](https://github.com/google-gemini/gemini-cli/pull/17042)
- fix(core,cli): enable recursive directory access for by @galz10 in
[#17094](https://github.com/google-gemini/gemini-cli/pull/17094)
- Docs: Marking for experimental features by @jkcinouye in
[#16760](https://github.com/google-gemini/gemini-cli/pull/16760)
- Support command/ctrl/alt backspace correctly by @scidomino in
[#17175](https://github.com/google-gemini/gemini-cli/pull/17175)
- feat(plan): add approval mode instructions to system prompt by @jerop in
[#17151](https://github.com/google-gemini/gemini-cli/pull/17151)
- feat(core): enable disableLLMCorrection by default by @SandyTao520 in
[#17223](https://github.com/google-gemini/gemini-cli/pull/17223)
- Remove unused slug from sidebar by @chrstnb in
[#17229](https://github.com/google-gemini/gemini-cli/pull/17229)
- drain stdin on exit by @scidomino in
[#17241](https://github.com/google-gemini/gemini-cli/pull/17241)
- refactor(cli): decouple UI from live tool execution via ToolActionsContext by
@abhipatel12 in
[#17183](https://github.com/google-gemini/gemini-cli/pull/17183)
- fix(core): update token count and telemetry on /chat resume history load by
@psinha40898 in
[#16279](https://github.com/google-gemini/gemini-cli/pull/16279)
- fix: /policy to display policies according to mode by @ishaanxgupta in
[#16772](https://github.com/google-gemini/gemini-cli/pull/16772)
- fix(core): simplify replace tool error message by @SandyTao520 in
[#17246](https://github.com/google-gemini/gemini-cli/pull/17246)
- feat(cli): consolidate shell inactivity and redirection monitoring by
@NTaylorMullen in
[#17086](https://github.com/google-gemini/gemini-cli/pull/17086)
- fix(scheduler): prevent stale tool re-publication and fix stuck UI state by
@abhipatel12 in
[#17227](https://github.com/google-gemini/gemini-cli/pull/17227)
- feat(config): default enableEventDrivenScheduler to true by @abhipatel12 in
[#17211](https://github.com/google-gemini/gemini-cli/pull/17211)
- feat(hooks): enable hooks system by default by @abhipatel12 in
[#17247](https://github.com/google-gemini/gemini-cli/pull/17247)
- feat(core): Enable AgentRegistry to track all discovered subagents by
@SandyTao520 in
[#17253](https://github.com/google-gemini/gemini-cli/pull/17253)
- feat(core): Have subagents use a JSON schema type for input. by @joshualitt in
[#17152](https://github.com/google-gemini/gemini-cli/pull/17152)
- feat: replace large text pastes with [Pasted Text: X lines] placeholder by
@jackwotherspoon in
[#16422](https://github.com/google-gemini/gemini-cli/pull/16422)
- security(hooks): Wrap hook-injected context in distinct XML tags by @yunaseoul
in [#17237](https://github.com/google-gemini/gemini-cli/pull/17237)
- Enable the ability to queue specific nightly eval tests by @gundermanc in
[#17262](https://github.com/google-gemini/gemini-cli/pull/17262)
- docs(hooks): comprehensive update of hook documentation and specs by
@abhipatel12 in
[#16816](https://github.com/google-gemini/gemini-cli/pull/16816)
- refactor: improve large text paste placeholder by @jacob314 in
[#17269](https://github.com/google-gemini/gemini-cli/pull/17269)
- feat: implement /rewind command by @Adib234 in
[#15720](https://github.com/google-gemini/gemini-cli/pull/15720)
- Feature/jetbrains ide detection by @SoLoHiC in
[#16243](https://github.com/google-gemini/gemini-cli/pull/16243)
- docs: update typo in mcp-server.md file by @schifferl in
[#17099](https://github.com/google-gemini/gemini-cli/pull/17099)
- Sanitize command names and descriptions by @ehedlund in
[#17228](https://github.com/google-gemini/gemini-cli/pull/17228)
- fix(auth): don't crash when initial auth fails by @skeshive in
[#17308](https://github.com/google-gemini/gemini-cli/pull/17308)
- Added image pasting capabilities for Wayland and X11 on Linux by @devr0306 in
[#17144](https://github.com/google-gemini/gemini-cli/pull/17144)
- feat: add AskUser tool schema by @jackwotherspoon in
[#16988](https://github.com/google-gemini/gemini-cli/pull/16988)
- fix cli settings: resolve layout jitter in settings bar by @Mag1ck in
[#16256](https://github.com/google-gemini/gemini-cli/pull/16256)
- fix: show whitespace changes in edit tool diffs by @Ujjiyara in
[#17213](https://github.com/google-gemini/gemini-cli/pull/17213)
- Remove redundant calls setting linuxClipboardTool. getUserLinuxClipboardTool()
now handles the caching internally by @jacob314 in
[#17320](https://github.com/google-gemini/gemini-cli/pull/17320)
- ci: allow failure in evals-nightly run step by @gundermanc in
[#17319](https://github.com/google-gemini/gemini-cli/pull/17319)
- feat(cli): Add state management and plumbing for agent configuration dialog by
@SandyTao520 in
[#17259](https://github.com/google-gemini/gemini-cli/pull/17259)
- bug: fix ide-client connection to ide-companion when inside docker via
ssh/devcontainer by @kapsner in
[#15049](https://github.com/google-gemini/gemini-cli/pull/15049)
- Emit correct newline type return by @scidomino in
[#17331](https://github.com/google-gemini/gemini-cli/pull/17331)
- New skill: docs-writer by @g-samroberts in
[#17268](https://github.com/google-gemini/gemini-cli/pull/17268)
- fix(core): Resolve AbortSignal MaxListenersExceededWarning (#5950) by
@spencer426 in
[#16735](https://github.com/google-gemini/gemini-cli/pull/16735)
- Disable tips after 10 runs by @Adib234 in
[#17101](https://github.com/google-gemini/gemini-cli/pull/17101)
- Fix so rewind starts at the bottom and loadHistory refreshes static content.
by @jacob314 in
[#17335](https://github.com/google-gemini/gemini-cli/pull/17335)
- feat(core): Remove legacy settings. by @joshualitt in
[#17244](https://github.com/google-gemini/gemini-cli/pull/17244)
- feat(plan): add 'communicate' tool kind by @jerop in
[#17341](https://github.com/google-gemini/gemini-cli/pull/17341)
- feat(routing): A/B Test Numerical Complexity Scoring for Gemini 3 by
@mattKorwel in
[#16041](https://github.com/google-gemini/gemini-cli/pull/16041)
- feat(plan): update UI Theme for Plan Mode by @Adib234 in
[#17243](https://github.com/google-gemini/gemini-cli/pull/17243)
- fix(ui): stabilize rendering during terminal resize in alternate buffer by
@lkk214 in [#15783](https://github.com/google-gemini/gemini-cli/pull/15783)
- feat(cli): add /agents config command and improve agent discovery by
@SandyTao520 in
[#17342](https://github.com/google-gemini/gemini-cli/pull/17342)
- feat(mcp): add enable/disable commands for MCP servers (#11057) by @jasmeetsb
in [#16299](https://github.com/google-gemini/gemini-cli/pull/16299)
- fix(cli)!: Default to interactive mode for positional arguments by
@ishaanxgupta in
[#16329](https://github.com/google-gemini/gemini-cli/pull/16329)
- Fix issue #17080 by @jacob314 in
[#17100](https://github.com/google-gemini/gemini-cli/pull/17100)
- feat(core): Refresh agents after loading an extension. by @joshualitt in
[#17355](https://github.com/google-gemini/gemini-cli/pull/17355)
- fix(cli): include source in policy rule display by @allenhutchison in
[#17358](https://github.com/google-gemini/gemini-cli/pull/17358)
- fix: remove obsolete CloudCode PerDay quota and 120s terminal threshold by
@gsquared94 in
[#17236](https://github.com/google-gemini/gemini-cli/pull/17236)
- Refactor subagent delegation to be one tool per agent by @gundermanc in
[#17346](https://github.com/google-gemini/gemini-cli/pull/17346)
- fix(core): Include MCP server name in OAuth message by @jerop in
[#17351](https://github.com/google-gemini/gemini-cli/pull/17351)
- Fix pr-triage.sh script to update pull requests with tags "help wanted" and
"maintainer only" by @jacob314 in
[#17324](https://github.com/google-gemini/gemini-cli/pull/17324)
- feat(plan): implement simple workflow for planning in main agent by @jerop in
[#17326](https://github.com/google-gemini/gemini-cli/pull/17326)
- fix: exit with non-zero code when esbuild is missing by @yuvrajangadsingh in
[#16967](https://github.com/google-gemini/gemini-cli/pull/16967)
- fix: ensure @docs/cli/custom-commands.md UI message ordering and test by
[#16604](https://github.com/google-gemini/gemini-cli/pull/16604)
- docs: clarify F12 to open debug console by @jackwotherspoon in
[#16570](https://github.com/google-gemini/gemini-cli/pull/16570)
- docs: Remove .md extension from internal links in architecture.md by
@medic-code in
[#12038](https://github.com/google-gemini/gemini-cli/pull/12038)
- fix(core): add alternative command names for Antigravity editor detec… by
@BaeSeokJae in
[#16829](https://github.com/google-gemini/gemini-cli/pull/16829)
- Refactor: Migrate CLI appEvents to Core coreEvents by @Adib234 in
[#15737](https://github.com/google-gemini/gemini-cli/pull/15737)
- fix(core): await MCP initialization in non-interactive mode by @Ratish1 in
[#17390](https://github.com/google-gemini/gemini-cli/pull/17390)
- Fix modifyOtherKeys enablement on unsupported terminals by @seekskyworld in
[#16714](https://github.com/google-gemini/gemini-cli/pull/16714)
- fix(core): gracefully handle disk full errors in chat recording by
@godwiniheuwa in
[#17305](https://github.com/google-gemini/gemini-cli/pull/17305)
- fix(oauth): update oauth to use 127.0.0.1 instead of localhost by @skeshive in
[#17388](https://github.com/google-gemini/gemini-cli/pull/17388)
- fix(core): use RFC 9728 compliant path-based OAuth protected resource
discovery by @vrv in
[#15756](https://github.com/google-gemini/gemini-cli/pull/15756)
- Update Code Wiki README badge by @PatoBeltran in
[#15229](https://github.com/google-gemini/gemini-cli/pull/15229)
- Add conda installation instructions for Gemini CLI by @ishaanxgupta in
[#16921](https://github.com/google-gemini/gemini-cli/pull/16921)
- chore(refactor): extract BaseSettingsDialog component by @SandyTao520 in
[#17369](https://github.com/google-gemini/gemini-cli/pull/17369)
- fix(cli): preserve input text when declining tool approval (#15624) by
@ManojINaik in
[#15659](https://github.com/google-gemini/gemini-cli/pull/15659)
- chore: upgrade dep: diff 7.0.0-> 8.0.3 by @scidomino in
[#17403](https://github.com/google-gemini/gemini-cli/pull/17403)
- feat: add AskUserDialog for UI component of AskUser tool by @jackwotherspoon
in [#17344](https://github.com/google-gemini/gemini-cli/pull/17344)
- feat(ui): display user tier in about command by @sehoon38 in
[#17400](https://github.com/google-gemini/gemini-cli/pull/17400)
- feat: add clearContext to AfterAgent hooks by @jackwotherspoon in
[#16574](https://github.com/google-gemini/gemini-cli/pull/16574)
- fix(cli): change image paste location to global temp directory (#17396) by
@devr0306 in [#17396](https://github.com/google-gemini/gemini-cli/pull/17396)
- Fix line endings issue with Notice file by @scidomino in
[#17417](https://github.com/google-gemini/gemini-cli/pull/17417)
- feat(plan): implement persistent approvalMode setting by @Adib234 in
[#17350](https://github.com/google-gemini/gemini-cli/pull/17350)
- feat(ui): Move keyboard handling into BaseSettingsDialog by @SandyTao520 in
[#17404](https://github.com/google-gemini/gemini-cli/pull/17404)
- Allow prompt queueing during MCP initialization by @Adib234 in
[#17395](https://github.com/google-gemini/gemini-cli/pull/17395)
- feat: implement AgentConfigDialog for /agents config command by @SandyTao520
in [#17370](https://github.com/google-gemini/gemini-cli/pull/17370)
- fix(agents): default to all tools when tool list is omitted in subagents by
@gundermanc in
[#17422](https://github.com/google-gemini/gemini-cli/pull/17422)
- feat(cli): Moves tool confirmations to a queue UX by @abhipatel12 in
[#17276](https://github.com/google-gemini/gemini-cli/pull/17276)
- fix(core): hide user tier name by @sehoon38 in
[#17418](https://github.com/google-gemini/gemini-cli/pull/17418)
- feat: Enforce unified folder trust for /directory add by @galz10 in
[#17359](https://github.com/google-gemini/gemini-cli/pull/17359)
- migrate fireToolNotificationHook to hookSystem by @ved015 in
[#17398](https://github.com/google-gemini/gemini-cli/pull/17398)
- Clean up dead code by @scidomino in
[#17443](https://github.com/google-gemini/gemini-cli/pull/17443)
- feat(workflow): add stale pull request closer with linked-issue enforcement by
@bdmorgan in [#17449](https://github.com/google-gemini/gemini-cli/pull/17449)
- feat(workflow): expand stale-exempt labels to include help wanted and Public
Roadmap by @bdmorgan in
[#17459](https://github.com/google-gemini/gemini-cli/pull/17459)
- chore(workflow): remove redundant label-enforcer workflow by @bdmorgan in
[#17460](https://github.com/google-gemini/gemini-cli/pull/17460)
- Resolves the confusing error message `ripgrep exited with code null that
occurs when a search operation is cancelled or aborted by @maximmasiutin in
[#14267](https://github.com/google-gemini/gemini-cli/pull/14267)
- fix: detect pnpm/pnpx in ~/.local by @rwakulszowa in
[#15254](https://github.com/google-gemini/gemini-cli/pull/15254)
- docs: Add instructions for MacPorts and uninstall instructions for Homebrew by
@breun in [#17412](https://github.com/google-gemini/gemini-cli/pull/17412)
- docs(hooks): clarify mandatory 'type' field and update hook schema
documentation by @abhipatel12 in
[#17499](https://github.com/google-gemini/gemini-cli/pull/17499)
- Improve error messages on failed onboarding by @gsquared94 in
[#17357](https://github.com/google-gemini/gemini-cli/pull/17357)
- Follow up to "enableInteractiveShell for external tooling relying on a2a
server" by @DavidAPierce in
[#17130](https://github.com/google-gemini/gemini-cli/pull/17130)
- Fix/issue 17070 by @alih552 in
[#17242](https://github.com/google-gemini/gemini-cli/pull/17242)
- fix(core): handle URI-encoded workspace paths in IdeClient by @dong-jun-shin
in [#17476](https://github.com/google-gemini/gemini-cli/pull/17476)
- feat(cli): add quick clear input shortcuts in vim mode by @harshanadim in
[#17470](https://github.com/google-gemini/gemini-cli/pull/17470)
- feat(core): optimize shell tool llmContent output format by @SandyTao520 in
[#17538](https://github.com/google-gemini/gemini-cli/pull/17538)
- Fix bug in detecting already added paths. by @jacob314 in
[#17430](https://github.com/google-gemini/gemini-cli/pull/17430)
- feat(scheduler): support multi-scheduler tool aggregation and nested call IDs
by @abhipatel12 in
[#17429](https://github.com/google-gemini/gemini-cli/pull/17429)
- feat(agents): implement first-run experience for project-level sub-agents by
@gundermanc in
[#17266](https://github.com/google-gemini/gemini-cli/pull/17266)
- Update extensions docs by @chrstnb in
[#16093](https://github.com/google-gemini/gemini-cli/pull/16093)
- Docs: Refactor left nav on the website by @jkcinouye in
[#17558](https://github.com/google-gemini/gemini-cli/pull/17558)
- fix(core): stream grep/ripgrep output to prevent OOM by @adamfweidman in
[#17146](https://github.com/google-gemini/gemini-cli/pull/17146)
- feat(plan): add persistent plan file storage by @jerop in
[#17563](https://github.com/google-gemini/gemini-cli/pull/17563)
- feat(agents): migrate subagents to event-driven scheduler by @abhipatel12 in
[#17567](https://github.com/google-gemini/gemini-cli/pull/17567)
- Fix extensions config error by @chrstnb in
[#17580](https://github.com/google-gemini/gemini-cli/pull/17580)
- fix(plan): remove subagent invocation from plan mode by @jerop in
[#17593](https://github.com/google-gemini/gemini-cli/pull/17593)
- feat(ui): add solid background color option for input prompt by @jacob314 in
[#16563](https://github.com/google-gemini/gemini-cli/pull/16563)
- feat(plan): refresh system prompt when approval mode changes (Shift+Tab) by
@jerop in [#17585](https://github.com/google-gemini/gemini-cli/pull/17585)
- feat(cli): add global setting to disable UI spinners by @galz10 in
[#17234](https://github.com/google-gemini/gemini-cli/pull/17234)
- fix(security): enforce strict policy directory permissions by @yunaseoul in
[#17353](https://github.com/google-gemini/gemini-cli/pull/17353)
- test(core): fix tests in windows by @scidomino in
[#17592](https://github.com/google-gemini/gemini-cli/pull/17592)
- feat(mcp/extensions): Allow users to selectively enable/disable MCP servers
included in an extension( Issue #11057 & #17402) by @jasmeetsb in
[#17434](https://github.com/google-gemini/gemini-cli/pull/17434)
- Always map mac keys, even on other platforms by @scidomino in
[#17618](https://github.com/google-gemini/gemini-cli/pull/17618)
- Ctrl-O by @jacob314 in
[#17617](https://github.com/google-gemini/gemini-cli/pull/17617)
- feat(plan): update cycling order of approval modes by @Adib234 in
[#17622](https://github.com/google-gemini/gemini-cli/pull/17622)
- fix(cli): restore 'Modify with editor' option in external terminals by
[#12899](https://github.com/google-gemini/gemini-cli/pull/12899)
- Add an experimental setting for extension config by @chrstnb in
[#16506](https://github.com/google-gemini/gemini-cli/pull/16506)
- feat: add Rewind Confirmation dialog and Rewind Viewer component by @Adib234
in [#15717](https://github.com/google-gemini/gemini-cli/pull/15717)
- fix(a2a): Don't throw errors for GeminiEventType Retry and InvalidStream. by
@ehedlund in [#16541](https://github.com/google-gemini/gemini-cli/pull/16541)
- prefactor: add rootCommands as array so it can be used for policy parsing by
@abhipatel12 in
[#17621](https://github.com/google-gemini/gemini-cli/pull/17621)
- Slash command for helping in debugging by @gundermanc in
[#17609](https://github.com/google-gemini/gemini-cli/pull/17609)
- feat: add double-click to expand/collapse large paste placeholders by
@jackwotherspoon in
[#17471](https://github.com/google-gemini/gemini-cli/pull/17471)
- refactor(cli): migrate non-interactive flow to event-driven scheduler by
[#16640](https://github.com/google-gemini/gemini-cli/pull/16640)
- remove unnecessary \x7f key bindings by @scidomino in
[#16646](https://github.com/google-gemini/gemini-cli/pull/16646)
- docs(skills): use body-file in pr-creator skill for better reliability by
@abhipatel12 in
[#17572](https://github.com/google-gemini/gemini-cli/pull/17572)
- fix: loadcodeassist eligible tiers getting ignored for unlicensed users
(regression) by @gsquared94 in
[#17581](https://github.com/google-gemini/gemini-cli/pull/17581)
- chore(core): delete legacy nonInteractiveToolExecutor by @abhipatel12 in
[#17573](https://github.com/google-gemini/gemini-cli/pull/17573)
- feat(core): enforce server prefixes for MCP tools in agent definitions by
[#16642](https://github.com/google-gemini/gemini-cli/pull/16642)
- chore(automation): recursive labeling for workstream descendants by @bdmorgan
in [#16609](https://github.com/google-gemini/gemini-cli/pull/16609)
- feat: introduce 'skill-creator' built-in skill and CJS management tools by
@NTaylorMullen in
[#16394](https://github.com/google-gemini/gemini-cli/pull/16394)
- chore(automation): remove automated PR size and complexity labeler by
@bdmorgan in [#16648](https://github.com/google-gemini/gemini-cli/pull/16648)
- refactor(skills): replace 'project' with 'workspace' scope by @NTaylorMullen
in [#16380](https://github.com/google-gemini/gemini-cli/pull/16380)
- Docs: Update release notes for 1/13/2026 by @jkcinouye in
[#16583](https://github.com/google-gemini/gemini-cli/pull/16583)
- Simplify paste handling by @scidomino in
[#16654](https://github.com/google-gemini/gemini-cli/pull/16654)
- chore(automation): improve scheduled issue triage discovery and throughput by
@bdmorgan in [#16652](https://github.com/google-gemini/gemini-cli/pull/16652)
- fix(acp): run exit cleanup when stdin closes by @codefromthecrypt in
[#14953](https://github.com/google-gemini/gemini-cli/pull/14953)
- feat(scheduler): add types needed for event driven scheduler by @abhipatel12
in [#16641](https://github.com/google-gemini/gemini-cli/pull/16641)
- Remove unused rewind key binding by @scidomino in
[#16659](https://github.com/google-gemini/gemini-cli/pull/16659)
- Remove sequence binding by @scidomino in
[#16664](https://github.com/google-gemini/gemini-cli/pull/16664)
- feat(cli): undeprecate the --prompt flag by @alexaustin007 in
[#13981](https://github.com/google-gemini/gemini-cli/pull/13981)
- chore: update dependabot configuration by @cosmopax in
[#13507](https://github.com/google-gemini/gemini-cli/pull/13507)
- feat(config): add 'auto' alias for default model selection by @sehoon38 in
[#16661](https://github.com/google-gemini/gemini-cli/pull/16661)
- Enable & disable agents by @sehoon38 in
[#16225](https://github.com/google-gemini/gemini-cli/pull/16225)
- cleanup: Improve keybindings by @scidomino in
[#16672](https://github.com/google-gemini/gemini-cli/pull/16672)
- Add timeout for shell-utils to prevent hangs. by @jacob314 in
[#16667](https://github.com/google-gemini/gemini-cli/pull/16667)
- feat(plan): add experimental plan flag by @jerop in
[#16650](https://github.com/google-gemini/gemini-cli/pull/16650)
- feat(cli): add security consent prompts for skill installation by
@NTaylorMullen in
[#16549](https://github.com/google-gemini/gemini-cli/pull/16549)
- fix: replace 3 consecutive periods with ellipsis character by @Vist233 in
[#16587](https://github.com/google-gemini/gemini-cli/pull/16587)
- chore(automation): ensure status/need-triage is applied and never cleared
automatically by @bdmorgan in
[#16657](https://github.com/google-gemini/gemini-cli/pull/16657)
- fix: Handle colons in skill description frontmatter by @maru0804 in
[#16345](https://github.com/google-gemini/gemini-cli/pull/16345)
- refactor(core): harden skill frontmatter parsing by @NTaylorMullen in
[#16705](https://github.com/google-gemini/gemini-cli/pull/16705)
- feat(skills): add conflict detection and warnings for skill overrides by
@NTaylorMullen in
[#16709](https://github.com/google-gemini/gemini-cli/pull/16709)
- feat(scheduler): add SchedulerStateManager for reactive tool state by
@abhipatel12 in
[#17574](https://github.com/google-gemini/gemini-cli/pull/17574)
- feat (mcp): Refresh MCP prompts on list changed notification by @MrLesk in
[#14863](https://github.com/google-gemini/gemini-cli/pull/14863)
- feat(ui): pretty JSON rendering tool outputs by @medic-code in
[#9767](https://github.com/google-gemini/gemini-cli/pull/9767)
- Fix iterm alternate buffer mode issue rendering backgrounds by @jacob314 in
[#17634](https://github.com/google-gemini/gemini-cli/pull/17634)
- feat(cli): add gemini extensions list --output-format=json by @AkihiroSuda in
[#14479](https://github.com/google-gemini/gemini-cli/pull/14479)
- fix(extensions): add .gitignore to extension templates by @godwiniheuwa in
[#17293](https://github.com/google-gemini/gemini-cli/pull/17293)
- paste transform followup by @jacob314 in
[#17624](https://github.com/google-gemini/gemini-cli/pull/17624)
- refactor: rename formatMemoryUsage to formatBytes by @Nubebuster in
[#14997](https://github.com/google-gemini/gemini-cli/pull/14997)
- chore: remove extra top margin from /hooks and /extensions by @jackwotherspoon
in [#17663](https://github.com/google-gemini/gemini-cli/pull/17663)
- feat(cli): add oncall command for issue triage by @sehoon38 in
[#17661](https://github.com/google-gemini/gemini-cli/pull/17661)
- Fix sidebar issue for extensions link by @chrstnb in
[#17668](https://github.com/google-gemini/gemini-cli/pull/17668)
- Change formatting to prevent UI redressing attacks by @scidomino in
[#17611](https://github.com/google-gemini/gemini-cli/pull/17611)
- Fix cluster of bugs in the settings dialog. by @jacob314 in
[#17628](https://github.com/google-gemini/gemini-cli/pull/17628)
- Update sidebar to resolve site build issues by @chrstnb in
[#17674](https://github.com/google-gemini/gemini-cli/pull/17674)
- fix(admin): fix a few bugs related to admin controls by @skeshive in
[#17590](https://github.com/google-gemini/gemini-cli/pull/17590)
- revert bad changes to tests by @scidomino in
[#17673](https://github.com/google-gemini/gemini-cli/pull/17673)
- feat(cli): show candidate issue state reason and duplicate status in triage by
@sehoon38 in [#17676](https://github.com/google-gemini/gemini-cli/pull/17676)
- Fix missing slash commands when Gemini CLI is in a project with a package.json
that doesn't follow semantic versioning by @Adib234 in
[#17561](https://github.com/google-gemini/gemini-cli/pull/17561)
- feat(core): Model family-specific system prompts by @joshualitt in
[#17614](https://github.com/google-gemini/gemini-cli/pull/17614)
- Sub-agents documentation. by @gundermanc in
[#16639](https://github.com/google-gemini/gemini-cli/pull/16639)
- feat: wire up AskUserTool with dialog by @jackwotherspoon in
[#17411](https://github.com/google-gemini/gemini-cli/pull/17411)
- Load extension settings for hooks, agents, skills by @chrstnb in
[#17245](https://github.com/google-gemini/gemini-cli/pull/17245)
- Fix issue where Gemini CLI can make changes when simply asked a question by
@gundermanc in
[#17608](https://github.com/google-gemini/gemini-cli/pull/17608)
- Update docs-writer skill for editing and add style guide for reference. by
@g-samroberts in
[#17669](https://github.com/google-gemini/gemini-cli/pull/17669)
- fix(ux): have user message display a short path for pasted images by @devr0306
in [#17613](https://github.com/google-gemini/gemini-cli/pull/17613)
- feat(plan): enable AskUser tool in Plan mode for clarifying questions by
@jerop in [#17694](https://github.com/google-gemini/gemini-cli/pull/17694)
- GEMINI.md polish by @jacob314 in
[#17680](https://github.com/google-gemini/gemini-cli/pull/17680)
- refactor(core): centralize path validation and allow temp dir access for tools
by @NTaylorMullen in
[#17185](https://github.com/google-gemini/gemini-cli/pull/17185)
- feat(skills): promote Agent Skills to stable by @abhipatel12 in
[#17693](https://github.com/google-gemini/gemini-cli/pull/17693)
- refactor(cli): keyboard handling and AskUserDialog by @jacob314 in
[#17414](https://github.com/google-gemini/gemini-cli/pull/17414)
- docs: Add Experimental Remote Agent Docs by @adamfweidman in
[#17697](https://github.com/google-gemini/gemini-cli/pull/17697)
- revert: promote Agent Skills to stable (#17693) by @abhipatel12 in
[#17712](https://github.com/google-gemini/gemini-cli/pull/17712)
- feat(ux) Expandable (ctrl-O) and scrollable approvals in alternate buffer
mode. by @jacob314 in
[#17640](https://github.com/google-gemini/gemini-cli/pull/17640)
- feat(skills): promote skills settings to stable by @abhipatel12 in
[#17713](https://github.com/google-gemini/gemini-cli/pull/17713)
- fix(cli): Preserve settings dialog focus when searching by @SandyTao520 in
[#17701](https://github.com/google-gemini/gemini-cli/pull/17701)
- feat(ui): add terminal cursor support by @jacob314 in
[#17711](https://github.com/google-gemini/gemini-cli/pull/17711)
- docs(skills): remove experimental labels and update tutorials by @abhipatel12
in [#17714](https://github.com/google-gemini/gemini-cli/pull/17714)
- docs: remove 'experimental' syntax for hooks in docs by @abhipatel12 in
[#17660](https://github.com/google-gemini/gemini-cli/pull/17660)
- Add support for an additional exclusion file besides .gitignore and
.geminiignore by @alisa-alisa in
[#16487](https://github.com/google-gemini/gemini-cli/pull/16487)
- feat: add review-frontend-and-fix command by @galz10 in
[#17707](https://github.com/google-gemini/gemini-cli/pull/17707)
[#16651](https://github.com/google-gemini/gemini-cli/pull/16651)
- chore(automation): enforce 'help wanted' label permissions and update
guidelines by @bdmorgan in
[#16707](https://github.com/google-gemini/gemini-cli/pull/16707)
- fix(core): resolve circular dependency via tsconfig paths by @sehoon38 in
[#16730](https://github.com/google-gemini/gemini-cli/pull/16730)
- chore/release: bump version to 0.26.0-nightly.20260115.6cb3ae4e0 by
@gemini-cli-robot in
[#16738](https://github.com/google-gemini/gemini-cli/pull/16738)
- fix(automation): correct status/need-issue label matching wildcard by
@bdmorgan in [#16727](https://github.com/google-gemini/gemini-cli/pull/16727)
- fix(automation): prevent label-enforcer loop by ignoring all bots by @bdmorgan
in [#16746](https://github.com/google-gemini/gemini-cli/pull/16746)
- Add links to supported locations and minor fixes by @g-samroberts in
[#16476](https://github.com/google-gemini/gemini-cli/pull/16476)
- feat(policy): add source tracking to policy rules by @allenhutchison in
[#16670](https://github.com/google-gemini/gemini-cli/pull/16670)
- feat(automation): enforce '🔒 maintainer only' and fix bot loop by @bdmorgan
in [#16751](https://github.com/google-gemini/gemini-cli/pull/16751)
- Make merged settings non-nullable and fix all lints related to that. by
@jacob314 in [#16647](https://github.com/google-gemini/gemini-cli/pull/16647)
- fix(core): prevent ModelInfo event emission on aborted signal by @sehoon38 in
[#16752](https://github.com/google-gemini/gemini-cli/pull/16752)
- Replace relative paths to fix website build by @chrstnb in
[#16755](https://github.com/google-gemini/gemini-cli/pull/16755)
- Restricting to localhost by @cocosheng-g in
[#16548](https://github.com/google-gemini/gemini-cli/pull/16548)
- fix(cli): add explicit dependency on color-convert by @sehoon38 in
[#16757](https://github.com/google-gemini/gemini-cli/pull/16757)
- fix(automation): robust label enforcement with permission checks by @bdmorgan
in [#16762](https://github.com/google-gemini/gemini-cli/pull/16762)
- fix(cli): prevent OOM crash by limiting file search traversal and adding
timeout by @galz10 in
[#16696](https://github.com/google-gemini/gemini-cli/pull/16696)
- fix(cli): safely handle /dev/tty access on macOS by @korade-krushna in
[#16531](https://github.com/google-gemini/gemini-cli/pull/16531)
- docs: clarify workspace test execution in GEMINI.md by @mattKorwel in
[#16764](https://github.com/google-gemini/gemini-cli/pull/16764)
- Add support for running available commands prior to MCP servers loading by
@Adib234 in [#15596](https://github.com/google-gemini/gemini-cli/pull/15596)
- feat(plan): add experimental 'plan' approval mode by @jerop in
[#16753](https://github.com/google-gemini/gemini-cli/pull/16753)
- feat(scheduler): add functional awaitConfirmation utility by @abhipatel12 in
[#16721](https://github.com/google-gemini/gemini-cli/pull/16721)
- fix(infra): update maintainer rollup label to 'workstream-rollup' by @bdmorgan
in [#16809](https://github.com/google-gemini/gemini-cli/pull/16809)
- fix(infra): use GraphQL to detect direct parents in rollup workflow by
@bdmorgan in [#16811](https://github.com/google-gemini/gemini-cli/pull/16811)
- chore(workflows): rename label-workstream-rollup workflow by @bdmorgan in
[#16818](https://github.com/google-gemini/gemini-cli/pull/16818)
- skip simple-mcp-server.test.ts by @scidomino in
[#16842](https://github.com/google-gemini/gemini-cli/pull/16842)
- Steer outer agent to use expert subagents when present by @gundermanc in
[#16763](https://github.com/google-gemini/gemini-cli/pull/16763)
- Fix race condition by awaiting scheduleToolCalls by @chrstnb in
[#16759](https://github.com/google-gemini/gemini-cli/pull/16759)
- cleanup: Organize key bindings by @scidomino in
[#16798](https://github.com/google-gemini/gemini-cli/pull/16798)
- feat(core): Add generalist agent. by @joshualitt in
[#16638](https://github.com/google-gemini/gemini-cli/pull/16638)
- perf(ui): optimize text buffer and highlighting for large inputs by
@NTaylorMullen in
[#16782](https://github.com/google-gemini/gemini-cli/pull/16782)
- fix(core): fix PTY descriptor shell leak by @galz10 in
[#16773](https://github.com/google-gemini/gemini-cli/pull/16773)
- feat(plan): enforce strict read-only policy and halt execution on violation by
@jerop in [#16849](https://github.com/google-gemini/gemini-cli/pull/16849)
- remove need-triage label from bug_report template by @sehoon38 in
[#16864](https://github.com/google-gemini/gemini-cli/pull/16864)
- fix(core): truncate large telemetry log entries by @sehoon38 in
[#16769](https://github.com/google-gemini/gemini-cli/pull/16769)
- docs(extensions): add Agent Skills support and mark feature as experimental by
@NTaylorMullen in
[#16859](https://github.com/google-gemini/gemini-cli/pull/16859)
- fix(core): surface warnings for invalid hook event names in configuration
([#16788](https://github.com/google-gemini/gemini-cli/pull/16788)) by
@sehoon38 in [#16873](https://github.com/google-gemini/gemini-cli/pull/16873)
- feat(plan): remove read_many_files from approval mode policies by @jerop in
[#16876](https://github.com/google-gemini/gemini-cli/pull/16876)
- feat(admin): implement admin controls polling and restart prompt by @skeshive
in [#16627](https://github.com/google-gemini/gemini-cli/pull/16627)
- Remove LRUCache class migrating to mnemoist by @jacob314 in
[#16872](https://github.com/google-gemini/gemini-cli/pull/16872)
- feat(settings): rename negative settings to positive naming (disable* ->
enable*) by @afarber in
[#14142](https://github.com/google-gemini/gemini-cli/pull/14142)
- refactor(cli): unify shell confirmation dialogs by @NTaylorMullen in
[#16828](https://github.com/google-gemini/gemini-cli/pull/16828)
- feat(agent): enable agent skills by default by @NTaylorMullen in
[#16736](https://github.com/google-gemini/gemini-cli/pull/16736)
- refactor(core): foundational truncation refactoring and token estimation
optimization by @NTaylorMullen in
[#16824](https://github.com/google-gemini/gemini-cli/pull/16824)
- fix(hooks): enable /hooks disable to reliably stop single hooks by
@abhipatel12 in
[#16804](https://github.com/google-gemini/gemini-cli/pull/16804)
- Don't commit unless user asks us to. by @gundermanc in
[#16902](https://github.com/google-gemini/gemini-cli/pull/16902)
- chore: remove a2a-adapter and bump @a2a-js/sdk to 0.3.8 by @adamfweidman in
[#16800](https://github.com/google-gemini/gemini-cli/pull/16800)
- fix: Show experiment values in settings UI for compressionThreshold by
@ishaanxgupta in
[#16267](https://github.com/google-gemini/gemini-cli/pull/16267)
- feat(cli): replace relative keyboard shortcuts link with web URL by
@imaliabbas in
[#16479](https://github.com/google-gemini/gemini-cli/pull/16479)
- fix(core): resolve PKCE length issue and stabilize OAuth redirect port by
@sehoon38 in [#16815](https://github.com/google-gemini/gemini-cli/pull/16815)
- Delete rewind documentation for now by @Adib234 in
[#16932](https://github.com/google-gemini/gemini-cli/pull/16932)
- Stabilize skill-creator CI and package format by @NTaylorMullen in
[#17001](https://github.com/google-gemini/gemini-cli/pull/17001)
- Stabilize the git evals by @gundermanc in
[#16989](https://github.com/google-gemini/gemini-cli/pull/16989)
- fix(core): attempt compression before context overflow check by @NTaylorMullen
in [#16914](https://github.com/google-gemini/gemini-cli/pull/16914)
- Fix inverted logic. by @gundermanc in
[#17007](https://github.com/google-gemini/gemini-cli/pull/17007)
- chore(scripts): add duplicate issue closer script and fix lint errors by
@bdmorgan in [#16997](https://github.com/google-gemini/gemini-cli/pull/16997)
- docs: update README and config guide to reference Gemini 3 by @JayadityaGit in
[#15806](https://github.com/google-gemini/gemini-cli/pull/15806)
- fix(cli): correct Homebrew installation detection by @kij in
[#14727](https://github.com/google-gemini/gemini-cli/pull/14727)
- Demote git evals to nightly run. by @gundermanc in
[#17030](https://github.com/google-gemini/gemini-cli/pull/17030)
- fix(cli): use OSC-52 clipboard copy in Windows Terminal by @Thomas-Shephard in
[#16920](https://github.com/google-gemini/gemini-cli/pull/16920)
- Fix: Process all parts in response chunks when thought is first by @pyrytakala
in [#13539](https://github.com/google-gemini/gemini-cli/pull/13539)
- fix(automation): fix jq quoting error in pr-triage.sh by @Kimsoo0119 in
[#16958](https://github.com/google-gemini/gemini-cli/pull/16958)
- refactor(core): decouple scheduler into orchestration, policy, and
confirmation by @abhipatel12 in
[#16895](https://github.com/google-gemini/gemini-cli/pull/16895)
- feat: add /introspect slash command by @NTaylorMullen in
[#17048](https://github.com/google-gemini/gemini-cli/pull/17048)
- refactor(cli): centralize tool mapping and decouple legacy scheduler by
@abhipatel12 in
[#17044](https://github.com/google-gemini/gemini-cli/pull/17044)
- fix(ui): ensure rationale renders before tool calls by @NTaylorMullen in
[#17043](https://github.com/google-gemini/gemini-cli/pull/17043)
- fix(workflows): use author_association for maintainer check by @bdmorgan in
[#17060](https://github.com/google-gemini/gemini-cli/pull/17060)
- fix return type of fireSessionStartEvent to defaultHookOutput by @ved015 in
[#16833](https://github.com/google-gemini/gemini-cli/pull/16833)
- feat(cli): add experiment gate for event-driven scheduler by @abhipatel12 in
[#17055](https://github.com/google-gemini/gemini-cli/pull/17055)
- feat(core): improve shell redirection transparency and security by
@NTaylorMullen in
[#16486](https://github.com/google-gemini/gemini-cli/pull/16486)
- fix(core): deduplicate ModelInfo emission in GeminiClient by @NTaylorMullen in
[#17075](https://github.com/google-gemini/gemini-cli/pull/17075)
- docs(themes): remove unsupported DiffModified color key by @jw409 in
[#17073](https://github.com/google-gemini/gemini-cli/pull/17073)
- fix: update currentSequenceModel when modelChanged by @adamfweidman in
[#17051](https://github.com/google-gemini/gemini-cli/pull/17051)
- feat(core): enhanced anchored iterative context compression with
self-verification by @rmedranollamas in
[#15710](https://github.com/google-gemini/gemini-cli/pull/15710)
- Fix mcp instructions by @chrstnb in
[#16439](https://github.com/google-gemini/gemini-cli/pull/16439)
- [A2A] Disable checkpointing if git is not installed by @cocosheng-g in
[#16896](https://github.com/google-gemini/gemini-cli/pull/16896)
- feat(admin): set admin.skills.enabled based on advancedFeaturesEnabled setting
by @skeshive in
[#17095](https://github.com/google-gemini/gemini-cli/pull/17095)
- Test coverage for hook exit code cases by @gundermanc in
[#17041](https://github.com/google-gemini/gemini-cli/pull/17041)
- Revert "Revert "Update extension examples"" by @chrstnb in
[#16445](https://github.com/google-gemini/gemini-cli/pull/16445)
- fix(core): Provide compact, actionable errors for agent delegation failures by
@SandyTao520 in
[#16493](https://github.com/google-gemini/gemini-cli/pull/16493)
- fix: migrate BeforeModel and AfterModel hooks to HookSystem by @ved015 in
[#16599](https://github.com/google-gemini/gemini-cli/pull/16599)
- feat(admin): apply admin settings to gemini skills/mcp/extensions commands by
@skeshive in [#17102](https://github.com/google-gemini/gemini-cli/pull/17102)
- fix(core): update telemetry token count after session resume by @psinha40898
in [#15491](https://github.com/google-gemini/gemini-cli/pull/15491)
- Demote the subagent test to nightly by @gundermanc in
[#17105](https://github.com/google-gemini/gemini-cli/pull/17105)
- feat(plan): telemetry to track adoption and usage of plan mode by @Adib234 in
[#16863](https://github.com/google-gemini/gemini-cli/pull/16863)
- feat: Add flash lite utility fallback chain by @adamfweidman in
[#17056](https://github.com/google-gemini/gemini-cli/pull/17056)
- Fixes Windows crash: "Cannot resize a pty that has already exited" by @dzammit
in [#15757](https://github.com/google-gemini/gemini-cli/pull/15757)
- feat(core): Add initial eval for generalist agent. by @joshualitt in
[#16856](https://github.com/google-gemini/gemini-cli/pull/16856)
- feat(core): unify agent enabled and disabled flags by @SandyTao520 in
[#17127](https://github.com/google-gemini/gemini-cli/pull/17127)
- fix(core): resolve auto model in default strategy by @sehoon38 in
[#17116](https://github.com/google-gemini/gemini-cli/pull/17116)
- docs: update project context and pr-creator workflow by @NTaylorMullen in
[#17119](https://github.com/google-gemini/gemini-cli/pull/17119)
- fix(cli): send gemini-cli version as mcp client version by @dsp in
[#13407](https://github.com/google-gemini/gemini-cli/pull/13407)
- fix(cli): resolve Ctrl+Enter and Ctrl+J newline issues by @imadraude in
[#17021](https://github.com/google-gemini/gemini-cli/pull/17021)
- Remove missing sidebar item by @chrstnb in
[#17145](https://github.com/google-gemini/gemini-cli/pull/17145)
- feat(core): Ensure all properties in hooks object are event names. by
@joshualitt in
[#16870](https://github.com/google-gemini/gemini-cli/pull/16870)
- fix(cli): fix newline support broken in previous PR by @scidomino in
[#17159](https://github.com/google-gemini/gemini-cli/pull/17159)
- Add interactive ValidationDialog for handling 403 VALIDATION_REQUIRED errors.
by @gsquared94 in
[#16231](https://github.com/google-gemini/gemini-cli/pull/16231)
- Add Esc-Esc to clear prompt when it's not empty by @Adib234 in
[#17131](https://github.com/google-gemini/gemini-cli/pull/17131)
- Avoid spurious warnings about unexpected renders triggered by appEvents and
coreEvents. by @jacob314 in
[#17160](https://github.com/google-gemini/gemini-cli/pull/17160)
- fix(cli): resolve home/end keybinding conflict by @scidomino in
[#17124](https://github.com/google-gemini/gemini-cli/pull/17124)
- fix(cli): display 'http' type on mcp list by @pamanta in
[#16915](https://github.com/google-gemini/gemini-cli/pull/16915)
- fix bad fallback logic external editor logic by @scidomino in
[#17166](https://github.com/google-gemini/gemini-cli/pull/17166)
- Fix bug where System scopes weren't migrated. by @jacob314 in
[#17174](https://github.com/google-gemini/gemini-cli/pull/17174)
- Fix mcp tool lookup in tool registry by @werdnum in
[#17054](https://github.com/google-gemini/gemini-cli/pull/17054)
**Full changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.26.0-preview.5...v0.27.0-preview.0
https://github.com/google-gemini/gemini-cli/compare/v0.25.0-preview.4...v0.26.0-preview.0
-101
View File
@@ -1,101 +0,0 @@
# CLI cheatsheet
This page provides a reference for commonly used Gemini CLI commands, options,
and parameters.
## CLI commands
| Command | Description | Example |
| ---------------------------------- | ---------------------------------- | --------------------------------------------------- |
| `gemini` | Start interactive REPL | `gemini` |
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
| `gemini -p "query"` | Query via SDK, then exit | `gemini -p "explain this function"` |
| `cat file \| gemini -p "query"` | Process piped content | `cat logs.txt \| gemini -p "explain"` |
| `gemini -i "query"` | Execute and continue interactively | `gemini -i "What is the purpose of this project?"` |
| `gemini -r "latest"` | Continue most recent session | `gemini -r "latest"` |
| `gemini -r "latest" "query"` | Continue session with a new prompt | `gemini -r "latest" "Check for type errors"` |
| `gemini -r "<session-id>" "query"` | Resume session by ID | `gemini -r "abc123" "Finish this PR"` |
| `gemini update` | Update to latest version | `gemini update` |
| `gemini extensions` | Manage extensions | See [Extensions Management](#extensions-management) |
| `gemini mcp` | Configure MCP servers | See [MCP Server Management](#mcp-server-management) |
### Positional arguments
| Argument | Type | Description |
| -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
| `query` | string (variadic) | Positional prompt. Defaults to one-shot mode. Use `-i/--prompt-interactive` to execute and continue interactively. |
## CLI Options
| Option | Alias | Type | Default | Description |
| -------------------------------- | ----- | ------- | --------- | ---------------------------------------------------------------------------------------------------------- |
| `--debug` | `-d` | boolean | `false` | Run in debug mode with verbose logging |
| `--version` | `-v` | - | - | Show CLI version number and exit |
| `--help` | `-h` | - | - | Show help information |
| `--model` | `-m` | string | `auto` | Model to use. See [Model Selection](#model-selection) for available values. |
| `--prompt` | `-p` | string | - | Prompt text. Appended to stdin input if provided. **Deprecated:** Use positional arguments instead. |
| `--prompt-interactive` | `-i` | string | - | Execute prompt and continue in interactive mode |
| `--sandbox` | `-s` | boolean | `false` | Run in a sandboxed environment for safer execution |
| `--approval-mode` | - | string | `default` | Approval mode for tool execution. Choices: `default`, `auto_edit`, `yolo` |
| `--yolo` | `-y` | boolean | `false` | **Deprecated.** Auto-approve all actions. Use `--approval-mode=yolo` instead. |
| `--experimental-acp` | - | boolean | - | Start in ACP (Agent Code Pilot) mode. **Experimental feature.** |
| `--experimental-zed-integration` | - | boolean | - | Run in Zed editor integration mode. **Experimental feature.** |
| `--allowed-mcp-server-names` | - | array | - | Allowed MCP server names (comma-separated or multiple flags) |
| `--allowed-tools` | - | array | - | Tools that are allowed to run without confirmation (comma-separated or multiple flags) |
| `--extensions` | `-e` | array | - | List of extensions to use. If not provided, all extensions are enabled (comma-separated or multiple flags) |
| `--list-extensions` | `-l` | boolean | - | List all available extensions and exit |
| `--resume` | `-r` | string | - | Resume a previous session. Use `"latest"` for most recent or index number (e.g. `--resume 5`) |
| `--list-sessions` | - | boolean | - | List available sessions for the current project and exit |
| `--delete-session` | - | string | - | Delete a session by index number (use `--list-sessions` to see available sessions) |
| `--include-directories` | - | array | - | Additional directories to include in the workspace (comma-separated or multiple flags) |
| `--screen-reader` | - | boolean | - | Enable screen reader mode for accessibility |
| `--output-format` | `-o` | string | `text` | The format of the CLI output. Choices: `text`, `json`, `stream-json` |
## Model selection
The `--model` (or `-m`) flag allows you to specify which Gemini model to use.
You can use either model aliases (user-friendly names) or concrete model names.
### Model aliases
These are convenient shortcuts that map to specific models:
| Alias | Resolves To | Description |
| ------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `auto` | `gemini-2.5-pro` or `gemini-3-pro-preview` | **Default.** Resolves to the preview model if preview features are enabled, otherwise resolves to the standard pro model. |
| `pro` | `gemini-2.5-pro` or `gemini-3-pro-preview` | For complex reasoning tasks. Uses preview model if enabled. |
| `flash` | `gemini-2.5-flash` | Fast, balanced model for most tasks. |
| `flash-lite` | `gemini-2.5-flash-lite` | Fastest model for simple tasks. |
## Extensions management
| Command | Description | Example |
| -------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------ |
| `gemini extensions install <source>` | Install extension from Git URL or local path | `gemini extensions install https://github.com/user/my-extension` |
| `gemini extensions install <source> --ref <ref>` | Install from specific branch/tag/commit | `gemini extensions install https://github.com/user/my-extension --ref develop` |
| `gemini extensions install <source> --auto-update` | Install with auto-update enabled | `gemini extensions install https://github.com/user/my-extension --auto-update` |
| `gemini extensions uninstall <name>` | Uninstall one or more extensions | `gemini extensions uninstall my-extension` |
| `gemini extensions list` | List all installed extensions | `gemini extensions list` |
| `gemini extensions update <name>` | Update a specific extension | `gemini extensions update my-extension` |
| `gemini extensions update --all` | Update all extensions | `gemini extensions update --all` |
| `gemini extensions enable <name>` | Enable an extension | `gemini extensions enable my-extension` |
| `gemini extensions disable <name>` | Disable an extension | `gemini extensions disable my-extension` |
| `gemini extensions link <path>` | Link local extension for development | `gemini extensions link /path/to/extension` |
| `gemini extensions new <path>` | Create new extension from template | `gemini extensions new ./my-extension` |
| `gemini extensions validate <path>` | Validate extension structure | `gemini extensions validate ./my-extension` |
See [Extensions Documentation](../extensions/index.md) for more details.
## MCP server management
| Command | Description | Example |
| ------------------------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `gemini mcp add <name> <command>` | Add stdio-based MCP server | `gemini mcp add github npx -y @modelcontextprotocol/server-github` |
| `gemini mcp add <name> <url> --transport http` | Add HTTP-based MCP server | `gemini mcp add api-server http://localhost:3000 --transport http` |
| `gemini mcp add <name> <command> --env KEY=value` | Add with environment variables | `gemini mcp add slack node server.js --env SLACK_TOKEN=xoxb-xxx` |
| `gemini mcp add <name> <command> --scope user` | Add with user scope | `gemini mcp add db node db-server.js --scope user` |
| `gemini mcp add <name> <command> --include-tools tool1,tool2` | Add with specific tools | `gemini mcp add github npx -y @modelcontextprotocol/server-github --include-tools list_repos,get_pr` |
| `gemini mcp remove <name>` | Remove an MCP server | `gemini mcp remove github` |
| `gemini mcp list` | List all configured MCP servers | `gemini mcp list` |
See [MCP Server Integration](../tools/mcp-server.md) for more details.
+85 -140
View File
@@ -10,14 +10,6 @@ Slash commands provide meta-level control over the CLI itself.
### Built-in Commands
- **`/about`**
- **Description:** Show version info. Please share this information when
filing issues.
- **`/auth`**
- **Description:** Open a dialog that lets you change the authentication
method.
- **`/bug`**
- **Description:** File an issue about Gemini CLI. By default, the issue is
filed within the GitHub repository for Gemini CLI. The string you enter
@@ -30,21 +22,10 @@ Slash commands provide meta-level control over the CLI itself.
conversation state interactively, or resuming a previous state from a later
session.
- **Sub-commands:**
- **`delete <tag>`**
- **Description:** Deletes a saved conversation checkpoint.
- **`list`**
- **Description:** Lists available tags for chat state resumption.
- **Note:** This command only lists chats saved within the current
project. Because chat history is project-scoped, chats saved in other
project directories will not be displayed.
- **`resume <tag>`**
- **Description:** Resumes a conversation from a previous save.
- **Note:** You can only resume chats that were saved within the current
project. To resume a chat from a different project, you must run the
Gemini CLI from that project's directory.
- **`save <tag>`**
- **`save`**
- **Description:** Saves the current conversation history. You must add a
`<tag>` for identifying the conversation state.
- **Usage:** `/chat save <tag>`
- **Details on checkpoint location:** The default locations for saved chat
checkpoints are:
- Linux/macOS: `~/.gemini/tmp/<project_hash>/`
@@ -56,11 +37,25 @@ Slash commands provide meta-level control over the CLI itself.
conversation states. For automatic checkpoints created before file
modifications, see the
[Checkpointing documentation](../cli/checkpointing.md).
- **`share [filename]`**
- **`resume`**
- **Description:** Resumes a conversation from a previous save.
- **Usage:** `/chat resume <tag>`
- **Note:** You can only resume chats that were saved within the current
project. To resume a chat from a different project, you must run the
Gemini CLI from that project's directory.
- **`list`**
- **Description:** Lists available tags for chat state resumption.
- **Note:** This command only lists chats saved within the current
project. Because chat history is project-scoped, chats saved in other
project directories will not be displayed.
- **`delete`**
- **Description:** Deletes a saved conversation checkpoint.
- **Usage:** `/chat delete <tag>`
- **`share`**
- **Description** Writes the current conversation to a provided Markdown
or JSON file. If no filename is provided, then the CLI will generate
one.
- **Usage** `/chat share file.md` or `/chat share file.json`.
or JSON file.
- **Usage** `/chat share file.md` or `/chat share file.json`. If no
filename is provided, then the CLI will generate one.
- **`/clear`**
- **Description:** Clear the terminal screen, including the visible session
@@ -103,9 +98,6 @@ Slash commands provide meta-level control over the CLI itself.
`--include-directories`.
- **Usage:** `/directory show`
- **`/docs`**
- **Description:** Open the Gemini CLI documentation in your browser.
- **`/editor`**
- **Description:** Open a dialog for selecting supported editors.
@@ -117,65 +109,30 @@ Slash commands provide meta-level control over the CLI itself.
- **Description:** Display help information about Gemini CLI, including
available commands and their usage.
- **`/hooks`**
- **Description:** Manage hooks, which allow you to intercept and customize
Gemini CLI behavior at specific lifecycle events.
- **Sub-commands:**
- **`disable-all`**:
- **Description:** Disable all enabled hooks.
- **`disable <hook-name>`**:
- **Description:** Disable a hook by name.
- **`enable-all`**:
- **Description:** Enable all disabled hooks.
- **`enable <hook-name>`**:
- **Description:** Enable a hook by name.
- **`list`** (or `show`, `panel`):
- **Description:** Display all registered hooks with their status.
- **`/ide`**
- **Description:** Manage IDE integration.
- **Sub-commands:**
- **`disable`**:
- **Description:** Disable IDE integration.
- **`enable`**:
- **Description:** Enable IDE integration.
- **`install`**:
- **Description:** Install required IDE companion.
- **`status`**:
- **Description:** Check status of IDE integration.
- **`/init`**
- **Description:** To help users easily create a `GEMINI.md` file, this
command analyzes the current directory and generates a tailored context
file, making it simpler for them to provide project-specific instructions to
the Gemini agent.
- **`/introspect`**
- **Description:** Provide debugging information about the current Gemini CLI
session, including the state of loaded sub-agents and active hooks. This
command is primarily for advanced users and developers.
- **`/mcp`**
- **Description:** Manage configured Model Context Protocol (MCP) servers.
- **Sub-commands:**
- **`list`** or **`ls`**:
- **Description:** List configured MCP servers and tools. This is the
default action if no subcommand is specified.
- **`desc`**
- **Description:** List configured MCP servers and tools with
descriptions.
- **`schema`**:
- **Description:** List configured MCP servers and tools with descriptions
and schemas.
- **`auth`**:
- **Description:** Authenticate with an OAuth-enabled MCP server.
- **Usage:** `/mcp auth <server-name>`
- **Details:** If `<server-name>` is provided, it initiates the OAuth flow
for that server. If no server name is provided, it lists all configured
servers that support OAuth authentication.
- **`desc`**
- **Description:** List configured MCP servers and tools with
descriptions.
- **`list`** or **`ls`**:
- **Description:** List configured MCP servers and tools. This is the
default action if no subcommand is specified.
- **`refresh`**:
- **Description:** Restarts all MCP servers and re-discovers their
available tools.
- **`schema`**:
- **Description:** List configured MCP servers and tools with descriptions
and schemas.
- [**`/model`**](./model.md)
- **Description:** Opens a dialog to choose your Gemini model.
- **`/memory`**
- **Description:** Manage the AI's instructional context (hierarchical memory
@@ -184,40 +141,23 @@ Slash commands provide meta-level control over the CLI itself.
- **`add`**:
- **Description:** Adds the following text to the AI's memory. Usage:
`/memory add <text to remember>`
- **`list`**:
- **Description:** Lists the paths of the GEMINI.md files in use for
hierarchical memory.
- **`refresh`**:
- **Description:** Reload the hierarchical instructional memory from all
`GEMINI.md` files found in the configured locations (global,
project/ancestors, and sub-directories). This command updates the model
with the latest `GEMINI.md` content.
- **`show`**:
- **Description:** Display the full, concatenated content of the current
hierarchical memory that has been loaded from all `GEMINI.md` files.
This lets you inspect the instructional context being provided to the
Gemini model.
- **`refresh`**:
- **Description:** Reload the hierarchical instructional memory from all
`GEMINI.md` files found in the configured locations (global,
project/ancestors, and sub-directories). This command updates the model
with the latest `GEMINI.md` content.
- **`list`**:
- **Description:** Lists the paths of the GEMINI.md files in use for
hierarchical memory.
- **Note:** For more details on how `GEMINI.md` files contribute to
hierarchical memory, see the
[CLI Configuration documentation](../get-started/configuration.md).
- [**`/model`**](./model.md)
- **Description:** Opens a dialog to choose your Gemini model.
- **`/policies`**
- **Description:** Manage policies.
- **Sub-commands:**
- **`list`**:
- **Description:** List all active policies grouped by mode.
- **`/privacy`**
- **Description:** Display the Privacy Notice and allow users to select
whether they consent to the collection of their data for service improvement
purposes.
- **`/quit`** (or **`/exit`**)
- **Description:** Exit Gemini CLI.
- **`/restore`**
- **Description:** Restores the project files to the state they were in just
before a tool was executed. This is particularly useful for undoing file
@@ -228,24 +168,18 @@ Slash commands provide meta-level control over the CLI itself.
[settings](../get-started/configuration.md). See
[Checkpointing documentation](../cli/checkpointing.md) for more details.
- [**`/rewind`**](./rewind.md)
- **Description:** Navigates backward through the conversation history,
allowing you to review past interactions and potentially revert to a
previous state. This feature helps in managing complex or branched
conversations.
- **`/resume`**
- **Description:** Browse and resume previous conversation sessions. Opens an
interactive session browser where you can search, filter, and select from
automatically saved conversations.
- **Features:**
- **Management:** Delete unwanted sessions directly from the browser
- **Resume:** Select any session to resume and continue the conversation
- **Search:** Use `/` to search through conversation content across all
sessions
- **Session Browser:** Interactive interface showing all saved sessions with
timestamps, message counts, and first user message for context
- **Search:** Use `/` to search through conversation content across all
sessions
- **Sorting:** Sort sessions by date or message count
- **Management:** Delete unwanted sessions directly from the browser
- **Resume:** Select any session to resume and continue the conversation
- **Note:** All conversations are automatically saved as you chat - no manual
saving required. See [Session Management](../cli/session-management.md) for
complete details.
@@ -264,26 +198,19 @@ Slash commands provide meta-level control over the CLI itself.
modify them as desired. Changes to some settings are applied immediately,
while others require a restart.
- **`/shells`** (or **`/bashes`**)
- **Description:** Toggle the background shells view. This allows you to view
and manage long-running processes that you've sent to the background.
- **`/setup-github`**
- **Description:** Set up GitHub Actions to triage issues and review PRs with
Gemini.
- [**`/skills`**](./skills.md)
- **Description:** Manage Agent Skills, which provide on-demand expertise and
specialized workflows.
- **Description:** (Experimental) Manage Agent Skills, which provide on-demand
expertise and specialized workflows.
- **Sub-commands:**
- **`disable <name>`**:
- **Description:** Disable a specific skill by name.
- **Usage:** `/skills disable <name>`
- **`enable <name>`**:
- **Description:** Enable a specific skill by name.
- **Usage:** `/skills enable <name>`
- **`list`**:
- **Description:** List all discovered skills and their current status
(enabled/disabled).
- **`enable`**:
- **Description:** Enable a specific skill by name.
- **Usage:** `/skills enable <name>`
- **`disable`**:
- **Description:** Disable a specific skill by name.
- **Usage:** `/skills disable <name>`
- **`reload`**:
- **Description:** Refresh the list of discovered skills from all tiers
(workspace, user, and extensions).
@@ -295,14 +222,18 @@ Slash commands provide meta-level control over the CLI itself.
cached tokens are being used, which occurs with API key authentication but
not with OAuth authentication at this time.
- **`/terminal-setup`**
- **Description:** Configure terminal keybindings for multiline input (VS
Code, Cursor, Windsurf).
- [**`/theme`**](./themes.md)
- **Description:** Open a dialog that lets you change the visual theme of
Gemini CLI.
- **`/auth`**
- **Description:** Open a dialog that lets you change the authentication
method.
- **`/about`**
- **Description:** Show version info. Please share this information when
filing issues.
- [**`/tools`**](../tools/index.md)
- **Description:** Display a list of tools that are currently available within
Gemini CLI.
@@ -314,23 +245,37 @@ Slash commands provide meta-level control over the CLI itself.
- **`nodesc`** or **`nodescriptions`**:
- **Description:** Hide tool descriptions, showing only the tool names.
- **`/privacy`**
- **Description:** Display the Privacy Notice and allow users to select
whether they consent to the collection of their data for service improvement
purposes.
- **`/quit`** (or **`/exit`**)
- **Description:** Exit Gemini CLI.
- **`/vim`**
- **Description:** Toggle vim mode on or off. When vim mode is enabled, the
input area supports vim-style navigation and editing commands in both NORMAL
and INSERT modes.
- **Features:**
- **Count support:** Prefix commands with numbers (e.g., `3h`, `5w`, `10G`)
- **Editing commands:** Delete with `x`, change with `c`, insert with `i`,
`a`, `o`, `O`; complex operations like `dd`, `cc`, `dw`, `cw`
- **INSERT mode:** Standard text input with escape to return to NORMAL mode
- **NORMAL mode:** Navigate with `h`, `j`, `k`, `l`; jump by words with `w`,
`b`, `e`; go to line start/end with `0`, `$`, `^`; go to specific lines
with `G` (or `gg` for first line)
- **INSERT mode:** Standard text input with escape to return to NORMAL mode
- **Editing commands:** Delete with `x`, change with `c`, insert with `i`,
`a`, `o`, `O`; complex operations like `dd`, `cc`, `dw`, `cw`
- **Count support:** Prefix commands with numbers (e.g., `3h`, `5w`, `10G`)
- **Repeat last command:** Use `.` to repeat the last editing operation
- **Persistent setting:** Vim mode preference is saved to
`~/.gemini/settings.json` and restored between sessions
- **Repeat last command:** Use `.` to repeat the last editing operation
- **Status indicator:** When enabled, shows `[NORMAL]` or `[INSERT]` in the
footer
- **Status indicator:** When enabled, shows `[NORMAL]` or `[INSERT]` in the
footer
- **`/init`**
- **Description:** To help users easily create a `GEMINI.md` file, this
command analyzes the current directory and generates a tailored context
file, making it simpler for them to provide project-specific instructions to
the Gemini agent.
### Custom commands
@@ -343,12 +288,12 @@ please see the dedicated [Custom Commands documentation](./custom-commands.md).
These shortcuts apply directly to the input prompt for text manipulation.
- **Undo:**
- **Keyboard shortcut:** Press **Cmd+z** or **Alt+z** to undo the last action
in the input prompt.
- **Keyboard shortcut:** Press **Ctrl+z** to undo the last action in the input
prompt.
- **Redo:**
- **Keyboard shortcut:** Press **Shift+Cmd+Z** or **Shift+Alt+Z** to redo the
last undone action in the input prompt.
- **Keyboard shortcut:** Press **Ctrl+Shift+Z** to redo the last undone action
in the input prompt.
## At commands (`@`)
-80
View File
@@ -1,80 +0,0 @@
# Creating Agent Skills
This guide provides an overview of how to create your own Agent Skills to extend
the capabilities of Gemini CLI.
## Getting started: The `skill-creator` skill
The recommended way to create a new skill is to use the built-in `skill-creator`
skill. To use it, ask Gemini CLI to create a new skill for you.
**Example prompt:**
> "create a new skill called 'code-reviewer'"
Gemini CLI will then use the `skill-creator` to generate the skill:
1. Generate a new directory for your skill (e.g., `my-new-skill/`).
2. Create a `SKILL.md` file with the necessary YAML frontmatter (`name` and
`description`).
3. Create the standard resource directories: `scripts/`, `references/`, and
`assets/`.
## Manual skill creation
If you prefer to create skills manually:
1. **Create a directory** for your skill (e.g., `my-new-skill/`).
2. **Create a `SKILL.md` file** inside the new directory.
To add additional resources that support the skill, refer to the skill
structure.
## Skill structure
A skill is a directory containing a `SKILL.md` file at its root.
### Folder structure
While a `SKILL.md` file is the only required component, we recommend the
following structure for organizing your skill's resources:
```text
my-skill/
├── SKILL.md (Required) Instructions and metadata
├── scripts/ (Optional) Executable scripts
├── references/ (Optional) Static documentation
└── assets/ (Optional) Templates and other resources
```
### `SKILL.md` file
The `SKILL.md` file is the core of your skill. This file uses YAML frontmatter
for metadata and Markdown for instructions. For example:
```markdown
---
name: code-reviewer
description:
Use this skill to review code. It supports both local changes and remote Pull
Requests.
---
# Code Reviewer
This skill guides the agent in conducting thorough code reviews.
## Workflow
### 1. Determine Review Target
- **Remote PR**: If the user gives a PR number or URL, target that remote PR.
- **Local Changes**: If changes are local... ...
```
- **`name`**: A unique identifier for the skill. This should match the directory
name.
- **`description`**: A description of what the skill does and when Gemini should
use it.
- **Body**: The Markdown body of the file contains the instructions that guide
the agent's behavior when the skill is active.
-17
View File
@@ -203,23 +203,6 @@ with the actual Gemini CLI process, which inherits the environment variable.
This makes it significantly more difficult for a user to bypass the enforced
settings.
## User isolation in shared environments
In shared compute environments (like ML experiment runners or shared build
servers), you can isolate Gemini CLI state by overriding the user's home
directory.
By default, Gemini CLI stores configuration and history in `~/.gemini`. You can
use the `GEMINI_CLI_HOME` environment variable to point to a unique directory
for a specific user or job. The CLI will create a `.gemini` folder inside the
specified path.
```bash
# Isolate state for a specific job
export GEMINI_CLI_HOME="/tmp/gemini-job-123"
gemini
```
## Restricting tool access
You can significantly enhance security by controlling which tools the Gemini
+2 -2
View File
@@ -29,8 +29,8 @@ overview of Gemini CLI, see the [main documentation page](../index.md).
in an enterprise environment.
- **[Sandboxing](./sandbox.md):** Isolate tool execution in a secure,
containerized environment.
- **[Agent Skills](./skills.md):** Extend the CLI with specialized expertise and
procedural workflows.
- **[Agent Skills](./skills.md):** (Experimental) Extend the CLI with
specialized expertise and procedural workflows.
- **[Telemetry](./telemetry.md):** Configure observability to monitor usage and
performance.
- **[Token caching](./token-caching.md):** Optimize API costs by caching tokens.
+18 -29
View File
@@ -23,24 +23,24 @@ available combinations.
| Move the cursor to the end of the line. | `Ctrl + E`<br />`End (no Shift, Ctrl)` |
| Move the cursor up one line. | `Up Arrow (no Shift, Alt, Ctrl, Cmd)` |
| Move the cursor down one line. | `Down Arrow (no Shift, Alt, Ctrl, Cmd)` |
| Move the cursor one character to the left. | `Left Arrow (no Shift, Alt, Ctrl, Cmd)` |
| Move the cursor one character to the left. | `Left Arrow (no Shift, Alt, Ctrl, Cmd)`<br />`Ctrl + B` |
| Move the cursor one character to the right. | `Right Arrow (no Shift, Alt, Ctrl, Cmd)`<br />`Ctrl + F` |
| Move the cursor one word to the left. | `Ctrl + Left Arrow`<br />`Alt + Left Arrow`<br />`Alt + B` |
| Move the cursor one word to the right. | `Ctrl + Right Arrow`<br />`Alt + Right Arrow`<br />`Alt + 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 />`Alt + Backspace`<br />`Ctrl + W` |
| Delete the next word. | `Ctrl + Delete`<br />`Alt + Delete` |
| Delete the character to the left. | `Backspace`<br />`Ctrl + H` |
| Delete the character to the right. | `Delete`<br />`Ctrl + D` |
| Undo the most recent text edit. | `Cmd + Z (no Shift)`<br />`Alt + Z (no Shift)` |
| Redo the most recent undone text edit. | `Shift + Ctrl + Z`<br />`Shift + Cmd + Z`<br />`Shift + Alt + Z` |
| 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 />`Alt + Backspace`<br />`Ctrl + W` |
| Delete the next word. | `Ctrl + Delete`<br />`Alt + Delete` |
| Delete the character to the left. | `Backspace`<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. | `Shift + Ctrl + Z` |
#### Scrolling
@@ -66,14 +66,12 @@ available combinations.
#### Navigation
| Action | Keys |
| -------------------------------------------------- | ------------------------------------------- |
| Move selection up in lists. | `Up Arrow (no Shift)` |
| Move selection down in lists. | `Down Arrow (no Shift)` |
| Move up within dialog options. | `Up Arrow (no Shift)`<br />`K (no Shift)` |
| Move down within dialog options. | `Down Arrow (no Shift)`<br />`J (no Shift)` |
| Move to the next item or question in a dialog. | `Tab (no Shift)` |
| Move to the previous item or question in a dialog. | `Shift + Tab` |
| Action | Keys |
| -------------------------------- | ------------------------------------------- |
| Move selection up in lists. | `Up Arrow (no Shift)` |
| Move selection down in lists. | `Down Arrow (no Shift)` |
| Move up within dialog options. | `Up Arrow (no Shift)`<br />`K (no Shift)` |
| Move down within dialog options. | `Down Arrow (no Shift)`<br />`J (no Shift)` |
#### Suggestions & Completions
@@ -106,19 +104,10 @@ available combinations.
| Toggle YOLO (auto-approval) mode for tool calls. | `Ctrl + Y` |
| Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only). | `Shift + Tab` |
| Expand a height-constrained response to show additional lines when not in alternate buffer mode. | `Ctrl + O`<br />`Ctrl + S` |
| Ctrl+B | `Ctrl + B` |
| Ctrl+L | `Ctrl + L` |
| Ctrl+K | `Ctrl + K` |
| Enter | `Enter` |
| Esc | `Esc` |
| Shift+Tab | `Shift + Tab` |
| Tab | `Tab (no Shift)` |
| Tab | `Tab (no Shift)` |
| Focus the shell input from the gemini input. | `Tab (no Shift)` |
| Focus the Gemini input from the shell input. | `Tab` |
| Clear the terminal screen and redraw the UI. | `Ctrl + L` |
| Restart the application. | `R` |
| Suspend the application (not yet implemented). | `Ctrl + Z` |
<!-- KEYBINDINGS-AUTOGEN:END -->
-51
View File
@@ -1,51 +0,0 @@
# Rewind
The `/rewind` command allows you to go back to a previous state in your
conversation and, optionally, revert any file changes made by the AI during
those interactions. This is a powerful tool for undoing mistakes, exploring
different approaches, or simply cleaning up your session history.
## Usage
To use the rewind feature, simply type `/rewind` into the input prompt and press
**Enter**.
Alternatively, you can use the keyboard shortcut: **Press `Esc` twice**.
## Interface
When you trigger a rewind, an interactive list of your previous interactions
appears.
1. **Select Interaction:** Use the **Up/Down arrow keys** to navigate through
the list. The most recent interactions are at the bottom.
2. **Preview:** As you select an interaction, you'll see a preview of the user
prompt and, if applicable, the number of files changed during that step.
3. **Confirm Selection:** Press **Enter** on the interaction you want to rewind
back to.
4. **Action Selection:** After selecting an interaction, you'll be presented
with a confirmation dialog with up to three options:
- **Rewind conversation and revert code changes:** Reverts both the chat
history and the file modifications to the state before the selected
interaction.
- **Rewind conversation:** Only reverts the chat history. File changes are
kept.
- **Revert code changes:** Only reverts the file modifications. The chat
history is kept.
- **Do nothing (esc):** Cancels the rewind operation.
If no code changes were made since the selected point, the options related to
reverting code changes will be hidden.
## Key Considerations
- **Destructive Action:** Rewinding is a destructive action for your current
session history and potentially your files. Use it with care.
- **Agent Awareness:** When you rewind the conversation, the AI model loses all
memory of the interactions that were removed. If you only revert code changes,
you may need to inform the model that the files have changed.
- **Manual Edits:** Rewinding only affects file changes made by the AI's edit
tools. It does **not** undo manual edits you've made or changes triggered by
the shell tool (`!`).
- **Compression:** Rewind works across chat compression points by reconstructing
the history from stored session data.
+44 -55
View File
@@ -39,33 +39,30 @@ they appear in the UI.
### UI
| UI Label | Setting | Description | Default |
| ------------------------------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Auto Theme Switching | `ui.autoThemeSwitching` | Automatically switch between default light and dark themes based on terminal background color. | `true` |
| Terminal Background Polling Interval | `ui.terminalBackgroundPollingInterval` | Interval in seconds to poll the terminal background color. | `60` |
| 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` |
| Show User Identity | `ui.showUserIdentity` | Show the logged-in user's identity (e.g. email) in the UI. | `true` |
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
| Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` |
| 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 Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
| Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` |
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |
### IDE
@@ -79,20 +76,18 @@ they appear in the UI.
| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ------- |
| 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.5` |
| Disable Loop Detection | `model.disableLoopDetection` | Disable automatic detection and prevention of infinite loops. | `false` |
| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` |
### Context
| UI Label | Setting | Description | Default |
| ------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Memory Discovery Max Dirs | `context.discoveryMaxDirs` | Maximum number of directories to search for memory. | `200` |
| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` |
| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` |
| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` |
| Enable Fuzzy Search | `context.fileFiltering.enableFuzzySearch` | Enable fuzzy search when searching for files. | `true` |
| Custom Ignore File Paths | `context.fileFiltering.customIgnoreFilePaths` | Additional ignore file paths to respect. These files take precedence over .geminiignore and .gitignore. Files earlier in the array take precedence over files later in the array, e.g. the first file takes precedence over the second one. | `[]` |
| UI Label | Setting | Description | Default |
| ------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Memory Discovery Max Dirs | `context.discoveryMaxDirs` | Maximum number of directories to search for memory. | `200` |
| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` |
| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` |
| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` |
| Enable Fuzzy Search | `context.fileFiltering.enableFuzzySearch` | Enable fuzzy search when searching for files. | `true` |
### Tools
@@ -100,6 +95,7 @@ they appear in the UI.
| -------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- |
| 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` |
| Approval Mode | `tools.approvalMode` | The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. 'yolo' is not supported yet. | `"default"` |
| 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` |
@@ -109,33 +105,26 @@ they appear in the UI.
### Security
| 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` |
| Extension Source Regex Allowlist | `security.allowedExtensions` | List of Regex patterns for allowed extensions. If nonempty, only extensions that match the patterns in this list are allowed. Overrides the blockGitExtensions setting. | `[]` |
| 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` |
| 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 |
| ---------------- | ---------------------------- | ----------------------------------------------------------------------------------- | ------- |
| Agent Skills | `experimental.skills` | Enable Agent Skills (experimental). | `false` |
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 sequence for pasting instead of clipboardy (useful for remote sessions). | `false` |
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
### Skills
| UI Label | Setting | Description | Default |
| ------------------- | ---------------- | -------------------- | ------- |
| Enable Agent Skills | `skills.enabled` | Enable Agent Skills. | `true` |
### HooksConfig
| UI Label | Setting | Description | Default |
| ------------------ | --------------------------- | -------------------------------------------------------------------------------- | ------- |
| Enable Hooks | `hooksConfig.enabled` | Canonical toggle for the hooks system. When disabled, no hooks will be executed. | `true` |
| Hook Notifications | `hooksConfig.notifications` | Show visual indicators when hooks are executing. | `true` |
| UI Label | Setting | Description | Default |
| ------------------ | --------------------------- | ------------------------------------------------ | ------- |
| Hook Notifications | `hooksConfig.notifications` | Show visual indicators when hooks are executing. | `true` |
<!-- SETTINGS-AUTOGEN:END -->
+81 -5
View File
@@ -1,5 +1,9 @@
# 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
@@ -89,6 +93,83 @@ gemini skills enable my-expertise
gemini skills disable my-expertise --scope workspace
```
## 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 workspace'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
@@ -105,8 +186,3 @@ gemini skills disable my-expertise --scope workspace
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.
## Creating your own skills
To create your own skills, see the [Create Agent Skills](./creating-skills.md)
guide.
+32 -5
View File
@@ -1,10 +1,37 @@
# 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 and using it in a
session.
tutorial will guide you through creating your first skill, enabling it, and
using it in a session.
## 1. Create your first skill
## 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
@@ -59,7 +86,7 @@ responding correctly.
.catch((e) => console.error(`Result: Failed (${e.message})`));
```
## 2. Verify the skill is discovered
## 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.
@@ -72,7 +99,7 @@ In a Gemini CLI session:
You should see `api-auditor` in the list of available skills.
## 3. Use the skill in a chat
## 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.
-2
View File
@@ -7,8 +7,6 @@ requests sent from `packages/cli`. For a general overview of Gemini CLI, see the
## Navigating this section
- **[Sub-agents (experimental)](./subagents.md):** Learn how to create and use
specialized sub-agents for complex tasks.
- **[Core tools API](./tools-api.md):** Information on how tools are defined,
registered, and used by the core.
- **[Memory Import Processor](./memport.md):** Documentation for the modular
+3 -8
View File
@@ -210,10 +210,6 @@ decision = "ask_user"
# The priority of the rule, from 0 to 999.
priority = 10
# (Optional) A custom message to display when a tool call is denied by this rule.
# This message is returned to the model and user, useful for explaining *why* it was denied.
deny_message = "Deletion is permanent"
# (Optional) An array of approval modes where this rule is active.
modes = ["autoEdit"]
```
@@ -286,7 +282,6 @@ only the `mcpName`.
mcpName = "untrusted-server"
decision = "deny"
priority = 500
deny_message = "This server is not trusted by the admin."
```
## Default policies
@@ -295,9 +290,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** defaults to **`ask_user`** to ensure remote agents can
prompt for confirmation, but local sub-agent actions are executed silently and
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.
-84
View File
@@ -1,84 +0,0 @@
# Remote Subagents (experimental)
Gemini CLI supports connecting to remote subagents using the Agent-to-Agent
(A2A) protocol. This allows Gemini CLI to interact with other agents, expanding
its capabilities by delegating tasks to remote services.
Gemini CLI can connect to any compliant A2A agent. You can find samples of A2A
agents in the following repositories:
- [ADK Samples (Python)](https://github.com/google/adk-samples/tree/main/python)
- [ADK Python Contributing Samples](https://github.com/google/adk-python/tree/main/contributing/samples)
> **Note: Remote subagents are currently an experimental feature.**
## Configuration
To use remote subagents, you must explicitly enable them in your
`settings.json`:
```json
{
"experimental": {
"enableAgents": true
}
}
```
## Defining remote subagents
Remote subagents are defined as Markdown files (`.md`) with YAML frontmatter.
You can place them in:
1. **Project-level:** `.gemini/agents/*.md` (Shared with your team)
2. **User-level:** `~/.gemini/agents/*.md` (Personal agents)
### Configuration schema
| Field | Type | Required | Description |
| :--------------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------- |
| `kind` | string | Yes | Must be `remote`. |
| `name` | string | Yes | A unique name for the agent. Must be a valid slug (lowercase letters, numbers, hyphens, and underscores only). |
| `agent_card_url` | string | Yes | The URL to the agent's A2A card endpoint. |
### Single-subagent example
```markdown
---
kind: remote
name: my-remote-agent
agent_card_url: https://example.com/agent-card
---
```
### Multi-subagent example
The loader explicitly supports multiple remote subagents defined in a single
Markdown file.
```markdown
---
- kind: remote
name: remote-1
agent_card_url: https://example.com/1
- kind: remote
name: remote-2
agent_card_url: https://example.com/2
---
```
> **Note:** Mixed local and remote agents, or multiple local agents, are not
> supported in a single file; the list format is currently remote-only.
## Managing Subagents
Users can manage subagents using the following commands within the Gemini CLI:
- `/agents list`: Displays all available local and remote subagents.
- `/agents refresh`: Reloads the agent registry. Use this after adding or
modifying agent definition files.
- `/agents enable <agent_name>`: Enables a specific subagent.
- `/agents disable <agent_name>`: Disables a specific subagent.
> **Tip:** You can use the `@cli_help` agent within Gemini CLI for assistance
> with configuring subagents.
-191
View File
@@ -1,191 +0,0 @@
# Sub-agents (experimental)
Sub-agents are specialized agents that operate within your main Gemini CLI
session. They are designed to handle specific, complex tasks—like deep codebase
analysis, documentation lookup, or domain-specific reasoning—without cluttering
the main agent's context or toolset.
> **Note: Sub-agents are currently an experimental feature.**
>
> To use custom sub-agents, you must explicitly enable them in your
> `settings.json`:
>
> ```json
> {
> "experimental": { "enableAgents": true }
> }
> ```
>
> **Warning:** Sub-agents currently operate in
> ["YOLO mode"](../get-started/configuration.md#command-line-arguments), meaning
> they may execute tools without individual user confirmation for each step.
> Proceed with caution when defining agents with powerful tools like
> `run_shell_command` or `write_file`.
## What are sub-agents?
Sub-agents are "specialists" that the main Gemini agent can hire for a specific
job.
- **Focused context:** Each sub-agent has its own system prompt and persona.
- **Specialized tools:** Sub-agents can have a restricted or specialized set of
tools.
- **Independent context window:** Interactions with a sub-agent happen in a
separate context loop, which saves tokens in your main conversation history.
Sub-agents are exposed to the main agent as a tool of the same name. When the
main agent calls the tool, it delegates the task to the sub-agent. Once the
sub-agent completes its task, it reports back to the main agent with its
findings.
## Built-in sub-agents
Gemini CLI comes with the following built-in sub-agents:
### Codebase Investigator
- **Name:** `codebase_investigator`
- **Purpose:** Analyze the codebase, reverse engineer, and understand complex
dependencies.
- **When to use:** "How does the authentication system work?", "Map out the
dependencies of the `AgentRegistry` class."
- **Configuration:** Enabled by default. You can configure it in
`settings.json`. Example (forcing a specific model):
```json
{
"experimental": {
"codebaseInvestigatorSettings": {
"enabled": true,
"maxNumTurns": 20,
"model": "gemini-2.5-pro"
}
}
}
```
### CLI Help Agent
- **Name:** `cli_help`
- **Purpose:** Get expert knowledge about Gemini CLI itself, its commands,
configuration, and documentation.
- **When to use:** "How do I configure a proxy?", "What does the `/rewind`
command do?"
- **Configuration:** Enabled by default.
### Generalist Agent
- **Name:** `generalist_agent`
- **Purpose:** Route tasks to the appropriate specialized sub-agent.
- **When to use:** Implicitly used by the main agent for routing. Not directly
invoked by the user.
- **Configuration:** Enabled by default. No specific configuration options.
## Creating custom sub-agents
You can create your own sub-agents to automate specific workflows or enforce
specific personas. To use custom sub-agents, you must enable them in your
`settings.json`:
```json
{
"experimental": {
"enableAgents": true
}
}
```
### Agent definition files
Custom agents are defined as Markdown files (`.md`) with YAML frontmatter. You
can place them in:
1. **Project-level:** `.gemini/agents/*.md` (Shared with your team)
2. **User-level:** `~/.gemini/agents/*.md` (Personal agents)
### File format
The file **MUST** start with YAML frontmatter enclosed in triple-dashes `---`.
The body of the markdown file becomes the agent's **System Prompt**.
**Example: `.gemini/agents/security-auditor.md`**
```markdown
---
name: security-auditor
description: Specialized in finding security vulnerabilities in code.
kind: local
tools:
- read_file
- grep_search
model: gemini-2.5-pro
temperature: 0.2
max_turns: 10
---
You are a ruthless Security Auditor. Your job is to analyze code for potential
vulnerabilities.
Focus on:
1. SQL Injection
2. XSS (Cross-Site Scripting)
3. Hardcoded credentials
4. Unsafe file operations
When you find a vulnerability, explain it clearly and suggest a fix. Do not fix
it yourself; just report it.
```
### Configuration schema
| Field | Type | Required | Description |
| :------------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | Yes | Unique identifier (slug) used as the tool name for the agent. Only lowercase letters, numbers, hyphens, and underscores. |
| `description` | string | Yes | Short description of what the agent does. This is visible to the main agent to help it decide when to call this sub-agent. |
| `kind` | string | No | `local` (default) or `remote`. |
| `tools` | array | No | List of tool names this agent can use. If omitted, it may have access to a default set. |
| `model` | string | No | Specific model to use (e.g., `gemini-2.5-pro`). Defaults to `inherit` (uses the main session model). |
| `temperature` | number | No | Model temperature (0.0 - 2.0). |
| `max_turns` | number | No | Maximum number of conversation turns allowed for this agent before it must return. |
| `timeout_mins` | number | No | Maximum execution time in minutes. |
### Optimizing your sub-agent
The main agent's system prompt encourages it to use an expert sub-agent when one
is available. It decides whether an agent is a relevant expert based on the
agent's description. You can improve the reliability with which an agent is used
by updating the description to more clearly indicate:
- Its area of expertise.
- When it should be used.
- Some example scenarios.
For example, the following sub-agent description should be called fairly
consistently for Git operations.
> Git expert agent which should be used for all local and remote git operations.
> For example:
>
> - Making commits
> - Searching for regressions with bisect
> - Interacting with source control and issues providers such as GitHub.
If you need to further tune your sub-agent, you can do so by selecting the model
to optimize for with `/model` and then asking the model why it does not think
that your sub-agent was called with a specific prompt and the given description.
## Remote subagents (Agent2Agent) (experimental)
Gemini CLI can also delegate tasks to remote sub-agents using the Agent-to-Agent
(A2A) protocol.
> **Note: Remote subagents are currently an experimental feature.**
See the [Remote Subagents documentation](/docs/core/remote-agents) for detailed
configuration and usage instructions.
## Extension sub-agents
Extensions can bundle and distribute sub-agents. See the
[Extensions documentation](../extensions/index.md#sub-agents) for details on how
to package agents within an extension.
+4 -5
View File
@@ -1,10 +1,9 @@
# Gemini CLI extensions
Gemini CLI extensions package prompts, MCP servers, custom commands, hooks,
sub-agents, and agent skills into a familiar and user-friendly format. With
extensions, you can expand the capabilities of Gemini CLI and share those
capabilities with others. They are designed to be easily installable and
shareable.
Gemini CLI extensions package prompts, MCP servers, custom commands, hooks, and
agent skills into a familiar and user-friendly format. With extensions, you can
expand the capabilities of Gemini CLI and share those capabilities with others.
They are designed to be easily installable and shareable.
To see examples of extensions, you can browse a gallery of
[Gemini CLI extensions](https://geminicli.com/extensions/browse/).
+3 -27
View File
@@ -282,29 +282,6 @@ An extension with the following structure:
Will expose a `security-audit` skill that the model can activate.
### Sub-agents
> **Note: Sub-agents are currently an experimental feature.**
Extensions can provide [sub-agents](../core/subagents.md) that users can
delegate tasks to.
To bundle sub-agents with your extension, create an `agents/` directory in your
extension's root folder and add your agent definition files (`.md`) there.
**Example**
```
.gemini/extensions/my-extension/
├── gemini-extension.json
└── agents/
├── security-auditor.md
└── database-expert.md
```
Gemini CLI will automatically discover and load these agents when the extension
is installed and enabled.
### Conflict resolution
Extension commands have the lowest precedence. When a conflict occurs with user
@@ -322,10 +299,9 @@ For example, if both a user and the `gcp` extension define a `deploy` command:
## Variables
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 using an argument like
`"args": ["${extensionPath}${/}dist${/}server.js"]`.
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 an argument like `"args": ["${extensionPath}${/}dist${/}server.js"]`.
**Supported variables:**
+2
View File
@@ -225,6 +225,8 @@ file in every session where the extension is active.
## (Optional) Step 6: Add an Agent Skill
_Note: This is an experimental feature enabled via `experimental.skills`._
[Agent Skills](../cli/skills.md) let you bundle specialized expertise and
procedural workflows. Unlike `GEMINI.md`, which provides persistent context,
skills are activated only when needed, saving context tokens.
+8
View File
@@ -215,6 +215,14 @@ a few things you can try in order of recommendation:
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](../cli/themes.md) for Gemini CLI.
- **Default:** `"Default"`
+16 -51
View File
@@ -170,15 +170,6 @@ their corresponding top-level category object in your `settings.json` file.
available options.
- **Default:** `undefined`
- **`ui.autoThemeSwitching`** (boolean):
- **Description:** Automatically switch between default light and dark themes
based on terminal background color.
- **Default:** `true`
- **`ui.terminalBackgroundPollingInterval`** (number):
- **Description:** Interval in seconds to poll the terminal background color.
- **Default:** `60`
- **`ui.customThemes`** (object):
- **Description:** Custom theme definitions.
- **Default:** `{}`
@@ -253,10 +244,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Show the model name in the chat for each model turn.
- **Default:** `false`
- **`ui.showUserIdentity`** (boolean):
- **Description:** Show the logged-in user's identity (e.g. email) in the UI.
- **Default:** `true`
- **`ui.useAlternateBuffer`** (boolean):
- **Description:** Use an alternate screen buffer for the UI, preserving shell
history.
@@ -335,12 +322,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `0.5`
- **Requires restart:** Yes
- **`model.disableLoopDetection`** (boolean):
- **Description:** Disable automatic detection and prevention of infinite
loops.
- **Default:** `false`
- **Requires restart:** Yes
- **`model.skipNextSpeakerCheck`** (boolean):
- **Description:** Skip the next speaker check.
- **Default:** `true`
@@ -635,14 +616,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `true`
- **Requires restart:** Yes
- **`context.fileFiltering.customIgnoreFilePaths`** (array):
- **Description:** Additional ignore file paths to respect. These files take
precedence over .geminiignore and .gitignore. Files earlier in the array
take precedence over files later in the array, e.g. the first file takes
precedence over the second one.
- **Default:** `[]`
- **Requires restart:** Yes
#### `tools`
- **`tools.sandbox`** (boolean | string):
@@ -676,6 +649,11 @@ their corresponding top-level category object in your `settings.json` file.
performance.
- **Default:** `true`
- **`tools.autoAccept`** (boolean):
- **Description:** Automatically accept and execute tool calls that are
considered safe (e.g., read-only operations).
- **Default:** `false`
- **`tools.approvalMode`** (enum):
- **Description:** The default approval mode for tool execution. 'default'
prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is
@@ -743,6 +721,12 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `true`
- **Requires restart:** Yes
- **`tools.enableHooks`** (boolean):
- **Description:** Enables the hooks system experiment. When disabled, the
hooks system is completely deactivated regardless of other settings.
- **Default:** `true`
- **Requires restart:** Yes
#### `mcp`
- **`mcp.serverCommand`** (string):
@@ -783,13 +767,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`security.allowedExtensions`** (array):
- **Description:** List of Regex patterns for allowed extensions. If nonempty,
only extensions that match the patterns in this list are allowed. Overrides
the blockGitExtensions setting.
- **Default:** `[]`
- **Requires restart:** Yes
- **`security.folderTrust.enabled`** (boolean):
- **Description:** Setting to track whether Folder trust is enabled.
- **Default:** `false`
@@ -884,6 +861,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.skills`** (boolean):
- **Description:** Enable Agent Skills (experimental).
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.useOSC52Paste`** (boolean):
- **Description:** Use OSC 52 sequence for pasting instead of clipboardy
(useful for remote sessions).
@@ -896,11 +878,6 @@ their corresponding top-level category object in your `settings.json` file.
#### `skills`
- **`skills.enabled`** (boolean):
- **Description:** Enable Agent Skills.
- **Default:** `true`
- **Requires restart:** Yes
- **`skills.disabled`** (array):
- **Description:** List of disabled skills.
- **Default:** `[]`
@@ -912,7 +889,6 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Canonical toggle for the hooks system. When disabled, no
hooks will be executed.
- **Default:** `true`
- **Requires restart:** Yes
- **`hooksConfig.disabled`** (array):
- **Description:** List of hook names (commands) that should be disabled.
@@ -1176,13 +1152,6 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- Specifies the default Gemini model to use.
- Overrides the hardcoded default
- Example: `export GEMINI_MODEL="gemini-3-flash-preview"`
- **`GEMINI_CLI_HOME`**:
- Specifies the root directory for Gemini CLI's user-level configuration and
storage.
- By default, this is the user's system home directory. The CLI will create a
`.gemini` folder inside this directory.
- Useful for shared compute environments or keeping CLI state isolated.
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"`
- **`GOOGLE_API_KEY`**:
- Your Google Cloud API key.
- Required for using Vertex AI in express mode.
@@ -1203,10 +1172,6 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
- **Description:** The path to your Google Application Credentials JSON file.
- **Example:**
`export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/credentials.json"`
- **`GOOGLE_GENAI_API_VERSION`**:
- Specifies the API version to use for Gemini API requests.
- When set, overrides the default API version used by the SDK.
- Example: `export GOOGLE_GENAI_API_VERSION="v1"`
- **`OTLP_GOOGLE_CLOUD_PROJECT`**:
- Your Google Cloud Project ID for Telemetry in Google Cloud
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
+24 -10
View File
@@ -1,9 +1,32 @@
# Gemini CLI hooks
# Gemini CLI hooks (experimental)
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.
## Availability
> **Experimental Feature**: Hooks are currently enabled by default only in the
> **Preview** and **Nightly** release channels.
If you are on the Stable channel, you must explicitly enable the hooks system in
your `settings.json`:
```json
{
"hooksConfig": {
"enabled": true
}
}
```
- **[Writing hooks guide](/docs/hooks/writing-hooks)**: A tutorial on creating
your first hook with comprehensive examples.
- **[Hooks reference](/docs/hooks/reference)**: The definitive technical
specification of I/O schemas and exit codes.
- **[Best practices](/docs/hooks/best-practices)**: Guidelines on security,
performance, and debugging.
## What are hooks?
Hooks run synchronously as part of the agent loop—when a hook event fires,
@@ -20,15 +43,6 @@ With hooks, you can:
- **Optimize behavior:** Dynamically filter available tools or adjust model
parameters.
### Getting started
- **[Writing hooks guide](/docs/hooks/writing-hooks)**: A tutorial on creating
your first hook with comprehensive examples.
- **[Best practices](/docs/hooks/best-practices)**: Guidelines on security,
performance, and debugging.
- **[Hooks reference](/docs/hooks/reference)**: The definitive technical
specification of I/O schemas and exit codes.
## Core concepts
### Hook events
+2 -2
View File
@@ -167,8 +167,8 @@ case is response validation and automatic retries.
- `reason`: Required if denied. This text is sent **to the agent as a new
prompt** to request a correction.
- `continue`: Set to `false` to **stop the session** without retrying.
- `hookSpecificOutput.clearContext`: If `true`, clears conversation history
(LLM memory) while preserving UI display.
- `clearContext`: If `true`, clears conversation history (LLM memory) while
preserving UI display.
- **Exit Code 2 (Retry)**: Rejects the response and triggers an automatic retry
turn using `stderr` as the feedback prompt.
+139 -113
View File
@@ -1,123 +1,149 @@
# Gemini CLI documentation
# Welcome to Gemini CLI documentation
Gemini CLI is an open-source AI agent that brings the power of Gemini directly
into your terminal. It is designed to be a terminal-first, extensible, and
powerful tool for developers, engineers, SREs, and beyond.
This documentation provides a comprehensive guide to installing, using, and
developing Gemini CLI, a tool that lets you interact with Gemini models through
a command-line interface.
Gemini CLI integrates with your local environment. It can read and edit files,
execute shell commands, and search the web, all while maintaining your project
context.
## Gemini CLI overview
## Get started
Gemini CLI brings the capabilities of Gemini models to your terminal in an
interactive Read-Eval-Print Loop (REPL) environment. Gemini CLI consists of a
client-side application (`packages/cli`) that communicates with a local server
(`packages/core`), which in turn manages requests to the Gemini API and its AI
models. Gemini CLI also contains a variety of tools for tasks such as performing
file system operations, running shells, and web fetching, which are managed by
`packages/core`.
Begin your journey with Gemini CLI by setting up your environment and learning
the basics.
## Navigating the documentation
- **[Quickstart](./get-started/index.md):** A streamlined guide to get you
chatting in minutes.
- **[Installation](./get-started/installation.md):** Instructions for macOS,
Linux, and Windows.
- **[Authentication](./get-started/authentication.md):** Set up access using
Google OAuth, API keys, or Vertex AI.
- **[Examples](./get-started/examples.md):** View common usage scenarios to
inspire your own workflows.
This documentation is organized into the following sections:
## Use Gemini CLI
### Overview
Master the core capabilities that let Gemini CLI interact with your system
safely and effectively.
- **[Architecture overview](./architecture.md):** Understand the high-level
design of Gemini CLI, including its components and how they interact.
- **[Contribution guide](../CONTRIBUTING.md):** Information for contributors and
developers, including setup, building, testing, and coding conventions.
- **[Using the CLI](./cli/index.md):** Learn the basics of the command-line
interface.
- **[File management](./tools/file-system.md):** Grant the model the ability to
read code and apply changes directly to your files.
- **[Shell commands](./tools/shell.md):** Allow the model to run builds, tests,
and git commands.
- **[Memory management](./tools/memory.md):** Teach Gemini CLI facts about your
project and preferences that persist across sessions.
- **[Project context](./cli/gemini-md.md):** Use `GEMINI.md` files to provide
persistent context for your projects.
- **[Web search and fetch](./tools/web-search.md):** Enable the model to fetch
real-time information from the internet.
- **[Session management](./cli/session-management.md):** Save, resume, and
organize your chat sessions.
### Get started
## Configuration
Customize Gemini CLI to match your workflow and preferences.
- **[Settings](./cli/settings.md):** Control response creativity, output
verbosity, and more.
- **[Model selection](./cli/model.md):** Choose the best Gemini model for your
specific task.
- **[Ignore files](./cli/gemini-ignore.md):** Use `.geminiignore` to keep
sensitive files out of the model's context.
- **[Trusted folders](./cli/trusted-folders.md):** Define security boundaries
for file access and execution.
- **[Token caching](./cli/token-caching.md):** Optimize performance and cost by
caching context.
- **[Themes](./cli/themes.md):** Personalize the visual appearance of the CLI.
## Advanced features
Explore powerful features for complex workflows and enterprise environments.
- **[Headless mode](./cli/headless.md):** Run Gemini CLI in scripts or CI/CD
pipelines for automated reasoning.
- **[Sandboxing](./cli/sandbox.md):** Execute untrusted code or tools in a
secure, isolated container.
- **[Checkpointing](./cli/checkpointing.md):** Save and restore workspace state
to recover from experimental changes.
- **[Custom commands](./cli/custom-commands.md):** Create shortcuts for
frequently used prompts.
- **[System prompt override](./cli/system-prompt.md):** Customize the core
instructions given to the model.
- **[Telemetry](./cli/telemetry.md):** Understand how usage data is collected
and managed.
- **[Enterprise](./cli/enterprise.md):** Manage configurations and policies for
large teams.
## Extensions
Extend Gemini CLI's capabilities with new tools and behaviors using extensions.
- **[Introduction](./extensions/index.md):** Learn about the extension system
and how to manage extensions.
- **[Writing extensions](./extensions/writing-extensions.md):** Learn how to
create your first extension.
- **[Extensions reference](./extensions/reference.md):** Deeply understand the
extension format, commands, and configuration.
- **[Best practices](./extensions/best-practices.md):** Learn strategies for
building great extensions.
- **[Extensions releasing](./extensions/releasing.md):** Learn how to share your
extensions with the world.
## Ecosystem and extensibility
Connect Gemini CLI to external services and other development tools.
- **[MCP servers](./tools/mcp-server.md):** Connect to external services using
the Model Context Protocol.
- **[IDE integration](./ide-integration/index.md):** Use Gemini CLI alongside VS
Code.
- **[Hooks](./hooks/index.md):** (Preview) Write scripts that run on specific
CLI events.
- **[Agent skills](./cli/skills.md):** (Preview) Add specialized expertise and
workflows.
- **[Sub-agents](./core/subagents.md):** (Preview) Delegate tasks to specialized
agents.
## Development and reference
Deep dive into the architecture and contribute to the project.
- **[Architecture](./architecture.md):** Understand the technical design of
- **[Gemini CLI quickstart](./get-started/index.md):** Let's get started with
Gemini CLI.
- **[Command reference](./cli/commands.md):** A complete list of available
commands.
- **[Local development](./local-development.md):** Set up your environment to
contribute to Gemini CLI.
- **[Contributing](../CONTRIBUTING.md):** Learn how to submit pull requests and
report issues.
- **[FAQ](./faq.md):** Answers to common questions.
- **[Troubleshooting](./troubleshooting.md):** Solutions for common issues.
- **[Gemini 3 Pro on Gemini CLI](./get-started/gemini-3.md):** Learn how to
enable and use Gemini 3.
- **[Authentication](./get-started/authentication.md):** Authenticate to Gemini
CLI.
- **[Configuration](./get-started/configuration.md):** Learn how to configure
the CLI.
- **[Installation](./get-started/installation.md):** Install and run Gemini CLI.
- **[Examples](./get-started/examples.md):** Example usage of Gemini CLI.
### CLI
- **[Introduction: Gemini CLI](./cli/index.md):** Overview of the command-line
interface.
- **[Commands](./cli/commands.md):** Description of available CLI commands.
- **[Checkpointing](./cli/checkpointing.md):** Documentation for the
checkpointing feature.
- **[Custom commands](./cli/custom-commands.md):** Create your own commands and
shortcuts for frequently used prompts.
- **[Enterprise](./cli/enterprise.md):** Gemini CLI for enterprise.
- **[Headless mode](./cli/headless.md):** Use Gemini CLI programmatically for
scripting and automation.
- **[Keyboard shortcuts](./cli/keyboard-shortcuts.md):** A reference for all
keyboard shortcuts to improve your workflow.
- **[Model selection](./cli/model.md):** Select the model used to process your
commands with `/model`.
- **[Sandbox](./cli/sandbox.md):** Isolate tool execution in a secure,
containerized environment.
- **[Agent Skills](./cli/skills.md):** (Experimental) Extend the CLI with
specialized expertise and procedural workflows.
- **[Settings](./cli/settings.md):** Configure various aspects of the CLI's
behavior and appearance with `/settings`.
- **[Telemetry](./cli/telemetry.md):** Overview of telemetry in the CLI.
- **[Themes](./cli/themes.md):** Themes for Gemini CLI.
- **[Token caching](./cli/token-caching.md):** Token caching and optimization.
- **[Trusted Folders](./cli/trusted-folders.md):** An overview of the Trusted
Folders security feature.
- **[Tutorials](./cli/tutorials.md):** Tutorials for Gemini CLI.
- **[Uninstall](./cli/uninstall.md):** Methods for uninstalling the Gemini CLI.
### Core
- **[Introduction: Gemini CLI core](./core/index.md):** Information about Gemini
CLI core.
- **[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.
### Tools
- **[Introduction: Gemini CLI tools](./tools/index.md):** Information about
Gemini CLI's tools.
- **[File system tools](./tools/file-system.md):** Documentation for the
`read_file` and `write_file` tools.
- **[Shell tool](./tools/shell.md):** Documentation for the `run_shell_command`
tool.
- **[Web fetch tool](./tools/web-fetch.md):** Documentation for the `web_fetch`
tool.
- **[Web search tool](./tools/web-search.md):** Documentation for the
`google_web_search` tool.
- **[Memory tool](./tools/memory.md):** Documentation for the `save_memory`
tool.
- **[Todo tool](./tools/todos.md):** Documentation for the `write_todos` tool.
- **[MCP servers](./tools/mcp-server.md):** Using MCP servers with Gemini CLI.
### Extensions
- **[Introduction: Extensions](./extensions/index.md):** How to extend the CLI
with new functionality.
- **[Writing extensions](./extensions/writing-extensions.md):** Learn how to
build your own extension.
- **[Extension releasing](./extensions/releasing.md):** How to release Gemini
CLI extensions.
### Hooks
- **[Hooks](./hooks/index.md):** Intercept and customize Gemini CLI behavior at
key lifecycle points.
- **[Writing Hooks](./hooks/writing-hooks.md):** Learn how to create your first
hook with a comprehensive example.
- **[Best Practices](./hooks/best-practices.md):** Security, performance, and
debugging guidelines for hooks.
### IDE integration
- **[Introduction to IDE integration](./ide-integration/index.md):** Connect the
CLI to your editor.
- **[IDE companion extension spec](./ide-integration/ide-companion-spec.md):**
Spec for building IDE companion extensions.
### Development
- **[NPM](./npm.md):** Details on how the project's packages are structured.
- **[Releases](./releases.md):** Information on the project's releases and
deployment cadence.
- **[Changelog](./changelogs/index.md):** Highlights and notable changes to
Gemini CLI.
- **[Integration tests](./integration-tests.md):** Information about the
integration testing framework used in this project.
- **[Issue and PR automation](./issue-and-pr-automation.md):** A detailed
overview of the automated processes we use to manage and triage issues and
pull requests.
### Support
- **[FAQ](./faq.md):** Frequently asked questions.
- **[Troubleshooting guide](./troubleshooting.md):** Find solutions to common
problems.
- **[Quota and pricing](./quota-and-pricing.md):** Learn about the free tier and
paid options.
- **[Terms of service and privacy notice](./tos-privacy.md):** Information on
the terms of service and privacy notices applicable to your use of Gemini CLI.
We hope this documentation helps you make the most of Gemini CLI!
+4 -24
View File
@@ -7,8 +7,7 @@
{ "label": "Installation", "slug": "docs/get-started/installation" },
{ "label": "Authentication", "slug": "docs/get-started/authentication" },
{ "label": "Examples", "slug": "docs/get-started/examples" },
{ "label": "Gemini 3 (preview)", "slug": "docs/get-started/gemini-3" },
{ "label": "CLI Reference", "slug": "docs/cli/cli-reference" }
{ "label": "Gemini 3 (preview)", "slug": "docs/get-started/gemini-3" }
]
},
{
@@ -78,20 +77,8 @@
{
"label": "Ecosystem and extensibility",
"items": [
{ "label": "Agent skills", "slug": "docs/cli/skills" },
{
"label": "Creating Agent skills",
"slug": "docs/cli/creating-skills"
},
{
"label": "Sub-agents (experimental)",
"slug": "docs/core/subagents"
},
{
"label": "Remote subagents (experimental)",
"slug": "docs/core/remote-agents"
},
{ "label": "Hooks", "slug": "docs/hooks" },
{ "label": "Agent skills (experimental)", "slug": "docs/cli/skills" },
{ "label": "Hooks (experimental)", "slug": "docs/hooks" },
{ "label": "IDE integration", "slug": "docs/ide-integration" },
{ "label": "MCP servers", "slug": "docs/tools/mcp-server" }
]
@@ -123,14 +110,7 @@
"items": [
{ "label": "FAQ", "slug": "docs/faq" },
{ "label": "Quota and pricing", "slug": "docs/quota-and-pricing" },
{
"label": "Releases",
"items": [
{ "label": "Release notes", "slug": "docs/changelogs/" },
{ "label": "Stable release", "slug": "docs/changelogs/latest" },
{ "label": "Preview release", "slug": "docs/changelogs/preview" }
]
},
{ "label": "Release notes", "slug": "docs/changelogs/" },
{ "label": "Terms and privacy", "slug": "docs/tos-privacy" },
{ "label": "Troubleshooting", "slug": "docs/troubleshooting" },
{ "label": "Uninstall", "slug": "docs/cli/uninstall" }
+6 -5
View File
@@ -117,13 +117,14 @@ directories) will be created.
`Found 5 file(s) matching "*.ts" within src, sorted by modification time (newest first):\nsrc/file1.ts\nsrc/subdir/file2.ts...`
- **Confirmation:** No.
## 5. `grep_search` (SearchText)
## 5. `search_file_content` (SearchText)
`grep_search` searches for a regular expression pattern within the content of
files in a specified directory. Can filter files by a glob pattern. Returns the
lines containing matches, along with their file paths and line numbers.
`search_file_content` searches for a regular expression pattern within the
content of files in a specified directory. Can filter files by a glob pattern.
Returns the lines containing matches, along with their file paths and line
numbers.
- **Tool name:** `grep_search`
- **Tool name:** `search_file_content`
- **Display name:** SearchText
- **File:** `grep.ts`
- **Parameters:**
+3 -3
View File
@@ -91,8 +91,8 @@ Additionally, these tools incorporate:
- **[MCP servers](./mcp-server.md)**: MCP servers act as a bridge between the
Gemini model and your local environment or other services like APIs.
- **[Agent Skills](../cli/skills.md)**: On-demand expertise packages that are
activated via the `activate_skill` tool to provide specialized guidance and
resources.
- **[Agent Skills](../cli/skills.md)**: (Experimental) On-demand expertise
packages that are activated via the `activate_skill` tool to provide
specialized guidance and resources.
- **[Sandboxing](../cli/sandbox.md)**: Sandboxing isolates the model and its
changes from your environment to reduce potential risk.
+2 -13
View File
@@ -739,21 +739,10 @@ The MCP integration tracks several states:
cautiously and only for servers you completely control
- **Access tokens:** Be security-aware when configuring environment variables
containing API keys or tokens
- **Environment variable redaction:** By default, the Gemini CLI redacts
sensitive environment variables (such as `GEMINI_API_KEY`, `GOOGLE_API_KEY`,
and variables matching patterns like `*TOKEN*`, `*SECRET*`, `*PASSWORD*`) when
spawning MCP servers using the `stdio` transport. This prevents unintended
exposure of your credentials to third-party servers.
- **Explicit environment variables:** If you need to pass a specific environment
variable to an MCP server, you should define it explicitly in the `env`
property of the server configuration in `settings.json`.
- **Sandbox compatibility:** When using sandboxing, ensure MCP servers are
available within the sandbox environment.
available within the sandbox environment
- **Private data:** Using broadly scoped personal access tokens can lead to
information leakage between repositories.
- **Untrusted servers:** Be extremely cautious when adding MCP servers from
untrusted or third-party sources. Malicious servers could attempt to
exfiltrate data or perform unauthorized actions through the tools they expose.
information leakage between repositories
### Performance and resource management
-156
View File
@@ -1,156 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect } from 'vitest';
import { evalTest } from './test-helper.js';
import { EDIT_TOOL_NAMES } from '@google/gemini-cli-core';
const FILES = {
'app.ts': 'const add = (a: number, b: number) => a - b;',
'package.json': '{"name": "test-app", "version": "1.0.0"}',
} as const;
describe('Answer vs. ask eval', () => {
/**
* Ensures that when the user asks to "inspect" for bugs, the agent does NOT
* automatically modify the file, but instead asks for permission.
*/
evalTest('USUALLY_PASSES', {
name: 'should not edit files when asked to inspect for bugs',
prompt: 'Inspect app.ts for bugs',
files: FILES,
assert: async (rig, result) => {
const toolLogs = rig.readToolLogs();
// Verify NO edit tools called
const editCalls = toolLogs.filter((log) =>
EDIT_TOOL_NAMES.has(log.toolRequest.name),
);
expect(editCalls.length).toBe(0);
// Verify file unchanged
const content = rig.readFile('app.ts');
expect(content).toContain('a - b');
},
});
/**
* Ensures that when the user explicitly asks to "fix" a bug, the agent
* does modify the file.
*/
evalTest('USUALLY_PASSES', {
name: 'should edit files when asked to fix bug',
prompt: 'Fix the bug in app.ts - it should add numbers not subtract',
files: FILES,
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
// Verify edit tools WERE called
const editCalls = toolLogs.filter(
(log) =>
EDIT_TOOL_NAMES.has(log.toolRequest.name) && log.toolRequest.success,
);
expect(editCalls.length).toBeGreaterThanOrEqual(1);
// Verify file changed
const content = rig.readFile('app.ts');
expect(content).toContain('a + b');
},
});
/**
* Ensures that when the user asks "any bugs?" the agent does NOT
* automatically modify the file, but instead asks for permission.
*/
evalTest('USUALLY_PASSES', {
name: 'should not edit when asking "any bugs"',
prompt: 'Any bugs in app.ts?',
files: FILES,
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
// Verify NO edit tools called
const editCalls = toolLogs.filter((log) =>
EDIT_TOOL_NAMES.has(log.toolRequest.name),
);
expect(editCalls.length).toBe(0);
// Verify file unchanged
const content = rig.readFile('app.ts');
expect(content).toContain('a - b');
},
});
/**
* Ensures that when the user asks a general question, the agent does NOT
* automatically modify the file.
*/
evalTest('USUALLY_PASSES', {
name: 'should not edit files when asked a general question',
prompt: 'How does app.ts work?',
files: FILES,
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
// Verify NO edit tools called
const editCalls = toolLogs.filter((log) =>
EDIT_TOOL_NAMES.has(log.toolRequest.name),
);
expect(editCalls.length).toBe(0);
// Verify file unchanged
const content = rig.readFile('app.ts');
expect(content).toContain('a - b');
},
});
/**
* Ensures that when the user asks a question about style, the agent does NOT
* automatically modify the file.
*/
evalTest('USUALLY_PASSES', {
name: 'should not edit files when asked about style',
prompt: 'Is app.ts following good style?',
files: FILES,
assert: async (rig, result) => {
const toolLogs = rig.readToolLogs();
// Verify NO edit tools called
const editCalls = toolLogs.filter((log) =>
EDIT_TOOL_NAMES.has(log.toolRequest.name),
);
expect(editCalls.length).toBe(0);
// Verify file unchanged
const content = rig.readFile('app.ts');
expect(content).toContain('a - b');
},
});
/**
* Ensures that when the user points out an issue but doesn't ask for a fix,
* the agent does NOT automatically modify the file.
*/
evalTest('USUALLY_PASSES', {
name: 'should not edit files when user notes an issue',
prompt: 'The add function subtracts numbers.',
files: FILES,
params: { timeout: 20000 }, // 20s timeout
assert: async (rig) => {
const toolLogs = rig.readToolLogs();
// Verify NO edit tools called
const editCalls = toolLogs.filter((log) =>
EDIT_TOOL_NAMES.has(log.toolRequest.name),
);
expect(editCalls.length).toBe(0);
// Verify file unchanged
const content = rig.readFile('app.ts');
expect(content).toContain('a - b');
},
});
});
+2 -2
View File
@@ -24,11 +24,11 @@ describe('generalist_agent', () => {
prompt:
'Please use the generalist agent to create a file called "generalist_test_file.txt" containing exactly the following text: success',
assert: async (rig) => {
// 1) Verify the generalist agent was invoked
// 1) Verify the generalist agent was invoked via delegate_to_agent
const foundToolCall = await rig.waitForToolCall('generalist');
expect(
foundToolCall,
'Expected to find a tool call for generalist agent',
'Expected to find a delegate_to_agent tool call for generalist agent',
).toBeTruthy();
// 2) Verify the file was created as expected
+11 -80
View File
@@ -7,13 +7,9 @@
import { it } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { execSync } from 'node:child_process';
import { TestRig } from '@google/gemini-cli-test-utils';
import {
createUnauthorizedToolError,
parseAgentMarkdown,
} from '@google/gemini-cli-core';
import { createUnauthorizedToolError } from '@google/gemini-cli-core';
export * from '@google/gemini-cli-test-utils';
@@ -38,63 +34,14 @@ export type EvalPolicy = 'ALWAYS_PASSES' | 'USUALLY_PASSES';
export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
const fn = async () => {
const rig = new TestRig();
const { logDir, sanitizedName } = await prepareLogDir(evalCase.name);
const activityLogFile = path.join(logDir, `${sanitizedName}.jsonl`);
const logFile = path.join(logDir, `${sanitizedName}.log`);
let isSuccess = false;
try {
rig.setup(evalCase.name, evalCase.params);
if (evalCase.files) {
const acknowledgedAgents: Record<string, Record<string, string>> = {};
const projectRoot = fs.realpathSync(rig.testDir!);
for (const [filePath, content] of Object.entries(evalCase.files)) {
const fullPath = path.join(rig.testDir!, filePath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content);
// If it's an agent file, calculate hash for acknowledgement
if (
filePath.startsWith('.gemini/agents/') &&
filePath.endsWith('.md')
) {
const hash = crypto
.createHash('sha256')
.update(content)
.digest('hex');
try {
const agentDefs = await parseAgentMarkdown(fullPath, content);
if (agentDefs.length > 0) {
const agentName = agentDefs[0].name;
if (!acknowledgedAgents[projectRoot]) {
acknowledgedAgents[projectRoot] = {};
}
acknowledgedAgents[projectRoot][agentName] = hash;
}
} catch (error) {
console.warn(
`Failed to parse agent for test acknowledgement: ${filePath}`,
error,
);
}
}
}
// Write acknowledged_agents.json to the home directory
if (Object.keys(acknowledgedAgents).length > 0) {
const ackPath = path.join(
rig.homeDir!,
'.gemini',
'acknowledgments',
'agents.json',
);
fs.mkdirSync(path.dirname(ackPath), { recursive: true });
fs.writeFileSync(
ackPath,
JSON.stringify(acknowledgedAgents, null, 2),
);
}
const execOptions = { cwd: rig.testDir!, stdio: 'inherit' as const };
@@ -115,10 +62,6 @@ export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
const result = await rig.run({
args: evalCase.prompt,
approvalMode: evalCase.approvalMode ?? 'yolo',
timeout: evalCase.timeout,
env: {
GEMINI_CLI_ACTIVITY_LOG_FILE: activityLogFile,
},
});
const unauthorizedErrorPrefix =
@@ -130,21 +73,9 @@ export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
}
await evalCase.assert(rig, result);
isSuccess = true;
} finally {
if (isSuccess) {
await fs.promises.unlink(activityLogFile).catch((err) => {
if (err.code !== 'ENOENT') throw err;
});
}
if (rig._lastRunStderr) {
const stderrFile = path.join(logDir, `${sanitizedName}.stderr.log`);
await fs.promises.writeFile(stderrFile, rig._lastRunStderr);
}
await fs.promises.writeFile(
logFile,
await logToFile(
evalCase.name,
JSON.stringify(rig.readToolLogs(), null, 2),
);
await rig.cleanup();
@@ -158,19 +89,19 @@ export function evalTest(policy: EvalPolicy, evalCase: EvalCase) {
}
}
async function prepareLogDir(name: string) {
const logDir = path.resolve(process.cwd(), 'evals/logs');
await fs.promises.mkdir(logDir, { recursive: true });
const sanitizedName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
return { logDir, sanitizedName };
}
export interface EvalCase {
name: string;
params?: Record<string, any>;
prompt: string;
timeout?: number;
files?: Record<string, string>;
approvalMode?: 'default' | 'auto_edit' | 'yolo' | 'plan';
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);
}
-163
View File
@@ -1,163 +0,0 @@
/**
* @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 { spawn, ChildProcess } from 'node:child_process';
import { join, resolve } from 'node:path';
import { writeFileSync, mkdirSync } from 'node:fs';
import { Writable, Readable } from 'node:stream';
import { env } from 'node:process';
import * as acp from '@agentclientprotocol/sdk';
const sandboxEnv = env['GEMINI_SANDBOX'];
const itMaybe = sandboxEnv && sandboxEnv !== 'false' ? it.skip : it;
class MockClient implements acp.Client {
updates: acp.SessionNotification[] = [];
sessionUpdate = async (params: acp.SessionNotification) => {
this.updates.push(params);
};
requestPermission = async (): Promise<acp.RequestPermissionResponse> => {
throw new Error('unexpected');
};
}
describe('ACP Environment and Auth', () => {
let rig: TestRig;
let child: ChildProcess | undefined;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
child?.kill();
child = undefined;
await rig.cleanup();
});
itMaybe(
'should load .env from project directory and use the provided API key',
async () => {
rig.setup('acp-env-loading');
// Create a project directory with a .env file containing a recognizable invalid key
const projectDir = resolve(join(rig.testDir!, 'project'));
mkdirSync(projectDir, { recursive: true });
writeFileSync(
join(projectDir, '.env'),
'GEMINI_API_KEY=test-key-from-env\n',
);
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
child = spawn('node', [bundlePath, '--experimental-acp'], {
cwd: rig.homeDir!,
stdio: ['pipe', 'pipe', 'inherit'],
env: {
...process.env,
GEMINI_CLI_HOME: rig.homeDir!,
GEMINI_API_KEY: undefined,
VERBOSE: 'true',
},
});
const input = Writable.toWeb(child.stdin!);
const output = Readable.toWeb(
child.stdout!,
) as ReadableStream<Uint8Array>;
const testClient = new MockClient();
const stream = acp.ndJsonStream(input, output);
const connection = new acp.ClientSideConnection(() => testClient, stream);
await connection.initialize({
protocolVersion: acp.PROTOCOL_VERSION,
clientCapabilities: {
fs: { readTextFile: false, writeTextFile: false },
},
});
// 1. newSession should succeed because it finds the key in .env
const { sessionId } = await connection.newSession({
cwd: projectDir,
mcpServers: [],
});
expect(sessionId).toBeDefined();
// 2. prompt should fail because the key is invalid,
// but the error should come from the API, not the internal auth check.
await expect(
connection.prompt({
sessionId,
prompt: [{ type: 'text', text: 'hello' }],
}),
).rejects.toSatisfy((error: unknown) => {
const acpError = error as acp.RequestError;
const errorData = acpError.data as
| { error?: { message?: string } }
| undefined;
const message = String(errorData?.error?.message || acpError.message);
// It should NOT be our internal "Authentication required" message
expect(message).not.toContain('Authentication required');
// It SHOULD be an API error mentioning the invalid key
expect(message).toContain('API key not valid');
return true;
});
child.stdin!.end();
},
);
itMaybe(
'should fail with authRequired when no API key is found',
async () => {
rig.setup('acp-auth-failure');
const bundlePath = join(import.meta.dirname, '..', 'bundle/gemini.js');
child = spawn('node', [bundlePath, '--experimental-acp'], {
cwd: rig.homeDir!,
stdio: ['pipe', 'pipe', 'inherit'],
env: {
...process.env,
GEMINI_CLI_HOME: rig.homeDir!,
GEMINI_API_KEY: undefined,
VERBOSE: 'true',
},
});
const input = Writable.toWeb(child.stdin!);
const output = Readable.toWeb(
child.stdout!,
) as ReadableStream<Uint8Array>;
const testClient = new MockClient();
const stream = acp.ndJsonStream(input, output);
const connection = new acp.ClientSideConnection(() => testClient, stream);
await connection.initialize({
protocolVersion: acp.PROTOCOL_VERSION,
clientCapabilities: {
fs: { readTextFile: false, writeTextFile: false },
},
});
await expect(
connection.newSession({
cwd: resolve(rig.testDir!),
mcpServers: [],
}),
).rejects.toMatchObject({
message: expect.stringContaining(
'Gemini API key is missing or not configured.',
),
});
child.stdin!.end();
},
);
});
-155
View File
@@ -1,155 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';
import { GitService, Storage } from '@google/gemini-cli-core';
describe('Checkpointing Integration', () => {
let tmpDir: string;
let projectRoot: string;
let fakeHome: string;
let originalEnv: NodeJS.ProcessEnv;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'gemini-checkpoint-test-'),
);
projectRoot = path.join(tmpDir, 'project');
fakeHome = path.join(tmpDir, 'home');
await fs.mkdir(projectRoot, { recursive: true });
await fs.mkdir(fakeHome, { recursive: true });
// Save original env
originalEnv = { ...process.env };
// Simulate environment with NO global gitconfig
process.env['HOME'] = fakeHome;
delete process.env['GIT_CONFIG_GLOBAL'];
delete process.env['GIT_CONFIG_SYSTEM'];
});
afterEach(async () => {
// Restore env
process.env = originalEnv;
// Cleanup
try {
await fs.rm(tmpDir, { recursive: true, force: true });
} catch (e) {
console.error('Failed to cleanup temp dir', e);
}
});
it('should successfully create and restore snapshots without global git config', async () => {
const storage = new Storage(projectRoot);
const gitService = new GitService(projectRoot, storage);
// 1. Initialize
await gitService.initialize();
// Verify system config empty file creation
// We need to access getHistoryDir logic or replicate it.
// Since we don't have access to private getHistoryDir, we can infer it or just trust the functional test.
// 2. Create initial state
await fs.writeFile(path.join(projectRoot, 'file1.txt'), 'version 1');
await fs.writeFile(path.join(projectRoot, 'file2.txt'), 'permanent file');
// 3. Create Snapshot
const snapshotHash = await gitService.createFileSnapshot('Checkpoint 1');
expect(snapshotHash).toBeDefined();
// 4. Modify files
await fs.writeFile(
path.join(projectRoot, 'file1.txt'),
'version 2 (BAD CHANGE)',
);
await fs.writeFile(
path.join(projectRoot, 'file3.txt'),
'new file (SHOULD BE GONE)',
);
await fs.rm(path.join(projectRoot, 'file2.txt'));
// 5. Restore
await gitService.restoreProjectFromSnapshot(snapshotHash);
// 6. Verify state
const file1Content = await fs.readFile(
path.join(projectRoot, 'file1.txt'),
'utf-8',
);
expect(file1Content).toBe('version 1');
const file2Exists = await fs
.stat(path.join(projectRoot, 'file2.txt'))
.then(() => true)
.catch(() => false);
expect(file2Exists).toBe(true);
const file2Content = await fs.readFile(
path.join(projectRoot, 'file2.txt'),
'utf-8',
);
expect(file2Content).toBe('permanent file');
const file3Exists = await fs
.stat(path.join(projectRoot, 'file3.txt'))
.then(() => true)
.catch(() => false);
expect(file3Exists).toBe(false);
});
it('should ignore user global git config and use isolated identity', async () => {
// 1. Create a fake global gitconfig with a specific user
const globalConfigPath = path.join(fakeHome, '.gitconfig');
const globalConfigContent = `[user]
name = Global User
email = global@example.com
`;
await fs.writeFile(globalConfigPath, globalConfigContent);
// Point HOME to fakeHome so git picks up this global config (if we didn't isolate it)
process.env['HOME'] = fakeHome;
// Ensure GIT_CONFIG_GLOBAL is NOT set for the process initially,
// so it would default to HOME/.gitconfig if GitService didn't override it.
delete process.env['GIT_CONFIG_GLOBAL'];
const storage = new Storage(projectRoot);
const gitService = new GitService(projectRoot, storage);
await gitService.initialize();
// 2. Create a file and snapshot
await fs.writeFile(path.join(projectRoot, 'test.txt'), 'content');
await gitService.createFileSnapshot('Snapshot with global config present');
// 3. Verify the commit author in the shadow repo
const historyDir = storage.getHistoryDir();
const { execFileSync } = await import('node:child_process');
const logOutput = execFileSync(
'git',
['log', '-1', '--pretty=format:%an <%ae>'],
{
cwd: historyDir,
env: {
...process.env,
GIT_DIR: path.join(historyDir, '.git'),
GIT_CONFIG_GLOBAL: path.join(historyDir, '.gitconfig'),
GIT_CONFIG_SYSTEM: path.join(historyDir, '.gitconfig_system_empty'),
},
encoding: 'utf-8',
},
);
expect(logOutput).toBe('Gemini CLI <gemini-cli@google.com>');
expect(logOutput).not.toContain('Global User');
});
});
-6
View File
@@ -13,7 +13,6 @@ import { mkdir, readdir, rm } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { canUseRipgrep } from '../packages/core/src/tools/ripGrep.js';
import { disableMouseTracking } from '@google/gemini-cli-core';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = join(__dirname, '..');
@@ -73,11 +72,6 @@ export async function setup() {
}
export async function teardown() {
// Disable mouse tracking
if (process.stdout.isTTY) {
disableMouseTracking();
}
// Cleanup the test run directory unless KEEP_OUTPUT is set
if (process.env['KEEP_OUTPUT'] !== 'true' && runDir) {
try {
+4 -14
View File
@@ -484,19 +484,9 @@ console.log(JSON.stringify({
'hooks-system.before-tool-selection.responses',
),
});
// Write hook script to file (inline node -e has quoting issues on Windows)
const hookScript = `console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: 'BeforeToolSelection',
toolConfig: {
mode: 'ANY',
allowedFunctionNames: ['read_file', 'run_shell_command']
}
}
}));`;
const scriptPath = join(rig.testDir!, 'before_tool_selection_hook.cjs');
writeFileSync(scriptPath, hookScript);
// Create inline hook command (works on both Unix and Windows)
const hookCommand =
"node -e \"console.log(JSON.stringify({hookSpecificOutput: {hookEventName: 'BeforeToolSelection', toolConfig: {mode: 'ANY', allowedFunctionNames: ['read_file', 'run_shell_command']}}}))\"";
rig.setup('should modify tool selection with BeforeToolSelection hooks', {
settings: {
@@ -510,7 +500,7 @@ console.log(JSON.stringify({
hooks: [
{
type: 'command',
command: `node "${scriptPath.replace(/\\/g, '/')}"`,
command: hookCommand,
timeout: 5000,
},
],
-16
View File
@@ -28,25 +28,9 @@ class MockConfig {
return true;
}
getFileFilteringRespectGitIgnore() {
return true;
}
getFileFilteringRespectGeminiIgnore() {
return true;
}
getFileFilteringOptions() {
return {
respectGitIgnore: true,
respectGeminiIgnore: true,
customIgnoreFilePaths: [],
};
}
validatePathAccess() {
return null;
}
}
describe('ripgrep-real-direct', () => {
+1 -7
View File
@@ -564,17 +564,11 @@ describe('run_shell_command', () => {
it('rejects invalid shell expressions', async () => {
await rig.setup('rejects invalid shell expressions', {
settings: {
tools: {
core: ['run_shell_command'],
allowed: ['run_shell_command(echo)'], // Specifically allow echo
},
},
settings: { tools: { core: ['run_shell_command'] } },
});
const invalidCommand = getInvalidCommand();
const result = await rig.run({
args: `I am testing the error handling of the run_shell_command tool. Please attempt to run the following command, which I know has invalid syntax: \`${invalidCommand}\`. If the command fails as expected, please return the word FAIL, otherwise return the word SUCCESS.`,
approvalMode: 'default', // Use default mode so safety fallback triggers confirmation
});
expect(result).toContain('FAIL');
@@ -1 +0,0 @@
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I could not find the file `nonexistent-file-that-does-not-exist.txt` in the current directory or its subdirectories. Please verify the file path or name."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":25,"totalTokenCount":35,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10}]}}]}
@@ -1 +0,0 @@
{"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}]}}]}
@@ -1,62 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { join } from 'node:path';
import { TestRig } from './test-helper.js';
describe('stdout-stderr-output', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => {
await rig.cleanup();
});
it('should send model response to stdout and app messages to stderr', async ({
signal,
}) => {
await rig.setup('prompt-output-test', {
fakeResponsesPath: join(
import.meta.dirname,
'stdout-stderr-output.responses',
),
});
const { stdout, exitCode } = await rig.runWithStreams(['-p', 'Say hello'], {
signal,
});
expect(exitCode).toBe(0);
expect(stdout.toLowerCase()).toContain('hello');
expect(stdout).not.toMatch(/^\[ERROR\]/m);
expect(stdout).not.toMatch(/^\[INFO\]/m);
});
it('should handle missing file with message to stdout and error to stderr', async ({
signal,
}) => {
await rig.setup('error-output-test', {
fakeResponsesPath: join(
import.meta.dirname,
'stdout-stderr-output-error.responses',
),
});
const { stdout, exitCode } = await rig.runWithStreams(
['-p', '@nonexistent-file-that-does-not-exist.txt explain this'],
{ signal },
);
expect(exitCode).toBe(0);
expect(stdout.toLowerCase()).toMatch(
/could not find|not exist|does not exist/,
);
});
});
+31 -8
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"workspaces": [
"packages/*"
],
@@ -2251,6 +2251,7 @@
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.2",
@@ -2431,6 +2432,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -2464,6 +2466,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -2832,6 +2835,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -2865,6 +2869,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1"
@@ -2917,6 +2922,7 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1",
@@ -4122,6 +4128,7 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4399,6 +4406,7 @@
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.35.0",
"@typescript-eslint/types": "8.35.0",
@@ -5391,6 +5399,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -8400,6 +8409,7 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8940,6 +8950,7 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -10541,6 +10552,7 @@
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.8.tgz",
"integrity": "sha512-v0thcXIKl9hqF/1w4HqA6MKxIcMoWSP3YtEZIAA+eeJngXpN5lGnMkb6rllB7FnOdwyEyYaFTcu1ZVr4/JZpWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.2.1",
"ansi-escapes": "^7.0.0",
@@ -14299,6 +14311,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -14309,6 +14322,7 @@
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
@@ -16545,6 +16559,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -16768,7 +16783,8 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
"license": "0BSD",
"peer": true
},
"node_modules/tsx": {
"version": "4.20.3",
@@ -16776,6 +16792,7 @@
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -16948,6 +16965,7 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -17155,6 +17173,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -17268,6 +17287,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -17280,6 +17300,7 @@
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -17984,6 +18005,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -17999,7 +18021,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
"@google-cloud/storage": "^7.16.0",
@@ -18055,7 +18077,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -18142,7 +18164,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "^0.3.8",
@@ -18278,6 +18300,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -18300,7 +18323,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -18317,7 +18340,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"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.28.0-nightly.20260128.adc8e11bb"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.27.0-nightly.20260121.97aac696f"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+80 -101
View File
@@ -5,127 +5,106 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as path from 'node:path';
import { loadConfig } from './config.js';
import type { ExtensionLoader } from '@google/gemini-cli-core';
import type { Settings } from './settings.js';
import {
type ExtensionLoader,
FileDiscoveryService,
} from '@google/gemini-cli-core';
// Mock dependencies
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
Config: vi.fn().mockImplementation((params) => ({
initialize: vi.fn(),
refreshAuth: vi.fn(),
...params, // Expose params for assertion
})),
loadServerHierarchicalMemory: vi
.fn()
.mockResolvedValue({ memoryContent: '', fileCount: 0, filePaths: [] }),
startupProfiler: {
flush: vi.fn(),
},
FileDiscoveryService: vi.fn(),
};
});
const {
mockLoadServerHierarchicalMemory,
mockConfigConstructor,
mockVerifyGitAvailability,
} = vi.hoisted(() => ({
mockLoadServerHierarchicalMemory: vi.fn().mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
}),
mockConfigConstructor: vi.fn(),
mockVerifyGitAvailability: vi.fn(),
}));
vi.mock('../utils/logger.js', () => ({
logger: {
info: vi.fn(),
error: vi.fn(),
vi.mock('@google/gemini-cli-core', async () => ({
Config: class MockConfig {
constructor(params: unknown) {
mockConfigConstructor(params);
}
initialize = vi.fn();
refreshAuth = vi.fn();
},
loadServerHierarchicalMemory: mockLoadServerHierarchicalMemory,
startupProfiler: {
flush: vi.fn(),
},
FileDiscoveryService: vi.fn(),
ApprovalMode: { DEFAULT: 'default', YOLO: 'yolo' },
AuthType: {
LOGIN_WITH_GOOGLE: 'login_with_google',
USE_GEMINI: 'use_gemini',
},
GEMINI_DIR: '.gemini',
DEFAULT_GEMINI_EMBEDDING_MODEL: 'models/embedding-001',
DEFAULT_GEMINI_MODEL: 'models/gemini-1.5-flash',
PREVIEW_GEMINI_MODEL: 'models/gemini-1.5-pro-latest',
homedir: () => '/tmp',
GitService: {
verifyGitAvailability: mockVerifyGitAvailability,
},
}));
describe('loadConfig', () => {
const mockSettings = {} as Settings;
const mockExtensionLoader = {} as ExtensionLoader;
const taskId = 'test-task-id';
const mockSettings = {
checkpointing: { enabled: true },
};
const mockExtensionLoader = {
start: vi.fn(),
getExtensions: vi.fn().mockReturnValue([]),
} as unknown as ExtensionLoader;
beforeEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
process.env['GEMINI_API_KEY'] = 'test-key';
// Reset the mock return value just in case
mockLoadServerHierarchicalMemory.mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
});
afterEach(() => {
delete process.env['CUSTOM_IGNORE_FILE_PATHS'];
delete process.env['GEMINI_API_KEY'];
delete process.env['CHECKPOINTING'];
});
it('should set customIgnoreFilePaths when CUSTOM_IGNORE_FILE_PATHS env var is present', async () => {
const testPath = '/tmp/ignore';
process.env['CUSTOM_IGNORE_FILE_PATHS'] = testPath;
const config = await loadConfig(mockSettings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([
testPath,
]);
it('should disable checkpointing if git is not installed', async () => {
mockVerifyGitAvailability.mockResolvedValue(false);
await loadConfig(
mockSettings as unknown as Settings,
mockExtensionLoader,
'test-task',
);
expect(mockConfigConstructor).toHaveBeenCalledWith(
expect.objectContaining({
checkpointing: false,
}),
);
});
it('should set customIgnoreFilePaths when settings.fileFiltering.customIgnoreFilePaths is present', async () => {
const testPath = '/settings/ignore';
const settings: Settings = {
fileFiltering: {
customIgnoreFilePaths: [testPath],
},
};
const config = await loadConfig(settings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([
testPath,
]);
});
it('should enable checkpointing if git is installed', async () => {
mockVerifyGitAvailability.mockResolvedValue(true);
it('should merge customIgnoreFilePaths from settings and env var', async () => {
const envPath = '/env/ignore';
const settingsPath = '/settings/ignore';
process.env['CUSTOM_IGNORE_FILE_PATHS'] = envPath;
const settings: Settings = {
fileFiltering: {
customIgnoreFilePaths: [settingsPath],
},
};
const config = await loadConfig(settings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([
settingsPath,
envPath,
]);
});
await loadConfig(
mockSettings as unknown as Settings,
mockExtensionLoader,
'test-task',
);
it('should split CUSTOM_IGNORE_FILE_PATHS using system delimiter', async () => {
const paths = ['/path/one', '/path/two'];
process.env['CUSTOM_IGNORE_FILE_PATHS'] = paths.join(path.delimiter);
const config = await loadConfig(mockSettings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual(paths);
});
it('should have empty customIgnoreFilePaths when both are missing', async () => {
const config = await loadConfig(mockSettings, mockExtensionLoader, taskId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([]);
});
it('should initialize FileDiscoveryService with correct options', async () => {
const testPath = '/tmp/ignore';
process.env['CUSTOM_IGNORE_FILE_PATHS'] = testPath;
const settings: Settings = {
fileFiltering: {
respectGitIgnore: false,
},
};
await loadConfig(settings, mockExtensionLoader, taskId);
expect(FileDiscoveryService).toHaveBeenCalledWith(expect.any(String), {
respectGitIgnore: false,
respectGeminiIgnore: undefined,
customIgnoreFilePaths: [testPath],
});
expect(mockConfigConstructor).toHaveBeenCalledWith(
expect.objectContaining({
checkpointing: true,
}),
);
});
});
+1 -12
View File
@@ -86,15 +86,8 @@ export async function loadConfig(
// Git-aware file filtering settings
fileFiltering: {
respectGitIgnore: settings.fileFiltering?.respectGitIgnore,
respectGeminiIgnore: settings.fileFiltering?.respectGeminiIgnore,
enableRecursiveFileSearch:
settings.fileFiltering?.enableRecursiveFileSearch,
customIgnoreFilePaths: [
...(settings.fileFiltering?.customIgnoreFilePaths || []),
...(process.env['CUSTOM_IGNORE_FILE_PATHS']
? process.env['CUSTOM_IGNORE_FILE_PATHS'].split(path.delimiter)
: []),
],
},
ideMode: false,
folderTrust,
@@ -107,11 +100,7 @@ export async function loadConfig(
ptyInfo: 'auto',
};
const fileService = new FileDiscoveryService(workspaceDir, {
respectGitIgnore: configParams?.fileFiltering?.respectGitIgnore,
respectGeminiIgnore: configParams?.fileFiltering?.respectGeminiIgnore,
customIgnoreFilePaths: configParams?.fileFiltering?.customIgnoreFilePaths,
});
const fileService = new FileDiscoveryService(workspaceDir);
const { memoryContent, fileCount, filePaths } =
await loadServerHierarchicalMemory(
workspaceDir,
@@ -38,9 +38,7 @@ export interface Settings {
// Git-aware file filtering settings
fileFiltering?: {
respectGitIgnore?: boolean;
respectGeminiIgnore?: boolean;
enableRecursiveFileSearch?: boolean;
customIgnoreFilePaths?: string[];
};
}
-15
View File
@@ -1,15 +0,0 @@
## React & Ink (CLI UI)
- **Side Effects**: Use reducers for complex state transitions; avoid `setState`
triggers in callbacks.
- Always fix react-hooks/exhaustive-deps lint errors by adding the missing
dependencies.
- **Shortcuts**: only define keyboard shortcuts in
`packages/cli/src/config/keyBindings.ts
## Testing
- **Utilities**: Use `renderWithProviders` and `waitFor` from
`packages/cli/src/test-utils/`.
- **Snapshots**: Use `toMatchSnapshot()` to verify Ink output.
- **Mocks**: Use mocks as sparingly as possilble.
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.28.0-nightly.20260128.adc8e11bb",
"version": "0.27.0-nightly.20260121.97aac696f",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -26,7 +26,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.28.0-nightly.20260128.adc8e11bb"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.27.0-nightly.20260121.97aac696f"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.12.0",
@@ -22,7 +22,6 @@ import {
type ExtensionSetting,
} from '../../config/extensions/extensionSettings.js';
import prompts from 'prompts';
import * as fs from 'node:fs';
const {
mockExtensionManager,
@@ -80,15 +79,11 @@ vi.mock('../../config/settings.js', () => ({
}));
describe('extensions configure command', () => {
let tempWorkspaceDir: string;
beforeEach(() => {
vi.spyOn(debugLogger, 'log');
vi.spyOn(debugLogger, 'error');
vi.clearAllMocks();
tempWorkspaceDir = fs.mkdtempSync('gemini-cli-test-workspace');
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
// Default behaviors
mockLoadSettings.mockReturnValue({ merged: {} });
mockGetExtensionAndManager.mockResolvedValue({
@@ -146,7 +141,6 @@ describe('extensions configure command', () => {
'TEST_VAR',
promptForSetting,
'user',
tempWorkspaceDir,
);
});
@@ -192,7 +186,6 @@ describe('extensions configure command', () => {
'VAR_1',
promptForSetting,
'user',
tempWorkspaceDir,
);
});
@@ -111,7 +111,6 @@ async function configureSpecificSetting(
settingKey,
promptForSetting,
scope,
process.cwd(),
);
}
@@ -219,7 +218,6 @@ async function configureExtensionSettings(
setting.envVar,
promptForSetting,
scope,
process.cwd(),
);
}
}
@@ -0,0 +1,26 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-error.log
yarn-debug.log
# Build output
dist/
# OS metadata
.DS_Store
Thumbs.db
# TypeScript
*.tsbuildinfo
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDEs
.vscode/
.idea/
@@ -0,0 +1,14 @@
# Ink Library Screen Reader Guidance
When building custom components, it's important to keep accessibility in mind.
While Ink provides the building blocks, ensuring your components are accessible
will make your CLIs usable by a wider audience.
## General Principles
Provide screen reader-friendly output: Use the useIsScreenReaderEnabled hook to
detect if a screen reader is active. You can then render a more descriptive
output for screen reader users. Leverage ARIA props: For components that have a
specific role (e.g., a checkbox or a button), use the aria-role, aria-state, and
aria-label props on <Box> and <Text> to provide semantic meaning to screen
readers.
@@ -0,0 +1,4 @@
{
"name": "context-example",
"version": "1.0.0"
}
@@ -1,28 +0,0 @@
# Themes Example
This is an example of a Gemini CLI extension that adds a custom theme.
## How to use
1. Link this extension:
```bash
gemini extensions link packages/cli/src/commands/extensions/examples/themes-example
```
2. Set the theme in your settings file (`~/.gemini/config.yaml`):
```yaml
ui:
theme: 'shades-of-green-theme (themes-example)'
```
Alternatively, you can set it through the UI by running `gemini` and then
typing `/theme` and pressing Enter.
3. **Observe the Changes:**
After setting the theme, you should see the changes reflected in the Gemini
CLI's UI. The background will be a dark green, the primary text a lighter
green, and various other UI elements will display different shades of green,
as defined in this extension's `gemini-extension.json` file.
@@ -1,29 +0,0 @@
{
"name": "themes-example",
"version": "1.0.0",
"themes": [
{
"name": "shades-of-green-theme",
"type": "custom",
"background": {
"primary": "#1a362a"
},
"text": {
"primary": "#a6e3a1",
"secondary": "#6e8e7a",
"link": "#89e689"
},
"status": {
"success": "#76c076",
"warning": "#d9e689",
"error": "#b34e4e"
},
"border": {
"default": "#4a6c5a"
},
"ui": {
"comment": "#6e8e7a"
}
}
]
}
@@ -24,7 +24,6 @@ import { ExtensionUpdateState } from '../../ui/state/extensions.js';
// Mock dependencies
const emitConsoleLog = vi.hoisted(() => vi.fn());
const emitFeedback = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
log: vi.fn((message, ...args) => {
emitConsoleLog('log', format(message, ...args));
@@ -41,7 +40,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
...actual,
coreEvents: {
emitConsoleLog,
emitFeedback,
},
debugLogger,
};
@@ -86,42 +84,6 @@ describe('extensions update command', () => {
});
describe('handleUpdate', () => {
it('should list installed extensions when requested extension is not found', async () => {
const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
const extensions = [
{ name: 'ext1', version: '1.0.0' },
{ name: 'ext2', version: '2.0.0' },
];
mockExtensionManager.prototype.loadExtensions = vi
.fn()
.mockResolvedValue(extensions);
await handleUpdate({ name: 'missing-extension' });
expect(emitFeedback).toHaveBeenCalledWith(
'error',
'Extension "missing-extension" not found.\n\nInstalled extensions:\next1 (1.0.0)\next2 (2.0.0)\n\nRun "gemini extensions list" for details.',
);
expect(mockUpdateExtension).not.toHaveBeenCalled();
mockCwd.mockRestore();
});
it('should log a helpful message when no extensions are installed and requested extension is not found', async () => {
const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
mockExtensionManager.prototype.loadExtensions = vi
.fn()
.mockResolvedValue([]);
await handleUpdate({ name: 'missing-extension' });
expect(emitFeedback).toHaveBeenCalledWith(
'error',
'Extension "missing-extension" not found.\n\nNo extensions installed.',
);
expect(mockUpdateExtension).not.toHaveBeenCalled();
mockCwd.mockRestore();
});
it.each([
{
state: ExtensionUpdateState.UPDATE_AVAILABLE,
+3 -16
View File
@@ -14,7 +14,7 @@ import {
import { checkForExtensionUpdate } from '../../config/extensions/github.js';
import { getErrorMessage } from '../../utils/errors.js';
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
import { coreEvents, debugLogger } from '@google/gemini-cli-core';
import { debugLogger } from '@google/gemini-cli-core';
import { ExtensionManager } from '../../config/extension-manager.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { loadSettings } from '../../config/settings.js';
@@ -46,21 +46,7 @@ export async function handleUpdate(args: UpdateArgs) {
(extension) => extension.name === args.name,
);
if (!extension) {
if (extensions.length === 0) {
coreEvents.emitFeedback(
'error',
`Extension "${args.name}" not found.\n\nNo extensions installed.`,
);
return;
}
const installedExtensions = extensions
.map((extension) => `${extension.name} (${extension.version})`)
.join('\n');
coreEvents.emitFeedback(
'error',
`Extension "${args.name}" not found.\n\nInstalled extensions:\n${installedExtensions}\n\nRun "gemini extensions list" for details.`,
);
debugLogger.log(`Extension "${args.name}" not found.`);
return;
}
if (!extension.installMetadata) {
@@ -77,6 +63,7 @@ export async function handleUpdate(args: UpdateArgs) {
debugLogger.log(`Extension "${args.name}" is already up to date.`);
return;
}
// TODO(chrstnb): we should list extensions if the requested extension is not installed.
const updatedExtensionInfo = (await updateExtension(
extension,
extensionManager,
-7
View File
@@ -128,13 +128,6 @@ async function addMcpServer(
settings.setValue(settingsScope, 'mcpServers', mcpServers);
if (transport === 'stdio') {
debugLogger.warn(
'Security Warning: Running MCP servers with stdio transport can expose inherited environment variables. ' +
'While the Gemini CLI redacts common API keys and secrets by default, you should only run servers from trusted sources.',
);
}
if (isExistingServer) {
debugLogger.log(`MCP server "${name}" updated in ${scope} settings.`);
} else {
+1 -1
View File
@@ -8,7 +8,7 @@ import { AuthType } from '@google/gemini-cli-core';
import { loadEnvironment, loadSettings } from './settings.js';
export function validateAuthMethod(authMethod: string): string | null {
loadEnvironment(loadSettings().merged, process.cwd());
loadEnvironment(loadSettings().merged);
if (
authMethod === AuthType.LOGIN_WITH_GOOGLE ||
authMethod === AuthType.COMPUTE_ADC
+7 -12
View File
@@ -126,12 +126,10 @@ vi.mock('@google/gemini-cli-core', async () => {
DEFAULT_MEMORY_FILE_FILTERING_OPTIONS: {
respectGitIgnore: false,
respectGeminiIgnore: true,
customIgnoreFilePaths: [],
},
DEFAULT_FILE_FILTERING_OPTIONS: {
respectGitIgnore: true,
respectGeminiIgnore: true,
customIgnoreFilePaths: [],
},
createPolicyEngineConfig: vi.fn(async () => ({
rules: [],
@@ -588,7 +586,7 @@ describe('parseArguments', () => {
process.argv = ['node', 'script.js', 'skills', 'list'];
// Skills command enabled by default or via experimental
const settings = createTestMergedSettings({
skills: { enabled: true },
experimental: { skills: true },
});
const argv = await parseArguments(settings);
expect(argv.isCommand).toBe(true);
@@ -596,9 +594,9 @@ describe('parseArguments', () => {
it('should set isCommand to true for hooks command', async () => {
process.argv = ['node', 'script.js', 'hooks', 'migrate'];
// Hooks command enabled via hooksConfig settings
// Hooks command enabled via tools settings
const settings = createTestMergedSettings({
hooksConfig: { enabled: true },
tools: { enableHooks: true },
});
const argv = await parseArguments(settings);
expect(argv.isCommand).toBe(true);
@@ -706,9 +704,6 @@ describe('loadCliConfig', () => {
expect(config.getFileFilteringRespectGeminiIgnore()).toBe(
DEFAULT_FILE_FILTERING_OPTIONS.respectGeminiIgnore,
);
expect(config.getCustomIgnoreFilePaths()).toEqual(
DEFAULT_FILE_FILTERING_OPTIONS.customIgnoreFilePaths,
);
expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
});
@@ -1255,7 +1250,7 @@ describe('Approval mode tool exclusion logic', () => {
});
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
'Cannot start in YOLO mode since it is disabled by your admin',
);
});
@@ -2928,7 +2923,7 @@ describe('loadCliConfig disableYoloMode', () => {
security: { disableYoloMode: true },
});
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
'Cannot start in YOLO mode since it is disabled by your admin',
);
});
});
@@ -2960,7 +2955,7 @@ describe('loadCliConfig secureModeEnabled', () => {
});
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
'Cannot start in YOLO mode since it is disabled by your admin',
);
});
@@ -2974,7 +2969,7 @@ describe('loadCliConfig secureModeEnabled', () => {
});
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
'Cannot start in YOLO mode since it is disabled by your admin',
);
});
+10 -9
View File
@@ -38,7 +38,6 @@ import {
type OutputFormat,
coreEvents,
GEMINI_MODEL_ALIAS_AUTO,
getAdminErrorMessage,
} from '@google/gemini-cli-core';
import {
type Settings,
@@ -305,11 +304,11 @@ export async function parseArguments(
yargsInstance.command(extensionsCommand);
}
if (settings.skills?.enabled ?? true) {
if (settings.experimental?.skills || (settings.skills?.enabled ?? true)) {
yargsInstance.command(skillsCommand);
}
// Register hooks command if hooks are enabled
if (settings.hooksConfig.enabled) {
if (settings.tools?.enableHooks) {
yargsInstance.command(hooksCommand);
}
@@ -433,7 +432,7 @@ export async function loadCliConfig(
const ideMode = settings.ide?.enabled ?? false;
const folderTrust = settings.security?.folderTrust?.enabled ?? false;
const trustedFolder = isWorkspaceTrusted(settings, cwd)?.isTrusted ?? false;
const trustedFolder = isWorkspaceTrusted(settings)?.isTrusted ?? false;
// Set the context filename in the server's memoryTool module BEFORE loading memory
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
@@ -551,7 +550,7 @@ export async function loadCliConfig(
);
}
throw new FatalConfigError(
getAdminErrorMessage('YOLO mode', undefined /* config */),
'Cannot start in YOLO mode since it is disabled by your admin',
);
}
} else if (approvalMode === ApprovalMode.YOLO) {
@@ -756,13 +755,13 @@ export async function loadCliConfig(
plan: settings.experimental?.plan,
enableEventDrivenScheduler:
settings.experimental?.enableEventDrivenScheduler,
skillsSupport: settings.skills?.enabled ?? true,
skillsSupport:
settings.experimental?.skills || (settings.skills?.enabled ?? true),
disabledSkills: settings.skills?.disabled,
experimentalJitContext: settings.experimental?.jitContext,
noBrowser: !!process.env['NO_BROWSER'],
summarizeToolOutput: settings.model?.summarizeToolOutput,
ideMode,
disableLoopDetection: settings.model?.disableLoopDetection,
compressionThreshold: settings.model?.compressionThreshold,
folderTrust,
interactive,
@@ -792,8 +791,10 @@ export async function loadCliConfig(
acceptRawOutputRisk: argv.acceptRawOutputRisk,
modelConfigServiceConfig: settings.modelConfigs,
// TODO: loading of hooks based on workspace trust
enableHooks: settings.hooksConfig.enabled,
enableHooksUI: settings.hooksConfig.enabled,
enableHooks:
(settings.tools?.enableHooks ?? true) &&
(settings.hooksConfig?.enabled ?? true),
enableHooksUI: settings.tools?.enableHooks ?? true,
hooks: settings.hooks || {},
disabledHooks: settings.hooksConfig?.disabled || [],
projectHooks: projectHooks || {},
@@ -1,318 +0,0 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { ExtensionManager } from './extension-manager.js';
import { debugLogger, coreEvents } from '@google/gemini-cli-core';
import { createTestMergedSettings } from './settings.js';
import { createExtension } from '../test-utils/createExtension.js';
import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
vi.mock('node:os', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:os')>();
return {
...actual,
homedir: mockHomedir,
};
});
// Mock @google/gemini-cli-core
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
homedir: mockHomedir,
// Use actual implementations for loading skills and agents to test hydration
loadAgentsFromDirectory: actual.loadAgentsFromDirectory,
loadSkillsFromDir: actual.loadSkillsFromDir,
};
});
describe('ExtensionManager hydration', () => {
let extensionManager: ExtensionManager;
let tempDir: string;
let extensionsDir: string;
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(coreEvents, 'emitFeedback');
vi.spyOn(debugLogger, 'debug').mockImplementation(() => {});
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-test-'));
mockHomedir.mockReturnValue(tempDir);
// Create the extensions directory that ExtensionManager expects
extensionsDir = path.join(tempDir, '.gemini', EXTENSIONS_DIRECTORY_NAME);
fs.mkdirSync(extensionsDir, { recursive: true });
extensionManager = new ExtensionManager({
settings: createTestMergedSettings({
telemetry: { enabled: false },
experimental: { extensionConfig: true },
}),
requestConsent: vi.fn().mockResolvedValue(true),
requestSetting: vi.fn(),
workspaceDir: tempDir,
});
});
afterEach(() => {
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// ignore
}
});
it('should hydrate skill body with extension settings', async () => {
const sourceDir = path.join(tempDir, 'source-ext-skill');
const extensionName = 'skill-hydration-ext';
createExtension({
extensionsDir: sourceDir,
name: extensionName,
version: '1.0.0',
settings: [
{
name: 'API Key',
description: 'API Key',
envVar: 'MY_API_KEY',
},
],
installMetadata: {
type: 'local',
source: path.join(sourceDir, extensionName),
},
});
const extensionPath = path.join(sourceDir, extensionName);
// Create skill with variable
const skillsDir = path.join(extensionPath, 'skills');
const skillSubdir = path.join(skillsDir, 'my-skill');
fs.mkdirSync(skillSubdir, { recursive: true });
fs.writeFileSync(
path.join(skillSubdir, 'SKILL.md'),
`---
name: my-skill
description: test
---
Use key: \${MY_API_KEY}
`,
);
await extensionManager.loadExtensions();
extensionManager.setRequestSetting(async (setting) => {
if (setting.envVar === 'MY_API_KEY') return 'secret-123';
return '';
});
const extension = await extensionManager.installOrUpdateExtension({
type: 'local',
source: extensionPath,
});
expect(extension.skills).toHaveLength(1);
expect(extension.skills![0].body).toContain('Use key: secret-123');
});
it('should hydrate agent system prompt with extension settings', async () => {
const sourceDir = path.join(tempDir, 'source-ext-agent');
const extensionName = 'agent-hydration-ext';
createExtension({
extensionsDir: sourceDir,
name: extensionName,
version: '1.0.0',
settings: [
{
name: 'Model Name',
description: 'Model',
envVar: 'MODEL_NAME',
},
],
installMetadata: {
type: 'local',
source: path.join(sourceDir, extensionName),
},
});
const extensionPath = path.join(sourceDir, extensionName);
// Create agent with variable
const agentsDir = path.join(extensionPath, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
fs.writeFileSync(
path.join(agentsDir, 'my-agent.md'),
`---
name: my-agent
description: test
---
System using model: \${MODEL_NAME}
`,
);
await extensionManager.loadExtensions();
extensionManager.setRequestSetting(async (setting) => {
if (setting.envVar === 'MODEL_NAME') return 'gemini-pro';
return '';
});
const extension = await extensionManager.installOrUpdateExtension({
type: 'local',
source: extensionPath,
});
expect(extension.agents).toHaveLength(1);
const agent = extension.agents![0];
if (agent.kind === 'local') {
expect(agent.promptConfig.systemPrompt).toContain(
'System using model: gemini-pro',
);
} else {
throw new Error('Expected local agent');
}
});
it('should hydrate hooks with extension settings', async () => {
const sourceDir = path.join(tempDir, 'source-ext-hooks');
const extensionName = 'hooks-hydration-ext';
createExtension({
extensionsDir: sourceDir,
name: extensionName,
version: '1.0.0',
settings: [
{
name: 'Hook Command',
description: 'Cmd',
envVar: 'HOOK_CMD',
},
],
installMetadata: {
type: 'local',
source: path.join(sourceDir, extensionName),
},
});
const extensionPath = path.join(sourceDir, extensionName);
const hooksDir = path.join(extensionPath, 'hooks');
fs.mkdirSync(hooksDir, { recursive: true });
fs.writeFileSync(
path.join(hooksDir, 'hooks.json'),
JSON.stringify({
hooks: {
BeforeTool: [
{
hooks: [
{
type: 'command',
command: 'echo $HOOK_CMD',
},
],
},
],
},
}),
);
// Enable hooks in settings
extensionManager = new ExtensionManager({
settings: createTestMergedSettings({
telemetry: { enabled: false },
experimental: { extensionConfig: true },
hooksConfig: { enabled: true },
}),
requestConsent: vi.fn().mockResolvedValue(true),
requestSetting: vi.fn(),
workspaceDir: tempDir,
});
await extensionManager.loadExtensions();
extensionManager.setRequestSetting(async (setting) => {
if (setting.envVar === 'HOOK_CMD') return 'hello-world';
return '';
});
const extension = await extensionManager.installOrUpdateExtension({
type: 'local',
source: extensionPath,
});
expect(extension.hooks).toBeDefined();
expect(extension.hooks?.BeforeTool).toHaveLength(1);
expect(extension.hooks?.BeforeTool![0].hooks[0].env?.['HOOK_CMD']).toBe(
'hello-world',
);
});
it('should pick up new settings after restartExtension', async () => {
const sourceDir = path.join(tempDir, 'source-ext-restart');
const extensionName = 'restart-hydration-ext';
createExtension({
extensionsDir: sourceDir,
name: extensionName,
version: '1.0.0',
settings: [
{
name: 'Value',
description: 'Val',
envVar: 'MY_VALUE',
},
],
installMetadata: {
type: 'local',
source: path.join(sourceDir, extensionName),
},
});
const extensionPath = path.join(sourceDir, extensionName);
const skillsDir = path.join(extensionPath, 'skills');
const skillSubdir = path.join(skillsDir, 'my-skill');
fs.mkdirSync(skillSubdir, { recursive: true });
fs.writeFileSync(
path.join(skillSubdir, 'SKILL.md'),
'---\nname: my-skill\ndescription: test\n---\nValue is: ${MY_VALUE}',
);
await extensionManager.loadExtensions();
// Initial setting
extensionManager.setRequestSetting(async () => 'first');
const extension = await extensionManager.installOrUpdateExtension({
type: 'local',
source: extensionPath,
});
expect(extension.skills![0].body).toContain('Value is: first');
const { updateSetting, ExtensionSettingScope } = await import(
'./extensions/extensionSettings.js'
);
const extensionConfig =
await extensionManager.loadExtensionConfig(extensionPath);
const mockRequestSetting = vi.fn().mockResolvedValue('second');
await updateSetting(
extensionConfig,
extension.id,
'MY_VALUE',
mockRequestSetting,
ExtensionSettingScope.USER,
process.cwd(),
);
await extensionManager.restartExtension(extension);
const reloadedExtension = extensionManager
.getExtensions()
.find((e) => e.name === extensionName)!;
expect(reloadedExtension.skills![0].body).toContain('Value is: second');
});
});
@@ -1,225 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import {
beforeAll,
afterAll,
beforeEach,
describe,
expect,
it,
vi,
afterEach,
} from 'vitest';
import { createExtension } from '../test-utils/createExtension.js';
import { ExtensionManager } from './extension-manager.js';
import { themeManager, DEFAULT_THEME } from '../ui/themes/theme-manager.js';
import { GEMINI_DIR, type Config } from '@google/gemini-cli-core';
import { createTestMergedSettings, SettingScope } from './settings.js';
describe('ExtensionManager theme loading', () => {
let extensionManager: ExtensionManager;
let userExtensionsDir: string;
let tempHomeDir: string;
beforeAll(async () => {
tempHomeDir = await fs.promises.mkdtemp(
path.join(fs.realpathSync('/tmp'), 'gemini-cli-test-'),
);
});
afterAll(async () => {
if (tempHomeDir) {
await fs.promises.rm(tempHomeDir, { recursive: true, force: true });
}
});
beforeEach(() => {
process.env['GEMINI_CLI_HOME'] = tempHomeDir;
userExtensionsDir = path.join(tempHomeDir, GEMINI_DIR, 'extensions');
// Ensure userExtensionsDir is clean for each test
fs.rmSync(userExtensionsDir, { recursive: true, force: true });
fs.mkdirSync(userExtensionsDir, { recursive: true });
extensionManager = new ExtensionManager({
settings: createTestMergedSettings({
experimental: { extensionConfig: true },
security: { blockGitExtensions: false },
admin: { extensions: { enabled: true }, mcp: { enabled: true } },
}),
requestConsent: async () => true,
requestSetting: async () => '',
workspaceDir: tempHomeDir,
enabledExtensionOverrides: [],
});
vi.clearAllMocks();
themeManager.clearExtensionThemes();
themeManager.loadCustomThemes({});
themeManager.setActiveTheme(DEFAULT_THEME.name);
});
afterEach(() => {
delete process.env['GEMINI_CLI_HOME'];
});
it('should register themes from an extension when started', async () => {
const registerSpy = vi.spyOn(themeManager, 'registerExtensionThemes');
createExtension({
extensionsDir: userExtensionsDir,
name: 'my-theme-extension',
themes: [
{
name: 'My-Awesome-Theme',
type: 'custom',
text: {
primary: '#FF00FF',
},
},
],
});
await extensionManager.loadExtensions();
const mockConfig = {
getEnableExtensionReloading: () => false,
getMcpClientManager: () => ({
startExtension: vi.fn().mockResolvedValue(undefined),
}),
getGeminiClient: () => ({
isInitialized: () => false,
updateSystemInstruction: vi.fn(),
setTools: vi.fn(),
}),
getHookSystem: () => undefined,
getWorkingDir: () => tempHomeDir,
shouldLoadMemoryFromIncludeDirectories: () => false,
getDebugMode: () => false,
getFileExclusions: () => ({
isIgnored: () => false,
}),
getGeminiMdFilePaths: () => [],
getMcpServers: () => ({}),
getAllowedMcpServers: () => [],
getSanitizationConfig: () => ({
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],
enableEnvironmentVariableRedaction: false,
}),
getShellExecutionConfig: () => ({
terminalWidth: 80,
terminalHeight: 24,
showColor: false,
pager: 'cat',
sanitizationConfig: {
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],
enableEnvironmentVariableRedaction: false,
},
}),
getToolRegistry: () => ({
getTools: () => [],
}),
getProxy: () => undefined,
getFileService: () => ({
findFiles: async () => [],
}),
getExtensionLoader: () => ({
getExtensions: () => [],
}),
isTrustedFolder: () => true,
getImportFormat: () => 'tree',
} as unknown as Config;
await extensionManager.start(mockConfig);
expect(registerSpy).toHaveBeenCalledWith('my-theme-extension', [
{
name: 'My-Awesome-Theme',
type: 'custom',
text: {
primary: '#FF00FF',
},
},
]);
});
it('should revert to default theme when extension is stopped', async () => {
const extensionName = 'my-theme-extension';
const themeName = 'My-Awesome-Theme';
const namespacedThemeName = `${themeName} (${extensionName})`;
createExtension({
extensionsDir: userExtensionsDir,
name: extensionName,
themes: [
{
name: themeName,
type: 'custom',
text: {
primary: '#FF00FF',
},
},
],
});
await extensionManager.loadExtensions();
const mockConfig = {
getWorkingDir: () => tempHomeDir,
shouldLoadMemoryFromIncludeDirectories: () => false,
getWorkspaceContext: () => ({
getDirectories: () => [],
}),
getDebugMode: () => false,
getFileService: () => ({
findFiles: async () => [],
}),
getExtensionLoader: () => ({
getExtensions: () => [],
}),
isTrustedFolder: () => true,
getImportFormat: () => 'tree',
getFileFilteringOptions: () => ({
respectGitIgnore: true,
respectGeminiIgnore: true,
}),
getDiscoveryMaxDirs: () => 200,
getMcpClientManager: () => ({
getMcpInstructions: () => '',
startExtension: vi.fn().mockResolvedValue(undefined),
stopExtension: vi.fn().mockResolvedValue(undefined),
}),
setUserMemory: vi.fn(),
setGeminiMdFileCount: vi.fn(),
setGeminiMdFilePaths: vi.fn(),
getEnableExtensionReloading: () => true,
getGeminiClient: () => ({
isInitialized: () => false,
updateSystemInstruction: vi.fn(),
setTools: vi.fn(),
}),
getHookSystem: () => undefined,
getProxy: () => undefined,
getAgentRegistry: () => ({
reload: vi.fn().mockResolvedValue(undefined),
}),
} as unknown as Config;
await extensionManager.start(mockConfig);
// Set the active theme to the one from the extension
themeManager.setActiveTheme(namespacedThemeName);
expect(themeManager.getActiveTheme().name).toBe(namespacedThemeName);
// Stop the extension
await extensionManager.disableExtension(extensionName, SettingScope.User);
// Check that the active theme has reverted to the default
expect(themeManager.getActiveTheme().name).toBe(DEFAULT_THEME.name);
});
});
+16 -129
View File
@@ -57,7 +57,6 @@ import {
INSTALL_METADATA_FILENAME,
recursivelyHydrateStrings,
type JsonObject,
type VariableContext,
} from './extensions/variables.js';
import {
getEnvContents,
@@ -69,7 +68,6 @@ import {
ExtensionSettingScope,
} from './extensions/extensionSettings.js';
import type { EventEmitter } from 'node:stream';
import { themeManager } from '../ui/themes/theme-manager.js';
interface ExtensionManagerParams {
enabledExtensionOverrides?: string[];
@@ -144,26 +142,6 @@ export class ExtensionManager extends ExtensionLoader {
previousExtensionConfig?: ExtensionConfig,
): Promise<GeminiCLIExtension> {
if (
this.settings.security?.allowedExtensions &&
this.settings.security?.allowedExtensions.length > 0
) {
const extensionAllowed = this.settings.security?.allowedExtensions.some(
(pattern) => {
try {
return new RegExp(pattern).test(installMetadata.source);
} catch (e) {
throw new Error(
`Invalid regex pattern in allowedExtensions setting: "${pattern}. Error: ${getErrorMessage(e)}`,
);
}
},
);
if (!extensionAllowed) {
throw new Error(
`Installing extension from source "${installMetadata.source}" is not allowed by the "allowedExtensions" security setting.`,
);
}
} else if (
(installMetadata.type === 'git' ||
installMetadata.type === 'github-release') &&
this.settings.security.blockGitExtensions
@@ -172,7 +150,6 @@ export class ExtensionManager extends ExtensionLoader {
'Installing extensions from remote sources is disallowed by your current settings.',
);
}
const isUpdate = !!previousExtensionConfig;
let newExtensionConfig: ExtensionConfig | null = null;
let localSourcePath: string | undefined;
@@ -490,20 +467,6 @@ Would you like to attempt to install via "git clone" instead?`,
);
}
protected override async startExtension(extension: GeminiCLIExtension) {
await super.startExtension(extension);
if (extension.themes) {
themeManager.registerExtensionThemes(extension.name, extension.themes);
}
}
protected override async stopExtension(extension: GeminiCLIExtension) {
await super.stopExtension(extension);
if (extension.themes) {
themeManager.unregisterExtensionThemes(extension.name, extension.themes);
}
}
/**
* Loads all installed extensions, should only be called once.
*/
@@ -543,39 +506,10 @@ Would you like to attempt to install via "git clone" instead?`,
const installMetadata = loadInstallMetadata(extensionDir);
let effectiveExtensionPath = extensionDir;
if (
this.settings.security?.allowedExtensions &&
this.settings.security?.allowedExtensions.length > 0
) {
if (!installMetadata?.source) {
throw new Error(
`Failed to load extension ${extensionDir}. The ${INSTALL_METADATA_FILENAME} file is missing or misconfigured.`,
);
}
const extensionAllowed = this.settings.security?.allowedExtensions.some(
(pattern) => {
try {
return new RegExp(pattern).test(installMetadata?.source);
} catch (e) {
throw new Error(
`Invalid regex pattern in allowedExtensions setting: "${pattern}. Error: ${getErrorMessage(e)}`,
);
}
},
);
if (!extensionAllowed) {
debugLogger.warn(
`Failed to load extension ${extensionDir}. This extension is not allowed by the "allowedExtensions" security setting.`,
);
return null;
}
} else if (
(installMetadata?.type === 'git' ||
installMetadata?.type === 'github-release') &&
this.settings.security.blockGitExtensions
) {
debugLogger.warn(
`Failed to load extension ${extensionDir}. Extensions from remote sources is disallowed by your current settings.`,
);
return null;
}
@@ -604,14 +538,12 @@ Would you like to attempt to install via "git clone" instead?`,
extensionId,
ExtensionSettingScope.USER,
);
if (isWorkspaceTrusted(this.settings).isTrusted) {
workspaceSettings = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.WORKSPACE,
this.workspaceDir,
);
}
workspaceSettings = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.WORKSPACE,
this.workspaceDir,
);
}
const customEnv = { ...userSettings, ...workspaceSettings };
@@ -680,60 +612,24 @@ Would you like to attempt to install via "git clone" instead?`,
)
.filter((contextFilePath) => fs.existsSync(contextFilePath));
const hydrationContext: VariableContext = {
extensionPath: effectiveExtensionPath,
workspacePath: this.workspaceDir,
'/': path.sep,
pathSeparator: path.sep,
...customEnv,
};
let hooks: { [K in HookEventName]?: HookDefinition[] } | undefined;
if (this.settings.hooksConfig.enabled) {
hooks = await this.loadExtensionHooks(
effectiveExtensionPath,
hydrationContext,
);
if (
this.settings.tools.enableHooks &&
this.settings.hooksConfig.enabled
) {
hooks = await this.loadExtensionHooks(effectiveExtensionPath, {
extensionPath: effectiveExtensionPath,
workspacePath: this.workspaceDir,
});
}
// Hydrate hooks with extension settings as environment variables
if (hooks && config.settings) {
const hookEnv: Record<string, string> = {};
for (const setting of config.settings) {
const value = customEnv[setting.envVar];
if (value !== undefined) {
hookEnv[setting.envVar] = value;
}
}
if (Object.keys(hookEnv).length > 0) {
for (const eventName of Object.keys(hooks)) {
const eventHooks = hooks[eventName as HookEventName];
if (eventHooks) {
for (const definition of eventHooks) {
for (const hook of definition.hooks) {
// Merge existing env with new env vars, giving extension settings precedence.
hook.env = { ...hook.env, ...hookEnv };
}
}
}
}
}
}
let skills = await loadSkillsFromDir(
const skills = await loadSkillsFromDir(
path.join(effectiveExtensionPath, 'skills'),
);
skills = skills.map((skill) =>
recursivelyHydrateStrings(skill, hydrationContext),
);
const agentLoadResult = await loadAgentsFromDirectory(
path.join(effectiveExtensionPath, 'agents'),
);
agentLoadResult.agents = agentLoadResult.agents.map((agent) =>
recursivelyHydrateStrings(agent, hydrationContext),
);
// Log errors but don't fail the entire extension load
for (const error of agentLoadResult.errors) {
@@ -760,7 +656,6 @@ Would you like to attempt to install via "git clone" instead?`,
resolvedSettings,
skills,
agents: agentLoadResult.agents,
themes: config.themes,
};
this.loadedExtensions = [...this.loadedExtensions, extension];
@@ -776,14 +671,6 @@ Would you like to attempt to install via "git clone" instead?`,
}
}
override async restartExtension(
extension: GeminiCLIExtension,
): Promise<void> {
const extensionDir = extension.path;
await this.unloadExtension(extension);
await this.loadExtension(extensionDir);
}
/**
* Removes `extension` from the list of extensions and stops it if
* appropriate.
@@ -833,7 +720,7 @@ Would you like to attempt to install via "git clone" instead?`,
private async loadExtensionHooks(
extensionDir: string,
context: VariableContext,
context: { extensionPath: string; workspacePath: string },
): Promise<{ [K in HookEventName]?: HookDefinition[] } | undefined> {
const hooksFilePath = path.join(extensionDir, 'hooks', 'hooks.json');
-92
View File
@@ -622,7 +622,6 @@ describe('extension tests', () => {
});
it('should not load github extensions if blockGitExtensions is set', async () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
createExtension({
extensionsDir: userExtensionsDir,
name: 'my-ext',
@@ -646,73 +645,6 @@ describe('extension tests', () => {
const extension = extensions.find((e) => e.name === 'my-ext');
expect(extension).toBeUndefined();
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Extensions from remote sources is disallowed by your current settings.',
),
);
consoleSpy.mockRestore();
});
it('should load allowed extensions if the allowlist is set.', async () => {
createExtension({
extensionsDir: userExtensionsDir,
name: 'my-ext',
version: '1.0.0',
installMetadata: {
type: 'git',
source: 'http://allowed.com/foo/bar',
},
});
const extensionAllowlistSetting = createTestMergedSettings({
security: {
allowedExtensions: ['\\b(https?:\\/\\/)?(www\\.)?allowed\\.com\\S*'],
},
});
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
requestConsent: mockRequestConsent,
requestSetting: mockPromptForSettings,
settings: extensionAllowlistSetting,
});
const extensions = await extensionManager.loadExtensions();
expect(extensions).toHaveLength(1);
expect(extensions[0].name).toBe('my-ext');
});
it('should not load disallowed extensions if the allowlist is set.', async () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
createExtension({
extensionsDir: userExtensionsDir,
name: 'my-ext',
version: '1.0.0',
installMetadata: {
type: 'git',
source: 'http://notallowed.com/foo/bar',
},
});
const extensionAllowlistSetting = createTestMergedSettings({
security: {
allowedExtensions: ['\\b(https?:\\/\\/)?(www\\.)?allowed\\.com\\S*'],
},
});
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
requestConsent: mockRequestConsent,
requestSetting: mockPromptForSettings,
settings: extensionAllowlistSetting,
});
const extensions = await extensionManager.loadExtensions();
const extension = extensions.find((e) => e.name === 'my-ext');
expect(extension).toBeUndefined();
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining(
'This extension is not allowed by the "allowedExtensions" security setting',
),
);
consoleSpy.mockRestore();
});
it('should not load any extensions if admin.extensions.enabled is false', async () => {
@@ -1184,30 +1116,6 @@ describe('extension tests', () => {
);
});
it('should not install a disallowed extension if the allowlist is set', async () => {
const gitUrl = 'https://somehost.com/somerepo.git';
const allowedExtensionsSetting = createTestMergedSettings({
security: {
allowedExtensions: ['\\b(https?:\\/\\/)?(www\\.)?allowed\\.com\\S*'],
},
});
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
requestConsent: mockRequestConsent,
requestSetting: mockPromptForSettings,
settings: allowedExtensionsSetting,
});
await extensionManager.loadExtensions();
await expect(
extensionManager.installOrUpdateExtension({
source: gitUrl,
type: 'git',
}),
).rejects.toThrow(
`Installing extension from source "${gitUrl}" is not allowed by the "allowedExtensions" security setting.`,
);
});
it('should prompt for trust if workspace is not trusted', async () => {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: false,
-6
View File
@@ -7,7 +7,6 @@
import type {
MCPServerConfig,
ExtensionInstallMetadata,
CustomTheme,
} from '@google/gemini-cli-core';
import * as fs from 'node:fs';
import * as path from 'node:path';
@@ -28,11 +27,6 @@ export interface ExtensionConfig {
contextFileName?: string | string[];
excludeTools?: string[];
settings?: ExtensionSetting[];
/**
* Custom themes contributed by this extension.
* These themes will be registered when the extension is activated.
*/
themes?: CustomTheme[];
}
export interface ExtensionUpdateInfo {
@@ -398,35 +398,6 @@ describe('extensionSettings', () => {
expect(actualContent).toBe('VAR1="a value with spaces"\n');
});
it('should not set sensitive settings if the value is empty during initial setup', async () => {
const config: ExtensionConfig = {
name: 'test-ext',
version: '1.0.0',
settings: [
{
name: 's1',
description: 'd1',
envVar: 'SENSITIVE_VAR',
sensitive: true,
},
],
};
mockRequestSetting.mockResolvedValue('');
await maybePromptForSettings(
config,
'12345',
mockRequestSetting,
undefined,
undefined,
);
const userKeychain = new KeychainTokenStorage(
`Gemini CLI Extensions test-ext 12345`,
);
expect(await userKeychain.getSecret('SENSITIVE_VAR')).toBeNull();
});
it('should not attempt to clear secrets if keychain is unavailable', async () => {
// Arrange
const mockIsAvailable = vi.fn().mockResolvedValue(false);
@@ -767,42 +738,5 @@ describe('extensionSettings', () => {
const lines = actualContent.split('\n').filter((line) => line.length > 0);
expect(lines).toHaveLength(3); // Should only have the three variables
});
it('should delete a sensitive setting if the new value is empty', async () => {
mockRequestSetting.mockResolvedValue('');
await updateSetting(
config,
'12345',
'VAR2',
mockRequestSetting,
ExtensionSettingScope.USER,
tempWorkspaceDir,
);
const userKeychain = new KeychainTokenStorage(
`Gemini CLI Extensions test-ext 12345`,
);
expect(await userKeychain.getSecret('VAR2')).toBeNull();
});
it('should not throw if deleting a non-existent sensitive setting with empty value', async () => {
mockRequestSetting.mockResolvedValue('');
// Ensure it doesn't exist first
const userKeychain = new KeychainTokenStorage(
`Gemini CLI Extensions test-ext 12345`,
);
await userKeychain.deleteSecret('VAR2');
await updateSetting(
config,
'12345',
'VAR2',
mockRequestSetting,
ExtensionSettingScope.USER,
tempWorkspaceDir,
);
// Should complete without error
});
});
});
@@ -112,7 +112,7 @@ export async function maybePromptForSettings(
const nonSensitiveSettings: Record<string, string> = {};
for (const setting of settings) {
const value = allSettings[setting.envVar];
if (value === undefined || value === '') {
if (value === undefined) {
continue;
}
if (setting.sensitive) {
@@ -207,7 +207,7 @@ export async function updateSetting(
settingKey: string,
requestSetting: (setting: ExtensionSetting) => Promise<string>,
scope: ExtensionSettingScope,
workspaceDir: string,
workspaceDir?: string,
): Promise<void> {
const { name: extensionName, settings } = extensionConfig;
if (!settings || settings.length === 0) {
@@ -230,15 +230,7 @@ export async function updateSetting(
);
if (settingToUpdate.sensitive) {
if (newValue) {
await keychain.setSecret(settingToUpdate.envVar, newValue);
} else {
try {
await keychain.deleteSecret(settingToUpdate.envVar);
} catch {
// Ignore if secret does not exist
}
}
await keychain.setSecret(settingToUpdate.envVar, newValue);
return;
}
+13 -16
View File
@@ -24,7 +24,7 @@ export type JsonValue =
| JsonArray;
export type VariableContext = {
[key: string]: string | undefined;
[key in keyof typeof VARIABLE_SCHEMA]?: string;
};
export function validateVariables(
@@ -33,7 +33,7 @@ export function validateVariables(
) {
for (const key in schema) {
const definition = schema[key];
if (definition.required && !variables[key]) {
if (definition.required && !variables[key as keyof VariableContext]) {
throw new Error(`Missing required variable: ${key}`);
}
}
@@ -43,33 +43,30 @@ export function hydrateString(str: string, context: VariableContext): string {
validateVariables(context, VARIABLE_SCHEMA);
const regex = /\${(.*?)}/g;
return str.replace(regex, (match, key) =>
context[key] == null ? match : context[key],
context[key as keyof VariableContext] == null
? match
: (context[key as keyof VariableContext] as string),
);
}
export function recursivelyHydrateStrings<T>(
obj: T,
export function recursivelyHydrateStrings(
obj: JsonValue,
values: VariableContext,
): T {
): JsonValue {
if (typeof obj === 'string') {
return hydrateString(obj, values) as unknown as T;
return hydrateString(obj, values);
}
if (Array.isArray(obj)) {
return obj.map((item) =>
recursivelyHydrateStrings(item, values),
) as unknown as T;
return obj.map((item) => recursivelyHydrateStrings(item, values));
}
if (typeof obj === 'object' && obj !== null) {
const newObj: Record<string, unknown> = {};
const newObj: JsonObject = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
newObj[key] = recursivelyHydrateStrings(
(obj as Record<string, unknown>)[key],
values,
);
newObj[key] = recursivelyHydrateStrings(obj[key], values);
}
}
return newObj as T;
return newObj;
}
return obj;
}
+3 -56
View File
@@ -56,8 +56,6 @@ export enum Command {
NAVIGATION_DOWN = 'nav.down',
DIALOG_NAVIGATION_UP = 'nav.dialog.up',
DIALOG_NAVIGATION_DOWN = 'nav.dialog.down',
DIALOG_NEXT = 'nav.dialog.next',
DIALOG_PREV = 'nav.dialog.previous',
// Suggestions & Completions
ACCEPT_SUGGESTION = 'suggest.accept',
@@ -72,15 +70,6 @@ export enum Command {
OPEN_EXTERNAL_EDITOR = 'input.openExternalEditor',
PASTE_CLIPBOARD = 'input.paste',
BACKGROUND_SHELL_ESCAPE = 'backgroundShellEscape',
BACKGROUND_SHELL_SELECT = 'backgroundShellSelect',
TOGGLE_BACKGROUND_SHELL = 'toggleBackgroundShell',
TOGGLE_BACKGROUND_SHELL_LIST = 'toggleBackgroundShellList',
KILL_BACKGROUND_SHELL = 'backgroundShell.kill',
UNFOCUS_BACKGROUND_SHELL = 'backgroundShell.unfocus',
UNFOCUS_BACKGROUND_SHELL_LIST = 'backgroundShell.listUnfocus',
SHOW_BACKGROUND_SHELL_UNFOCUS_WARNING = 'backgroundShell.unfocusWarning',
// App Controls
SHOW_ERROR_DETAILS = 'app.showErrorDetails',
SHOW_FULL_TODOS = 'app.showFullTodos',
@@ -94,7 +83,6 @@ export enum Command {
UNFOCUS_SHELL_INPUT = 'app.unfocusShellInput',
CLEAR_SCREEN = 'app.clearScreen',
RESTART_APP = 'app.restart',
SUSPEND_APP = 'app.suspend',
}
/**
@@ -148,6 +136,7 @@ export const defaultKeyBindings: KeyBindingConfig = {
],
[Command.MOVE_LEFT]: [
{ key: 'left', shift: false, alt: false, ctrl: false, cmd: false },
{ key: 'b', ctrl: true },
],
[Command.MOVE_RIGHT]: [
{ key: 'right', shift: false, alt: false, ctrl: false, cmd: false },
@@ -179,15 +168,8 @@ export const defaultKeyBindings: KeyBindingConfig = {
],
[Command.DELETE_CHAR_LEFT]: [{ key: 'backspace' }, { key: 'h', ctrl: true }],
[Command.DELETE_CHAR_RIGHT]: [{ key: 'delete' }, { key: 'd', ctrl: true }],
[Command.UNDO]: [
{ key: 'z', cmd: true, shift: false },
{ key: 'z', alt: true, shift: false },
],
[Command.REDO]: [
{ key: 'z', ctrl: true, shift: true },
{ key: 'z', cmd: true, shift: true },
{ key: 'z', alt: true, shift: true },
],
[Command.UNDO]: [{ key: 'z', shift: false, ctrl: true }],
[Command.REDO]: [{ key: 'z', shift: true, ctrl: true }],
// Scrolling
[Command.SCROLL_UP]: [{ key: 'up', shift: true }],
@@ -224,8 +206,6 @@ export const defaultKeyBindings: KeyBindingConfig = {
{ key: 'down', shift: false },
{ key: 'j', shift: false },
],
[Command.DIALOG_NEXT]: [{ key: 'tab', shift: false }],
[Command.DIALOG_PREV]: [{ key: 'tab', shift: true }],
// Suggestions & Completions
[Command.ACCEPT_SUGGESTION]: [{ key: 'tab' }, { key: 'return', ctrl: false }],
@@ -273,16 +253,6 @@ export const defaultKeyBindings: KeyBindingConfig = {
[Command.TOGGLE_COPY_MODE]: [{ key: 's', ctrl: true }],
[Command.TOGGLE_YOLO]: [{ key: 'y', ctrl: true }],
[Command.CYCLE_APPROVAL_MODE]: [{ key: 'tab', shift: true }],
[Command.TOGGLE_BACKGROUND_SHELL]: [{ key: 'b', ctrl: true }],
[Command.TOGGLE_BACKGROUND_SHELL_LIST]: [{ key: 'l', ctrl: true }],
[Command.KILL_BACKGROUND_SHELL]: [{ key: 'k', ctrl: true }],
[Command.UNFOCUS_BACKGROUND_SHELL]: [{ key: 'tab', shift: true }],
[Command.UNFOCUS_BACKGROUND_SHELL_LIST]: [{ key: 'tab', shift: false }],
[Command.SHOW_BACKGROUND_SHELL_UNFOCUS_WARNING]: [
{ key: 'tab', shift: false },
],
[Command.BACKGROUND_SHELL_SELECT]: [{ key: 'return' }],
[Command.BACKGROUND_SHELL_ESCAPE]: [{ key: 'escape' }],
[Command.SHOW_MORE_LINES]: [
{ key: 'o', ctrl: true },
{ key: 's', ctrl: true },
@@ -291,7 +261,6 @@ export const defaultKeyBindings: KeyBindingConfig = {
[Command.UNFOCUS_SHELL_INPUT]: [{ key: 'tab' }],
[Command.CLEAR_SCREEN]: [{ key: 'l', ctrl: true }],
[Command.RESTART_APP]: [{ key: 'r' }],
[Command.SUSPEND_APP]: [{ key: 'z', ctrl: true }],
};
interface CommandCategory {
@@ -363,8 +332,6 @@ export const commandCategories: readonly CommandCategory[] = [
Command.NAVIGATION_DOWN,
Command.DIALOG_NAVIGATION_UP,
Command.DIALOG_NAVIGATION_DOWN,
Command.DIALOG_NEXT,
Command.DIALOG_PREV,
],
},
{
@@ -397,19 +364,10 @@ export const commandCategories: readonly CommandCategory[] = [
Command.TOGGLE_YOLO,
Command.CYCLE_APPROVAL_MODE,
Command.SHOW_MORE_LINES,
Command.TOGGLE_BACKGROUND_SHELL,
Command.TOGGLE_BACKGROUND_SHELL_LIST,
Command.KILL_BACKGROUND_SHELL,
Command.BACKGROUND_SHELL_SELECT,
Command.BACKGROUND_SHELL_ESCAPE,
Command.UNFOCUS_BACKGROUND_SHELL,
Command.UNFOCUS_BACKGROUND_SHELL_LIST,
Command.SHOW_BACKGROUND_SHELL_UNFOCUS_WARNING,
Command.FOCUS_SHELL_INPUT,
Command.UNFOCUS_SHELL_INPUT,
Command.CLEAR_SCREEN,
Command.RESTART_APP,
Command.SUSPEND_APP,
],
},
];
@@ -468,8 +426,6 @@ export const commandDescriptions: Readonly<Record<Command, string>> = {
[Command.NAVIGATION_DOWN]: 'Move selection down in lists.',
[Command.DIALOG_NAVIGATION_UP]: 'Move up within dialog options.',
[Command.DIALOG_NAVIGATION_DOWN]: 'Move down within dialog options.',
[Command.DIALOG_NEXT]: 'Move to the next item or question in a dialog.',
[Command.DIALOG_PREV]: 'Move to the previous item or question in a dialog.',
// Suggestions & Completions
[Command.ACCEPT_SUGGESTION]: 'Accept the inline suggestion.',
@@ -496,17 +452,8 @@ export const commandDescriptions: Readonly<Record<Command, string>> = {
'Cycle through approval modes: default (prompt), auto_edit (auto-approve edits), and plan (read-only).',
[Command.SHOW_MORE_LINES]:
'Expand a height-constrained response to show additional lines when not in alternate buffer mode.',
[Command.BACKGROUND_SHELL_SELECT]: 'Enter',
[Command.BACKGROUND_SHELL_ESCAPE]: 'Esc',
[Command.TOGGLE_BACKGROUND_SHELL]: 'Ctrl+B',
[Command.TOGGLE_BACKGROUND_SHELL_LIST]: 'Ctrl+L',
[Command.KILL_BACKGROUND_SHELL]: 'Ctrl+K',
[Command.UNFOCUS_BACKGROUND_SHELL]: 'Shift+Tab',
[Command.UNFOCUS_BACKGROUND_SHELL_LIST]: 'Tab',
[Command.SHOW_BACKGROUND_SHELL_UNFOCUS_WARNING]: 'Tab',
[Command.FOCUS_SHELL_INPUT]: 'Focus the shell input from the gemini input.',
[Command.UNFOCUS_SHELL_INPUT]: 'Focus the Gemini input from the shell input.',
[Command.CLEAR_SCREEN]: 'Clear the terminal screen and redraw the UI.',
[Command.RESTART_APP]: 'Restart the application.',
[Command.SUSPEND_APP]: 'Suspend the application (not yet implemented).',
};
@@ -164,6 +164,7 @@ describe('Policy Engine Integration Tests', () => {
it('should handle complex mixed configurations', async () => {
const settings: Settings = {
tools: {
autoAccept: true, // Allows read-only tools
allowed: ['custom-tool', 'my-server__special-tool'],
exclude: ['glob', 'dangerous-tool'],
},
@@ -437,6 +438,7 @@ describe('Policy Engine Integration Tests', () => {
it('should verify priority ordering works correctly in practice', async () => {
const settings: Settings = {
tools: {
autoAccept: true, // Priority 50
allowed: ['specific-tool'], // Priority 100
exclude: ['blocked-tool'], // Priority 200
},
@@ -612,6 +614,7 @@ describe('Policy Engine Integration Tests', () => {
it('should verify rules are created with correct priorities', async () => {
const settings: Settings = {
tools: {
autoAccept: true,
allowed: ['tool1', 'tool2'],
exclude: ['tool3'],
},
@@ -124,6 +124,7 @@ describe('settings-validation', () => {
},
tools: {
sandbox: 'inherit',
autoAccept: false,
},
};
+33 -116
View File
@@ -29,11 +29,10 @@ vi.mock('./settings.js', async (importActual) => {
});
// Mock trustedFolders
import * as trustedFolders from './trustedFolders.js';
vi.mock('./trustedFolders.js', () => ({
isWorkspaceTrusted: vi.fn(),
isFolderTrustEnabled: vi.fn(),
loadTrustedFolders: vi.fn(),
isWorkspaceTrusted: vi
.fn()
.mockReturnValue({ isTrusted: true, source: 'file' }),
}));
vi.mock('./settingsSchema.js', async (importOriginal) => {
@@ -152,7 +151,7 @@ describe('Settings Loading and Merging', () => {
(mockFsExistsSync as Mock).mockReturnValue(false);
(fs.readFileSync as Mock).mockReturnValue('{}'); // Return valid empty JSON
(mockFsMkdirSync as Mock).mockImplementation(() => undefined);
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
source: 'file',
});
@@ -1636,7 +1635,7 @@ describe('Settings Loading and Merging', () => {
});
it('should NOT merge workspace settings when workspace is not trusted', () => {
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: false,
source: 'file',
});
@@ -1667,60 +1666,23 @@ describe('Settings Loading and Merging', () => {
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
expect(settings.merged.ui?.theme).toBe('dark'); // User setting
});
it('should NOT merge workspace settings when workspace trust is undefined', () => {
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
isTrusted: undefined,
source: undefined,
});
(mockFsExistsSync as Mock).mockReturnValue(true);
const userSettingsContent = {
ui: { theme: 'dark' },
tools: { sandbox: false },
context: { fileName: 'USER.md' },
};
const workspaceSettingsContent = {
tools: { sandbox: true },
context: { fileName: 'WORKSPACE.md' },
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
if (p === MOCK_WORKSPACE_SETTINGS_PATH)
return JSON.stringify(workspaceSettingsContent);
return '{}';
},
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(settings.merged.tools?.sandbox).toBe(false); // User setting
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
});
});
describe('loadEnvironment', () => {
function setup({
isFolderTrustEnabled = true,
isWorkspaceTrustedValue = true as boolean | undefined,
isWorkspaceTrustedValue = true,
}) {
delete process.env['TESTTEST']; // reset
const geminiEnvPath = path.resolve(
path.join(MOCK_WORKSPACE_DIR, GEMINI_DIR, '.env'),
);
const geminiEnvPath = path.resolve(path.join(GEMINI_DIR, '.env'));
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: isWorkspaceTrustedValue,
source: 'file',
});
(mockFsExistsSync as Mock).mockImplementation((p: fs.PathLike) => {
const normalizedP = path.resolve(p.toString());
return [path.resolve(USER_SETTINGS_PATH), geminiEnvPath].includes(
normalizedP,
);
});
(mockFsExistsSync as Mock).mockImplementation((p: fs.PathLike) =>
[USER_SETTINGS_PATH, geminiEnvPath].includes(p.toString()),
);
const userSettingsContent: Settings = {
ui: {
theme: 'dark',
@@ -1736,10 +1698,9 @@ describe('Settings Loading and Merging', () => {
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
const normalizedP = path.resolve(p.toString());
if (normalizedP === path.resolve(USER_SETTINGS_PATH))
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
if (normalizedP === geminiEnvPath) return 'TESTTEST=1234';
if (p === geminiEnvPath) return 'TESTTEST=1234';
return '{}';
},
);
@@ -1747,34 +1708,14 @@ describe('Settings Loading and Merging', () => {
it('sets environment variables from .env files', () => {
setup({ isFolderTrustEnabled: false, isWorkspaceTrustedValue: true });
const settings = {
security: { folderTrust: { enabled: false } },
} as Settings;
loadEnvironment(settings, MOCK_WORKSPACE_DIR, isWorkspaceTrusted);
loadEnvironment(loadSettings(MOCK_WORKSPACE_DIR).merged);
expect(process.env['TESTTEST']).toEqual('1234');
});
it('does not load env files from untrusted spaces', () => {
setup({ isFolderTrustEnabled: true, isWorkspaceTrustedValue: false });
const settings = {
security: { folderTrust: { enabled: true } },
} as Settings;
loadEnvironment(settings, MOCK_WORKSPACE_DIR, isWorkspaceTrusted);
expect(process.env['TESTTEST']).not.toEqual('1234');
});
it('does not load env files when trust is undefined', () => {
delete process.env['TESTTEST'];
// isWorkspaceTrusted returns {isTrusted: undefined} for matched rules with no trust value, or no matching rules.
setup({ isFolderTrustEnabled: true, isWorkspaceTrustedValue: undefined });
const settings = {
security: { folderTrust: { enabled: true } },
} as Settings;
const mockTrustFn = vi.fn().mockReturnValue({ isTrusted: undefined });
loadEnvironment(settings, MOCK_WORKSPACE_DIR, mockTrustFn);
loadEnvironment(loadSettings(MOCK_WORKSPACE_DIR).merged);
expect(process.env['TESTTEST']).not.toEqual('1234');
});
@@ -1790,7 +1731,7 @@ describe('Settings Loading and Merging', () => {
mockFsExistsSync.mockReturnValue(true);
mockFsReadFileSync = vi.mocked(fs.readFileSync);
mockFsReadFileSync.mockReturnValue('{}');
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
source: undefined,
});
@@ -2215,7 +2156,7 @@ describe('Settings Loading and Merging', () => {
// 2. Now, set remote admin settings.
loadedSettings.setRemoteAdminSettings({
strictModeDisabled: false,
secureModeEnabled: true,
mcpSetting: { mcpEnabled: false },
cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
});
@@ -2256,7 +2197,7 @@ describe('Settings Loading and Merging', () => {
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
const newRemoteSettings = {
strictModeDisabled: false,
secureModeEnabled: true,
mcpSetting: { mcpEnabled: false },
cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
};
@@ -2272,7 +2213,7 @@ describe('Settings Loading and Merging', () => {
// Verify that calling setRemoteAdminSettings with partial data overwrites previous remote settings
// and missing properties revert to schema defaults.
loadedSettings.setRemoteAdminSettings({ strictModeDisabled: true });
loadedSettings.setRemoteAdminSettings({ secureModeEnabled: false });
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false); // Defaulting to false if missing
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false); // Defaulting to false if missing
@@ -2330,9 +2271,9 @@ describe('Settings Loading and Merging', () => {
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
// Set remote settings with only strictModeDisabled (false -> secureModeEnabled: true)
// Set remote settings with only secureModeEnabled
loadedSettings.setRemoteAdminSettings({
strictModeDisabled: false,
secureModeEnabled: true,
});
// Verify secureModeEnabled is updated, others default to false
@@ -2345,8 +2286,8 @@ describe('Settings Loading and Merging', () => {
mcpSetting: { mcpEnabled: false },
});
// Verify mcpEnabled is updated, others remain defaults (secureModeEnabled defaults to true if strictModeDisabled is missing)
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
// Verify mcpEnabled is updated, others remain defaults (secureModeEnabled reverts to default:false)
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
@@ -2356,47 +2297,23 @@ describe('Settings Loading and Merging', () => {
});
// Verify extensionsEnabled is updated, others remain defaults
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
// Verify that missing strictModeDisabled falls back to secureModeEnabled
loadedSettings.setRemoteAdminSettings({
secureModeEnabled: false,
});
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
loadedSettings.setRemoteAdminSettings({
secureModeEnabled: true,
});
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
// Verify strictModeDisabled takes precedence over secureModeEnabled
loadedSettings.setRemoteAdminSettings({
strictModeDisabled: false,
secureModeEnabled: false,
});
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
loadedSettings.setRemoteAdminSettings({
strictModeDisabled: true,
secureModeEnabled: true,
});
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
});
it('should set skills based on unmanagedCapabilitiesEnabled', () => {
it('should set skills based on advancedFeaturesEnabled', () => {
const loadedSettings = loadSettings();
loadedSettings.setRemoteAdminSettings({
cliFeatureSetting: {
unmanagedCapabilitiesEnabled: true,
advancedFeaturesEnabled: true,
},
});
expect(loadedSettings.merged.admin.skills?.enabled).toBe(true);
loadedSettings.setRemoteAdminSettings({
cliFeatureSetting: {
unmanagedCapabilitiesEnabled: false,
advancedFeaturesEnabled: false,
},
});
expect(loadedSettings.merged.admin.skills?.enabled).toBe(false);
@@ -2420,12 +2337,12 @@ describe('Settings Loading and Merging', () => {
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
});
it('should force secureModeEnabled to true if undefined, overriding schema defaults', () => {
// Mock schema to have secureModeEnabled default to false to verify the override
it('should force secureModeEnabled to false if undefined, overriding schema defaults', () => {
// Mock schema to have secureModeEnabled default to true to verify the override
const originalSchema = getSettingsSchema();
const modifiedSchema = JSON.parse(JSON.stringify(originalSchema));
if (modifiedSchema.admin?.properties?.secureModeEnabled) {
modifiedSchema.admin.properties.secureModeEnabled.default = false;
modifiedSchema.admin.properties.secureModeEnabled.default = true;
}
vi.mocked(getSettingsSchema).mockReturnValue(modifiedSchema);
@@ -2435,13 +2352,13 @@ describe('Settings Loading and Merging', () => {
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
// Pass a non-empty object that doesn't have strictModeDisabled
// Pass a non-empty object that doesn't have secureModeEnabled
loadedSettings.setRemoteAdminSettings({
mcpSetting: {},
});
// It should be forced to true by the logic (default secure), overriding the mock default of false
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
// It should be forced to false by the logic, overriding the mock default of true
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
} finally {
vi.mocked(getSettingsSchema).mockReturnValue(originalSchema);
}
+12 -30
View File
@@ -50,9 +50,7 @@ import {
formatValidationError,
} from './settings-validation.js';
export function getMergeStrategyForPath(
path: string[],
): MergeStrategy | undefined {
function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined {
let current: SettingDefinition | undefined = undefined;
let currentSchema: SettingsSchema | undefined = getSettingsSchema();
let parent: SettingDefinition | undefined = undefined;
@@ -350,12 +348,7 @@ export class LoadedSettings {
setRemoteAdminSettings(remoteSettings: FetchAdminControlsResponse): void {
const admin: Settings['admin'] = {};
const {
secureModeEnabled,
strictModeDisabled,
mcpSetting,
cliFeatureSetting,
} = remoteSettings;
const { secureModeEnabled, mcpSetting, cliFeatureSetting } = remoteSettings;
if (Object.keys(remoteSettings).length === 0) {
this._remoteAdminSettings = { admin };
@@ -363,20 +356,15 @@ export class LoadedSettings {
return;
}
if (strictModeDisabled !== undefined) {
admin.secureModeEnabled = !strictModeDisabled;
} else if (secureModeEnabled !== undefined) {
admin.secureModeEnabled = secureModeEnabled;
} else {
admin.secureModeEnabled = true;
}
admin.secureModeEnabled = secureModeEnabled ?? false;
admin.mcp = { enabled: mcpSetting?.mcpEnabled ?? false };
admin.extensions = {
enabled: cliFeatureSetting?.extensionsSetting?.extensionsEnabled ?? false,
};
admin.skills = {
enabled: cliFeatureSetting?.unmanagedCapabilitiesEnabled ?? false,
};
if (cliFeatureSetting?.advancedFeaturesEnabled !== undefined) {
admin.skills = { enabled: cliFeatureSetting.advancedFeaturesEnabled };
}
this._remoteAdminSettings = { admin };
this._merged = this.computeMergedSettings();
@@ -434,15 +422,10 @@ export function setUpCloudShellEnvironment(envFilePath: string | null): void {
}
}
export function loadEnvironment(
settings: Settings,
workspaceDir: string,
isWorkspaceTrustedFn = isWorkspaceTrusted,
): void {
const envFilePath = findEnvFile(workspaceDir);
const trustResult = isWorkspaceTrustedFn(settings, workspaceDir);
export function loadEnvironment(settings: Settings): void {
const envFilePath = findEnvFile(process.cwd());
if (trustResult.isTrusted !== true) {
if (!isWorkspaceTrusted(settings).isTrusted) {
return;
}
@@ -607,8 +590,7 @@ export function loadSettings(
userSettings,
);
const isTrusted =
isWorkspaceTrusted(initialTrustCheckSettings as Settings, workspaceDir)
.isTrusted ?? false;
isWorkspaceTrusted(initialTrustCheckSettings as Settings).isTrusted ?? true;
// Create a temporary merged settings object to pass to loadEnvironment.
const tempMergedSettings = mergeSettings(
@@ -621,7 +603,7 @@ export function loadSettings(
// loadEnvironment depends on settings so we have to create a temp version of
// the settings to avoid a cycle
loadEnvironment(tempMergedSettings, workspaceDir);
loadEnvironment(tempMergedSettings);
// Check for any fatal errors before proceeding
const fatalErrors = settingsErrors.filter((e) => e.severity === 'error');
@@ -107,14 +107,6 @@ describe('SettingsSchema', () => {
getSettingsSchema().context.properties.fileFiltering.properties
?.enableRecursiveFileSearch,
).toBeDefined();
expect(
getSettingsSchema().context.properties.fileFiltering.properties
?.customIgnoreFilePaths,
).toBeDefined();
expect(
getSettingsSchema().context.properties.fileFiltering.properties
?.customIgnoreFilePaths.type,
).toBe('array');
});
it('should have unique categories', () => {
+41 -71
View File
@@ -9,17 +9,19 @@
// to regenerate the settings reference in `docs/get-started/configuration.md`.
// --------------------------------------------------------------------------
import type {
MCPServerConfig,
BugCommandSettings,
TelemetrySettings,
AuthType,
AgentOverride,
} from '@google/gemini-cli-core';
import {
DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES,
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
DEFAULT_MODEL_CONFIGS,
type MCPServerConfig,
type BugCommandSettings,
type TelemetrySettings,
type AuthType,
type AgentOverride,
type CustomTheme,
} from '@google/gemini-cli-core';
import type { CustomTheme } from '../ui/themes/theme.js';
import type { SessionRetentionSettings } from './settings.js';
import { DEFAULT_MIN_RETENTION } from '../utils/sessionCleanup.js';
@@ -351,26 +353,6 @@ const SETTINGS_SCHEMA = {
'The color theme for the UI. See the CLI themes guide for available options.',
showInDialog: false,
},
autoThemeSwitching: {
type: 'boolean',
label: 'Auto Theme Switching',
category: 'UI',
requiresRestart: false,
default: true,
description:
'Automatically switch between default light and dark themes based on terminal background color.',
showInDialog: true,
},
terminalBackgroundPollingInterval: {
type: 'number',
label: 'Terminal Background Polling Interval',
category: 'UI',
requiresRestart: false,
default: 60,
description:
'Interval in seconds to poll the terminal background color.',
showInDialog: true,
},
customThemes: {
type: 'object',
label: 'Custom Themes',
@@ -544,16 +526,6 @@ const SETTINGS_SCHEMA = {
description: 'Show the model name in the chat for each model turn.',
showInDialog: true,
},
showUserIdentity: {
type: 'boolean',
label: 'Show User Identity',
category: 'UI',
requiresRestart: false,
default: true,
description:
"Show the logged-in user's identity (e.g. email) in the UI.",
showInDialog: true,
},
useAlternateBuffer: {
type: 'boolean',
label: 'Use Alternate Screen Buffer',
@@ -759,16 +731,6 @@ const SETTINGS_SCHEMA = {
'The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3).',
showInDialog: true,
},
disableLoopDetection: {
type: 'boolean',
label: 'Disable Loop Detection',
category: 'Model',
requiresRestart: true,
default: false,
description:
'Disable automatic detection and prevention of infinite loops.',
showInDialog: true,
},
skipNextSpeakerCheck: {
type: 'boolean',
label: 'Skip Next Speaker Check',
@@ -970,18 +932,6 @@ const SETTINGS_SCHEMA = {
description: 'Enable fuzzy search when searching for files.',
showInDialog: true,
},
customIgnoreFilePaths: {
type: 'array',
label: 'Custom Ignore File Paths',
category: 'Context',
requiresRestart: true,
default: [] as string[],
description:
'Additional ignore file paths to respect. These files take precedence over .geminiignore and .gitignore. Files earlier in the array take precedence over files later in the array, e.g. the first file takes precedence over the second one.',
showInDialog: true,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
},
},
},
@@ -1071,6 +1021,17 @@ const SETTINGS_SCHEMA = {
},
},
},
autoAccept: {
type: 'boolean',
label: 'Auto Accept',
category: 'Tools',
requiresRestart: false,
default: false,
description: oneLine`
Automatically accept and execute tool calls that are considered safe (e.g., read-only operations).
`,
showInDialog: true,
},
approvalMode: {
type: 'enum',
label: 'Approval Mode',
@@ -1198,6 +1159,16 @@ const SETTINGS_SCHEMA = {
`,
showInDialog: true,
},
enableHooks: {
type: 'boolean',
label: 'Enable Hooks System (Experimental)',
category: 'Advanced',
requiresRestart: true,
default: true,
description:
'Enables the hooks system experiment. When disabled, the hooks system is completely deactivated regardless of other settings.',
showInDialog: false,
},
},
},
@@ -1287,17 +1258,6 @@ const SETTINGS_SCHEMA = {
description: 'Blocks installing and loading extensions from Git.',
showInDialog: true,
},
allowedExtensions: {
type: 'array',
label: 'Extension Source Regex Allowlist',
category: 'Security',
requiresRestart: true,
default: [] as string[],
description:
'List of Regex patterns for allowed extensions. If nonempty, only extensions that match the patterns in this list are allowed. Overrides the blockGitExtensions setting.',
showInDialog: true,
items: { type: 'string' },
},
folderTrust: {
type: 'object',
label: 'Folder Trust',
@@ -1518,6 +1478,15 @@ const SETTINGS_SCHEMA = {
description: 'Enable Just-In-Time (JIT) context loading.',
showInDialog: false,
},
skills: {
type: 'boolean',
label: 'Agent Skills',
category: 'Experimental',
requiresRestart: true,
default: false,
description: 'Enable Agent Skills (experimental).',
showInDialog: true,
},
useOSC52Paste: {
type: 'boolean',
label: 'Use OSC 52 Paste',
@@ -1592,6 +1561,7 @@ const SETTINGS_SCHEMA = {
default: true,
description: 'Enable Agent Skills.',
showInDialog: true,
ignoreInDocs: true,
},
disabled: {
type: 'array',
@@ -1621,11 +1591,11 @@ const SETTINGS_SCHEMA = {
type: 'boolean',
label: 'Enable Hooks',
category: 'Advanced',
requiresRestart: true,
requiresRestart: false,
default: true,
description:
'Canonical toggle for the hooks system. When disabled, no hooks will be executed.',
showInDialog: true,
showInDialog: false,
},
disabled: {
type: 'array',
@@ -0,0 +1,158 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { loadCliConfig, parseArguments } from './config.js';
import * as trustedFolders from './trustedFolders.js';
import { loadServerHierarchicalMemory } from '@google/gemini-cli-core';
import { type Settings, createTestMergedSettings } from './settings.js';
vi.mock('./trustedFolders.js');
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
loadServerHierarchicalMemory: vi.fn(),
getPty: vi.fn().mockResolvedValue({ name: 'test-pty' }),
getVersion: vi.fn().mockResolvedValue('0.0.0-test'),
};
});
describe('Agent Skills Backward Compatibility', () => {
const originalArgv = process.argv;
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(trustedFolders.isWorkspaceTrusted).mockReturnValue({
isTrusted: true,
} as unknown as trustedFolders.TrustResult);
});
afterEach(() => {
process.argv = originalArgv;
});
describe('loadCliConfig', () => {
it('should default skillsSupport to true when no settings are present', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
process.argv = ['node', 'gemini'];
const settings = createTestMergedSettings({});
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
it('should prioritize skills.enabled=false from settings', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
skills: { enabled: false },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(false);
});
it('should support legacy experimental.skills=true from settings', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
experimental: { skills: true },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
it('should prioritize legacy experimental.skills=true over new skills.enabled=false', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
skills: { enabled: false },
experimental: { skills: true },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
it('should still be enabled by default if legacy experimental.skills is false (since new default is true)', async () => {
vi.mocked(loadServerHierarchicalMemory).mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
const settings = createTestMergedSettings({
experimental: { skills: false },
} as unknown as Settings);
process.argv = ['node', 'gemini'];
const config = await loadCliConfig(
settings,
'test-session',
await parseArguments(settings),
);
expect(
(
config as unknown as { isSkillsSupportEnabled: () => boolean }
).isSkillsSupportEnabled(),
).toBe(true);
});
});
});
@@ -326,19 +326,6 @@ describe('isWorkspaceTrusted', () => {
});
});
it('should use workspaceDir instead of process.cwd() when provided', () => {
mockCwd = '/home/user/untrusted';
const workspaceDir = '/home/user/projectA';
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST;
// process.cwd() is untrusted, but workspaceDir is trusted
expect(isWorkspaceTrusted(mockSettings, workspaceDir)).toEqual({
isTrusted: true,
source: 'file',
});
});
it('should handle path normalization', () => {
mockCwd = '/home/user/projectA';
mockRules[`/home/user/../user/${path.basename('/home/user/projectA')}`] =
+2 -4
View File
@@ -227,7 +227,6 @@ export function isFolderTrustEnabled(settings: Settings): boolean {
}
function getWorkspaceTrustFromLocalConfig(
workspaceDir: string,
trustConfig?: Record<string, TrustLevel>,
): TrustResult {
const folders = loadTrustedFolders();
@@ -242,7 +241,7 @@ function getWorkspaceTrustFromLocalConfig(
);
}
const isTrusted = folders.isPathTrusted(workspaceDir, configToUse);
const isTrusted = folders.isPathTrusted(process.cwd(), configToUse);
return {
isTrusted,
source: isTrusted !== undefined ? 'file' : undefined,
@@ -251,7 +250,6 @@ function getWorkspaceTrustFromLocalConfig(
export function isWorkspaceTrusted(
settings: Settings,
workspaceDir: string = process.cwd(),
trustConfig?: Record<string, TrustLevel>,
): TrustResult {
if (!isFolderTrustEnabled(settings)) {
@@ -264,5 +262,5 @@ export function isWorkspaceTrusted(
}
// Fall back to the local user configuration
return getWorkspaceTrustFromLocalConfig(workspaceDir, trustConfig);
return getWorkspaceTrustFromLocalConfig(trustConfig);
}
+28 -2
View File
@@ -13,6 +13,8 @@ import {
StartSessionEvent,
logCliConfiguration,
startupProfiler,
type ConnectionConfig,
type IdeInfo,
} from '@google/gemini-cli-core';
import { type LoadedSettings } from '../config/settings.js';
import { performInitialAuth } from './auth.js';
@@ -23,6 +25,9 @@ export interface InitializationResult {
themeError: string | null;
shouldOpenAuthDialog: boolean;
geminiMdFileCount: number;
availableIdeConnections?: Array<
ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }
>;
}
/**
@@ -52,10 +57,30 @@ export async function initializeApp(
new StartSessionEvent(config, config.getToolRegistry()),
);
let availableIdeConnections:
| Array<ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }>
| undefined;
if (config.getIdeMode()) {
const ideClient = await IdeClient.getInstance();
await ideClient.connect();
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.START));
// Try to auto-connect if possible (legacy behavior or single match)
// We attempt to connect. If it requires selection, we get a list back?
// No, I need to implement the logic here.
const connections = await ideClient.discoverAvailableConnections();
// Heuristic: If we have a PID match, prioritize it.
// Ideally IdeClient.getInstance() already did some detection but didn't connect.
// Actually IdeClient.connect() (without args) tries to find "the one" config.
// If I want to support multiple, I should check here.
if (connections.length > 1) {
// Multiple connections found, let the UI handle selection
availableIdeConnections = connections;
} else {
// 0 or 1 connection, or let connect() handle the "best guess" fallback
await ideClient.connect();
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.START));
}
}
return {
@@ -63,5 +88,6 @@ export async function initializeApp(
themeError,
shouldOpenAuthDialog,
geminiMdFileCount: config.getGeminiMdFileCount(),
availableIdeConnections,
};
}
+15 -17
View File
@@ -16,10 +16,11 @@ import type { ArgumentsCamelCase, CommandModule } from 'yargs';
import type { MergedSettings } from './config/settings.js';
import type { MockInstance } from 'vitest';
const { mockRunExitCleanup, mockCoreEvents } = vi.hoisted(() => ({
const { mockRunExitCleanup, mockDebugLogger } = vi.hoisted(() => ({
mockRunExitCleanup: vi.fn(),
mockCoreEvents: {
emitFeedback: vi.fn(),
mockDebugLogger: {
log: vi.fn(),
error: vi.fn(),
},
}));
@@ -27,7 +28,7 @@ vi.mock('@google/gemini-cli-core', async () => {
const actual = await vi.importActual('@google/gemini-cli-core');
return {
...actual,
coreEvents: mockCoreEvents,
debugLogger: mockDebugLogger,
};
});
@@ -54,7 +55,8 @@ describe('deferred', () => {
describe('runDeferredCommand', () => {
it('should do nothing if no deferred command is set', async () => {
await runDeferredCommand(createMockSettings());
expect(mockCoreEvents.emitFeedback).not.toHaveBeenCalled();
expect(mockDebugLogger.log).not.toHaveBeenCalled();
expect(mockDebugLogger.error).not.toHaveBeenCalled();
expect(mockExit).not.toHaveBeenCalled();
});
@@ -83,9 +85,8 @@ describe('deferred', () => {
const settings = createMockSettings({ mcp: { enabled: false } });
await runDeferredCommand(settings);
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
'error',
'MCP is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
expect(mockDebugLogger.error).toHaveBeenCalledWith(
'Error: MCP is disabled by your admin.',
);
expect(mockRunExitCleanup).toHaveBeenCalled();
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
@@ -101,9 +102,8 @@ describe('deferred', () => {
const settings = createMockSettings({ extensions: { enabled: false } });
await runDeferredCommand(settings);
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
'error',
'Extensions is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
expect(mockDebugLogger.error).toHaveBeenCalledWith(
'Error: Extensions are disabled by your admin.',
);
expect(mockRunExitCleanup).toHaveBeenCalled();
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
@@ -119,9 +119,8 @@ describe('deferred', () => {
const settings = createMockSettings({ skills: { enabled: false } });
await runDeferredCommand(settings);
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
'error',
'Agent skills is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
expect(mockDebugLogger.error).toHaveBeenCalledWith(
'Error: Agent skills are disabled by your admin.',
);
expect(mockRunExitCleanup).toHaveBeenCalled();
expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
@@ -184,9 +183,8 @@ describe('deferred', () => {
const mcpSettings = createMockSettings({ mcp: { enabled: false } });
await runDeferredCommand(mcpSettings);
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
'error',
'MCP is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
expect(mockDebugLogger.error).toHaveBeenCalledWith(
'Error: MCP is disabled by your admin.',
);
});
+4 -17
View File
@@ -4,11 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { ArgumentsCamelCase, CommandModule } from 'yargs';
import {
coreEvents,
ExitCodes,
getAdminErrorMessage,
} from '@google/gemini-cli-core';
import { debugLogger, ExitCodes } from '@google/gemini-cli-core';
import { runExitCleanup } from './utils/cleanup.js';
import type { MergedSettings } from './config/settings.js';
import process from 'node:process';
@@ -34,10 +30,7 @@ export async function runDeferredCommand(settings: MergedSettings) {
const commandName = deferredCommand.commandName;
if (commandName === 'mcp' && adminSettings?.mcp?.enabled === false) {
coreEvents.emitFeedback(
'error',
getAdminErrorMessage('MCP', undefined /* config */),
);
debugLogger.error('Error: MCP is disabled by your admin.');
await runExitCleanup();
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
}
@@ -46,19 +39,13 @@ export async function runDeferredCommand(settings: MergedSettings) {
commandName === 'extensions' &&
adminSettings?.extensions?.enabled === false
) {
coreEvents.emitFeedback(
'error',
getAdminErrorMessage('Extensions', undefined /* config */),
);
debugLogger.error('Error: Extensions are disabled by your admin.');
await runExitCleanup();
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
}
if (commandName === 'skills' && adminSettings?.skills?.enabled === false) {
coreEvents.emitFeedback(
'error',
getAdminErrorMessage('Agent skills', undefined /* config */),
);
debugLogger.error('Error: Agent skills are disabled by your admin.');
await runExitCleanup();
process.exit(ExitCodes.FATAL_CONFIG_ERROR);
}
+16 -25
View File
@@ -32,10 +32,6 @@ import {
runExitCleanup,
registerTelemetryConfig,
} from './utils/cleanup.js';
import {
cleanupToolOutputFiles,
cleanupExpiredSessions,
} from './utils/sessionCleanup.js';
import {
type Config,
type ResumedSessionData,
@@ -75,6 +71,7 @@ import {
} from './core/initializer.js';
import { validateAuthMethod } from './config/auth.js';
import { runZedIntegration } from './zed-integration/zedIntegration.js';
import { cleanupExpiredSessions } from './utils/sessionCleanup.js';
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
import { checkForUpdates } from './ui/utils/updateCheck.js';
import { handleAutoUpdate } from './utils/handleAutoUpdate.js';
@@ -98,7 +95,6 @@ import { deleteSession, listSessions } from './utils/sessions.js';
import { createPolicyUpdater } from './config/policy.js';
import { ScrollProvider } from './ui/contexts/ScrollProvider.js';
import { isAlternateBufferEnabled } from './ui/hooks/useAlternateBuffer.js';
import { TerminalProvider } from './ui/contexts/TerminalContext.js';
import { setupTerminalAndTheme } from './utils/terminalTheme.js';
import { profiler } from './ui/components/DebugProfiler.js';
@@ -229,21 +225,19 @@ export async function startInteractiveUI(
settings.merged.general.debugKeystrokeLogging
}
>
<TerminalProvider>
<ScrollProvider>
<SessionStatsProvider>
<VimModeProvider settings={settings}>
<AppContainer
config={config}
startupWarnings={startupWarnings}
version={version}
resumedSessionData={resumedSessionData}
initializationResult={initializationResult}
/>
</VimModeProvider>
</SessionStatsProvider>
</ScrollProvider>
</TerminalProvider>
<ScrollProvider>
<SessionStatsProvider>
<VimModeProvider settings={settings}>
<AppContainer
config={config}
startupWarnings={startupWarnings}
version={version}
resumedSessionData={resumedSessionData}
initializationResult={initializationResult}
/>
</VimModeProvider>
</SessionStatsProvider>
</ScrollProvider>
</MouseProvider>
</KeypressProvider>
</SettingsContext.Provider>
@@ -326,10 +320,7 @@ export async function main() {
);
});
await Promise.all([
cleanupCheckpoints(),
cleanupToolOutputFiles(settings.merged),
]);
await cleanupCheckpoints();
const parseArgsHandle = startupProfiler.start('parse_arguments');
const argv = await parseArguments(settings.merged);
@@ -376,7 +367,7 @@ export async function main() {
) {
settings.setValue(
SettingScope.User,
'security.auth.selectedType',
'selectedAuthType',
AuthType.COMPUTE_ADC,
);
}
@@ -38,11 +38,6 @@ import type { LoadedSettings } from './config/settings.js';
// Mock core modules
vi.mock('./ui/hooks/atCommandProcessor.js');
const mockRegisterActivityLogger = vi.hoisted(() => vi.fn());
vi.mock('./utils/activityLogger.js', () => ({
registerActivityLogger: mockRegisterActivityLogger,
}));
const mockCoreEvents = vi.hoisted(() => ({
on: vi.fn(),
off: vi.fn(),
@@ -258,61 +253,12 @@ describe('runNonInteractive', () => {
[{ text: 'Test input' }],
expect.any(AbortSignal),
'prompt-id-1',
undefined,
false,
'Test input',
);
expect(getWrittenOutput()).toBe('Hello World\n');
// Note: Telemetry shutdown is now handled in runExitCleanup() in cleanup.ts
// so we no longer expect shutdownTelemetry to be called directly here
});
it('should register activity logger when GEMINI_CLI_ACTIVITY_LOG_FILE is set', async () => {
vi.stubEnv('GEMINI_CLI_ACTIVITY_LOG_FILE', '/tmp/test.jsonl');
const events: ServerGeminiStreamEvent[] = [
{
type: GeminiEventType.Finished,
value: { reason: undefined, usageMetadata: { totalTokenCount: 0 } },
},
];
mockGeminiClient.sendMessageStream.mockReturnValue(
createStreamFromEvents(events),
);
await runNonInteractive({
config: mockConfig,
settings: mockSettings,
input: 'test',
prompt_id: 'prompt-id-activity-logger',
});
expect(mockRegisterActivityLogger).toHaveBeenCalledWith(mockConfig);
vi.unstubAllEnvs();
});
it('should not register activity logger when GEMINI_CLI_ACTIVITY_LOG_FILE is not set', async () => {
vi.stubEnv('GEMINI_CLI_ACTIVITY_LOG_FILE', '');
const events: ServerGeminiStreamEvent[] = [
{
type: GeminiEventType.Finished,
value: { reason: undefined, usageMetadata: { totalTokenCount: 0 } },
},
];
mockGeminiClient.sendMessageStream.mockReturnValue(
createStreamFromEvents(events),
);
await runNonInteractive({
config: mockConfig,
settings: mockSettings,
input: 'test',
prompt_id: 'prompt-id-activity-logger-off',
});
expect(mockRegisterActivityLogger).not.toHaveBeenCalled();
vi.unstubAllEnvs();
});
it('should handle a single tool call and respond', async () => {
const toolCallEvent: ServerGeminiStreamEvent = {
type: GeminiEventType.ToolCallRequest,
@@ -377,9 +323,6 @@ describe('runNonInteractive', () => {
[{ text: 'Tool response' }],
expect.any(AbortSignal),
'prompt-id-2',
undefined,
false,
undefined,
);
expect(getWrittenOutput()).toBe('Final answer\n');
});
@@ -537,9 +480,6 @@ describe('runNonInteractive', () => {
],
expect.any(AbortSignal),
'prompt-id-3',
undefined,
false,
undefined,
);
expect(getWrittenOutput()).toBe('Sorry, let me try again.\n');
});
@@ -679,9 +619,6 @@ describe('runNonInteractive', () => {
processedParts,
expect.any(AbortSignal),
'prompt-id-7',
undefined,
false,
rawInput,
);
// 6. Assert the final output is correct
@@ -715,9 +652,6 @@ describe('runNonInteractive', () => {
[{ text: 'Test input' }],
expect.any(AbortSignal),
'prompt-id-1',
undefined,
false,
'Test input',
);
expect(processStdoutSpy).toHaveBeenCalledWith(
JSON.stringify(
@@ -848,9 +782,6 @@ describe('runNonInteractive', () => {
[{ text: 'Empty response test' }],
expect.any(AbortSignal),
'prompt-id-empty',
undefined,
false,
'Empty response test',
);
// This should output JSON with empty response but include stats
@@ -985,9 +916,6 @@ describe('runNonInteractive', () => {
[{ text: 'Prompt from command' }],
expect.any(AbortSignal),
'prompt-id-slash',
undefined,
false,
'/testcommand',
);
expect(getWrittenOutput()).toBe('Response from command\n');
@@ -1031,9 +959,6 @@ describe('runNonInteractive', () => {
[{ text: 'Slash command output' }],
expect.any(AbortSignal),
'prompt-id-slash',
undefined,
false,
'/help',
);
expect(getWrittenOutput()).toBe('Response to slash command\n');
handleSlashCommandSpy.mockRestore();
@@ -1208,9 +1133,6 @@ describe('runNonInteractive', () => {
[{ text: '/unknowncommand' }],
expect.any(AbortSignal),
'prompt-id-unknown',
undefined,
false,
'/unknowncommand',
);
expect(getWrittenOutput()).toBe('Response to unknown\n');
-11
View File
@@ -70,14 +70,6 @@ export async function runNonInteractive({
coreEvents.emitConsoleLog(msg.type, msg.content);
},
});
if (config.storage && process.env['GEMINI_CLI_ACTIVITY_LOG_FILE']) {
const { registerActivityLogger } = await import(
'./utils/activityLogger.js'
);
registerActivityLogger(config);
}
const { stdout: workingStdout } = createWorkingStdio();
const textOutput = new TextOutput(workingStdout);
@@ -301,9 +293,6 @@ export async function runNonInteractive({
currentMessages[0]?.parts || [],
abortController.signal,
prompt_id,
undefined,
false,
turnCount === 1 ? input : undefined,
);
let responseText = '';
@@ -119,12 +119,11 @@ describe('BuiltinCommandLoader', () => {
getEnableHooks: () => false,
getEnableHooksUI: () => false,
getExtensionsEnabled: vi.fn().mockReturnValue(true),
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
isSkillsSupportEnabled: vi.fn().mockReturnValue(false),
isAgentsEnabled: vi.fn().mockReturnValue(false),
getMcpEnabled: vi.fn().mockReturnValue(true),
getSkillManager: vi.fn().mockReturnValue({
getAllSkills: vi.fn().mockReturnValue([]),
isAdminEnabled: vi.fn().mockReturnValue(true),
}),
} as unknown as Config;
@@ -261,12 +260,11 @@ describe('BuiltinCommandLoader profile', () => {
getEnableHooks: () => false,
getEnableHooksUI: () => false,
getExtensionsEnabled: vi.fn().mockReturnValue(true),
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
isSkillsSupportEnabled: vi.fn().mockReturnValue(false),
isAgentsEnabled: vi.fn().mockReturnValue(false),
getMcpEnabled: vi.fn().mockReturnValue(true),
getSkillManager: vi.fn().mockReturnValue({
getAllSkills: vi.fn().mockReturnValue([]),
isAdminEnabled: vi.fn().mockReturnValue(true),
}),
} as unknown as Config;
});

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