Skip to content

Commit f82b0ac

Browse files
authored
Merge branch 'main' into main
2 parents 25f9a29 + 138ff73 commit f82b0ac

20 files changed

+1250
-367
lines changed

.github/workflows/e2e.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ on:
88
merge_group:
99

1010
jobs:
11-
e2e-test:
12-
name: E2E Test - ${{ matrix.sandbox }}
11+
e2e-test-linux:
12+
name: E2E Test (Linux) - ${{ matrix.sandbox }}
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
@@ -47,3 +47,27 @@ jobs:
4747
env:
4848
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
4949
run: npm run test:integration:${{ matrix.sandbox }} -- --verbose --keep-output
50+
51+
e2e-test-macos:
52+
name: E2E Test - macOS
53+
runs-on: macos-latest
54+
steps:
55+
- name: Checkout repository
56+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
57+
58+
- name: Set up Node.js
59+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
60+
with:
61+
node-version: 20.x
62+
cache: 'npm'
63+
64+
- name: Install dependencies
65+
run: npm ci
66+
67+
- name: Build project
68+
run: npm run build
69+
70+
- name: Run E2E tests
71+
env:
72+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
73+
run: npm run test:e2e

docs/cli/commands.md

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Gemini CLI supports several built-in commands to help you manage your session, c
66

77
Slash commands provide meta-level control over the CLI itself.
88

9+
### Built-in Commands
10+
911
- **`/bug`**
1012
- **Description:** File an issue about Gemini CLI. By default, the issue is filed within the GitHub repository for Gemini CLI. The string you enter after `/bug` will become the headline for the bug being filed. The default `/bug` behavior can be modified using the `bugCommand` setting in your `.gemini/settings.json` files.
1113

@@ -38,7 +40,7 @@ Slash commands provide meta-level control over the CLI itself.
3840
- **Description:** Lists all active extensions in the current Gemini CLI session. See [Gemini CLI Extensions](../extension.md).
3941

4042
- **`/help`** (or **`/?`**)
41-
- **Description:** Display help information about the Gemini CLI, including available commands and their usage.
43+
- **Description:** Display help information about Gemini CLI, including available commands and their usage.
4244

4345
- **`/mcp`**
4446
- **Description:** List configured Model Context Protocol (MCP) servers, their connection status, server details, and available tools.
@@ -93,6 +95,91 @@ Slash commands provide meta-level control over the CLI itself.
9395
- **`/quit`** (or **`/exit`**)
9496
- **Description:** Exit Gemini CLI.
9597

