Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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),
);
});
});
});
68 changes: 68 additions & 0 deletions packages/cli/src/ui/commands/commandsCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @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 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> {
context.ui.reloadCommands();

context.ui.addItem(
{
type: MessageType.INFO,
text: 'Custom commands reloaded successfully.',
icon: '✓ ',
color: 'green',
} as HistoryItemInfo,
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,
};