mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
feat: add /commands reload to refresh custom TOML commands (#19078)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
56
packages/cli/src/ui/commands/commandsCommand.test.ts
Normal file
56
packages/cli/src/ui/commands/commandsCommand.test.ts
Normal 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),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
80
packages/cli/src/ui/commands/commandsCommand.ts
Normal file
80
packages/cli/src/ui/commands/commandsCommand.ts
Normal 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,
|
||||
};
|
||||
Reference in New Issue
Block a user