feat: add /commands reload to refresh custom TOML commands (#19078)

This commit is contained in:
Krushna Korade
2026-02-15 01:55:30 +05:30
committed by GitHub
parent 5559d40f31
commit bcd547baf6
5 changed files with 152 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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';
@@ -89,6 +90,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
: chatCommand.subCommands,
},
clearCommand,
commandsCommand,
compressCommand,
copyCommand,
corgiCommand,

View File

@@ -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),
);
});
});
});

View File

@@ -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();
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(),
);
}
}
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,
};