Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f434734b2 | |||
| 8bc9238cab | |||
| ab64b15d51 | |||
| e406dcc249 | |||
| 680077631d | |||
| 9fc03a0c12 | |||
| 4f4431e4e1 | |||
| 527074b50a | |||
| a17691f0fc | |||
| d246315cea | |||
| e92ccec6c8 | |||
| b68d7bc0f9 | |||
| 4c9f9bb3e2 | |||
| e7b20c49ac | |||
| 743d05b37f | |||
| 95074a1a84 | |||
| 759575faa8 | |||
| d485e08606 | |||
| 0f1258305a | |||
| 09e99824d4 | |||
| 96b939f63a | |||
| 7837194ab5 | |||
| 35ee2a841a | |||
| a253938ac5 | |||
| 37ffd608fd | |||
| 0f019122b0 | |||
| 936f6240dd | |||
| f8dd6f4f4c | |||
| ca7ac00030 | |||
| d41735d6a9 | |||
| be1129aee5 | |||
| cb9cc88478 | |||
| 213cdccb6c |
@@ -60,20 +60,41 @@ All submissions, including submissions by project members, require review. We
|
||||
use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests)
|
||||
for this purpose.
|
||||
|
||||
If your pull request involves changes to `packages/cli` (the frontend), we
|
||||
recommend running our automated frontend review tool. **Note: This tool is
|
||||
currently experimental.** It helps detect common React anti-patterns, testing
|
||||
issues, and other frontend-specific best practices that are easy to miss.
|
||||
To assist with the review process, we provide an automated review tool that
|
||||
helps detect common anti-patterns, testing issues, and other best practices that
|
||||
are easy to miss.
|
||||
|
||||
To run the review tool, enter the following command from within Gemini CLI:
|
||||
#### Using the automated review tool
|
||||
|
||||
```text
|
||||
/review-frontend <PR_NUMBER>
|
||||
```
|
||||
You can run the review tool in two ways:
|
||||
|
||||
Replace `<PR_NUMBER>` with your pull request number. Authors are encouraged to
|
||||
run this on their own PRs for self-review, and reviewers should use it to
|
||||
augment their manual review process.
|
||||
1. **Using the helper script (Recommended):** We provide a script that
|
||||
automatically handles checking out the PR into a separate worktree,
|
||||
installing dependencies, building the project, and launching the review
|
||||
tool.
|
||||
|
||||
```bash
|
||||
./scripts/review.sh <PR_NUMBER> [model]
|
||||
```
|
||||
|
||||
**Authors are strongly encouraged to run this script on their own PRs**
|
||||
immediately after creation. This allows you to catch and fix simple issues
|
||||
locally before a maintainer performs a full review.
|
||||
|
||||
**Note on Models:** By default, the script uses the latest Pro model
|
||||
(`gemini-3.1-pro-preview`). If you do not have enough Pro quota, you can run
|
||||
it with the latest Flash model instead:
|
||||
`./scripts/review.sh <PR_NUMBER> gemini-3-flash-preview`.
|
||||
|
||||
2. **Manually from within Gemini CLI:** If you already have the PR checked out
|
||||
and built, you can run the tool directly from the CLI prompt:
|
||||
|
||||
```text
|
||||
/review-frontend <PR_NUMBER>
|
||||
```
|
||||
|
||||
Replace `<PR_NUMBER>` with your pull request number. Reviewers should use this
|
||||
tool to augment, not replace, their manual review process.
|
||||
|
||||
### Self-assigning and unassigning issues
|
||||
|
||||
@@ -267,7 +288,8 @@ npm run test:e2e
|
||||
```
|
||||
|
||||
For more detailed information on the integration testing framework, please see
|
||||
the [Integration Tests documentation](/docs/integration-tests.md).
|
||||
the
|
||||
[Integration Tests documentation](https://geminicli.com/docs/integration-tests).
|
||||
|
||||
### Linting and preflight checks
|
||||
|
||||
@@ -546,7 +568,7 @@ Before submitting your documentation pull request, please:
|
||||
|
||||
If you have questions about contributing documentation:
|
||||
|
||||
- Check our [FAQ](/docs/resources/faq.md).
|
||||
- Check our [FAQ](https://geminicli.com/docs/resources/faq).
|
||||
- Review existing documentation for examples.
|
||||
- Open [an issue](https://github.com/google-gemini/gemini-cli/issues) to discuss
|
||||
your proposed changes.
|
||||
|
||||
@@ -8,7 +8,8 @@ and parameters.
|
||||
| Command | Description | Example |
|
||||
| ---------------------------------- | ---------------------------------- | ------------------------------------------------------------ |
|
||||
| `gemini` | Start interactive REPL | `gemini` |
|
||||
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
|
||||
| `gemini -p "query"` | Query non-interactively | `gemini -p "summarize README.md"` |
|
||||
| `gemini "query"` | Query and continue interactively | `gemini "explain this project"` |
|
||||
| `cat file \| gemini` | Process piped content | `cat logs.txt \| gemini`<br>`Get-Content logs.txt \| gemini` |
|
||||
| `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"` |
|
||||
@@ -20,9 +21,9 @@ and parameters.
|
||||
|
||||
### Positional arguments
|
||||
|
||||
| Argument | Type | Description |
|
||||
| -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `query` | string (variadic) | Positional prompt. Defaults to one-shot mode. Use `-i/--prompt-interactive` to execute and continue interactively. |
|
||||
| Argument | Type | Description |
|
||||
| -------- | ----------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `query` | string (variadic) | Positional prompt. Defaults to interactive mode in a TTY. Use `-p/--prompt` for non-interactive execution. |
|
||||
|
||||
## Interactive commands
|
||||
|
||||
@@ -47,7 +48,7 @@ These commands are available within the interactive REPL.
|
||||
| `--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` | `-p` | string | - | Prompt text. Appended to stdin input if provided. Forces non-interactive mode. |
|
||||
| `--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` |
|
||||
|
||||
@@ -6,7 +6,7 @@ structured text or JSON output without an interactive terminal UI.
|
||||
## Technical reference
|
||||
|
||||
Headless mode is triggered when the CLI is run in a non-TTY environment or when
|
||||
providing a query as a positional argument without the interactive flag.
|
||||
providing a query with the `-p` (or `--prompt`) flag.
|
||||
|
||||
### Output formats
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Plan Mode (experimental)
|
||||
# Plan Mode
|
||||
|
||||
Plan Mode is a read-only environment for architecting robust solutions before
|
||||
implementation. With Plan Mode, you can:
|
||||
@@ -8,27 +8,8 @@ implementation. With Plan Mode, you can:
|
||||
- **Design:** Understand problems, evaluate trade-offs, and choose a solution.
|
||||
- **Plan:** Align on an execution strategy before any code is modified.
|
||||
|
||||
> **Note:** This is a preview feature currently under active development. Your
|
||||
> feedback is invaluable as we refine this feature. If you have ideas,
|
||||
> suggestions, or encounter issues:
|
||||
>
|
||||
> - [Open an issue] on GitHub.
|
||||
> - Use the **/bug** command within Gemini CLI to file an issue.
|
||||
|
||||
## How to enable Plan Mode
|
||||
|
||||
Enable Plan Mode in **Settings** or by editing your configuration file.
|
||||
|
||||
- **Settings:** Use the `/settings` command and set **Plan** to `true`.
|
||||
- **Configuration:** Add the following to your `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"plan": true
|
||||
}
|
||||
}
|
||||
```
|
||||
Plan Mode is enabled by default. You can manage this setting using the
|
||||
`/settings` command.
|
||||
|
||||
## How to enter Plan Mode
|
||||
|
||||
@@ -62,8 +43,11 @@ To start Plan Mode while using Gemini CLI:
|
||||
- **Command:** Type `/plan` in the input box.
|
||||
|
||||
- **Natural Language:** Ask Gemini CLI to "start a plan for...". Gemini CLI
|
||||
calls the [`enter_plan_mode`] tool to switch modes.
|
||||
> **Note:** This tool is not available when Gemini CLI is in [YOLO mode].
|
||||
calls the
|
||||
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode) tool
|
||||
to switch modes.
|
||||
> **Note:** This tool is not available when Gemini CLI is in
|
||||
> [YOLO mode](../reference/configuration.md#command-line-arguments).
|
||||
|
||||
## How to use Plan Mode
|
||||
|
||||
@@ -74,7 +58,8 @@ Gemini CLI takes action.
|
||||
will then enter Plan Mode (if it's not already) to research the task.
|
||||
2. **Review research and provide input:** As Gemini CLI analyzes your codebase,
|
||||
it may ask you questions or present different implementation options using
|
||||
[`ask_user`]. Provide your preferences to help guide the design.
|
||||
[`ask_user`](../tools/ask-user.md). Provide your preferences to help guide
|
||||
the design.
|
||||
3. **Review the plan:** Once Gemini CLI has a proposed strategy, it creates a
|
||||
detailed implementation plan as a Markdown file in your plans directory. You
|
||||
can open and read this file to understand the proposed changes.
|
||||
@@ -116,25 +101,33 @@ Plan Mode enforces strict safety policies to prevent accidental changes.
|
||||
|
||||
These are the only allowed tools:
|
||||
|
||||
- **FileSystem (Read):** [`read_file`], [`list_directory`], [`glob`]
|
||||
- **Search:** [`grep_search`], [`google_web_search`]
|
||||
- **Research Subagents:** [`codebase_investigator`], [`cli_help`]
|
||||
- **Interaction:** [`ask_user`]
|
||||
- **MCP tools (Read):** Read-only [MCP tools] (for example, `github_read_issue`,
|
||||
`postgres_read_schema`) are allowed.
|
||||
- **Planning (Write):** [`write_file`] and [`replace`] only allowed for `.md`
|
||||
- **FileSystem (Read):**
|
||||
[`read_file`](../tools/file-system.md#2-read_file-readfile),
|
||||
[`list_directory`](../tools/file-system.md#1-list_directory-readfolder),
|
||||
[`glob`](../tools/file-system.md#4-glob-findfiles)
|
||||
- **Search:** [`grep_search`](../tools/file-system.md#5-grep_search-searchtext),
|
||||
[`google_web_search`](../tools/web-search.md)
|
||||
- **Research Subagents:**
|
||||
[`codebase_investigator`](../core/subagents.md#codebase-investigator),
|
||||
[`cli_help`](../core/subagents.md#cli-help-agent)
|
||||
- **Interaction:** [`ask_user`](../tools/ask-user.md)
|
||||
- **MCP tools (Read):** Read-only [MCP tools](../tools/mcp-server.md) (for
|
||||
example, `github_read_issue`, `postgres_read_schema`) are allowed.
|
||||
- **Planning (Write):**
|
||||
[`write_file`](../tools/file-system.md#3-write_file-writefile) and
|
||||
[`replace`](../tools/file-system.md#6-replace-edit) only allowed for `.md`
|
||||
files in the `~/.gemini/tmp/<project>/<session-id>/plans/` directory or your
|
||||
[custom plans directory](#custom-plan-directory-and-policies).
|
||||
- **Memory:** [`save_memory`]
|
||||
- **Skills:** [`activate_skill`] (allows loading specialized instructions and
|
||||
resources in a read-only manner)
|
||||
- **Memory:** [`save_memory`](../tools/memory.md)
|
||||
- **Skills:** [`activate_skill`](../cli/skills.md) (allows loading specialized
|
||||
instructions and resources in a read-only manner)
|
||||
|
||||
### Custom planning with skills
|
||||
|
||||
You can use [Agent Skills] to customize how Gemini CLI approaches planning for
|
||||
specific types of tasks. When a skill is activated during Plan Mode, its
|
||||
specialized instructions and procedural workflows will guide the research,
|
||||
design, and planning phases.
|
||||
You can use [Agent Skills](../cli/skills.md) to customize how Gemini CLI
|
||||
approaches planning for specific types of tasks. When a skill is activated
|
||||
during Plan Mode, its specialized instructions and procedural workflows will
|
||||
guide the research, design, and planning phases.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -151,10 +144,11 @@ based on the task description.
|
||||
|
||||
### Custom policies
|
||||
|
||||
Plan Mode's default tool restrictions are managed by the [policy engine] and
|
||||
defined in the built-in [`plan.toml`] file. The built-in policy (Tier 1)
|
||||
enforces the read-only state, but you can customize these rules by creating your
|
||||
own policies in your `~/.gemini/policies/` directory (Tier 2).
|
||||
Plan Mode's default tool restrictions are managed by the
|
||||
[policy engine](../reference/policy-engine.md) and defined in the built-in
|
||||
[`plan.toml`] file. The built-in policy (Tier 1) enforces the read-only state,
|
||||
but you can customize these rules by creating your own policies in your
|
||||
`~/.gemini/policies/` directory (Tier 2).
|
||||
|
||||
#### Example: Automatically approve read-only MCP tools
|
||||
|
||||
@@ -173,8 +167,8 @@ priority = 100
|
||||
modes = ["plan"]
|
||||
```
|
||||
|
||||
For more information on how the policy engine works, see the [policy engine]
|
||||
docs.
|
||||
For more information on how the policy engine works, see the
|
||||
[policy engine](../reference/policy-engine.md) docs.
|
||||
|
||||
#### Example: Allow git commands in Plan Mode
|
||||
|
||||
@@ -194,9 +188,12 @@ modes = ["plan"]
|
||||
|
||||
#### Example: Enable custom subagents in Plan Mode
|
||||
|
||||
Built-in research [subagents] like [`codebase_investigator`] and [`cli_help`]
|
||||
are enabled by default in Plan Mode. You can enable additional [custom
|
||||
subagents] by adding a rule to your policy.
|
||||
Built-in research [subagents](../core/subagents.md) like
|
||||
[`codebase_investigator`](../core/subagents.md#codebase-investigator) and
|
||||
[`cli_help`](../core/subagents.md#cli-help-agent) are enabled by default in Plan
|
||||
Mode. You can enable additional
|
||||
[custom subagents](../core/subagents.md#creating-custom-subagents) by adding a
|
||||
rule to your policy.
|
||||
|
||||
`~/.gemini/policies/research-subagents.toml`
|
||||
|
||||
@@ -235,10 +232,11 @@ locations defined within a project's workspace cannot be used to escape and
|
||||
overwrite sensitive files elsewhere. Any user-configured directory must reside
|
||||
within the project boundary.
|
||||
|
||||
Using a custom directory requires updating your [policy engine] configurations
|
||||
to allow `write_file` and `replace` in that specific location. For example, to
|
||||
allow writing to the `.gemini/plans` directory within your project, create a
|
||||
policy file at `~/.gemini/policies/plan-custom-directory.toml`:
|
||||
Using a custom directory requires updating your
|
||||
[policy engine](../reference/policy-engine.md) configurations to allow
|
||||
`write_file` and `replace` in that specific location. For example, to allow
|
||||
writing to the `.gemini/plans` directory within your project, create a policy
|
||||
file at `~/.gemini/policies/plan-custom-directory.toml`:
|
||||
|
||||
```toml
|
||||
[[rule]]
|
||||
@@ -254,13 +252,16 @@ argsPattern = "\"file_path\":\"[^\"]+[\\\\/]+\\.gemini[\\\\/]+plans[\\\\/]+[\\w-
|
||||
## Planning workflows
|
||||
|
||||
Plan Mode provides building blocks for structured research and design. These are
|
||||
implemented as [extensions] using core planning tools like [`enter_plan_mode`],
|
||||
[`exit_plan_mode`], and [`ask_user`].
|
||||
implemented as [extensions](../extensions/index.md) using core planning tools
|
||||
like [`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode),
|
||||
[`exit_plan_mode`](../tools/planning.md#2-exit_plan_mode-exitplanmode), and
|
||||
[`ask_user`](../tools/ask-user.md).
|
||||
|
||||
### Built-in planning workflow
|
||||
|
||||
The built-in planner uses an adaptive workflow to analyze your project, consult
|
||||
you on trade-offs via [`ask_user`], and draft a plan for your approval.
|
||||
you on trade-offs via [`ask_user`](../tools/ask-user.md), and draft a plan for
|
||||
your approval.
|
||||
|
||||
### Custom planning workflows
|
||||
|
||||
@@ -272,23 +273,29 @@ You can install or create specialized planners to suit your workflow.
|
||||
"tracks" and stores persistent artifacts in your project's `conductor/`
|
||||
directory:
|
||||
|
||||
- **Automate transitions:** Switches to read-only mode via [`enter_plan_mode`].
|
||||
- **Streamline decisions:** Uses [`ask_user`] for architectural choices.
|
||||
- **Automate transitions:** Switches to read-only mode via
|
||||
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode).
|
||||
- **Streamline decisions:** Uses [`ask_user`](../tools/ask-user.md) for
|
||||
architectural choices.
|
||||
- **Maintain project context:** Stores artifacts in the project directory using
|
||||
[custom plan directory and policies](#custom-plan-directory-and-policies).
|
||||
- **Handoff execution:** Transitions to implementation via [`exit_plan_mode`].
|
||||
- **Handoff execution:** Transitions to implementation via
|
||||
[`exit_plan_mode`](../tools/planning.md#2-exit_plan_mode-exitplanmode).
|
||||
|
||||
#### Build your own
|
||||
|
||||
Since Plan Mode is built on modular building blocks, you can develop your own
|
||||
custom planning workflow as an [extensions]. By leveraging core tools and
|
||||
[custom policies](#custom-policies), you can define how Gemini CLI researches
|
||||
and stores plans for your specific domain.
|
||||
custom planning workflow as an [extensions](../extensions/index.md). By
|
||||
leveraging core tools and [custom policies](#custom-policies), you can define
|
||||
how Gemini CLI researches and stores plans for your specific domain.
|
||||
|
||||
To build a custom planning workflow, you can use:
|
||||
|
||||
- **Tool usage:** Use core tools like [`enter_plan_mode`], [`ask_user`], and
|
||||
[`exit_plan_mode`] to manage the research and design process.
|
||||
- **Tool usage:** Use core tools like
|
||||
[`enter_plan_mode`](../tools/planning.md#1-enter_plan_mode-enterplanmode),
|
||||
[`ask_user`](../tools/ask-user.md), and
|
||||
[`exit_plan_mode`](../tools/planning.md#2-exit_plan_mode-exitplanmode) to
|
||||
manage the research and design process.
|
||||
- **Customization:** Set your own storage locations and policy rules using
|
||||
[custom plan directories](#custom-plan-directory-and-policies) and
|
||||
[custom policies](#custom-policies).
|
||||
@@ -302,8 +309,9 @@ high-reasoning model routing.
|
||||
|
||||
## Automatic Model Routing
|
||||
|
||||
When using an [auto model], Gemini CLI automatically optimizes [model routing]
|
||||
based on the current phase of your task:
|
||||
When using an [auto model](../reference/configuration.md#model), Gemini CLI
|
||||
automatically optimizes [model routing](../cli/telemetry.md#model-routing) based
|
||||
on the current phase of your task:
|
||||
|
||||
1. **Planning Phase:** While in Plan Mode, the CLI routes requests to a
|
||||
high-reasoning **Pro** model to ensure robust architectural decisions and
|
||||
@@ -334,7 +342,8 @@ associated plan files and task trackers.
|
||||
- **Default behavior:** Sessions (and their plans) are retained for **30 days**.
|
||||
- **Configuration:** You can customize this behavior via the `/settings` command
|
||||
(search for **Session Retention**) or in your `settings.json` file. See
|
||||
[session retention] for more details.
|
||||
[session retention](../cli/session-management.md#session-retention) for more
|
||||
details.
|
||||
|
||||
Manual deletion also removes all associated artifacts:
|
||||
|
||||
@@ -344,32 +353,7 @@ Manual deletion also removes all associated artifacts:
|
||||
If you use a [custom plans directory](#custom-plan-directory-and-policies),
|
||||
those files are not automatically deleted and must be managed manually.
|
||||
|
||||
[`list_directory`]: /docs/tools/file-system.md#1-list_directory-readfolder
|
||||
[`read_file`]: /docs/tools/file-system.md#2-read_file-readfile
|
||||
[`grep_search`]: /docs/tools/file-system.md#5-grep_search-searchtext
|
||||
[`write_file`]: /docs/tools/file-system.md#3-write_file-writefile
|
||||
[`glob`]: /docs/tools/file-system.md#4-glob-findfiles
|
||||
[`google_web_search`]: /docs/tools/web-search.md
|
||||
[`replace`]: /docs/tools/file-system.md#6-replace-edit
|
||||
[MCP tools]: /docs/tools/mcp-server.md
|
||||
[`save_memory`]: /docs/tools/memory.md
|
||||
[`activate_skill`]: /docs/cli/skills.md
|
||||
[`codebase_investigator`]: /docs/core/subagents.md#codebase-investigator
|
||||
[`cli_help`]: /docs/core/subagents.md#cli-help-agent
|
||||
[subagents]: /docs/core/subagents.md
|
||||
[custom subagents]: /docs/core/subagents.md#creating-custom-subagents
|
||||
[policy engine]: /docs/reference/policy-engine.md
|
||||
[`enter_plan_mode`]: /docs/tools/planning.md#1-enter_plan_mode-enterplanmode
|
||||
[`exit_plan_mode`]: /docs/tools/planning.md#2-exit_plan_mode-exitplanmode
|
||||
[`ask_user`]: /docs/tools/ask-user.md
|
||||
[YOLO mode]: /docs/reference/configuration.md#command-line-arguments
|
||||
[`plan.toml`]:
|
||||
https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/policy/policies/plan.toml
|
||||
[auto model]: /docs/reference/configuration.md#model
|
||||
[model routing]: /docs/cli/telemetry.md#model-routing
|
||||
[preferred external editor]: /docs/reference/configuration.md#general
|
||||
[session retention]: /docs/cli/session-management.md#session-retention
|
||||
[extensions]: /docs/extensions/
|
||||
[Conductor]: https://github.com/gemini-cli-extensions/conductor
|
||||
[open an issue]: https://github.com/google-gemini/gemini-cli/issues
|
||||
[Agent Skills]: /docs/cli/skills.md
|
||||
|
||||
@@ -61,6 +61,15 @@ Browser**:
|
||||
/resume
|
||||
```
|
||||
|
||||
When typing `/resume` (or `/chat`) in slash completion, commands are grouped
|
||||
under titled separators:
|
||||
|
||||
- `-- auto --` (session browser)
|
||||
- `list` is selectable and opens the session browser
|
||||
- `-- checkpoints --` (manual tagged checkpoint commands)
|
||||
|
||||
Unique prefixes such as `/resum` and `/cha` resolve to the same grouped menu.
|
||||
|
||||
The Session Browser provides an interactive interface where you can perform the
|
||||
following actions:
|
||||
|
||||
@@ -72,6 +81,21 @@ following actions:
|
||||
- **Select:** Press **Enter** to resume the selected session.
|
||||
- **Esc:** Press **Esc** to exit the Session Browser.
|
||||
|
||||
### Manual chat checkpoints
|
||||
|
||||
For named branch points inside a session, use chat checkpoints:
|
||||
|
||||
```text
|
||||
/resume save decision-point
|
||||
/resume list
|
||||
/resume resume decision-point
|
||||
```
|
||||
|
||||
Compatibility aliases:
|
||||
|
||||
- `/chat ...` works for the same commands.
|
||||
- `/resume checkpoints ...` also remains supported during migration.
|
||||
|
||||
## Managing sessions
|
||||
|
||||
You can list and delete sessions to keep your history organized and manage disk
|
||||
|
||||
@@ -144,7 +144,7 @@ they appear in the UI.
|
||||
| Enable Tool Output Masking | `experimental.toolOutputMasking.enabled` | Enables tool output masking to save tokens. | `true` |
|
||||
| Use OSC 52 Paste | `experimental.useOSC52Paste` | Use OSC 52 for pasting. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
|
||||
| Use OSC 52 Copy | `experimental.useOSC52Copy` | Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it). | `false` |
|
||||
| Plan | `experimental.plan` | Enable planning features (Plan Mode and tools). | `false` |
|
||||
| Plan | `experimental.plan` | Enable Plan Mode. | `true` |
|
||||
| Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` |
|
||||
| Direct Web Fetch | `experimental.directWebFetch` | Enable web fetch behavior that bypasses LLM summarization. | `false` |
|
||||
|
||||
|
||||
@@ -19,14 +19,15 @@ Headless mode runs Gemini CLI once and exits. It's perfect for:
|
||||
|
||||
## How to use headless mode
|
||||
|
||||
Run Gemini CLI in headless mode by providing a prompt as a positional argument.
|
||||
This bypasses the interactive chat interface and prints the response to standard
|
||||
output (stdout).
|
||||
Run Gemini CLI in headless mode by providing a prompt with the `-p` (or
|
||||
`--prompt`) flag. This bypasses the interactive chat interface and prints the
|
||||
response to standard output (stdout). Positional arguments without the flag
|
||||
default to interactive mode, unless the input or output is piped or redirected.
|
||||
|
||||
Run a single command:
|
||||
|
||||
```bash
|
||||
gemini "Write a poem about TypeScript"
|
||||
gemini -p "Write a poem about TypeScript"
|
||||
```
|
||||
|
||||
## How to pipe input to Gemini CLI
|
||||
@@ -40,19 +41,19 @@ Pipe a file:
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
cat error.log | gemini "Explain why this failed"
|
||||
cat error.log | gemini -p "Explain why this failed"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
Get-Content error.log | gemini "Explain why this failed"
|
||||
Get-Content error.log | gemini -p "Explain why this failed"
|
||||
```
|
||||
|
||||
Pipe a command:
|
||||
|
||||
```bash
|
||||
git diff | gemini "Write a commit message for these changes"
|
||||
git diff | gemini -p "Write a commit message for these changes"
|
||||
```
|
||||
|
||||
## Use Gemini CLI output in scripts
|
||||
@@ -78,7 +79,7 @@ one.
|
||||
echo "Generating docs for $file..."
|
||||
|
||||
# Ask Gemini CLI to generate the documentation and print it to stdout
|
||||
gemini "Generate a Markdown documentation summary for @$file. Print the
|
||||
gemini -p "Generate a Markdown documentation summary for @$file. Print the
|
||||
result to standard output." > "${file%.py}.md"
|
||||
done
|
||||
```
|
||||
@@ -92,7 +93,7 @@ one.
|
||||
|
||||
$newName = $_.Name -replace '\.py$', '.md'
|
||||
# Ask Gemini CLI to generate the documentation and print it to stdout
|
||||
gemini "Generate a Markdown documentation summary for @$($_.Name). Print the result to standard output." | Out-File -FilePath $newName -Encoding utf8
|
||||
gemini -p "Generate a Markdown documentation summary for @$($_.Name). Print the result to standard output." | Out-File -FilePath $newName -Encoding utf8
|
||||
}
|
||||
```
|
||||
|
||||
@@ -214,7 +215,7 @@ wrapper that writes the message for you.
|
||||
|
||||
# Ask Gemini to write the message
|
||||
echo "Generating commit message..."
|
||||
msg=$(echo "$diff" | gemini "Write a concise Conventional Commit message for this diff. Output ONLY the message.")
|
||||
msg=$(echo "$diff" | gemini -p "Write a concise Conventional Commit message for this diff. Output ONLY the message.")
|
||||
|
||||
# Commit with the generated message
|
||||
git commit -m "$msg"
|
||||
@@ -251,7 +252,7 @@ wrapper that writes the message for you.
|
||||
|
||||
# Ask Gemini to write the message
|
||||
Write-Host "Generating commit message..."
|
||||
$msg = $diff | gemini "Write a concise Conventional Commit message for this diff. Output ONLY the message."
|
||||
$msg = $diff | gemini -p "Write a concise Conventional Commit message for this diff. Output ONLY the message."
|
||||
|
||||
# Commit with the generated message
|
||||
git commit -m "$msg"
|
||||
|
||||
@@ -89,9 +89,9 @@ Gemini gives you granular control over the undo process. You can choose to:
|
||||
Sometimes you want to try two different approaches to the same problem.
|
||||
|
||||
1. Start a session and get to a decision point.
|
||||
2. Save the current state with `/chat save decision-point`.
|
||||
2. Save the current state with `/resume save decision-point`.
|
||||
3. Try your first approach.
|
||||
4. Later, use `/chat resume decision-point` to fork the conversation back to
|
||||
4. Later, use `/resume resume decision-point` to fork the conversation back to
|
||||
that moment and try a different approach.
|
||||
|
||||
This creates a new branch of history without losing your original work.
|
||||
@@ -101,5 +101,5 @@ This creates a new branch of history without losing your original work.
|
||||
- Learn about [Checkpointing](../../cli/checkpointing.md) to understand the
|
||||
underlying safety mechanism.
|
||||
- Explore [Task planning](task-planning.md) to keep complex sessions organized.
|
||||
- See the [Command reference](../../reference/commands.md) for all `/chat` and
|
||||
`/resume` options.
|
||||
- See the [Command reference](../../reference/commands.md) for `/resume`
|
||||
options, grouped checkpoint menus, and `/chat` compatibility aliases.
|
||||
|
||||
@@ -297,7 +297,7 @@ Gemini CLI can also delegate tasks to remote subagents using the Agent-to-Agent
|
||||
|
||||
> **Note: Remote subagents are currently an experimental feature.**
|
||||
|
||||
See the [Remote Subagents documentation](/docs/core/remote-agents) for detailed
|
||||
See the [Remote Subagents documentation](remote-agents) for detailed
|
||||
configuration and usage instructions.
|
||||
|
||||
## Extension subagents
|
||||
|
||||
@@ -6,7 +6,7 @@ using the CLI.
|
||||
|
||||
> **Note:** Looking for a high-level comparison of all available subscriptions?
|
||||
> To compare features and find the right quota for your needs, see our
|
||||
> [Plans page](/plans/).
|
||||
> [Plans page](https://geminicli.com/plans/).
|
||||
|
||||
For most users, we recommend starting Gemini CLI and logging in with your
|
||||
personal Google account.
|
||||
|
||||
@@ -41,7 +41,7 @@ limit resets and Gemini 3 Pro can be used again.
|
||||
|
||||
> **Note:** Looking to upgrade for higher limits? To compare subscription
|
||||
> options and find the right quota for your needs, see our
|
||||
> [Plans page](/plans/).
|
||||
> [Plans page](https://geminicli.com/plans/).
|
||||
|
||||
Similarly, when you reach your daily usage limit for Gemini 2.5 Pro, you’ll see
|
||||
a message prompting fallback to Gemini 2.5 Flash.
|
||||
|
||||
@@ -70,7 +70,7 @@ gemini
|
||||
```
|
||||
|
||||
For a list of options and additional commands, see the
|
||||
[CLI cheatsheet](/docs/cli/cli-reference.md).
|
||||
[CLI cheatsheet](../cli/cli-reference.md).
|
||||
|
||||
You can also run Gemini CLI using one of the following advanced methods:
|
||||
|
||||
|
||||
@@ -449,7 +449,7 @@ When you open a project with hooks defined in `.gemini/settings.json`:
|
||||
|
||||
Hooks inherit the environment of the Gemini CLI process, which may include
|
||||
sensitive API keys. Gemini CLI provides a
|
||||
[redaction system](/docs/reference/configuration.md#environment-variable-redaction)
|
||||
[redaction system](../reference/configuration.md#environment-variable-redaction)
|
||||
that automatically filters variables matching sensitive patterns (e.g., `KEY`,
|
||||
`TOKEN`).
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ With hooks, you can:
|
||||
|
||||
### 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,
|
||||
- **[Writing hooks guide](../hooks/writing-hooks)**: A tutorial on creating your
|
||||
first hook with comprehensive examples.
|
||||
- **[Best practices](../hooks/best-practices)**: Guidelines on security,
|
||||
performance, and debugging.
|
||||
- **[Hooks reference](/docs/hooks/reference)**: The definitive technical
|
||||
- **[Hooks reference](../hooks/reference)**: The definitive technical
|
||||
specification of I/O schemas and exit codes.
|
||||
|
||||
## Core concepts
|
||||
@@ -152,8 +152,8 @@ Gemini CLI **fingerprints** project hooks. If a hook's name or command changes
|
||||
(e.g., via `git pull`), it is treated as a **new, untrusted hook** and you will
|
||||
be warned before it executes.
|
||||
|
||||
See [Security Considerations](/docs/hooks/best-practices#using-hooks-securely)
|
||||
for a detailed threat model.
|
||||
See [Security Considerations](../hooks/best-practices#using-hooks-securely) for
|
||||
a detailed threat model.
|
||||
|
||||
## Managing hooks
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ For `BeforeTool` and `AfterTool` events, the `matcher` field in your settings is
|
||||
compared against the name of the tool being executed.
|
||||
|
||||
- **Built-in Tools**: You can match any built-in tool (e.g., `read_file`,
|
||||
`run_shell_command`). See the [Tools Reference](/docs/reference/tools) for a
|
||||
full list of available tool names.
|
||||
`run_shell_command`). See the [Tools Reference](../reference/tools) for a full
|
||||
list of available tool names.
|
||||
- **MCP Tools**: Tools from MCP servers follow the naming pattern
|
||||
`mcp__<server_name>__<tool_name>`.
|
||||
- **Regex Support**: Matchers support regular expressions (e.g.,
|
||||
|
||||
@@ -28,24 +28,33 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
|
||||
### `/chat`
|
||||
|
||||
- **Description:** Save and resume conversation history for branching
|
||||
conversation state interactively, or resuming a previous state from a later
|
||||
session.
|
||||
- **Description:** Alias for `/resume`. Both commands now expose the same
|
||||
session browser action and checkpoint subcommands.
|
||||
- **Menu layout when typing `/chat` (or `/resume`)**:
|
||||
- `-- auto --`
|
||||
- `list` (selecting this opens the auto-saved session browser)
|
||||
- `-- checkpoints --`
|
||||
- `list`, `save`, `resume`, `delete`, `share` (manual tagged checkpoints)
|
||||
- **Note:** Unique prefixes (for example `/cha` or `/resum`) resolve to the
|
||||
same grouped menu.
|
||||
- **Sub-commands:**
|
||||
- **`debug`**
|
||||
- **Description:** Export the most recent API request as a JSON payload.
|
||||
- **`delete <tag>`**
|
||||
- **Description:** Deletes a saved conversation checkpoint.
|
||||
- **Equivalent:** `/resume delete <tag>`
|
||||
- **`list`**
|
||||
- **Description:** Lists available tags for chat state resumption.
|
||||
- **Description:** Lists available tags for manually saved checkpoints.
|
||||
- **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.
|
||||
- **Equivalent:** `/resume list`
|
||||
- **`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.
|
||||
- **Equivalent:** `/resume resume <tag>`
|
||||
- **`save <tag>`**
|
||||
- **Description:** Saves the current conversation history. You must add a
|
||||
`<tag>` for identifying the conversation state.
|
||||
@@ -60,10 +69,12 @@ 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).
|
||||
- **Equivalent:** `/resume save <tag>`
|
||||
- **`share [filename]`**
|
||||
- **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`.
|
||||
- **Equivalent:** `/resume share [filename]`
|
||||
|
||||
### `/clear`
|
||||
|
||||
@@ -268,8 +279,8 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
|
||||
- **Description:** Switch to Plan Mode (read-only) and view the current plan if
|
||||
one has been generated.
|
||||
- **Note:** This feature requires the `experimental.plan` setting to be
|
||||
enabled in your configuration.
|
||||
- **Note:** This feature is enabled by default. It can be disabled via the
|
||||
`experimental.plan` setting in your configuration.
|
||||
- **Sub-commands:**
|
||||
- **`copy`**:
|
||||
- **Description:** Copy the currently approved plan to your clipboard.
|
||||
@@ -314,10 +325,13 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
|
||||
### `/resume`
|
||||
|
||||
- **Description:** Browse and resume previous conversation sessions. Opens an
|
||||
interactive session browser where you can search, filter, and select from
|
||||
automatically saved conversations.
|
||||
- **Description:** Browse and resume previous conversation sessions, and manage
|
||||
manual chat checkpoints.
|
||||
- **Features:**
|
||||
- **Auto sessions:** Run `/resume` to open the interactive session browser for
|
||||
automatically saved conversations.
|
||||
- **Chat checkpoints:** Use checkpoint subcommands directly (`/resume save`,
|
||||
`/resume resume`, etc.).
|
||||
- **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
|
||||
@@ -328,6 +342,23 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **Note:** All conversations are automatically saved as you chat - no manual
|
||||
saving required. See [Session Management](../cli/session-management.md) for
|
||||
complete details.
|
||||
- **Alias:** `/chat` provides the same behavior and subcommands.
|
||||
- **Sub-commands:**
|
||||
- **`list`**
|
||||
- **Description:** Lists available tags for manual chat checkpoints.
|
||||
- **`save <tag>`**
|
||||
- **Description:** Saves the current conversation as a tagged checkpoint.
|
||||
- **`resume <tag>`** (alias: `load`)
|
||||
- **Description:** Loads a previously saved tagged checkpoint.
|
||||
- **`delete <tag>`**
|
||||
- **Description:** Deletes a tagged checkpoint.
|
||||
- **`share [filename]`**
|
||||
- **Description:** Exports the current conversation to Markdown or JSON.
|
||||
- **`debug`**
|
||||
- **Description:** Export the most recent API request as JSON payload
|
||||
(nightly builds).
|
||||
- **Compatibility alias:** `/resume checkpoints ...` is still accepted for the
|
||||
same checkpoint commands.
|
||||
|
||||
### `/settings`
|
||||
|
||||
@@ -408,6 +439,12 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **`nodesc`** or **`nodescriptions`**:
|
||||
- **Description:** Hide tool descriptions, showing only the tool names.
|
||||
|
||||
### `/upgrade`
|
||||
|
||||
- **Description:** Open the Gemini Code Assist upgrade page in your browser.
|
||||
This lets you upgrade your tier for higher usage limits.
|
||||
- **Note:** This command is only available when logged in with Google.
|
||||
|
||||
### `/vim`
|
||||
|
||||
- **Description:** Toggle vim mode on or off. When vim mode is enabled, the
|
||||
|
||||
@@ -1021,8 +1021,8 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`experimental.plan`** (boolean):
|
||||
- **Description:** Enable planning features (Plan Mode and tools).
|
||||
- **Default:** `false`
|
||||
- **Description:** Enable Plan Mode.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.taskTracker`** (boolean):
|
||||
|
||||
@@ -91,10 +91,17 @@ the arguments don't match the pattern, the rule does not apply.
|
||||
There are three possible decisions a rule can enforce:
|
||||
|
||||
- `allow`: The tool call is executed automatically without user interaction.
|
||||
- `deny`: The tool call is blocked and is not executed.
|
||||
- `deny`: The tool call is blocked and is not executed. For global rules (those
|
||||
without an `argsPattern`), tools that are denied are **completely excluded
|
||||
from the model's memory**. This means the model will not even see the tool as
|
||||
an option, which is more secure and saves context window space.
|
||||
- `ask_user`: The user is prompted to approve or deny the tool call. (In
|
||||
non-interactive mode, this is treated as `deny`.)
|
||||
|
||||
> **Note:** The `deny` decision is the recommended way to exclude tools. The
|
||||
> legacy `tools.exclude` setting in `settings.json` is deprecated in favor of
|
||||
> policy rules with a `deny` decision.
|
||||
|
||||
### Priority system and tiers
|
||||
|
||||
The policy engine uses a sophisticated priority system to resolve conflicts when
|
||||
@@ -143,8 +150,8 @@ always active.
|
||||
confirmation.
|
||||
- `autoEdit`: Optimized for automated code editing; some write tools may be
|
||||
auto-approved.
|
||||
- `plan`: A strict, read-only mode for research and design. See [Customizing
|
||||
Plan Mode Policies].
|
||||
- `plan`: A strict, read-only mode for research and design. See
|
||||
[Customizing Plan Mode Policies](../cli/plan-mode.md#customizing-policies).
|
||||
- `yolo`: A mode where all tools are auto-approved (use with extreme caution).
|
||||
|
||||
## Rule matching
|
||||
@@ -212,6 +219,10 @@ Here is a breakdown of the fields available in a TOML policy rule:
|
||||
# A unique name for the tool, or an array of names.
|
||||
toolName = "run_shell_command"
|
||||
|
||||
# (Optional) The name of a subagent. If provided, the rule only applies to tool calls
|
||||
# made by this specific subagent.
|
||||
subagent = "generalist"
|
||||
|
||||
# (Optional) The name of an MCP server. Can be combined with toolName
|
||||
# to form a composite name like "mcpName__toolName".
|
||||
mcpName = "my-custom-server"
|
||||
@@ -360,5 +371,3 @@ out-of-the-box experience.
|
||||
- In **`yolo`** mode, a high-priority rule allows all tools.
|
||||
- In **`autoEdit`** mode, rules allow certain write operations to happen without
|
||||
prompting.
|
||||
|
||||
[Customizing Plan Mode Policies]: /docs/cli/plan-mode.md#customizing-policies
|
||||
|
||||
@@ -5,7 +5,7 @@ use cases. For enterprise or professional usage, or if you need increased quota,
|
||||
several options are available depending on your authentication account type.
|
||||
|
||||
For a high-level comparison of available subscriptions and to select the right
|
||||
quota for your needs, see the [Plans page](/plans/).
|
||||
quota for your needs, see the [Plans page](https://geminicli.com/plans/).
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ account.
|
||||
Your Gemini CLI Usage Statistics are handled in accordance with Google's Privacy
|
||||
Policy.
|
||||
|
||||
**Note:** See [quotas and pricing](/docs/resources/quota-and-pricing.md) for the
|
||||
quota and pricing details that apply to your usage of the Gemini CLI.
|
||||
**Note:** See [quotas and pricing](quota-and-pricing.md) for the quota and
|
||||
pricing details that apply to your usage of the Gemini CLI.
|
||||
|
||||
## Supported authentication methods
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ Finds files matching specific glob patterns across the workspace.
|
||||
`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)
|
||||
### `grep_search` (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
|
||||
@@ -103,7 +103,7 @@ lines containing matches, along with their file paths and line numbers.
|
||||
```
|
||||
- **Confirmation:** No.
|
||||
|
||||
## 6. `replace` (Edit)
|
||||
### `replace` (Edit)
|
||||
|
||||
`replace` replaces text within a file. By default, the tool expects to find and
|
||||
replace exactly ONE occurrence of `old_string`. If you want to replace multiple
|
||||
|
||||
@@ -372,7 +372,7 @@ To authenticate with a server using Service Account Impersonation, you must set
|
||||
the `authProviderType` to `service_account_impersonation` and provide the
|
||||
following properties:
|
||||
|
||||
- **`targetAudience`** (string): The OAuth Client ID allowslisted on the
|
||||
- **`targetAudience`** (string): The OAuth Client ID allowlisted on the
|
||||
IAP-protected application you are trying to access.
|
||||
- **`targetServiceAccount`** (string): The email address of the Google Cloud
|
||||
Service Account to impersonate.
|
||||
|
||||
@@ -35,6 +35,11 @@ const commonRestrictedSyntaxRules = [
|
||||
message:
|
||||
'Do not throw string literals or non-Error objects. Throw new Error("...") instead.',
|
||||
},
|
||||
{
|
||||
selector: 'CallExpression[callee.name="fetch"]',
|
||||
message:
|
||||
'Use safeFetch() from "@/utils/fetch" instead of the global fetch() to ensure SSRF protection. If you are implementing a custom security layer, use an eslint-disable comment and explain why.',
|
||||
},
|
||||
];
|
||||
|
||||
export default tseslint.config(
|
||||
|
||||
@@ -71,30 +71,4 @@ describe('interactive_commands', () => {
|
||||
).toMatch(/(?:^|\s)(--yes|-y|--template\s+\S+)(?:\s|$|\\|")/);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates that the agent does not hang when creating a vite app.
|
||||
*/
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should not hang when creating a vite app',
|
||||
prompt: 'create a hello world app with vite',
|
||||
files: {
|
||||
//'.npmrc': 'cache=./.npm-cache',
|
||||
},
|
||||
assert: async (rig, result) => {
|
||||
await rig.waitForToolCall('run_shell_command');
|
||||
const logs = rig.readToolLogs();
|
||||
const viteCall = logs.find(
|
||||
(l) =>
|
||||
l.toolRequest.name === 'run_shell_command' &&
|
||||
l.toolRequest.args.toLowerCase().includes('vite'),
|
||||
);
|
||||
|
||||
expect(viteCall, 'Agent should have called vite').toBeDefined();
|
||||
expect(
|
||||
viteCall?.toolRequest.success,
|
||||
'Vite tool call should finish successfully without hanging',
|
||||
).toBe(true);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,9 +84,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@a2a-js/sdk": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.8.tgz",
|
||||
"integrity": "sha512-vAg6JQbhOnHTzApsB7nGzCQ9r7PuY4GMr8gt88dIR8Wc8G8RSqVTyTmFeMurgzcYrtHYXS3ru2rnDoGj9UDeSw==",
|
||||
"version": "0.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.10.tgz",
|
||||
"integrity": "sha512-t6w5ctnwJkSOMRl6M9rn95C1FTHCPqixxMR0yWXtzhZXEnF6mF1NAK0CfKlG3cz+tcwTxkmn287QZC3t9XPgrA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"uuid": "^11.1.0"
|
||||
@@ -95,9 +95,17 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@bufbuild/protobuf": "^2.10.2",
|
||||
"@grpc/grpc-js": "^1.11.0",
|
||||
"express": "^4.21.2 || ^5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@bufbuild/protobuf": {
|
||||
"optional": true
|
||||
},
|
||||
"@grpc/grpc-js": {
|
||||
"optional": true
|
||||
},
|
||||
"express": {
|
||||
"optional": true
|
||||
}
|
||||
@@ -515,6 +523,12 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
||||
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@bundled-es-modules/cookie": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
|
||||
@@ -1582,18 +1596,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
|
||||
"integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
||||
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
|
||||
"integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"long": "^5.0.0",
|
||||
"protobufjs": "^7.5.3",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/proto-loader": {
|
||||
"version": "0.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
|
||||
@@ -17447,11 +17479,13 @@
|
||||
"version": "0.34.0-nightly.20260304.28af4e127",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@a2a-js/sdk": "^0.3.10",
|
||||
"@bufbuild/protobuf": "^2.11.0",
|
||||
"@google-cloud/logging": "^11.2.1",
|
||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
|
||||
"@google/genai": "1.41.0",
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@joshua.litt/get-ripgrep": "^0.0.3",
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
@@ -17489,6 +17523,7 @@
|
||||
"html-to-text": "^9.0.5",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"ignore": "^7.0.0",
|
||||
"ipaddr.js": "^1.9.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"marked": "^15.0.12",
|
||||
"mime": "4.0.7",
|
||||
|
||||
@@ -75,6 +75,14 @@ export function createMockConfig(
|
||||
validatePathAccess: vi.fn().mockReturnValue(undefined),
|
||||
...overrides,
|
||||
} as unknown as Config;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(mockConfig as unknown as { config: Config; promptId: string }).config =
|
||||
mockConfig;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(mockConfig as unknown as { config: Config; promptId: string }).promptId =
|
||||
'test-prompt-id';
|
||||
|
||||
mockConfig.getMessageBus = vi.fn().mockReturnValue(createMockMessageBus());
|
||||
mockConfig.getHookSystem = vi
|
||||
.fn()
|
||||
|
||||
@@ -172,7 +172,7 @@ describe('GeminiAgent', () => {
|
||||
unsubscribe: vi.fn(),
|
||||
}),
|
||||
getApprovalMode: vi.fn().mockReturnValue('default'),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(false),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(true),
|
||||
getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
|
||||
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
|
||||
@@ -650,7 +650,7 @@ describe('Session', () => {
|
||||
getMessageBus: vi.fn().mockReturnValue(mockMessageBus),
|
||||
setApprovalMode: vi.fn(),
|
||||
setModel: vi.fn(),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(false),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(true),
|
||||
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
|
||||
getGitService: vi.fn().mockResolvedValue({} as GitService),
|
||||
waitForMcpInit: vi.fn(),
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('GeminiAgent Session Resume', () => {
|
||||
getProjectTempDir: vi.fn().mockReturnValue('/tmp/project'),
|
||||
},
|
||||
getApprovalMode: vi.fn().mockReturnValue('default'),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(false),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(true),
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getHasAccessToPreviewModel: vi.fn().mockReturnValue(false),
|
||||
getGemini31LaunchedSync: vi.fn().mockReturnValue(false),
|
||||
@@ -204,6 +204,11 @@ describe('GeminiAgent Session Resume', () => {
|
||||
name: 'YOLO',
|
||||
description: 'Auto-approves all tools',
|
||||
},
|
||||
{
|
||||
id: ApprovalMode.PLAN,
|
||||
name: 'Plan',
|
||||
description: 'Read-only mode',
|
||||
},
|
||||
],
|
||||
currentModeId: ApprovalMode.DEFAULT,
|
||||
},
|
||||
|
||||
@@ -14,11 +14,16 @@ import {
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { listMcpServers } from './list.js';
|
||||
import { loadSettings, mergeSettings } from '../../config/settings.js';
|
||||
import {
|
||||
loadSettings,
|
||||
mergeSettings,
|
||||
type LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import { createTransport, debugLogger } from '@google/gemini-cli-core';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { ExtensionStorage } from '../../config/extensions/storage.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
import { McpServerEnablementManager } from '../../config/mcp/index.js';
|
||||
|
||||
vi.mock('../../config/settings.js', async (importOriginal) => {
|
||||
const actual =
|
||||
@@ -45,6 +50,8 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
CONNECTED: 'CONNECTED',
|
||||
CONNECTING: 'CONNECTING',
|
||||
DISCONNECTED: 'DISCONNECTED',
|
||||
BLOCKED: 'BLOCKED',
|
||||
DISABLED: 'DISABLED',
|
||||
},
|
||||
Storage: Object.assign(
|
||||
vi.fn().mockImplementation((_cwd: string) => ({
|
||||
@@ -54,6 +61,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
})),
|
||||
{
|
||||
getGlobalSettingsPath: () => '/tmp/gemini/settings.json',
|
||||
getGlobalGeminiDir: () => '/tmp/gemini',
|
||||
},
|
||||
),
|
||||
GEMINI_DIR: '.gemini',
|
||||
@@ -96,6 +104,12 @@ describe('mcp list command', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.spyOn(debugLogger, 'log').mockImplementation(() => {});
|
||||
McpServerEnablementManager.resetInstance();
|
||||
// Use a mock for isFileEnabled to avoid reading real files
|
||||
vi.spyOn(
|
||||
McpServerEnablementManager.prototype,
|
||||
'isFileEnabled',
|
||||
).mockResolvedValue(true);
|
||||
|
||||
mockTransport = { close: vi.fn() };
|
||||
mockClient = {
|
||||
@@ -265,7 +279,10 @@ describe('mcp list command', () => {
|
||||
mockClient.connect.mockResolvedValue(undefined);
|
||||
mockClient.ping.mockResolvedValue(undefined);
|
||||
|
||||
await listMcpServers(settingsWithAllowlist);
|
||||
await listMcpServers({
|
||||
merged: settingsWithAllowlist,
|
||||
isTrusted: true,
|
||||
} as unknown as LoadedSettings);
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('allowed-server'),
|
||||
@@ -304,4 +321,56 @@ describe('mcp list command', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should display blocked status for servers in excluded list', async () => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
mockedLoadSettings.mockReturnValue({
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
mcp: {
|
||||
excluded: ['blocked-server'],
|
||||
},
|
||||
mcpServers: {
|
||||
'blocked-server': { command: '/test/server' },
|
||||
},
|
||||
},
|
||||
isTrusted: true,
|
||||
});
|
||||
|
||||
await listMcpServers();
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'blocked-server: /test/server (stdio) - Blocked',
|
||||
),
|
||||
);
|
||||
expect(mockedCreateTransport).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should display disabled status for servers disabled via enablement manager', async () => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
mockedLoadSettings.mockReturnValue({
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
mcpServers: {
|
||||
'disabled-server': { command: '/test/server' },
|
||||
},
|
||||
},
|
||||
isTrusted: true,
|
||||
});
|
||||
|
||||
vi.spyOn(
|
||||
McpServerEnablementManager.prototype,
|
||||
'isFileEnabled',
|
||||
).mockResolvedValue(false);
|
||||
|
||||
await listMcpServers();
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'disabled-server: /test/server (stdio) - Disabled',
|
||||
),
|
||||
);
|
||||
expect(mockedCreateTransport).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
|
||||
// File for 'gemini mcp list' command
|
||||
import type { CommandModule } from 'yargs';
|
||||
import { type MergedSettings, loadSettings } from '../../config/settings.js';
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
type MergedSettings,
|
||||
loadSettings,
|
||||
type LoadedSettings,
|
||||
} from '../../config/settings.js';
|
||||
import {
|
||||
MCPServerStatus,
|
||||
createTransport,
|
||||
@@ -15,8 +18,13 @@ import {
|
||||
applyAdminAllowlist,
|
||||
getAdminBlockedMcpServersMessage,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
import {
|
||||
canLoadServer,
|
||||
McpServerEnablementManager,
|
||||
} from '../../config/mcp/index.js';
|
||||
import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
|
||||
import { promptForSetting } from '../../config/extensions/extensionSettings.js';
|
||||
import { exitCli } from '../utils.js';
|
||||
@@ -61,13 +69,13 @@ export async function getMcpServersFromConfig(
|
||||
async function testMCPConnection(
|
||||
serverName: string,
|
||||
config: MCPServerConfig,
|
||||
isTrusted: boolean,
|
||||
activeSettings: MergedSettings,
|
||||
): Promise<MCPServerStatus> {
|
||||
const settings = loadSettings();
|
||||
|
||||
// SECURITY: Only test connection if workspace is trusted or if it's a remote server.
|
||||
// stdio servers execute local commands and must never run in untrusted workspaces.
|
||||
const isStdio = !!config.command;
|
||||
if (isStdio && !settings.isTrusted) {
|
||||
if (isStdio && !isTrusted) {
|
||||
return MCPServerStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
@@ -80,7 +88,7 @@ async function testMCPConnection(
|
||||
sanitizationConfig: {
|
||||
enableEnvironmentVariableRedaction: true,
|
||||
allowedEnvironmentVariables: [],
|
||||
blockedEnvironmentVariables: settings.merged.advanced.excludedEnvVars,
|
||||
blockedEnvironmentVariables: activeSettings.advanced.excludedEnvVars,
|
||||
},
|
||||
emitMcpDiagnostic: (
|
||||
severity: 'info' | 'warning' | 'error',
|
||||
@@ -105,7 +113,7 @@ async function testMCPConnection(
|
||||
debugLogger.log(message, error);
|
||||
}
|
||||
},
|
||||
isTrustedFolder: () => settings.isTrusted,
|
||||
isTrustedFolder: () => isTrusted,
|
||||
};
|
||||
|
||||
let transport;
|
||||
@@ -135,14 +143,40 @@ async function testMCPConnection(
|
||||
async function getServerStatus(
|
||||
serverName: string,
|
||||
server: MCPServerConfig,
|
||||
isTrusted: boolean,
|
||||
activeSettings: MergedSettings,
|
||||
): Promise<MCPServerStatus> {
|
||||
const mcpEnablementManager = McpServerEnablementManager.getInstance();
|
||||
const loadResult = await canLoadServer(serverName, {
|
||||
adminMcpEnabled: activeSettings.admin?.mcp?.enabled ?? true,
|
||||
allowedList: activeSettings.mcp?.allowed,
|
||||
excludedList: activeSettings.mcp?.excluded,
|
||||
enablement: mcpEnablementManager.getEnablementCallbacks(),
|
||||
});
|
||||
|
||||
if (!loadResult.allowed) {
|
||||
if (
|
||||
loadResult.blockType === 'admin' ||
|
||||
loadResult.blockType === 'allowlist' ||
|
||||
loadResult.blockType === 'excludelist'
|
||||
) {
|
||||
return MCPServerStatus.BLOCKED;
|
||||
}
|
||||
return MCPServerStatus.DISABLED;
|
||||
}
|
||||
|
||||
// Test all server types by attempting actual connection
|
||||
return testMCPConnection(serverName, server);
|
||||
return testMCPConnection(serverName, server, isTrusted, activeSettings);
|
||||
}
|
||||
|
||||
export async function listMcpServers(settings?: MergedSettings): Promise<void> {
|
||||
export async function listMcpServers(
|
||||
loadedSettingsArg?: LoadedSettings,
|
||||
): Promise<void> {
|
||||
const loadedSettings = loadedSettingsArg ?? loadSettings();
|
||||
const activeSettings = loadedSettings.merged;
|
||||
|
||||
const { mcpServers, blockedServerNames } =
|
||||
await getMcpServersFromConfig(settings);
|
||||
await getMcpServersFromConfig(activeSettings);
|
||||
const serverNames = Object.keys(mcpServers);
|
||||
|
||||
if (blockedServerNames.length > 0) {
|
||||
@@ -165,7 +199,12 @@ export async function listMcpServers(settings?: MergedSettings): Promise<void> {
|
||||
for (const serverName of serverNames) {
|
||||
const server = mcpServers[serverName];
|
||||
|
||||
const status = await getServerStatus(serverName, server);
|
||||
const status = await getServerStatus(
|
||||
serverName,
|
||||
server,
|
||||
loadedSettings.isTrusted,
|
||||
activeSettings,
|
||||
);
|
||||
|
||||
let statusIndicator = '';
|
||||
let statusText = '';
|
||||
@@ -178,6 +217,14 @@ export async function listMcpServers(settings?: MergedSettings): Promise<void> {
|
||||
statusIndicator = chalk.yellow('…');
|
||||
statusText = 'Connecting';
|
||||
break;
|
||||
case MCPServerStatus.BLOCKED:
|
||||
statusIndicator = chalk.red('⛔');
|
||||
statusText = 'Blocked';
|
||||
break;
|
||||
case MCPServerStatus.DISABLED:
|
||||
statusIndicator = chalk.gray('○');
|
||||
statusText = 'Disabled';
|
||||
break;
|
||||
case MCPServerStatus.DISCONNECTED:
|
||||
default:
|
||||
statusIndicator = chalk.red('✗');
|
||||
@@ -203,14 +250,14 @@ export async function listMcpServers(settings?: MergedSettings): Promise<void> {
|
||||
}
|
||||
|
||||
interface ListArgs {
|
||||
settings?: MergedSettings;
|
||||
loadedSettings?: LoadedSettings;
|
||||
}
|
||||
|
||||
export const listCommand: CommandModule<object, ListArgs> = {
|
||||
command: 'list',
|
||||
describe: 'List all configured MCP servers',
|
||||
handler: async (argv) => {
|
||||
await listMcpServers(argv.settings);
|
||||
await listMcpServers(argv.loadedSettings);
|
||||
await exitCli();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2622,13 +2622,13 @@ describe('loadCliConfig approval mode', () => {
|
||||
expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
|
||||
});
|
||||
|
||||
it('should throw error when --approval-mode=plan is used but experimental.plan setting is missing', async () => {
|
||||
it('should allow plan approval mode by default when --approval-mode=plan is used', async () => {
|
||||
process.argv = ['node', 'script.js', '--approval-mode', 'plan'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({});
|
||||
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
|
||||
expect(config.getApprovalMode()).toBe(ApprovalMode.PLAN);
|
||||
});
|
||||
|
||||
it('should pass planSettings.directory from settings to config', async () => {
|
||||
|
||||
@@ -124,4 +124,30 @@ describe('recursivelyHydrateStrings', () => {
|
||||
const result = recursivelyHydrateStrings(obj, context);
|
||||
expect(result).toEqual(obj);
|
||||
});
|
||||
|
||||
it('should not allow prototype pollution via __proto__', () => {
|
||||
const payload = JSON.parse('{"__proto__": {"polluted": "yes"}}');
|
||||
const result = recursivelyHydrateStrings(payload, context);
|
||||
|
||||
expect(result.polluted).toBeUndefined();
|
||||
expect(Object.prototype.hasOwnProperty.call(result, 'polluted')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not allow prototype pollution via constructor', () => {
|
||||
const payload = JSON.parse(
|
||||
'{"constructor": {"prototype": {"polluted": "yes"}}}',
|
||||
);
|
||||
const result = recursivelyHydrateStrings(payload, context);
|
||||
|
||||
expect(result.polluted).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not allow prototype pollution via prototype', () => {
|
||||
const payload = JSON.parse('{"prototype": {"polluted": "yes"}}');
|
||||
const result = recursivelyHydrateStrings(payload, context);
|
||||
|
||||
expect(result.polluted).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,16 @@ import * as path from 'node:path';
|
||||
import { type VariableSchema, VARIABLE_SCHEMA } from './variableSchema.js';
|
||||
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||
|
||||
/**
|
||||
* Represents a set of keys that will be considered invalid while unmarshalling
|
||||
* JSON in recursivelyHydrateStrings.
|
||||
*/
|
||||
const UNMARSHALL_KEY_IGNORE_LIST: Set<string> = new Set<string>([
|
||||
'__proto__',
|
||||
'constructor',
|
||||
'prototype',
|
||||
]);
|
||||
|
||||
export const EXTENSIONS_DIRECTORY_NAME = path.join(GEMINI_DIR, 'extensions');
|
||||
export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json';
|
||||
export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json';
|
||||
@@ -65,7 +75,10 @@ export function recursivelyHydrateStrings<T>(
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
const newObj: Record<string, unknown> = {};
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
if (
|
||||
!UNMARSHALL_KEY_IGNORE_LIST.has(key) &&
|
||||
Object.prototype.hasOwnProperty.call(obj, key)
|
||||
) {
|
||||
newObj[key] = recursivelyHydrateStrings(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(obj as Record<string, unknown>)[key],
|
||||
|
||||
@@ -424,12 +424,10 @@ describe('SettingsSchema', () => {
|
||||
expect(setting).toBeDefined();
|
||||
expect(setting.type).toBe('boolean');
|
||||
expect(setting.category).toBe('Experimental');
|
||||
expect(setting.default).toBe(false);
|
||||
expect(setting.default).toBe(true);
|
||||
expect(setting.requiresRestart).toBe(true);
|
||||
expect(setting.showInDialog).toBe(true);
|
||||
expect(setting.description).toBe(
|
||||
'Enable planning features (Plan Mode and tools).',
|
||||
);
|
||||
expect(setting.description).toBe('Enable Plan Mode.');
|
||||
});
|
||||
|
||||
it('should have hooksConfig.notifications setting in schema', () => {
|
||||
|
||||
@@ -1823,8 +1823,8 @@ const SETTINGS_SCHEMA = {
|
||||
label: 'Plan',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Enable planning features (Plan Mode and tools).',
|
||||
default: true,
|
||||
description: 'Enable Plan Mode.',
|
||||
showInDialog: true,
|
||||
},
|
||||
taskTracker: {
|
||||
|
||||
@@ -73,7 +73,17 @@ vi.mock('../ui/commands/agentsCommand.js', () => ({
|
||||
}));
|
||||
vi.mock('../ui/commands/bugCommand.js', () => ({ bugCommand: {} }));
|
||||
vi.mock('../ui/commands/chatCommand.js', () => ({
|
||||
chatCommand: { name: 'chat', subCommands: [] },
|
||||
chatCommand: {
|
||||
name: 'chat',
|
||||
subCommands: [
|
||||
{ name: 'list' },
|
||||
{ name: 'save' },
|
||||
{ name: 'resume' },
|
||||
{ name: 'delete' },
|
||||
{ name: 'share' },
|
||||
{ name: 'checkpoints', hidden: true, subCommands: [{ name: 'list' }] },
|
||||
],
|
||||
},
|
||||
debugCommand: { name: 'debug' },
|
||||
}));
|
||||
vi.mock('../ui/commands/clearCommand.js', () => ({ clearCommand: {} }));
|
||||
@@ -94,7 +104,19 @@ vi.mock('../ui/commands/modelCommand.js', () => ({
|
||||
}));
|
||||
vi.mock('../ui/commands/privacyCommand.js', () => ({ privacyCommand: {} }));
|
||||
vi.mock('../ui/commands/quitCommand.js', () => ({ quitCommand: {} }));
|
||||
vi.mock('../ui/commands/resumeCommand.js', () => ({ resumeCommand: {} }));
|
||||
vi.mock('../ui/commands/resumeCommand.js', () => ({
|
||||
resumeCommand: {
|
||||
name: 'resume',
|
||||
subCommands: [
|
||||
{ name: 'list' },
|
||||
{ name: 'save' },
|
||||
{ name: 'resume' },
|
||||
{ name: 'delete' },
|
||||
{ name: 'share' },
|
||||
{ name: 'checkpoints', hidden: true, subCommands: [{ name: 'list' }] },
|
||||
],
|
||||
},
|
||||
}));
|
||||
vi.mock('../ui/commands/statsCommand.js', () => ({ statsCommand: {} }));
|
||||
vi.mock('../ui/commands/themeCommand.js', () => ({ themeCommand: {} }));
|
||||
vi.mock('../ui/commands/toolsCommand.js', () => ({ toolsCommand: {} }));
|
||||
@@ -120,6 +142,14 @@ vi.mock('../ui/commands/mcpCommand.js', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../ui/commands/upgradeCommand.js', () => ({
|
||||
upgradeCommand: {
|
||||
name: 'upgrade',
|
||||
description: 'Upgrade command',
|
||||
kind: 'BUILT_IN',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('BuiltinCommandLoader', () => {
|
||||
let mockConfig: Config;
|
||||
|
||||
@@ -129,7 +159,7 @@ describe('BuiltinCommandLoader', () => {
|
||||
vi.clearAllMocks();
|
||||
mockConfig = {
|
||||
getFolderTrust: vi.fn().mockReturnValue(true),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(false),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(true),
|
||||
getEnableExtensionReloading: () => false,
|
||||
getEnableHooks: () => false,
|
||||
getEnableHooksUI: () => false,
|
||||
@@ -141,6 +171,9 @@ describe('BuiltinCommandLoader', () => {
|
||||
getAllSkills: vi.fn().mockReturnValue([]),
|
||||
isAdminEnabled: vi.fn().mockReturnValue(true),
|
||||
}),
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: 'other',
|
||||
}),
|
||||
} as unknown as Config;
|
||||
|
||||
restoreCommandMock.mockReturnValue({
|
||||
@@ -150,6 +183,27 @@ describe('BuiltinCommandLoader', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should include upgrade command when authType is login_with_google', async () => {
|
||||
const { AuthType } = await import('@google/gemini-cli-core');
|
||||
(mockConfig.getContentGeneratorConfig as Mock).mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
});
|
||||
const loader = new BuiltinCommandLoader(mockConfig);
|
||||
const commands = await loader.loadCommands(new AbortController().signal);
|
||||
const upgradeCmd = commands.find((c) => c.name === 'upgrade');
|
||||
expect(upgradeCmd).toBeDefined();
|
||||
});
|
||||
|
||||
it('should exclude upgrade command when authType is NOT login_with_google', async () => {
|
||||
(mockConfig.getContentGeneratorConfig as Mock).mockReturnValue({
|
||||
authType: 'other',
|
||||
});
|
||||
const loader = new BuiltinCommandLoader(mockConfig);
|
||||
const commands = await loader.loadCommands(new AbortController().signal);
|
||||
const upgradeCmd = commands.find((c) => c.name === 'upgrade');
|
||||
expect(upgradeCmd).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should correctly pass the config object to restore command factory', async () => {
|
||||
const loader = new BuiltinCommandLoader(mockConfig);
|
||||
await loader.loadCommands(new AbortController().signal);
|
||||
@@ -256,7 +310,7 @@ describe('BuiltinCommandLoader', () => {
|
||||
});
|
||||
|
||||
describe('chat debug command', () => {
|
||||
it('should NOT add debug subcommand to chatCommand if not a nightly build', async () => {
|
||||
it('should NOT add debug subcommand to chat/resume commands if not a nightly build', async () => {
|
||||
vi.mocked(isNightly).mockResolvedValue(false);
|
||||
const loader = new BuiltinCommandLoader(mockConfig);
|
||||
const commands = await loader.loadCommands(new AbortController().signal);
|
||||
@@ -265,9 +319,30 @@ describe('BuiltinCommandLoader', () => {
|
||||
expect(chatCmd?.subCommands).toBeDefined();
|
||||
const hasDebug = chatCmd!.subCommands!.some((c) => c.name === 'debug');
|
||||
expect(hasDebug).toBe(false);
|
||||
|
||||
const resumeCmd = commands.find((c) => c.name === 'resume');
|
||||
const resumeHasDebug =
|
||||
resumeCmd?.subCommands?.some((c) => c.name === 'debug') ?? false;
|
||||
expect(resumeHasDebug).toBe(false);
|
||||
|
||||
const chatCheckpointsCmd = chatCmd?.subCommands?.find(
|
||||
(c) => c.name === 'checkpoints',
|
||||
);
|
||||
const chatCheckpointHasDebug =
|
||||
chatCheckpointsCmd?.subCommands?.some((c) => c.name === 'debug') ??
|
||||
false;
|
||||
expect(chatCheckpointHasDebug).toBe(false);
|
||||
|
||||
const resumeCheckpointsCmd = resumeCmd?.subCommands?.find(
|
||||
(c) => c.name === 'checkpoints',
|
||||
);
|
||||
const resumeCheckpointHasDebug =
|
||||
resumeCheckpointsCmd?.subCommands?.some((c) => c.name === 'debug') ??
|
||||
false;
|
||||
expect(resumeCheckpointHasDebug).toBe(false);
|
||||
});
|
||||
|
||||
it('should add debug subcommand to chatCommand if it is a nightly build', async () => {
|
||||
it('should add debug subcommand to chat/resume commands if it is a nightly build', async () => {
|
||||
vi.mocked(isNightly).mockResolvedValue(true);
|
||||
const loader = new BuiltinCommandLoader(mockConfig);
|
||||
const commands = await loader.loadCommands(new AbortController().signal);
|
||||
@@ -276,6 +351,27 @@ describe('BuiltinCommandLoader', () => {
|
||||
expect(chatCmd?.subCommands).toBeDefined();
|
||||
const hasDebug = chatCmd!.subCommands!.some((c) => c.name === 'debug');
|
||||
expect(hasDebug).toBe(true);
|
||||
|
||||
const resumeCmd = commands.find((c) => c.name === 'resume');
|
||||
const resumeHasDebug =
|
||||
resumeCmd?.subCommands?.some((c) => c.name === 'debug') ?? false;
|
||||
expect(resumeHasDebug).toBe(true);
|
||||
|
||||
const chatCheckpointsCmd = chatCmd?.subCommands?.find(
|
||||
(c) => c.name === 'checkpoints',
|
||||
);
|
||||
const chatCheckpointHasDebug =
|
||||
chatCheckpointsCmd?.subCommands?.some((c) => c.name === 'debug') ??
|
||||
false;
|
||||
expect(chatCheckpointHasDebug).toBe(true);
|
||||
|
||||
const resumeCheckpointsCmd = resumeCmd?.subCommands?.find(
|
||||
(c) => c.name === 'checkpoints',
|
||||
);
|
||||
const resumeCheckpointHasDebug =
|
||||
resumeCheckpointsCmd?.subCommands?.some((c) => c.name === 'debug') ??
|
||||
false;
|
||||
expect(resumeCheckpointHasDebug).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -287,7 +383,7 @@ describe('BuiltinCommandLoader profile', () => {
|
||||
vi.resetModules();
|
||||
mockConfig = {
|
||||
getFolderTrust: vi.fn().mockReturnValue(false),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(false),
|
||||
isPlanEnabled: vi.fn().mockReturnValue(true),
|
||||
getCheckpointingEnabled: () => false,
|
||||
getEnableExtensionReloading: () => false,
|
||||
getEnableHooks: () => false,
|
||||
@@ -300,6 +396,9 @@ describe('BuiltinCommandLoader profile', () => {
|
||||
getAllSkills: vi.fn().mockReturnValue([]),
|
||||
isAdminEnabled: vi.fn().mockReturnValue(true),
|
||||
}),
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: 'other',
|
||||
}),
|
||||
} as unknown as Config;
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
isNightly,
|
||||
startupProfiler,
|
||||
getAdminErrorMessage,
|
||||
AuthType,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { aboutCommand } from '../ui/commands/aboutCommand.js';
|
||||
import { agentsCommand } from '../ui/commands/agentsCommand.js';
|
||||
@@ -59,6 +60,7 @@ import { shellsCommand } from '../ui/commands/shellsCommand.js';
|
||||
import { vimCommand } from '../ui/commands/vimCommand.js';
|
||||
import { setupGithubCommand } from '../ui/commands/setupGithubCommand.js';
|
||||
import { terminalSetupCommand } from '../ui/commands/terminalSetupCommand.js';
|
||||
import { upgradeCommand } from '../ui/commands/upgradeCommand.js';
|
||||
|
||||
/**
|
||||
* Loads the core, hard-coded slash commands that are an integral part
|
||||
@@ -78,6 +80,41 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
const handle = startupProfiler.start('load_builtin_commands');
|
||||
|
||||
const isNightlyBuild = await isNightly(process.cwd());
|
||||
const addDebugToChatResumeSubCommands = (
|
||||
subCommands: SlashCommand[] | undefined,
|
||||
): SlashCommand[] | undefined => {
|
||||
if (!subCommands) {
|
||||
return subCommands;
|
||||
}
|
||||
|
||||
const withNestedCompatibility = subCommands.map((subCommand) => {
|
||||
if (subCommand.name !== 'checkpoints') {
|
||||
return subCommand;
|
||||
}
|
||||
|
||||
return {
|
||||
...subCommand,
|
||||
subCommands: addDebugToChatResumeSubCommands(subCommand.subCommands),
|
||||
};
|
||||
});
|
||||
|
||||
if (!isNightlyBuild) {
|
||||
return withNestedCompatibility;
|
||||
}
|
||||
|
||||
return withNestedCompatibility.some(
|
||||
(cmd) => cmd.name === debugCommand.name,
|
||||
)
|
||||
? withNestedCompatibility
|
||||
: [
|
||||
...withNestedCompatibility,
|
||||
{ ...debugCommand, suggestionGroup: 'checkpoints' },
|
||||
];
|
||||
};
|
||||
|
||||
const chatResumeSubCommands = addDebugToChatResumeSubCommands(
|
||||
chatCommand.subCommands,
|
||||
);
|
||||
|
||||
const allDefinitions: Array<SlashCommand | null> = [
|
||||
aboutCommand,
|
||||
@@ -86,9 +123,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
bugCommand,
|
||||
{
|
||||
...chatCommand,
|
||||
subCommands: isNightlyBuild
|
||||
? [...(chatCommand.subCommands || []), debugCommand]
|
||||
: chatCommand.subCommands,
|
||||
subCommands: chatResumeSubCommands,
|
||||
},
|
||||
clearCommand,
|
||||
commandsCommand,
|
||||
@@ -155,7 +190,10 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
...(isDevelopment ? [profileCommand] : []),
|
||||
quitCommand,
|
||||
restoreCommand(this.config),
|
||||
resumeCommand,
|
||||
{
|
||||
...resumeCommand,
|
||||
subCommands: addDebugToChatResumeSubCommands(resumeCommand.subCommands),
|
||||
},
|
||||
statsCommand,
|
||||
themeCommand,
|
||||
toolsCommand,
|
||||
@@ -187,6 +225,10 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
vimCommand,
|
||||
setupGithubCommand,
|
||||
terminalSetupCommand,
|
||||
...(this.config?.getContentGeneratorConfig()?.authType ===
|
||||
AuthType.LOGIN_WITH_GOOGLE
|
||||
? [upgradeCommand]
|
||||
: []),
|
||||
];
|
||||
handle?.end();
|
||||
return allDefinitions.filter((cmd): cmd is SlashCommand => cmd !== null);
|
||||
|
||||
@@ -119,7 +119,7 @@ import { type InitializationResult } from '../core/initializer.js';
|
||||
import { useFocus } from './hooks/useFocus.js';
|
||||
import { useKeypress, type Key } from './hooks/useKeypress.js';
|
||||
import { KeypressPriority } from './contexts/KeypressContext.js';
|
||||
import { keyMatchers, Command } from './keyMatchers.js';
|
||||
import { Command } from './keyMatchers.js';
|
||||
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
||||
import { useShellInactivityStatus } from './hooks/useShellInactivityStatus.js';
|
||||
import { useFolderTrust } from './hooks/useFolderTrust.js';
|
||||
@@ -164,7 +164,7 @@ import { NewAgentsChoice } from './components/NewAgentsNotification.js';
|
||||
import { isSlashCommand } from './utils/commandUtils.js';
|
||||
import { useTerminalTheme } from './hooks/useTerminalTheme.js';
|
||||
import { useTimedMessage } from './hooks/useTimedMessage.js';
|
||||
import { shouldDismissShortcutsHelpOnHotkey } from './utils/shortcutsHelp.js';
|
||||
import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
|
||||
import { useSuspend } from './hooks/useSuspend.js';
|
||||
import { useRunEventNotifications } from './hooks/useRunEventNotifications.js';
|
||||
import { isNotificationsEnabled } from '../utils/terminalNotifications.js';
|
||||
@@ -205,6 +205,7 @@ import {
|
||||
useVisibilityToggle,
|
||||
APPROVAL_MODE_REVEAL_DURATION_MS,
|
||||
} from './hooks/useVisibilityToggle.js';
|
||||
import { useKeyMatchers } from './hooks/useKeyMatchers.js';
|
||||
|
||||
/**
|
||||
* The fraction of the terminal width to allocate to the shell.
|
||||
@@ -219,6 +220,8 @@ const SHELL_WIDTH_FRACTION = 0.89;
|
||||
const SHELL_HEIGHT_PADDING = 10;
|
||||
|
||||
export const AppContainer = (props: AppContainerProps) => {
|
||||
const isHelpDismissKey = useIsHelpDismissKey();
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { config, initializationResult, resumedSessionData } = props;
|
||||
const settings = useSettings();
|
||||
const { reset } = useOverflowActions()!;
|
||||
@@ -1654,7 +1657,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
|
||||
}
|
||||
|
||||
if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
|
||||
if (shortcutsHelpVisible && isHelpDismissKey(key)) {
|
||||
setShortcutsHelpVisible(false);
|
||||
}
|
||||
|
||||
@@ -1848,6 +1851,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
settings.merged.general.devtools,
|
||||
showErrorDetails,
|
||||
triggerExpandHint,
|
||||
keyMatchers,
|
||||
isHelpDismissKey,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ HistoryItemDisplay
|
||||
│ ls │
|
||||
│ Allow execution of: 'ls'? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. No, suggest changes (esc) │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import { useTextBuffer } from '../components/shared/text-buffer.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { clearApiKey, debugLogger } from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
interface ApiAuthDialogProps {
|
||||
onSubmit: (apiKey: string) => void;
|
||||
@@ -28,6 +29,7 @@ export function ApiAuthDialog({
|
||||
error,
|
||||
defaultValue = '',
|
||||
}: ApiAuthDialogProps): React.JSX.Element {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { terminalWidth } = useUIState();
|
||||
const viewportWidth = terminalWidth - 8;
|
||||
|
||||
|
||||
@@ -99,8 +99,11 @@ describe('chatCommand', () => {
|
||||
|
||||
it('should have the correct main command definition', () => {
|
||||
expect(chatCommand.name).toBe('chat');
|
||||
expect(chatCommand.description).toBe('Manage conversation history');
|
||||
expect(chatCommand.subCommands).toHaveLength(5);
|
||||
expect(chatCommand.description).toBe(
|
||||
'Browse auto-saved conversations and manage chat checkpoints',
|
||||
);
|
||||
expect(chatCommand.autoExecute).toBe(true);
|
||||
expect(chatCommand.subCommands).toHaveLength(6);
|
||||
});
|
||||
|
||||
describe('list subcommand', () => {
|
||||
@@ -158,7 +161,7 @@ describe('chatCommand', () => {
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Missing tag. Usage: /chat save <tag>',
|
||||
content: 'Missing tag. Usage: /resume save <tag>',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -252,7 +255,7 @@ describe('chatCommand', () => {
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Missing tag. Usage: /chat resume <tag>',
|
||||
content: 'Missing tag. Usage: /resume resume <tag>',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -386,7 +389,7 @@ describe('chatCommand', () => {
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Missing tag. Usage: /chat delete <tag>',
|
||||
content: 'Missing tag. Usage: /resume delete <tag>',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ import { MessageType } from '../types.js';
|
||||
import { exportHistoryToFile } from '../utils/historyExportUtils.js';
|
||||
import { convertToRestPayload } from '@google/gemini-cli-core';
|
||||
|
||||
const CHECKPOINT_MENU_GROUP = 'checkpoints';
|
||||
|
||||
const getSavedChatTags = async (
|
||||
context: CommandContext,
|
||||
mtSortDesc: boolean,
|
||||
@@ -70,7 +72,7 @@ const getSavedChatTags = async (
|
||||
|
||||
const listCommand: SlashCommand = {
|
||||
name: 'list',
|
||||
description: 'List saved conversation checkpoints',
|
||||
description: 'List saved manual conversation checkpoints',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context): Promise<void> => {
|
||||
@@ -88,7 +90,7 @@ const listCommand: SlashCommand = {
|
||||
const saveCommand: SlashCommand = {
|
||||
name: 'save',
|
||||
description:
|
||||
'Save the current conversation as a checkpoint. Usage: /chat save <tag>',
|
||||
'Save the current conversation as a checkpoint. Usage: /resume save <tag>',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
action: async (context, args): Promise<SlashCommandActionReturn | void> => {
|
||||
@@ -97,7 +99,7 @@ const saveCommand: SlashCommand = {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Missing tag. Usage: /chat save <tag>',
|
||||
content: 'Missing tag. Usage: /resume save <tag>',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,7 +119,7 @@ const saveCommand: SlashCommand = {
|
||||
' already exists. Do you want to overwrite it?',
|
||||
),
|
||||
originalInvocation: {
|
||||
raw: context.invocation?.raw || `/chat save ${tag}`,
|
||||
raw: context.invocation?.raw || `/resume save ${tag}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -153,11 +155,11 @@ const saveCommand: SlashCommand = {
|
||||
},
|
||||
};
|
||||
|
||||
const resumeCommand: SlashCommand = {
|
||||
const resumeCheckpointCommand: SlashCommand = {
|
||||
name: 'resume',
|
||||
altNames: ['load'],
|
||||
description:
|
||||
'Resume a conversation from a checkpoint. Usage: /chat resume <tag>',
|
||||
'Resume a conversation from a checkpoint. Usage: /resume resume <tag>',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, args) => {
|
||||
@@ -166,7 +168,7 @@ const resumeCommand: SlashCommand = {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Missing tag. Usage: /chat resume <tag>',
|
||||
content: 'Missing tag. Usage: /resume resume <tag>',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -235,7 +237,7 @@ const resumeCommand: SlashCommand = {
|
||||
|
||||
const deleteCommand: SlashCommand = {
|
||||
name: 'delete',
|
||||
description: 'Delete a conversation checkpoint. Usage: /chat delete <tag>',
|
||||
description: 'Delete a conversation checkpoint. Usage: /resume delete <tag>',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context, args): Promise<MessageActionReturn> => {
|
||||
@@ -244,7 +246,7 @@ const deleteCommand: SlashCommand = {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Missing tag. Usage: /chat delete <tag>',
|
||||
content: 'Missing tag. Usage: /resume delete <tag>',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -277,7 +279,7 @@ const deleteCommand: SlashCommand = {
|
||||
const shareCommand: SlashCommand = {
|
||||
name: 'share',
|
||||
description:
|
||||
'Share the current conversation to a markdown or json file. Usage: /chat share <file>',
|
||||
'Share the current conversation to a markdown or json file. Usage: /resume share <file>',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
action: async (context, args): Promise<MessageActionReturn> => {
|
||||
@@ -376,16 +378,40 @@ export const debugCommand: SlashCommand = {
|
||||
},
|
||||
};
|
||||
|
||||
export const checkpointSubCommands: SlashCommand[] = [
|
||||
listCommand,
|
||||
saveCommand,
|
||||
resumeCheckpointCommand,
|
||||
deleteCommand,
|
||||
shareCommand,
|
||||
];
|
||||
|
||||
const checkpointCompatibilityCommand: SlashCommand = {
|
||||
name: 'checkpoints',
|
||||
altNames: ['checkpoint'],
|
||||
description: 'Compatibility command for nested checkpoint operations',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
hidden: true,
|
||||
autoExecute: false,
|
||||
subCommands: checkpointSubCommands,
|
||||
};
|
||||
|
||||
export const chatResumeSubCommands: SlashCommand[] = [
|
||||
...checkpointSubCommands.map((subCommand) => ({
|
||||
...subCommand,
|
||||
suggestionGroup: CHECKPOINT_MENU_GROUP,
|
||||
})),
|
||||
checkpointCompatibilityCommand,
|
||||
];
|
||||
|
||||
export const chatCommand: SlashCommand = {
|
||||
name: 'chat',
|
||||
description: 'Manage conversation history',
|
||||
description: 'Browse auto-saved conversations and manage chat checkpoints',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
subCommands: [
|
||||
listCommand,
|
||||
saveCommand,
|
||||
resumeCommand,
|
||||
deleteCommand,
|
||||
shareCommand,
|
||||
],
|
||||
autoExecute: true,
|
||||
action: async () => ({
|
||||
type: 'dialog',
|
||||
dialog: 'sessionBrowser',
|
||||
}),
|
||||
subCommands: chatResumeSubCommands,
|
||||
};
|
||||
|
||||
@@ -131,4 +131,12 @@ describe('compressCommand', () => {
|
||||
await compressCommand.action!(context, '');
|
||||
expect(context.ui.setPendingItem).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
describe('metadata', () => {
|
||||
it('should have the correct name and aliases', () => {
|
||||
expect(compressCommand.name).toBe('compress');
|
||||
expect(compressCommand.altNames).toContain('summarize');
|
||||
expect(compressCommand.altNames).toContain('compact');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import { CommandKind } from './types.js';
|
||||
|
||||
export const compressCommand: SlashCommand = {
|
||||
name: 'compress',
|
||||
altNames: ['summarize'],
|
||||
altNames: ['summarize', 'compact'],
|
||||
description: 'Compresses the context by replacing it with a summary',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resumeCommand } from './resumeCommand.js';
|
||||
import type { CommandContext } from './types.js';
|
||||
|
||||
describe('resumeCommand', () => {
|
||||
it('should open the session browser for bare /resume', async () => {
|
||||
const result = await resumeCommand.action?.({} as CommandContext, '');
|
||||
expect(result).toEqual({
|
||||
type: 'dialog',
|
||||
dialog: 'sessionBrowser',
|
||||
});
|
||||
});
|
||||
|
||||
it('should expose unified chat subcommands directly under /resume', () => {
|
||||
const visibleSubCommandNames = (resumeCommand.subCommands ?? [])
|
||||
.filter((subCommand) => !subCommand.hidden)
|
||||
.map((subCommand) => subCommand.name);
|
||||
|
||||
expect(visibleSubCommandNames).toEqual(
|
||||
expect.arrayContaining(['list', 'save', 'resume', 'delete', 'share']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should keep a hidden /resume checkpoints compatibility alias', () => {
|
||||
const checkpoints = resumeCommand.subCommands?.find(
|
||||
(subCommand) => subCommand.name === 'checkpoints',
|
||||
);
|
||||
expect(checkpoints?.hidden).toBe(true);
|
||||
expect(
|
||||
checkpoints?.subCommands?.map((subCommand) => subCommand.name),
|
||||
).toEqual(
|
||||
expect.arrayContaining(['list', 'save', 'resume', 'delete', 'share']),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -10,10 +10,11 @@ import type {
|
||||
SlashCommand,
|
||||
} from './types.js';
|
||||
import { CommandKind } from './types.js';
|
||||
import { chatResumeSubCommands } from './chatCommand.js';
|
||||
|
||||
export const resumeCommand: SlashCommand = {
|
||||
name: 'resume',
|
||||
description: 'Browse and resume auto-saved conversations',
|
||||
description: 'Browse auto-saved conversations and manage chat checkpoints',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (
|
||||
@@ -23,4 +24,5 @@ export const resumeCommand: SlashCommand = {
|
||||
type: 'dialog',
|
||||
dialog: 'sessionBrowser',
|
||||
}),
|
||||
subCommands: chatResumeSubCommands,
|
||||
};
|
||||
|
||||
@@ -120,6 +120,7 @@ async function downloadFiles({
|
||||
downloads.push(
|
||||
(async () => {
|
||||
const endpoint = `${REPO_DOWNLOAD_URL}/refs/tags/${releaseTag}/${SOURCE_DIR}/${fileBasename}`;
|
||||
// eslint-disable-next-line no-restricted-syntax -- TODO: Migrate to safeFetch for SSRF protection
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'GET',
|
||||
dispatcher: proxy ? new ProxyAgent(proxy) : undefined,
|
||||
|
||||
@@ -110,4 +110,28 @@ describe('toolsCommand', () => {
|
||||
);
|
||||
expect(message.tools[1].description).toBe('Edits code files.');
|
||||
});
|
||||
|
||||
it('should expose a desc subcommand for TUI discoverability', async () => {
|
||||
const descSubCommand = toolsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'desc',
|
||||
);
|
||||
expect(descSubCommand).toBeDefined();
|
||||
expect(descSubCommand?.description).toContain('descriptions');
|
||||
|
||||
const mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!descSubCommand?.action) throw new Error('Action not defined');
|
||||
await descSubCommand.action(mockContext, '');
|
||||
|
||||
const [message] = (mockContext.ui.addItem as ReturnType<typeof vi.fn>).mock
|
||||
.calls[0];
|
||||
expect(message.type).toBe(MessageType.TOOLS_LIST);
|
||||
expect(message.showDescriptions).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,43 +11,60 @@ import {
|
||||
} from './types.js';
|
||||
import { MessageType, type HistoryItemToolsList } from '../types.js';
|
||||
|
||||
async function listTools(
|
||||
context: CommandContext,
|
||||
showDescriptions: boolean,
|
||||
): Promise<void> {
|
||||
const toolRegistry = context.services.config?.getToolRegistry();
|
||||
if (!toolRegistry) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
text: 'Could not retrieve tool registry.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const tools = toolRegistry.getAllTools();
|
||||
// Filter out MCP tools by checking for the absence of a serverName property
|
||||
const geminiTools = tools.filter((tool) => !('serverName' in tool));
|
||||
|
||||
const toolsListItem: HistoryItemToolsList = {
|
||||
type: MessageType.TOOLS_LIST,
|
||||
tools: geminiTools.map((tool) => ({
|
||||
name: tool.name,
|
||||
displayName: tool.displayName,
|
||||
description: tool.description,
|
||||
})),
|
||||
showDescriptions,
|
||||
};
|
||||
|
||||
context.ui.addItem(toolsListItem);
|
||||
}
|
||||
|
||||
const toolsDescSubCommand: SlashCommand = {
|
||||
name: 'desc',
|
||||
altNames: ['descriptions'],
|
||||
description: 'List available Gemini CLI tools with descriptions.',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: async (context: CommandContext): Promise<void> =>
|
||||
listTools(context, true),
|
||||
};
|
||||
|
||||
export const toolsCommand: SlashCommand = {
|
||||
name: 'tools',
|
||||
description: 'List available Gemini CLI tools. Usage: /tools [desc]',
|
||||
description:
|
||||
'List available Gemini CLI tools. Use /tools desc to include descriptions.',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: false,
|
||||
subCommands: [toolsDescSubCommand],
|
||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||
const subCommand = args?.trim();
|
||||
|
||||
// Default to NOT showing descriptions. The user must opt in with an argument.
|
||||
let useShowDescriptions = false;
|
||||
if (subCommand === 'desc' || subCommand === 'descriptions') {
|
||||
useShowDescriptions = true;
|
||||
}
|
||||
// Keep backward compatibility for typed arguments while exposing desc in TUI via subcommands.
|
||||
const useShowDescriptions =
|
||||
subCommand === 'desc' || subCommand === 'descriptions';
|
||||
|
||||
const toolRegistry = context.services.config?.getToolRegistry();
|
||||
if (!toolRegistry) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.ERROR,
|
||||
text: 'Could not retrieve tool registry.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const tools = toolRegistry.getAllTools();
|
||||
// Filter out MCP tools by checking for the absence of a serverName property
|
||||
const geminiTools = tools.filter((tool) => !('serverName' in tool));
|
||||
|
||||
const toolsListItem: HistoryItemToolsList = {
|
||||
type: MessageType.TOOLS_LIST,
|
||||
tools: geminiTools.map((tool) => ({
|
||||
name: tool.name,
|
||||
displayName: tool.displayName,
|
||||
description: tool.description,
|
||||
})),
|
||||
showDescriptions: useShowDescriptions,
|
||||
};
|
||||
|
||||
context.ui.addItem(toolsListItem);
|
||||
await listTools(context, useShowDescriptions);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -190,6 +190,11 @@ export interface SlashCommand {
|
||||
altNames?: string[];
|
||||
description: string;
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* Optional grouping label for slash completion UI sections.
|
||||
* Commands with the same label are rendered under one separator.
|
||||
*/
|
||||
suggestionGroup?: string;
|
||||
|
||||
kind: CommandKind;
|
||||
|
||||
@@ -217,7 +222,7 @@ export interface SlashCommand {
|
||||
| SlashCommandActionReturn
|
||||
| Promise<void | SlashCommandActionReturn>;
|
||||
|
||||
// Provides argument completion (e.g., completing a tag for `/chat resume <tag>`).
|
||||
// Provides argument completion (e.g., completing a tag for `/resume resume <tag>`).
|
||||
completion?: (
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { upgradeCommand } from './upgradeCommand.js';
|
||||
import { type CommandContext } from './types.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import {
|
||||
AuthType,
|
||||
openBrowserSecurely,
|
||||
UPGRADE_URL_PAGE,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
openBrowserSecurely: vi.fn(),
|
||||
UPGRADE_URL_PAGE: 'https://goo.gle/set-up-gemini-code-assist',
|
||||
};
|
||||
});
|
||||
|
||||
describe('upgradeCommand', () => {
|
||||
let mockContext: CommandContext;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as unknown as CommandContext);
|
||||
});
|
||||
|
||||
it('should have the correct name and description', () => {
|
||||
expect(upgradeCommand.name).toBe('upgrade');
|
||||
expect(upgradeCommand.description).toBe(
|
||||
'Upgrade your Gemini Code Assist tier for higher limits',
|
||||
);
|
||||
});
|
||||
|
||||
it('should call openBrowserSecurely with UPGRADE_URL_PAGE when logged in with Google', async () => {
|
||||
if (!upgradeCommand.action) {
|
||||
throw new Error('The upgrade command must have an action.');
|
||||
}
|
||||
|
||||
await upgradeCommand.action(mockContext, '');
|
||||
|
||||
expect(openBrowserSecurely).toHaveBeenCalledWith(UPGRADE_URL_PAGE);
|
||||
});
|
||||
|
||||
it('should return an error message when NOT logged in with Google', async () => {
|
||||
vi.mocked(
|
||||
mockContext.services.config!.getContentGeneratorConfig,
|
||||
).mockReturnValue({
|
||||
authType: AuthType.USE_GEMINI,
|
||||
});
|
||||
|
||||
if (!upgradeCommand.action) {
|
||||
throw new Error('The upgrade command must have an action.');
|
||||
}
|
||||
|
||||
const result = await upgradeCommand.action(mockContext, '');
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content:
|
||||
'The /upgrade command is only available when logged in with Google.',
|
||||
});
|
||||
expect(openBrowserSecurely).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return an error message if openBrowserSecurely fails', async () => {
|
||||
vi.mocked(openBrowserSecurely).mockRejectedValue(
|
||||
new Error('Failed to open'),
|
||||
);
|
||||
|
||||
if (!upgradeCommand.action) {
|
||||
throw new Error('The upgrade command must have an action.');
|
||||
}
|
||||
|
||||
const result = await upgradeCommand.action(mockContext, '');
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Failed to open upgrade page: Failed to open',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthType,
|
||||
openBrowserSecurely,
|
||||
UPGRADE_URL_PAGE,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SlashCommand } from './types.js';
|
||||
import { CommandKind } from './types.js';
|
||||
|
||||
/**
|
||||
* Command to open the upgrade page for Gemini Code Assist.
|
||||
* Only intended to be shown/available when the user is logged in with Google.
|
||||
*/
|
||||
export const upgradeCommand: SlashCommand = {
|
||||
name: 'upgrade',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
description: 'Upgrade your Gemini Code Assist tier for higher limits',
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
const authType =
|
||||
context.services.config?.getContentGeneratorConfig()?.authType;
|
||||
if (authType !== AuthType.LOGIN_WITH_GOOGLE) {
|
||||
// This command should ideally be hidden if not logged in with Google,
|
||||
// but we add a safety check here just in case.
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content:
|
||||
'The /upgrade command is only available when logged in with Google.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await openBrowserSecurely(UPGRADE_URL_PAGE);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: `Failed to open upgrade page: ${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
@@ -8,9 +8,11 @@ import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||
import { Command, keyMatchers } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
export const AdminSettingsChangedDialog = () => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { handleRestart } = useUIActions();
|
||||
|
||||
useKeypress(
|
||||
|
||||
@@ -327,5 +327,31 @@ describe('AgentConfigDialog', () => {
|
||||
expect(frame).toContain('false');
|
||||
unmount();
|
||||
});
|
||||
it('should respond to availableTerminalHeight and truncate list', async () => {
|
||||
const settings = createMockSettings();
|
||||
// Agent config has about 6 base items + 2 per tool
|
||||
// Render with very small height (20)
|
||||
const { lastFrame, unmount } = render(
|
||||
<KeypressProvider>
|
||||
<AgentConfigDialog
|
||||
agentName="test-agent"
|
||||
displayName="Test Agent"
|
||||
definition={createMockAgentDefinition()}
|
||||
settings={settings}
|
||||
onClose={mockOnClose}
|
||||
onSave={mockOnSave}
|
||||
availableTerminalHeight={20}
|
||||
/>
|
||||
</KeypressProvider>,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(lastFrame()).toContain('Configure: Test Agent'),
|
||||
);
|
||||
|
||||
const frame = lastFrame();
|
||||
// At height 20, it should be heavily truncated and show '▼'
|
||||
expect(frame).toContain('▼');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,6 +110,8 @@ interface AgentConfigDialogProps {
|
||||
settings: LoadedSettings;
|
||||
onClose: () => void;
|
||||
onSave?: () => void;
|
||||
/** Available terminal height for dynamic windowing */
|
||||
availableTerminalHeight?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,6 +194,7 @@ export function AgentConfigDialog({
|
||||
settings,
|
||||
onClose,
|
||||
onSave,
|
||||
availableTerminalHeight,
|
||||
}: AgentConfigDialogProps): React.JSX.Element {
|
||||
// Scope selector state (User by default)
|
||||
const [selectedScope, setSelectedScope] = useState<LoadableSettingScope>(
|
||||
@@ -395,12 +398,6 @@ export function AgentConfigDialog({
|
||||
[pendingOverride, saveFieldValue],
|
||||
);
|
||||
|
||||
// Footer content
|
||||
const footerContent =
|
||||
modifiedFields.size > 0 ? (
|
||||
<Text color={theme.text.secondary}>Changes saved automatically.</Text>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<BaseSettingsDialog
|
||||
title={`Configure: ${displayName}`}
|
||||
@@ -410,12 +407,24 @@ export function AgentConfigDialog({
|
||||
selectedScope={selectedScope}
|
||||
onScopeChange={handleScopeChange}
|
||||
maxItemsToShow={maxItemsToShow}
|
||||
availableHeight={availableTerminalHeight}
|
||||
maxLabelWidth={maxLabelWidth}
|
||||
onItemToggle={handleItemToggle}
|
||||
onEditCommit={handleEditCommit}
|
||||
onItemClear={handleItemClear}
|
||||
onClose={onClose}
|
||||
footerContent={footerContent}
|
||||
footer={
|
||||
modifiedFields.size > 0
|
||||
? {
|
||||
content: (
|
||||
<Text color={theme.text.secondary}>
|
||||
Changes saved automatically.
|
||||
</Text>
|
||||
),
|
||||
height: 1,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,16 +17,30 @@ import { theme } from '../semantic-colors.js';
|
||||
import { ThemedGradient } from './ThemedGradient.js';
|
||||
import { CliSpinner } from './CliSpinner.js';
|
||||
|
||||
import { isAppleTerminal } from '@google/gemini-cli-core';
|
||||
|
||||
interface AppHeaderProps {
|
||||
version: string;
|
||||
showDetails?: boolean;
|
||||
}
|
||||
|
||||
const ICON = `▝▜▄
|
||||
const DEFAULT_ICON = `▝▜▄
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▝▀ `;
|
||||
|
||||
/**
|
||||
* The default Apple Terminal.app adds significant line-height padding between
|
||||
* rows. This breaks Unicode block-drawing characters that rely on vertical
|
||||
* adjacency (like half-blocks). This version is perfectly symmetric vertically,
|
||||
* which makes the padding gaps look like an intentional "scanline" design
|
||||
* rather than a broken image.
|
||||
*/
|
||||
const MAC_TERMINAL_ICON = `▝▜▄
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▗▟▀ `;
|
||||
|
||||
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
const settings = useSettings();
|
||||
const config = useConfig();
|
||||
@@ -39,6 +53,8 @@ export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
settings.merged.ui.hideBanner || config.getScreenReader()
|
||||
);
|
||||
|
||||
const ICON = isAppleTerminal() ? MAC_TERMINAL_ICON : DEFAULT_ICON;
|
||||
|
||||
if (!showDetails) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
|
||||
// We mock the entire module to control the isAppleTerminal export
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
isAppleTerminal: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { isAppleTerminal } from '@google/gemini-cli-core';
|
||||
|
||||
describe('AppHeader Icon Rendering', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('renders the default icon in standard terminals', async () => {
|
||||
vi.mocked(isAppleTerminal).mockReturnValue(false);
|
||||
|
||||
const result = renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
await result.waitUntilReady();
|
||||
|
||||
await expect(result).toMatchSvgSnapshot();
|
||||
});
|
||||
|
||||
it('renders the symmetric icon in Apple Terminal', async () => {
|
||||
vi.mocked(isAppleTerminal).mockReturnValue(true);
|
||||
|
||||
const result = renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
await result.waitUntilReady();
|
||||
|
||||
await expect(result).toMatchSvgSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -20,7 +20,7 @@ import { BaseSelectionList } from './shared/BaseSelectionList.js';
|
||||
import type { SelectionListItem } from '../hooks/useSelectionList.js';
|
||||
import { TabHeader, type Tab } from './shared/TabHeader.js';
|
||||
import { useKeypress, type Key } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { checkExhaustive } from '@google/gemini-cli-core';
|
||||
import { TextInput } from './shared/TextInput.js';
|
||||
import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
@@ -36,6 +36,7 @@ import { RenderInline } from '../utils/InlineMarkdownRenderer.js';
|
||||
import { MaxSizedBox } from './shared/MaxSizedBox.js';
|
||||
import { UIStateContext } from '../contexts/UIStateContext.js';
|
||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
/** Padding for dialog content to prevent text from touching edges. */
|
||||
const DIALOG_PADDING = 4;
|
||||
@@ -208,6 +209,7 @@ const ReviewView: React.FC<ReviewViewProps> = ({
|
||||
progressHeader,
|
||||
extraParts,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const unansweredCount = questions.length - Object.keys(answers).length;
|
||||
const hasUnanswered = unansweredCount > 0;
|
||||
|
||||
@@ -288,6 +290,7 @@ const TextQuestionView: React.FC<TextQuestionViewProps> = ({
|
||||
progressHeader,
|
||||
keyboardHints,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const prefix = '> ';
|
||||
const horizontalPadding = 1; // 1 for cursor
|
||||
@@ -325,7 +328,7 @@ const TextQuestionView: React.FC<TextQuestionViewProps> = ({
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[buffer, textValue],
|
||||
[buffer, textValue, keyMatchers],
|
||||
);
|
||||
|
||||
useKeypress(handleExtraKeys, { isActive: true, priority: true });
|
||||
@@ -487,6 +490,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
|
||||
progressHeader,
|
||||
keyboardHints,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const numOptions =
|
||||
(question.options?.length ?? 0) + (question.type !== 'yesno' ? 1 : 0);
|
||||
@@ -680,6 +684,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
|
||||
customBuffer,
|
||||
onEditingCustomOption,
|
||||
customOptionText,
|
||||
keyMatchers,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -950,6 +955,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
|
||||
availableHeight: availableHeightProp,
|
||||
extraParts,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const uiState = useContext(UIStateContext);
|
||||
const availableHeight =
|
||||
availableHeightProp ??
|
||||
@@ -999,7 +1005,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[onCancel, submitted, isEditingCustomOption],
|
||||
[onCancel, submitted, isEditingCustomOption, keyMatchers],
|
||||
);
|
||||
|
||||
useKeypress(handleCancel, {
|
||||
@@ -1032,7 +1038,7 @@ export const AskUserDialog: React.FC<AskUserDialogProps> = ({
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[questions.length, submitted, goToNextTab, goToPrevTab],
|
||||
[questions.length, submitted, goToNextTab, goToPrevTab, keyMatchers],
|
||||
);
|
||||
|
||||
useKeypress(handleNavigation, {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
import { cpLen, cpSlice, getCachedStringWidth } from '../utils/textUtils.js';
|
||||
import { type BackgroundShell } from '../hooks/shellCommandProcessor.js';
|
||||
import { Command, keyMatchers } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
import {
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
RadioButtonSelect,
|
||||
type RadioSelectItem,
|
||||
} from './shared/RadioButtonSelect.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
interface BackgroundShellDisplayProps {
|
||||
shells: Map<number, BackgroundShell>;
|
||||
@@ -60,6 +61,7 @@ export const BackgroundShellDisplay = ({
|
||||
isFocused,
|
||||
isListOpenProp,
|
||||
}: BackgroundShellDisplayProps) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const {
|
||||
dismissBackgroundShell,
|
||||
setActiveBackgroundShellPid,
|
||||
|
||||
@@ -231,7 +231,7 @@ const createMockConfig = (overrides = {}): Config =>
|
||||
getDebugMode: vi.fn(() => false),
|
||||
getAccessibility: vi.fn(() => ({})),
|
||||
getMcpServers: vi.fn(() => ({})),
|
||||
isPlanEnabled: vi.fn(() => false),
|
||||
isPlanEnabled: vi.fn(() => true),
|
||||
getToolRegistry: () => ({
|
||||
getTool: vi.fn(),
|
||||
}),
|
||||
|
||||
@@ -252,6 +252,7 @@ export const DialogManager = ({
|
||||
displayName={uiState.selectedAgentDisplayName}
|
||||
definition={uiState.selectedAgentDefinition}
|
||||
settings={settings}
|
||||
availableTerminalHeight={terminalHeight - staticExtraHeight}
|
||||
onClose={uiActions.closeAgentConfigDialog}
|
||||
onSave={async () => {
|
||||
// Reload agent registry to pick up changes
|
||||
|
||||
@@ -10,7 +10,7 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { ExitPlanModeDialog } from './ExitPlanModeDialog.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import {
|
||||
ApprovalMode,
|
||||
validatePlanContent,
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type FileSystemService,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
vi.mock('../utils/editorUtils.js', () => ({
|
||||
openFileInEditor: vi.fn(),
|
||||
@@ -402,6 +403,7 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (keyMatchers[Command.QUIT](key)) {
|
||||
|
||||
@@ -22,8 +22,9 @@ import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { AskUserDialog } from './AskUserDialog.js';
|
||||
import { openFileInEditor } from '../utils/editorUtils.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
export interface ExitPlanModeDialogProps {
|
||||
planPath: string;
|
||||
@@ -147,6 +148,7 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
|
||||
width,
|
||||
availableHeight,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const config = useConfig();
|
||||
const { stdin, setRawMode } = useStdin();
|
||||
const planState = usePlanContent(planPath, config);
|
||||
|
||||
@@ -11,13 +11,14 @@ import { theme } from '../semantic-colors.js';
|
||||
import { useSettingsStore } from '../contexts/SettingsContext.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useKeypress, type Key } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { FooterRow, type FooterRowItem } from './Footer.js';
|
||||
import { ALL_ITEMS, resolveFooterState } from '../../config/footerItems.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import { BaseSelectionList } from './shared/BaseSelectionList.js';
|
||||
import type { SelectionListItem } from '../hooks/useSelectionList.js';
|
||||
import { DialogFooter } from './shared/DialogFooter.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
interface FooterConfigDialogProps {
|
||||
onClose?: () => void;
|
||||
@@ -82,6 +83,7 @@ function footerConfigReducer(
|
||||
export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { settings, setSetting } = useSettingsStore();
|
||||
const { constrainHeight, terminalHeight, staticExtraHeight } = useUIState();
|
||||
const [state, dispatch] = useReducer(footerConfigReducer, undefined, () =>
|
||||
|
||||
@@ -9,7 +9,8 @@ import { useState, useMemo } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
/**
|
||||
* Hook entry type matching HookRegistryEntry from core
|
||||
@@ -49,6 +50,7 @@ export const HooksDialog: React.FC<HooksDialogProps> = ({
|
||||
onClose,
|
||||
maxVisibleHooks = DEFAULT_MAX_VISIBLE_HOOKS,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const [scrollOffset, setScrollOffset] = useState(0);
|
||||
|
||||
// Flatten hooks with their event names for easier scrolling
|
||||
|
||||
@@ -44,7 +44,7 @@ import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js
|
||||
import type { UIState } from '../contexts/UIStateContext.js';
|
||||
import { isLowColorDepth } from '../utils/terminalUtils.js';
|
||||
import { cpLen } from '../utils/textUtils.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { defaultKeyMatchers, Command } from '../keyMatchers.js';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
import {
|
||||
appEvents,
|
||||
@@ -197,7 +197,7 @@ describe('InputPrompt', () => {
|
||||
visualCursor: [0, 0],
|
||||
visualScrollRow: 0,
|
||||
handleInput: vi.fn((key: Key) => {
|
||||
if (keyMatchers[Command.CLEAR_INPUT](key)) {
|
||||
if (defaultKeyMatchers[Command.CLEAR_INPUT](key)) {
|
||||
if (mockBuffer.text.length > 0) {
|
||||
mockBuffer.setText('');
|
||||
return true;
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
} from '../hooks/useCommandCompletion.js';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { formatCommand } from '../utils/keybindingUtils.js';
|
||||
import type { CommandContext, SlashCommand } from '../commands/types.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
@@ -72,8 +72,9 @@ import { useMouseClick } from '../hooks/useMouseClick.js';
|
||||
import { useMouse, type MouseEvent } from '../contexts/MouseContext.js';
|
||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||
import { shouldDismissShortcutsHelpOnHotkey } from '../utils/shortcutsHelp.js';
|
||||
import { useIsHelpDismissKey } from '../utils/shortcutsHelp.js';
|
||||
import { useRepeatedKeyPress } from '../hooks/useRepeatedKeyPress.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
/**
|
||||
* Returns if the terminal can be trusted to handle paste events atomically
|
||||
@@ -207,6 +208,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
suggestionsPosition = 'below',
|
||||
setBannerVisible,
|
||||
}) => {
|
||||
const isHelpDismissKey = useIsHelpDismissKey();
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { stdout } = useStdout();
|
||||
const { merged: settings } = useSettings();
|
||||
const kittyProtocol = useKittyKeyboardProtocol();
|
||||
@@ -737,7 +740,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shortcutsHelpVisible && shouldDismissShortcutsHelpOnHotkey(key)) {
|
||||
if (shortcutsHelpVisible && isHelpDismissKey(key)) {
|
||||
setShortcutsHelpVisible(false);
|
||||
}
|
||||
|
||||
@@ -990,6 +993,12 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
|
||||
if (isEnterKey && buffer.text.startsWith('/')) {
|
||||
if (suggestion.submitValue) {
|
||||
setExpandedSuggestionIndex(-1);
|
||||
handleSubmit(suggestion.submitValue.trim());
|
||||
return true;
|
||||
}
|
||||
|
||||
const { isArgumentCompletion, leafCommand } =
|
||||
completion.slashCompletionRange;
|
||||
|
||||
@@ -1259,6 +1268,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
shouldShowSuggestions,
|
||||
isShellSuggestionsVisible,
|
||||
forceShowShellSuggestions,
|
||||
keyMatchers,
|
||||
isHelpDismissKey,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import { theme } from '../semantic-colors.js';
|
||||
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
export enum PolicyUpdateChoice {
|
||||
ACCEPT = 'accept',
|
||||
@@ -34,6 +35,7 @@ export const PolicyUpdateDialog: React.FC<PolicyUpdateDialogProps> = ({
|
||||
request,
|
||||
onClose,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const isProcessing = useRef(false);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
|
||||
@@ -13,7 +13,8 @@ import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
|
||||
import type { FileChangeStats } from '../utils/rewindFileOps.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { formatTimeAgo } from '../utils/formatters.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
export enum RewindOutcome {
|
||||
RewindAndRevert = 'rewind_and_revert',
|
||||
@@ -58,6 +59,7 @@ export const RewindConfirmation: React.FC<RewindConfirmationProps> = ({
|
||||
terminalWidth,
|
||||
timestamp,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
||||
useKeypress(
|
||||
(key) => {
|
||||
|
||||
@@ -19,9 +19,10 @@ import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { useRewind } from '../hooks/useRewind.js';
|
||||
import { RewindConfirmation, RewindOutcome } from './RewindConfirmation.js';
|
||||
import { stripReferenceContent } from '../utils/formatters.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { CliSpinner } from './CliSpinner.js';
|
||||
import { ExpandableText } from './shared/ExpandableText.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
interface RewindViewerProps {
|
||||
conversation: ConversationRecord;
|
||||
@@ -48,6 +49,7 @@ export const RewindViewer: React.FC<RewindViewerProps> = ({
|
||||
onExit,
|
||||
onRewind,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const [isRewinding, setIsRewinding] = useState(false);
|
||||
const { terminalWidth, terminalHeight } = useUIState();
|
||||
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
||||
|
||||
@@ -346,94 +346,9 @@ export function SettingsDialog({
|
||||
[showRestartPrompt, onRestartRequest],
|
||||
);
|
||||
|
||||
// Calculate effective max items and scope visibility based on terminal height
|
||||
const { effectiveMaxItemsToShow, showScopeSelection, showSearch } =
|
||||
useMemo(() => {
|
||||
// Only show scope selector if we have a workspace
|
||||
const hasWorkspace = settings.workspace.path !== undefined;
|
||||
|
||||
// Search box is hidden when restart prompt is shown to save space and avoid key conflicts
|
||||
const shouldShowSearch = !showRestartPrompt;
|
||||
|
||||
if (!availableTerminalHeight) {
|
||||
return {
|
||||
effectiveMaxItemsToShow: Math.min(MAX_ITEMS_TO_SHOW, items.length),
|
||||
showScopeSelection: hasWorkspace,
|
||||
showSearch: shouldShowSearch,
|
||||
};
|
||||
}
|
||||
|
||||
// Layout constants based on BaseSettingsDialog structure:
|
||||
// 4 for border (2) and padding (2)
|
||||
const DIALOG_PADDING = 4;
|
||||
const SETTINGS_TITLE_HEIGHT = 1;
|
||||
// 3 for box + 1 for marginTop + 1 for spacing after
|
||||
const SEARCH_SECTION_HEIGHT = shouldShowSearch ? 5 : 0;
|
||||
const SCROLL_ARROWS_HEIGHT = 2;
|
||||
const ITEMS_SPACING_AFTER = 1;
|
||||
// 1 for Label + 3 for Scope items + 1 for spacing after
|
||||
const SCOPE_SECTION_HEIGHT = hasWorkspace ? 5 : 0;
|
||||
const HELP_TEXT_HEIGHT = 1;
|
||||
const RESTART_PROMPT_HEIGHT = showRestartPrompt ? 1 : 0;
|
||||
const ITEM_HEIGHT = 3; // Label + description + spacing
|
||||
|
||||
const currentAvailableHeight = availableTerminalHeight - DIALOG_PADDING;
|
||||
|
||||
const baseFixedHeight =
|
||||
SETTINGS_TITLE_HEIGHT +
|
||||
SEARCH_SECTION_HEIGHT +
|
||||
SCROLL_ARROWS_HEIGHT +
|
||||
ITEMS_SPACING_AFTER +
|
||||
HELP_TEXT_HEIGHT +
|
||||
RESTART_PROMPT_HEIGHT;
|
||||
|
||||
// Calculate max items with scope selector
|
||||
const heightWithScope = baseFixedHeight + SCOPE_SECTION_HEIGHT;
|
||||
const availableForItemsWithScope =
|
||||
currentAvailableHeight - heightWithScope;
|
||||
const maxItemsWithScope = Math.max(
|
||||
1,
|
||||
Math.floor(availableForItemsWithScope / ITEM_HEIGHT),
|
||||
);
|
||||
|
||||
// Calculate max items without scope selector
|
||||
const availableForItemsWithoutScope =
|
||||
currentAvailableHeight - baseFixedHeight;
|
||||
const maxItemsWithoutScope = Math.max(
|
||||
1,
|
||||
Math.floor(availableForItemsWithoutScope / ITEM_HEIGHT),
|
||||
);
|
||||
|
||||
// In small terminals, hide scope selector if it would allow more items to show
|
||||
let shouldShowScope = hasWorkspace;
|
||||
let maxItems = maxItemsWithScope;
|
||||
|
||||
if (hasWorkspace && availableTerminalHeight < 25) {
|
||||
// Hide scope selector if it gains us more than 1 extra item
|
||||
if (maxItemsWithoutScope > maxItemsWithScope + 1) {
|
||||
shouldShowScope = false;
|
||||
maxItems = maxItemsWithoutScope;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
effectiveMaxItemsToShow: Math.min(maxItems, items.length),
|
||||
showScopeSelection: shouldShowScope,
|
||||
showSearch: shouldShowSearch,
|
||||
};
|
||||
}, [
|
||||
availableTerminalHeight,
|
||||
items.length,
|
||||
settings.workspace.path,
|
||||
showRestartPrompt,
|
||||
]);
|
||||
|
||||
const footerContent = showRestartPrompt ? (
|
||||
<Text color={theme.status.warning}>
|
||||
Changes that require a restart have been modified. Press r to exit and
|
||||
apply changes now.
|
||||
</Text>
|
||||
) : null;
|
||||
// Decisions on what features to enable
|
||||
const hasWorkspace = settings.workspace.path !== undefined;
|
||||
const showSearch = !showRestartPrompt;
|
||||
|
||||
return (
|
||||
<BaseSettingsDialog
|
||||
@@ -442,17 +357,30 @@ export function SettingsDialog({
|
||||
searchEnabled={showSearch}
|
||||
searchBuffer={searchBuffer}
|
||||
items={items}
|
||||
showScopeSelector={showScopeSelection}
|
||||
showScopeSelector={hasWorkspace}
|
||||
selectedScope={selectedScope}
|
||||
onScopeChange={handleScopeChange}
|
||||
maxItemsToShow={effectiveMaxItemsToShow}
|
||||
maxItemsToShow={MAX_ITEMS_TO_SHOW}
|
||||
availableHeight={availableTerminalHeight}
|
||||
maxLabelWidth={maxLabelOrDescriptionWidth}
|
||||
onItemToggle={handleItemToggle}
|
||||
onEditCommit={handleEditCommit}
|
||||
onItemClear={handleItemClear}
|
||||
onClose={handleClose}
|
||||
onKeyPress={handleKeyPress}
|
||||
footerContent={footerContent}
|
||||
footer={
|
||||
showRestartPrompt
|
||||
? {
|
||||
content: (
|
||||
<Text color={theme.status.warning}>
|
||||
Changes that require a restart have been modified. Press r to
|
||||
exit and apply changes now.
|
||||
</Text>
|
||||
),
|
||||
height: 1,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { ShellExecutionService } from '@google/gemini-cli-core';
|
||||
import { keyToAnsi, type Key } from '../hooks/keyToAnsi.js';
|
||||
import { ACTIVE_SHELL_MAX_LINES } from '../constants.js';
|
||||
import { Command, keyMatchers } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
export interface ShellInputPromptProps {
|
||||
activeShellPtyId: number | null;
|
||||
@@ -23,6 +24,7 @@ export const ShellInputPrompt: React.FC<ShellInputPromptProps> = ({
|
||||
focus = true,
|
||||
scrollPageSize = ACTIVE_SHELL_MAX_LINES,
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const handleShellInputSubmit = useCallback(
|
||||
(input: string) => {
|
||||
if (activeShellPtyId) {
|
||||
@@ -73,7 +75,13 @@ export const ShellInputPrompt: React.FC<ShellInputPromptProps> = ({
|
||||
|
||||
return false;
|
||||
},
|
||||
[focus, handleShellInputSubmit, activeShellPtyId, scrollPageSize],
|
||||
[
|
||||
focus,
|
||||
handleShellInputSubmit,
|
||||
activeShellPtyId,
|
||||
scrollPageSize,
|
||||
keyMatchers,
|
||||
],
|
||||
);
|
||||
|
||||
useKeypress(handleInput, { isActive: focus });
|
||||
|
||||
@@ -127,4 +127,44 @@ describe('SuggestionsDisplay', () => {
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders command section separators for slash mode', async () => {
|
||||
const groupedSuggestions = [
|
||||
{
|
||||
label: 'list',
|
||||
value: 'list',
|
||||
description: 'Browse auto-saved chats',
|
||||
sectionTitle: 'auto',
|
||||
},
|
||||
{
|
||||
label: 'list',
|
||||
value: 'list',
|
||||
description: 'List checkpoints',
|
||||
sectionTitle: 'checkpoints',
|
||||
},
|
||||
{
|
||||
label: 'save',
|
||||
value: 'save',
|
||||
description: 'Save checkpoint',
|
||||
sectionTitle: 'checkpoints',
|
||||
},
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady } = render(
|
||||
<SuggestionsDisplay
|
||||
suggestions={groupedSuggestions}
|
||||
activeIndex={0}
|
||||
isLoading={false}
|
||||
width={100}
|
||||
scrollOffset={0}
|
||||
userInput="/resume"
|
||||
mode="slash"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
const frame = lastFrame();
|
||||
expect(frame).toContain('-- auto --');
|
||||
expect(frame).toContain('-- checkpoints --');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,9 +14,12 @@ import { sanitizeForDisplay } from '../utils/textUtils.js';
|
||||
export interface Suggestion {
|
||||
label: string;
|
||||
value: string;
|
||||
insertValue?: string;
|
||||
description?: string;
|
||||
matchedIndex?: number;
|
||||
commandKind?: CommandKind;
|
||||
sectionTitle?: string;
|
||||
submitValue?: string;
|
||||
}
|
||||
interface SuggestionsDisplayProps {
|
||||
suggestions: Suggestion[];
|
||||
@@ -86,6 +89,12 @@ export function SuggestionsDisplay({
|
||||
const isExpanded = originalIndex === expandedIndex;
|
||||
const textColor = isActive ? theme.ui.focus : theme.text.secondary;
|
||||
const isLong = suggestion.value.length >= MAX_WIDTH;
|
||||
const previousSectionTitle =
|
||||
suggestions[originalIndex - 1]?.sectionTitle;
|
||||
const shouldRenderSectionHeader =
|
||||
mode === 'slash' &&
|
||||
!!suggestion.sectionTitle &&
|
||||
suggestion.sectionTitle !== previousSectionTitle;
|
||||
const labelElement = (
|
||||
<ExpandableText
|
||||
label={suggestion.value}
|
||||
@@ -99,37 +108,48 @@ export function SuggestionsDisplay({
|
||||
return (
|
||||
<Box
|
||||
key={`${suggestion.value}-${originalIndex}`}
|
||||
flexDirection="row"
|
||||
backgroundColor={isActive ? theme.background.focus : undefined}
|
||||
flexDirection="column"
|
||||
>
|
||||
<Box
|
||||
{...(mode === 'slash'
|
||||
? { width: commandColumnWidth, flexShrink: 0 as const }
|
||||
: { flexShrink: 1 as const })}
|
||||
>
|
||||
<Box>
|
||||
{labelElement}
|
||||
{suggestion.commandKind &&
|
||||
COMMAND_KIND_SUFFIX[suggestion.commandKind] && (
|
||||
<Text color={textColor}>
|
||||
{COMMAND_KIND_SUFFIX[suggestion.commandKind]}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{shouldRenderSectionHeader && (
|
||||
<Text color={theme.text.secondary}>
|
||||
-- {suggestion.sectionTitle} --
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{suggestion.description && (
|
||||
<Box flexGrow={1} paddingLeft={3}>
|
||||
<Text color={textColor} wrap="truncate">
|
||||
{sanitizeForDisplay(suggestion.description, 100)}
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection="row"
|
||||
backgroundColor={isActive ? theme.background.focus : undefined}
|
||||
>
|
||||
<Box
|
||||
{...(mode === 'slash'
|
||||
? { width: commandColumnWidth, flexShrink: 0 as const }
|
||||
: { flexShrink: 1 as const })}
|
||||
>
|
||||
<Box>
|
||||
{labelElement}
|
||||
{suggestion.commandKind &&
|
||||
COMMAND_KIND_SUFFIX[suggestion.commandKind] && (
|
||||
<Text color={textColor}>
|
||||
{COMMAND_KIND_SUFFIX[suggestion.commandKind]}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{isActive && isLong && (
|
||||
<Box width={3} flexShrink={0}>
|
||||
<Text color={Colors.Gray}>{isExpanded ? ' ← ' : ' → '}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{suggestion.description && (
|
||||
<Box flexGrow={1} paddingLeft={3}>
|
||||
<Text color={textColor} wrap="truncate">
|
||||
{sanitizeForDisplay(suggestion.description, 100)}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isActive && isLong && (
|
||||
<Box width={3} flexShrink={0}>
|
||||
<Text color={Colors.Gray}>{isExpanded ? ' ← ' : ' → '}</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -51,6 +51,24 @@ describe('<UserIdentity />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render the user email on the very first frame (regression test)', () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
model: 'gemini-pro',
|
||||
} as unknown as ContentGeneratorConfig);
|
||||
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
|
||||
|
||||
const { lastFrameRaw, unmount } = renderWithProviders(
|
||||
<UserIdentity config={mockConfig} />,
|
||||
);
|
||||
|
||||
// Assert immediately on the first available frame before any async ticks happen
|
||||
const output = lastFrameRaw();
|
||||
expect(output).toContain('test@example.com');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render login message if email is missing', async () => {
|
||||
// Modify the mock for this specific test
|
||||
vi.mocked(UserAccountManager).mockImplementationOnce(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useMemo, useEffect, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import {
|
||||
@@ -20,13 +20,12 @@ interface UserIdentityProps {
|
||||
|
||||
export const UserIdentity: React.FC<UserIdentityProps> = ({ config }) => {
|
||||
const authType = config.getContentGeneratorConfig()?.authType;
|
||||
const [email, setEmail] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const email = useMemo(() => {
|
||||
if (authType) {
|
||||
const userAccountManager = new UserAccountManager();
|
||||
setEmail(userAccountManager.getCachedGoogleAccount() ?? undefined);
|
||||
return userAccountManager.getCachedGoogleAccount() ?? undefined;
|
||||
}
|
||||
return undefined;
|
||||
}, [authType]);
|
||||
|
||||
const tierName = useMemo(
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
type ValidationIntent,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { keyMatchers, Command } from '../keyMatchers.js';
|
||||
import { Command } from '../keyMatchers.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
|
||||
interface ValidationDialogProps {
|
||||
validationLink?: string;
|
||||
@@ -32,6 +33,7 @@ export function ValidationDialog({
|
||||
learnMoreUrl,
|
||||
onChoice,
|
||||
}: ValidationDialogProps): React.JSX.Element {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const [state, setState] = useState<DialogState>('choosing');
|
||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="224" viewBox="0 0 920 224">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="224" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="27" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="36" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="90" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="180" y="19" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.0.0</text>
|
||||
<text x="36" y="36" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="45" y="36" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="54" y="36" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="27" y="53" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▗</text>
|
||||
<text x="36" y="53" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▟</text>
|
||||
<text x="45" y="53" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="18" y="70" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="0" y="121" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="138" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="138" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="155" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="155" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="224" viewBox="0 0 920 224">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="224" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="27" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="36" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="81" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="171" y="19" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.0.0</text>
|
||||
<text x="36" y="36" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="45" y="36" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="54" y="36" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="36" y="53" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▗</text>
|
||||
<text x="45" y="53" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▟</text>
|
||||
<text x="54" y="53" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="18" y="70" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▗</text>
|
||||
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▟</text>
|
||||
<text x="36" y="70" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="0" y="121" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="138" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="138" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="155" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="155" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,31 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`AppHeader Icon Rendering > renders the default icon in standard terminals 1`] = `
|
||||
"
|
||||
▝▜▄ Gemini CLI v1.0.0
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▝▀
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results"
|
||||
`;
|
||||
|
||||
exports[`AppHeader Icon Rendering > renders the symmetric icon in Apple Terminal 1`] = `
|
||||
"
|
||||
▝▜▄ Gemini CLI v1.0.0
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▗▟▀
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results"
|
||||
`;
|
||||
@@ -3,9 +3,9 @@
|
||||
exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 1`] = `
|
||||
"Select your preferred language:
|
||||
|
||||
1. TypeScript
|
||||
2. JavaScript
|
||||
● 3. Enter a custom value
|
||||
1. TypeScript
|
||||
2. JavaScript
|
||||
● 3. Enter a custom value
|
||||
|
||||
Enter to submit · Esc to cancel
|
||||
"
|
||||
@@ -14,9 +14,9 @@ Enter to submit · Esc to cancel
|
||||
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = `
|
||||
"Select your preferred language:
|
||||
|
||||
1. TypeScript
|
||||
2. JavaScript
|
||||
● 3. Type another language...
|
||||
1. TypeScript
|
||||
2. JavaScript
|
||||
● 3. Type another language...
|
||||
|
||||
Enter to submit · Esc to cancel
|
||||
"
|
||||
@@ -26,10 +26,10 @@ exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scrol
|
||||
"Choose an option
|
||||
|
||||
▲
|
||||
● 1. Option 1
|
||||
Description 1
|
||||
2. Option 2
|
||||
Description 2
|
||||
● 1. Option 1
|
||||
Description 1
|
||||
2. Option 2
|
||||
Description 2
|
||||
▼
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
@@ -39,37 +39,37 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
|
||||
"Choose an option
|
||||
|
||||
● 1. Option 1
|
||||
Description 1
|
||||
2. Option 2
|
||||
Description 2
|
||||
3. Option 3
|
||||
Description 3
|
||||
4. Option 4
|
||||
Description 4
|
||||
5. Option 5
|
||||
Description 5
|
||||
6. Option 6
|
||||
Description 6
|
||||
7. Option 7
|
||||
Description 7
|
||||
8. Option 8
|
||||
Description 8
|
||||
9. Option 9
|
||||
Description 9
|
||||
10. Option 10
|
||||
Description 10
|
||||
11. Option 11
|
||||
Description 11
|
||||
12. Option 12
|
||||
Description 12
|
||||
13. Option 13
|
||||
Description 13
|
||||
14. Option 14
|
||||
Description 14
|
||||
15. Option 15
|
||||
Description 15
|
||||
16. Enter a custom value
|
||||
● 1. Option 1
|
||||
Description 1
|
||||
2. Option 2
|
||||
Description 2
|
||||
3. Option 3
|
||||
Description 3
|
||||
4. Option 4
|
||||
Description 4
|
||||
5. Option 5
|
||||
Description 5
|
||||
6. Option 6
|
||||
Description 6
|
||||
7. Option 7
|
||||
Description 7
|
||||
8. Option 8
|
||||
Description 8
|
||||
9. Option 9
|
||||
Description 9
|
||||
10. Option 10
|
||||
Description 10
|
||||
11. Option 11
|
||||
Description 11
|
||||
12. Option 12
|
||||
Description 12
|
||||
13. Option 13
|
||||
Description 13
|
||||
14. Option 14
|
||||
Description 14
|
||||
15. Option 15
|
||||
Description 15
|
||||
16. Enter a custom value
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
@@ -122,11 +122,11 @@ Enter to submit · Tab/Shift+Tab to edit answers · Esc to cancel
|
||||
exports[`AskUserDialog > hides progress header for single question 1`] = `
|
||||
"Which authentication method should we use?
|
||||
|
||||
● 1. OAuth 2.0
|
||||
Industry standard, supports SSO
|
||||
2. JWT tokens
|
||||
Stateless, good for APIs
|
||||
3. Enter a custom value
|
||||
● 1. OAuth 2.0
|
||||
Industry standard, supports SSO
|
||||
2. JWT tokens
|
||||
Stateless, good for APIs
|
||||
3. Enter a custom value
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
@@ -135,11 +135,11 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
exports[`AskUserDialog > renders question and options 1`] = `
|
||||
"Which authentication method should we use?
|
||||
|
||||
● 1. OAuth 2.0
|
||||
Industry standard, supports SSO
|
||||
2. JWT tokens
|
||||
Stateless, good for APIs
|
||||
3. Enter a custom value
|
||||
● 1. OAuth 2.0
|
||||
Industry standard, supports SSO
|
||||
2. JWT tokens
|
||||
Stateless, good for APIs
|
||||
3. Enter a custom value
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
@@ -150,11 +150,11 @@ exports[`AskUserDialog > shows Review tab in progress header for multiple questi
|
||||
|
||||
Which framework?
|
||||
|
||||
● 1. React
|
||||
Component library
|
||||
2. Vue
|
||||
Progressive framework
|
||||
3. Enter a custom value
|
||||
● 1. React
|
||||
Component library
|
||||
2. Vue
|
||||
Progressive framework
|
||||
3. Enter a custom value
|
||||
|
||||
Enter to select · ←/→ to switch questions · Esc to cancel
|
||||
"
|
||||
@@ -163,11 +163,11 @@ Enter to select · ←/→ to switch questions · Esc to cancel
|
||||
exports[`AskUserDialog > shows keyboard hints 1`] = `
|
||||
"Which authentication method should we use?
|
||||
|
||||
● 1. OAuth 2.0
|
||||
Industry standard, supports SSO
|
||||
2. JWT tokens
|
||||
Stateless, good for APIs
|
||||
3. Enter a custom value
|
||||
● 1. OAuth 2.0
|
||||
Industry standard, supports SSO
|
||||
2. JWT tokens
|
||||
Stateless, good for APIs
|
||||
3. Enter a custom value
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Esc to cancel
|
||||
"
|
||||
@@ -178,11 +178,11 @@ exports[`AskUserDialog > shows progress header for multiple questions 1`] = `
|
||||
|
||||
Which database should we use?
|
||||
|
||||
● 1. PostgreSQL
|
||||
Relational database
|
||||
2. MongoDB
|
||||
Document database
|
||||
3. Enter a custom value
|
||||
● 1. PostgreSQL
|
||||
Relational database
|
||||
2. MongoDB
|
||||
Document database
|
||||
3. Enter a custom value
|
||||
|
||||
Enter to select · ←/→ to switch questions · Esc to cancel
|
||||
"
|
||||
|
||||
@@ -16,9 +16,9 @@ exports[`<BackgroundShellDisplay /> > keeps exit code status color even when sel
|
||||
│ │
|
||||
│ Select Process (Enter to select, Ctrl+K to kill, Esc to cancel): │
|
||||
│ │
|
||||
│ 1. npm start (PID: 1001) │
|
||||
│ 2. tail -f log.txt (PID: 1002) │
|
||||
│ ● 3. exit 0 (PID: 1003) (Exit Code: 0) │
|
||||
│ 1. npm start (PID: 1001) │
|
||||
│ 2. tail -f log.txt (PID: 1002) │
|
||||
│ ● 3. exit 0 (PID: 1003) (Exit Code: 0) │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
"
|
||||
`;
|
||||
@@ -46,8 +46,8 @@ exports[`<BackgroundShellDisplay /> > renders the process list when isListOpenPr
|
||||
│ │
|
||||
│ Select Process (Enter to select, Ctrl+K to kill, Esc to cancel): │
|
||||
│ │
|
||||
│ ● 1. npm start (PID: 1001) │
|
||||
│ 2. tail -f log.txt (PID: 1002) │
|
||||
│ ● 1. npm start (PID: 1001) │
|
||||
│ 2. tail -f log.txt (PID: 1002) │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
"
|
||||
`;
|
||||
@@ -59,8 +59,8 @@ exports[`<BackgroundShellDisplay /> > scrolls to active shell when list opens 1`
|
||||
│ │
|
||||
│ Select Process (Enter to select, Ctrl+K to kill, Esc to cancel): │
|
||||
│ │
|
||||
│ 1. npm start (PID: 1001) │
|
||||
│ ● 2. tail -f log.txt (PID: 1002) │
|
||||
│ 1. npm start (PID: 1001) │
|
||||
│ ● 2. tail -f log.txt (PID: 1002) │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -4,12 +4,12 @@ exports[`EditorSettingsDialog > renders correctly 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ > Select Editor Editor Preference │
|
||||
│ ● 1. VS Code │
|
||||
│ 2. Vim These editors are currently supported. Please note │
|
||||
│ ● 1. VS Code │
|
||||
│ 2. Vim These editors are currently supported. Please note │
|
||||
│ that some editors cannot be used in sandbox mode. │
|
||||
│ Apply To │
|
||||
│ ● 1. User Settings Your preferred editor is: VS Code. │
|
||||
│ 2. Workspace Settings │
|
||||
│ ● 1. User Settings Your preferred editor is: VS Code. │
|
||||
│ 2. Workspace Settings │
|
||||
│ │
|
||||
│ (Use Enter to select, Tab to change │
|
||||
│ focus, Esc to close) │
|
||||
|
||||
@@ -16,9 +16,9 @@ exports[`EmptyWalletDialog > rendering > should match snapshot with fallback ava
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Get AI Credits - Open browser to purchase credits │
|
||||
│ 2. Switch to gemini-3-flash-preview │
|
||||
│ 3. Stop - Abort request │
|
||||
│ ● 1. Get AI Credits - Open browser to purchase credits │
|
||||
│ 2. Switch to gemini-3-flash-preview │
|
||||
│ 3. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
@@ -40,8 +40,8 @@ exports[`EmptyWalletDialog > rendering > should match snapshot without fallback
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Get AI Credits - Open browser to purchase credits │
|
||||
│ 2. Stop - Abort request │
|
||||
│ ● 1. Get AI Credits - Open browser to purchase credits │
|
||||
│ 2. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
@@ -17,11 +17,11 @@ Files to Modify
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
● 2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
● 2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -44,11 +44,11 @@ Files to Modify
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -76,11 +76,11 @@ Implementation Steps
|
||||
8. Add multi-factor authentication in src/auth/MFAService.ts
|
||||
... last 22 lines hidden (Ctrl+O to show) ...
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -103,11 +103,11 @@ Files to Modify
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -130,11 +130,11 @@ Files to Modify
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
● 2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
● 2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -157,11 +157,11 @@ Files to Modify
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -210,11 +210,11 @@ Testing Strategy
|
||||
- Security penetration testing
|
||||
- Load testing for session management
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
@@ -237,11 +237,11 @@ Files to Modify
|
||||
- src/index.ts - Add auth middleware
|
||||
- src/config.ts - Add auth configuration options
|
||||
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
● 1. Yes, automatically accept edits
|
||||
Approves plan and allows tools to run automatically
|
||||
2. Yes, manually accept edits
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
|
||||
@@ -7,8 +7,8 @@ exports[`LoopDetectionConfirmation > renders correctly 1`] = `
|
||||
│ This can happen due to repetitive tool calls or other model behavior. Do you want to keep loop │
|
||||
│ detection enabled or disable it for this session? │
|
||||
│ │
|
||||
│ ● 1. Keep loop detection enabled (esc) │
|
||||
│ 2. Disable loop detection for this session │
|
||||
│ ● 1. Keep loop detection enabled (esc) │
|
||||
│ 2. Disable loop detection for this session │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -13,8 +13,8 @@ exports[`NewAgentsNotification > renders agent list 1`] = `
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ● 1. Acknowledge and Enable │
|
||||
│ 2. Do not enable (Ask again next time) │
|
||||
│ ● 1. Acknowledge and Enable │
|
||||
│ 2. Do not enable (Ask again next time) │
|
||||
│ │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
@@ -37,8 +37,8 @@ exports[`NewAgentsNotification > truncates list if more than 5 agents 1`] = `
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ● 1. Acknowledge and Enable │
|
||||
│ 2. Do not enable (Ask again next time) │
|
||||
│ ● 1. Acknowledge and Enable │
|
||||
│ 2. Do not enable (Ask again next time) │
|
||||
│ │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
|
||||
@@ -14,10 +14,10 @@ exports[`OverageMenuDialog > rendering > should match snapshot with fallback ava
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Use AI Credits - Continue this request (Overage) │
|
||||
│ 2. Manage - View balance and purchase more credits │
|
||||
│ 3. Switch to gemini-3-flash-preview │
|
||||
│ 4. Stop - Abort request │
|
||||
│ ● 1. Use AI Credits - Continue this request (Overage) │
|
||||
│ 2. Manage - View balance and purchase more credits │
|
||||
│ 3. Switch to gemini-3-flash-preview │
|
||||
│ 4. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
@@ -37,9 +37,9 @@ exports[`OverageMenuDialog > rendering > should match snapshot without fallback
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Use AI Credits - Continue this request (Overage) │
|
||||
│ 2. Manage - View balance and purchase more credits │
|
||||
│ 3. Stop - Abort request │
|
||||
│ ● 1. Use AI Credits - Continue this request (Overage) │
|
||||
│ 2. Manage - View balance and purchase more credits │
|
||||
│ 3. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
@@ -7,8 +7,8 @@ exports[`PolicyUpdateDialog > renders correctly and matches snapshot 1`] = `
|
||||
│ Location: /test/workspace/.gemini/policies │
|
||||
│ Do you want to accept and load these policies? │
|
||||
│ │
|
||||
│ ● 1. Accept and Load │
|
||||
│ 2. Ignore (Use Default Policies) │
|
||||
│ ● 1. Accept and Load │
|
||||
│ 2. Ignore (Use Default Policies) │
|
||||
│ │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
|
||||
@@ -14,10 +14,10 @@ exports[`RewindConfirmation > renders correctly with stats 1`] = `
|
||||
│ │
|
||||
│ Select an action: │
|
||||
│ │
|
||||
│ ● 1. Rewind conversation and revert code changes │
|
||||
│ 2. Rewind conversation │
|
||||
│ 3. Revert code changes │
|
||||
│ 4. Do nothing (esc) │
|
||||
│ ● 1. Rewind conversation and revert code changes │
|
||||
│ 2. Rewind conversation │
|
||||
│ 3. Revert code changes │
|
||||
│ 4. Do nothing (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
@@ -32,8 +32,8 @@ exports[`RewindConfirmation > renders correctly without stats 1`] = `
|
||||
│ │
|
||||
│ Select an action: │
|
||||
│ │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
@@ -48,8 +48,8 @@ exports[`RewindConfirmation > renders timestamp when provided 1`] = `
|
||||
│ │
|
||||
│ Select an action: │
|
||||
│ │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
|
||||
@@ -5,11 +5,11 @@ exports[`RewindViewer > Content Filtering > 'removes reference markers' 1`] = `
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ some command @file │
|
||||
│ No files have been changed │
|
||||
│ some command @file │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -23,11 +23,11 @@ exports[`RewindViewer > Content Filtering > 'strips expanded MCP resource conten
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ read @server3:mcp://demo-resource hello │
|
||||
│ No files have been changed │
|
||||
│ read @server3:mcp://demo-resource hello │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -41,11 +41,11 @@ exports[`RewindViewer > Content Filtering > 'uses displayContent if present and
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ clean display content │
|
||||
│ No files have been changed │
|
||||
│ clean display content │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -63,8 +63,8 @@ exports[`RewindViewer > Interaction Selection > 'cancels on Escape' > confirmati
|
||||
│ │
|
||||
│ Select an action: │
|
||||
│ │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
@@ -79,8 +79,8 @@ exports[`RewindViewer > Interaction Selection > 'confirms on Enter' > confirmati
|
||||
│ │
|
||||
│ Select an action: │
|
||||
│ │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ ● 1. Rewind conversation │
|
||||
│ 2. Do nothing (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
@@ -91,17 +91,17 @@ exports[`RewindViewer > Navigation > handles 'down' navigation > after-down 1`]
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Q3 │
|
||||
│ No files have been changed │
|
||||
│ Q3 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -115,17 +115,17 @@ exports[`RewindViewer > Navigation > handles 'up' navigation > after-up 1`] = `
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Q3 │
|
||||
│ No files have been changed │
|
||||
│ ● Q3 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -139,17 +139,17 @@ exports[`RewindViewer > Navigation > handles cyclic navigation > cyclic-down 1`]
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Q3 │
|
||||
│ No files have been changed │
|
||||
│ Q3 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -163,17 +163,17 @@ exports[`RewindViewer > Navigation > handles cyclic navigation > cyclic-up 1`] =
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ Q1 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ Q2 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Q3 │
|
||||
│ No files have been changed │
|
||||
│ ● Q3 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -187,11 +187,11 @@ exports[`RewindViewer > Rendering > renders 'a single interaction' 1`] = `
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Hello │
|
||||
│ No files have been changed │
|
||||
│ Hello │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -205,12 +205,12 @@ exports[`RewindViewer > Rendering > renders 'full text for selected item' 1`] =
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ 1 │
|
||||
│ 2... │
|
||||
│ No files have been changed │
|
||||
│ 1 │
|
||||
│ 2... │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -224,8 +224,8 @@ exports[`RewindViewer > Rendering > renders 'nothing interesting for empty conve
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -239,14 +239,14 @@ exports[`RewindViewer > updates content when conversation changes (background up
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Message 1 │
|
||||
│ No files have been changed │
|
||||
│ Message 1 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Message 2 │
|
||||
│ No files have been changed │
|
||||
│ Message 2 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -260,11 +260,11 @@ exports[`RewindViewer > updates content when conversation changes (background up
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Message 1 │
|
||||
│ No files have been changed │
|
||||
│ Message 1 │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -278,16 +278,16 @@ exports[`RewindViewer > updates selection and expansion on navigation > after-do
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Line A │
|
||||
│ Line B... │
|
||||
│ No files have been changed │
|
||||
│ Line A │
|
||||
│ Line B... │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Line 1 │
|
||||
│ Line 2... │
|
||||
│ No files have been changed │
|
||||
│ Line 1 │
|
||||
│ Line 2... │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
@@ -301,16 +301,16 @@ exports[`RewindViewer > updates selection and expansion on navigation > initial-
|
||||
│ │
|
||||
│ > Rewind │
|
||||
│ │
|
||||
│ Line A │
|
||||
│ Line B... │
|
||||
│ No files have been changed │
|
||||
│ Line A │
|
||||
│ Line B... │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ Line 1 │
|
||||
│ Line 2... │
|
||||
│ No files have been changed │
|
||||
│ Line 1 │
|
||||
│ Line 2... │
|
||||
│ No files have been changed │
|
||||
│ │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ ● Stay at current position │
|
||||
│ Cancel rewind and stay here │
|
||||
│ │
|
||||
│ │
|
||||
│ (Use Enter to select a message, Esc to close, Right/Left to expand/collapse) │
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="711" height="17" fill="#005f00" />
|
||||
<rect x="828" y="153" width="45" height="17" fill="#005f00" />
|
||||
<text x="828" y="155" fill="#d7ffd7" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="711" height="17" fill="#005f00" />
|
||||
<rect x="828" y="153" width="45" height="17" fill="#005f00" />
|
||||
<text x="828" y="155" fill="#d7ffd7" textLength="45" lengthAdjust="spacingAndGlyphs">true*</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="702" height="17" fill="#005f00" />
|
||||
<rect x="819" y="153" width="54" height="17" fill="#005f00" />
|
||||
<text x="819" y="155" fill="#d7ffd7" textLength="54" lengthAdjust="spacingAndGlyphs">false*</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="711" height="17" fill="#005f00" />
|
||||
<rect x="828" y="153" width="45" height="17" fill="#005f00" />
|
||||
<text x="828" y="155" fill="#d7ffd7" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="711" height="17" fill="#005f00" />
|
||||
<rect x="828" y="153" width="45" height="17" fill="#005f00" />
|
||||
<text x="828" y="155" fill="#d7ffd7" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -110,22 +110,23 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="18" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="63" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="72" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="72" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="189" y="612" width="684" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="18" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="72" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="81" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="198" y="612" width="675" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="72" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="81" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="72" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="81" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="702" height="17" fill="#005f00" />
|
||||
<rect x="819" y="153" width="54" height="17" fill="#005f00" />
|
||||
<text x="819" y="155" fill="#d7ffd7" textLength="54" lengthAdjust="spacingAndGlyphs">false*</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="711" height="17" fill="#005f00" />
|
||||
<rect x="828" y="153" width="45" height="17" fill="#005f00" />
|
||||
<text x="828" y="155" fill="#d7ffd7" textLength="45" lengthAdjust="spacingAndGlyphs">false</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -31,6 +31,7 @@
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">▲</text>
|
||||
<text x="891" y="138" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="155" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#005f00" />
|
||||
@@ -39,12 +40,13 @@
|
||||
<rect x="117" y="153" width="711" height="17" fill="#005f00" />
|
||||
<rect x="828" y="153" width="45" height="17" fill="#005f00" />
|
||||
<text x="828" y="155" fill="#d7ffd7" textLength="45" lengthAdjust="spacingAndGlyphs">true*</text>
|
||||
<rect x="873" y="153" width="9" height="17" fill="#005f00" />
|
||||
<text x="891" y="155" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="170" width="18" height="17" fill="#005f00" />
|
||||
<rect x="18" y="170" width="27" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="198" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#afafaf" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="630" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Enable Vim keybindings</text>
|
||||
<rect x="243" y="170" width="639" height="17" fill="#005f00" />
|
||||
<text x="891" y="172" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
@@ -121,17 +123,18 @@
|
||||
<text x="891" y="597" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="27" y="612" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="36" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="45" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="162" y="612" width="711" height="17" fill="#005f00" />
|
||||
<text x="36" y="614" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="45" y="612" width="9" height="17" fill="#005f00" />
|
||||
<rect x="54" y="612" width="117" height="17" fill="#005f00" />
|
||||
<text x="54" y="614" fill="#d7ffd7" textLength="117" lengthAdjust="spacingAndGlyphs">User Settings</text>
|
||||
<rect x="171" y="612" width="702" height="17" fill="#005f00" />
|
||||
<text x="891" y="614" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="54" y="631" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Workspace Settings</text>
|
||||
<text x="891" y="631" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="45" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="54" y="648" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">System Settings</text>
|
||||
<text x="891" y="648" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="665" fill="#878787" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |