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.
|
the visual display is cleared.
|
||||||
- **Keyboard shortcut:** Press **Ctrl+L** at any time to perform a clear action.
|
- **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`
|
### `/compress`
|
||||||
|
|
||||||
- **Description:** Replace the entire chat context with a summary. This saves on
|
- **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
|
- A file at `<project>/.gemini/commands/git/commit.toml` becomes the namespaced
|
||||||
command `/git:commit`.
|
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)
|
## TOML file format (v1)
|
||||||
|
|
||||||
Your command definition files must be written in the TOML format and use the
|
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 { bugCommand } from '../ui/commands/bugCommand.js';
|
||||||
import { chatCommand, debugCommand } from '../ui/commands/chatCommand.js';
|
import { chatCommand, debugCommand } from '../ui/commands/chatCommand.js';
|
||||||
import { clearCommand } from '../ui/commands/clearCommand.js';
|
import { clearCommand } from '../ui/commands/clearCommand.js';
|
||||||
|
import { commandsCommand } from '../ui/commands/commandsCommand.js';
|
||||||
import { compressCommand } from '../ui/commands/compressCommand.js';
|
import { compressCommand } from '../ui/commands/compressCommand.js';
|
||||||
import { copyCommand } from '../ui/commands/copyCommand.js';
|
import { copyCommand } from '../ui/commands/copyCommand.js';
|
||||||
import { corgiCommand } from '../ui/commands/corgiCommand.js';
|
import { corgiCommand } from '../ui/commands/corgiCommand.js';
|
||||||
@@ -89,6 +90,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
|||||||
: chatCommand.subCommands,
|
: chatCommand.subCommands,
|
||||||
},
|
},
|
||||||
clearCommand,
|
clearCommand,
|
||||||
|
commandsCommand,
|
||||||
compressCommand,
|
compressCommand,
|
||||||
copyCommand,
|
copyCommand,
|
||||||
corgiCommand,
|
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