mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
feat(cli): give visibility to /tools list command in the TUI and follow the subcommand pattern of other commands (#21213)
This commit is contained in:
@@ -67,7 +67,7 @@ describe('toolsCommand', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should list tools without descriptions by default', async () => {
|
it('should list tools without descriptions by default (no args)', async () => {
|
||||||
const mockContext = createMockCommandContext({
|
const mockContext = createMockCommandContext({
|
||||||
services: {
|
services: {
|
||||||
config: {
|
config: {
|
||||||
@@ -88,6 +88,27 @@ describe('toolsCommand', () => {
|
|||||||
expect(message.tools[1].displayName).toBe('Code Editor');
|
expect(message.tools[1].displayName).toBe('Code Editor');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should list tools without descriptions when "list" arg is passed', async () => {
|
||||||
|
const mockContext = createMockCommandContext({
|
||||||
|
services: {
|
||||||
|
config: {
|
||||||
|
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!toolsCommand.action) throw new Error('Action not defined');
|
||||||
|
await toolsCommand.action(mockContext, 'list');
|
||||||
|
|
||||||
|
const [message] = (mockContext.ui.addItem as ReturnType<typeof vi.fn>).mock
|
||||||
|
.calls[0];
|
||||||
|
expect(message.type).toBe(MessageType.TOOLS_LIST);
|
||||||
|
expect(message.showDescriptions).toBe(false);
|
||||||
|
expect(message.tools).toHaveLength(2);
|
||||||
|
expect(message.tools[0].displayName).toBe('File Reader');
|
||||||
|
expect(message.tools[1].displayName).toBe('Code Editor');
|
||||||
|
});
|
||||||
|
|
||||||
it('should list tools with descriptions when "desc" arg is passed', async () => {
|
it('should list tools with descriptions when "desc" arg is passed', async () => {
|
||||||
const mockContext = createMockCommandContext({
|
const mockContext = createMockCommandContext({
|
||||||
services: {
|
services: {
|
||||||
@@ -105,9 +126,65 @@ describe('toolsCommand', () => {
|
|||||||
expect(message.type).toBe(MessageType.TOOLS_LIST);
|
expect(message.type).toBe(MessageType.TOOLS_LIST);
|
||||||
expect(message.showDescriptions).toBe(true);
|
expect(message.showDescriptions).toBe(true);
|
||||||
expect(message.tools).toHaveLength(2);
|
expect(message.tools).toHaveLength(2);
|
||||||
|
expect(message.tools[0].displayName).toBe('File Reader');
|
||||||
expect(message.tools[0].description).toBe(
|
expect(message.tools[0].description).toBe(
|
||||||
'Reads files from the local system.',
|
'Reads files from the local system.',
|
||||||
);
|
);
|
||||||
|
expect(message.tools[1].displayName).toBe('Code Editor');
|
||||||
|
expect(message.tools[1].description).toBe('Edits code files.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have "list" and "desc" subcommands', () => {
|
||||||
|
expect(toolsCommand.subCommands).toBeDefined();
|
||||||
|
const names = toolsCommand.subCommands?.map((s) => s.name);
|
||||||
|
expect(names).toContain('list');
|
||||||
|
expect(names).toContain('desc');
|
||||||
|
expect(names).not.toContain('descriptions');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subcommand "list" should display tools without descriptions', async () => {
|
||||||
|
const mockContext = createMockCommandContext({
|
||||||
|
services: {
|
||||||
|
config: {
|
||||||
|
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const listCmd = toolsCommand.subCommands?.find((s) => s.name === 'list');
|
||||||
|
if (!listCmd?.action) throw new Error('Action not defined');
|
||||||
|
await listCmd.action(mockContext, '');
|
||||||
|
|
||||||
|
const [message] = (mockContext.ui.addItem as ReturnType<typeof vi.fn>).mock
|
||||||
|
.calls[0];
|
||||||
|
expect(message.showDescriptions).toBe(false);
|
||||||
|
expect(message.tools).toHaveLength(2);
|
||||||
|
expect(message.tools[0].displayName).toBe('File Reader');
|
||||||
|
expect(message.tools[1].displayName).toBe('Code Editor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subcommand "desc" should display tools with descriptions', async () => {
|
||||||
|
const mockContext = createMockCommandContext({
|
||||||
|
services: {
|
||||||
|
config: {
|
||||||
|
getToolRegistry: () => ({ getAllTools: () => mockTools }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const descCmd = toolsCommand.subCommands?.find((s) => s.name === 'desc');
|
||||||
|
if (!descCmd?.action) throw new Error('Action not defined');
|
||||||
|
await descCmd.action(mockContext, '');
|
||||||
|
|
||||||
|
const [message] = (mockContext.ui.addItem as ReturnType<typeof vi.fn>).mock
|
||||||
|
.calls[0];
|
||||||
|
expect(message.showDescriptions).toBe(true);
|
||||||
|
expect(message.tools).toHaveLength(2);
|
||||||
|
expect(message.tools[0].displayName).toBe('File Reader');
|
||||||
|
expect(message.tools[0].description).toBe(
|
||||||
|
'Reads files from the local system.',
|
||||||
|
);
|
||||||
|
expect(message.tools[1].displayName).toBe('Code Editor');
|
||||||
expect(message.tools[1].description).toBe('Edits code files.');
|
expect(message.tools[1].description).toBe('Edits code files.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,16 @@ async function listTools(
|
|||||||
context.ui.addItem(toolsListItem);
|
context.ui.addItem(toolsListItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolsDescSubCommand: SlashCommand = {
|
const listSubCommand: SlashCommand = {
|
||||||
|
name: 'list',
|
||||||
|
description: 'List available Gemini CLI tools.',
|
||||||
|
kind: CommandKind.BUILT_IN,
|
||||||
|
autoExecute: true,
|
||||||
|
action: async (context: CommandContext): Promise<void> =>
|
||||||
|
listTools(context, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
const descSubCommand: SlashCommand = {
|
||||||
name: 'desc',
|
name: 'desc',
|
||||||
altNames: ['descriptions'],
|
altNames: ['descriptions'],
|
||||||
description: 'List available Gemini CLI tools with descriptions.',
|
description: 'List available Gemini CLI tools with descriptions.',
|
||||||
@@ -57,11 +66,11 @@ export const toolsCommand: SlashCommand = {
|
|||||||
'List available Gemini CLI tools. Use /tools desc to include descriptions.',
|
'List available Gemini CLI tools. Use /tools desc to include descriptions.',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: false,
|
autoExecute: false,
|
||||||
subCommands: [toolsDescSubCommand],
|
subCommands: [listSubCommand, descSubCommand],
|
||||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||||
const subCommand = args?.trim();
|
const subCommand = args?.trim();
|
||||||
|
|
||||||
// Keep backward compatibility for typed arguments while exposing desc in TUI via subcommands.
|
// Keep backward compatibility for typed arguments while exposing subcommands in TUI.
|
||||||
const useShowDescriptions =
|
const useShowDescriptions =
|
||||||
subCommand === 'desc' || subCommand === 'descriptions';
|
subCommand === 'desc' || subCommand === 'descriptions';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user