Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ Slash commands provide meta-level control over the CLI itself.
the visual display is cleared.
- **Keyboard shortcut:** Press **Ctrl+L** at any time to perform a clear action.

### `/commands`

- **Description:** Manage custom slash commands loaded from `.toml` files.
- **Sub-commands:**
- **`reload`**:
- **Description:** Reload custom command definitions from all sources
(user-level `~/.gemini/commands/`, project-level
`<project>/.gemini/commands/`, MCP prompts, and extensions). Use this to
pick up new or modified `.toml` files without restarting the CLI.
- **Usage:** `/commands reload`

### `/compress`

- **Description:** Replace the entire chat context with a summary. This saves on
Expand Down
3 changes: 3 additions & 0 deletions docs/cli/custom-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ separator (`/` or `\`) being converted to a colon (`:`).
- A file at `<project>/.gemini/commands/git/commit.toml` becomes the namespaced
command `/git:commit`.

> [!TIP] After creating or modifying `.toml` command files, run
> `/commands reload` to pick up your changes without restarting the CLI.

## TOML file format (v1)

Your command definition files must be written in the TOML format and use the
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/services/BuiltinCommandLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { authCommand } from '../ui/commands/authCommand.js';
import { bugCommand } from '../ui/commands/bugCommand.js';
import { chatCommand, debugCommand } from '../ui/commands/chatCommand.js';
import { clearCommand } from '../ui/commands/clearCommand.js';
import { commandsCommand } from '../ui/commands/commandsCommand.js';
import { compressCommand } from '../ui/commands/compressCommand.js';
import { copyCommand } from '../ui/commands/copyCommand.js';
import { corgiCommand } from '../ui/commands/corgiCommand.js';
Expand Down Expand Up @@ -89,6 +90,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
: chatCommand.subCommands,
},
clearCommand,
commandsCommand,
compressCommand,
copyCommand,
corgiCommand,
Expand Down
56 changes: 56 additions & 0 deletions packages/cli/src/ui/commands/commandsCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { commandsCommand } from './commandsCommand.js';
import { MessageType } from '../types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import type { CommandContext } from './types.js';

describe('commandsCommand', () => {
let context: CommandContext;

beforeEach(() => {
vi.clearAllMocks();
context = createMockCommandContext({
ui: {
reloadCommands: vi.fn(),
},
});
});

describe('default action', () => {
it('should return an info message prompting subcommand usage', async () => {
const result = await commandsCommand.action!(context, '');

expect(result).toEqual({
type: 'message',
messageType: 'info',
content:
'Use "/commands reload" to reload custom command definitions from .toml files.',
});
});
});

describe('reload', () => {
it('should call reloadCommands and show a success message', async () => {
const reloadCmd = commandsCommand.subCommands!.find(
(s) => s.name === 'reload',
)!;

await reloadCmd.action!(context, '');

expect(context.ui.reloadCommands).toHaveBeenCalledTimes(1);
expect(context.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: 'Custom commands reloaded successfully.',
}),
expect.any(Number),
);
});
});
});
80 changes: 80 additions & 0 deletions packages/cli/src/ui/commands/commandsCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {
type CommandContext,
type SlashCommand,
type SlashCommandActionReturn,
CommandKind,
} from './types.js';
import {
MessageType,
type HistoryItemError,
type HistoryItemInfo,
} from '../types.js';

/**
* Action for the default `/commands` invocation.
* Displays a message prompting the user to use a subcommand.
*/
async function listAction(
_context: CommandContext,
_args: string,
): Promise<void | SlashCommandActionReturn> {
return {
type: 'message',
messageType: 'info',
content:
'Use "/commands reload" to reload custom command definitions from .toml files.',
};
}

/**
* Action for `/commands reload`.
* Triggers a full re-discovery and reload of all slash commands, including
* user/project-level .toml files, MCP prompts, and extension commands.
*/
async function reloadAction(
context: CommandContext,
): Promise<void | SlashCommandActionReturn> {
try {
context.ui.reloadCommands();
Comment thread
korade-krushna marked this conversation as resolved.

context.ui.addItem(
{
type: MessageType.INFO,
text: 'Custom commands reloaded successfully.',
} as HistoryItemInfo,
Date.now(),
);
} catch (error) {
context.ui.addItem(
{
type: MessageType.ERROR,
text: `Failed to reload commands: ${error instanceof Error ? error.message : String(error)}`,
} as HistoryItemError,
Date.now(),
);
}
}
Comment thread
korade-krushna marked this conversation as resolved.

export const commandsCommand: SlashCommand = {
name: 'commands',
description: 'Manage custom slash commands. Usage: /commands [reload]',
kind: CommandKind.BUILT_IN,
autoExecute: false,
subCommands: [
{
name: 'reload',
description:
'Reload custom command definitions from .toml files. Usage: /commands reload',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: reloadAction,
},
],
action: listAction,
};
Loading