98+
### Custom Commands
99+
100+
For a quick start, see the [example](#example-a-pure-function-refactoring-command) below.
101+
102+
Custom commands allow you to save and reuse your favorite or most frequently used prompts as personal shortcuts within Gemini CLI. You can create commands that are specific to a single project or commands that are available globally across all your projects, streamlining your workflow and ensuring consistency.
103+
104+
#### File Locations & Precedence
105+
106+
Gemini CLI discovers commands from two locations, loaded in a specific order:
107+
108+
1. **User Commands (Global):** Located in `~/.gemini/commands/`. These commands are available in any project you are working on.
109+
2. **Project Commands (Local):** Located in `<your-project-root>/.gemini/commands/`. These commands are specific to the current project and can be checked into version control to be shared with your team.
110+
111+
If a command in the project directory has the same name as a command in the user directory, the **project command will always be used.** This allows projects to override global commands with project-specific versions.
112+
113+
#### Naming and Namespacing
114+
115+
The name of a command is determined by its file path relative to its `commands` directory. Subdirectories are used to create namespaced commands, with the path separator (`/` or `\`) being converted to a colon (`:`).
116+
117+
- A file at `~/.gemini/commands/test.toml` becomes the command `/test`.
118+
- A file at `<project>/.gemini/commands/git/commit.toml` becomes the namespaced command `/git:commit`.
119+
120+
#### TOML File Format (v1)
121+
122+
Your command definition files must be written in the TOML format and use the `.toml` file extension.
123+
124+
##### Required Fields
125+
126+
- `prompt` (String): The prompt that will be sent to the Gemini model when the command is executed. This can be a single-line or multi-line string.
127+
128+
##### Optional Fields
129+
130+
- `description` (String): A brief, one-line description of what the command does. This text will be displayed next to your command in the `/help` menu. **If you omit this field, a generic description will be generated from the filename.**
131+
132+
---
133+
134+
#### Example: A "Pure Function" Refactoring Command
135+
136+
Let's create a global command that asks the model to refactor a piece of code.
137+
138+
**1. Create the file and directories:**
139+
140+
First, ensure the user commands directory exists, then create a `refactor` subdirectory for organization and the final TOML file.
141+
142+
```bash
143+
mkdir -p ~/.gemini/commands/refactor
144+
touch ~/.gemini/commands/refactor/pure.toml
145+
```
146+
147+
**2. Add the content to the file:**
148+
149+
Open `~/.gemini/commands/refactor/pure.toml` in your editor and add the following content. We are including the optional `description` for best practice.
150+
151+
```toml
152+
# In: ~/.gemini/commands/refactor/pure.toml
153+
# This command will be invoked via: /refactor:pure
154+
155+
description = "Asks the model to refactor the current context into a pure function."
156+
157+
prompt = """
158+
Please analyze the code I've provided in the current context.
159+
Refactor it into a pure function.
160+
161+
Your response should include:
162+
1. The refactored, pure function code block.
163+
2. A brief explanation of the key changes you made and why they contribute to purity.
164+
"""
165+
```
166+
167+
**3. Run the Command:**
168+
169+
That's it! You can now run your command in the CLI. First, you might add a file to the context, and then invoke your command:
170+
171+
```
172+
> @my-messy-function.js
173+
> /refactor:pure
174+
```
175+
176+
Gemini CLI will then execute the multi-line prompt defined in your TOML file.
177+
178+
This initial version of custom commands is focused on static prompts. Future updates are planned to introduce more dynamic capabilities, including:
179+
180+
- **Argument Support:** Passing arguments from the command line directly into your `prompt` template.
181+
- **Shell Execution:** Creating commands that can run local shell scripts to gather context before running the prompt.
182+
96183
## At commands (`@`)
97184

98185
At commands are used to include the content of files or directories as part of your prompt to Gemini. These commands include git-aware filtering.

package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@types/micromatch": "^4.0.9",
6161
"@types/mime-types": "^3.0.1",
6262
"@types/minimatch": "^5.1.2",
63+
"@types/mock-fs": "^4.13.4",
6364
"@types/shell-quote": "^1.7.5",
6465
"@vitest/coverage-v8": "^3.1.1",
6566
"concurrently": "^9.2.0",
@@ -76,6 +77,7 @@
7677
"json": "^11.0.0",
7778
"lodash": "^4.17.21",
7879
"memfs": "^4.17.2",
80+
"mock-fs": "^5.5.0",
7981
"prettier": "^3.5.3",
8082
"react-devtools-core": "^4.28.5",
8183
"typescript-eslint": "^8.30.1",

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@google/gemini-cli-core": "file:../core",
32+
"@iarna/toml": "^2.2.5",
3233
"@types/update-notifier": "^6.0.8",
3334
"command-exists": "^1.2.9",
3435
"diff": "^7.0.0",

packages/cli/src/services/BuiltinCommandLoader.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('BuiltinCommandLoader', () => {
7272

7373
it('should correctly pass the config object to command factory functions', async () => {
7474
const loader = new BuiltinCommandLoader(mockConfig);
75-
await loader.loadCommands();
75+
await loader.loadCommands(new AbortController().signal);
7676

7777
expect(ideCommandMock).toHaveBeenCalledTimes(1);
7878
expect(ideCommandMock).toHaveBeenCalledWith(mockConfig);
@@ -84,7 +84,7 @@ describe('BuiltinCommandLoader', () => {
8484
// Override the mock's behavior for this specific test.
8585
ideCommandMock.mockReturnValue(null);
8686
const loader = new BuiltinCommandLoader(mockConfig);
87-
const commands = await loader.loadCommands();
87+
const commands = await loader.loadCommands(new AbortController().signal);
8888

8989
// The 'ide' command should be filtered out.
9090
const ideCmd = commands.find((c) => c.name === 'ide');
@@ -97,7 +97,7 @@ describe('BuiltinCommandLoader', () => {
9797

9898
it('should handle a null config gracefully when calling factories', async () => {
9999
const loader = new BuiltinCommandLoader(null);
100-
await loader.loadCommands();
100+
await loader.loadCommands(new AbortController().signal);
101101
expect(ideCommandMock).toHaveBeenCalledTimes(1);
102102
expect(ideCommandMock).toHaveBeenCalledWith(null);
103103
expect(restoreCommandMock).toHaveBeenCalledTimes(1);
@@ -106,7 +106,7 @@ describe('BuiltinCommandLoader', () => {
106106

107107
it('should return a list of all loaded commands', async () => {
108108
const loader = new BuiltinCommandLoader(mockConfig);
109-
const commands = await loader.loadCommands();
109+
const commands = await loader.loadCommands(new AbortController().signal);
110110

111111
const aboutCmd = commands.find((c) => c.name === 'about');
112112
expect(aboutCmd).toBeDefined();

0 commit comments

Comments
 (0)