summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/commands
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/commands')
-rw-r--r--packages/cli/src/ui/commands/toolsCommand.test.ts108
-rw-r--r--packages/cli/src/ui/commands/toolsCommand.ts66
2 files changed, 174 insertions, 0 deletions
diff --git a/packages/cli/src/ui/commands/toolsCommand.test.ts b/packages/cli/src/ui/commands/toolsCommand.test.ts
new file mode 100644
index 00000000..41c5196b
--- /dev/null
+++ b/packages/cli/src/ui/commands/toolsCommand.test.ts
@@ -0,0 +1,108 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect, vi } from 'vitest';
+import { toolsCommand } from './toolsCommand.js';
+import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
+import { MessageType } from '../types.js';
+import { Tool } from '@google/gemini-cli-core';
+
+// Mock tools for testing
+const mockTools = [
+ {
+ name: 'file-reader',
+ displayName: 'File Reader',
+ description: 'Reads files from the local system.',
+ schema: {},
+ },
+ {
+ name: 'code-editor',
+ displayName: 'Code Editor',
+ description: 'Edits code files.',
+ schema: {},
+ },
+] as Tool[];
+
+describe('toolsCommand', () => {
+ it('should display an error if the tool registry is unavailable', async () => {
+ const mockContext = createMockCommandContext({
+ services: {
+ config: {
+ getToolRegistry: () => Promise.resolve(undefined),
+ },
+ },
+ });
+
+ if (!toolsCommand.action) throw new Error('Action not defined');
+ await toolsCommand.action(mockContext, '');
+
+ expect(mockContext.ui.addItem).toHaveBeenCalledWith(
+ {
+ type: MessageType.ERROR,
+ text: 'Could not retrieve tool registry.',
+ },
+ expect.any(Number),
+ );
+ });
+
+ it('should display "No tools available" when none are found', async () => {
+ const mockContext = createMockCommandContext({
+ services: {
+ config: {
+ getToolRegistry: () =>
+ Promise.resolve({ getAllTools: () => [] as Tool[] }),
+ },
+ },
+ });
+
+ if (!toolsCommand.action) throw new Error('Action not defined');
+ await toolsCommand.action(mockContext, '');
+
+ expect(mockContext.ui.addItem).toHaveBeenCalledWith(
+ expect.objectContaining({
+ text: expect.stringContaining('No tools available'),
+ }),
+ expect.any(Number),
+ );
+ });
+
+ it('should list tools without descriptions by default', async () => {
+ const mockContext = createMockCommandContext({
+ services: {
+ config: {
+ getToolRegistry: () =>
+ Promise.resolve({ getAllTools: () => mockTools }),
+ },
+ },
+ });
+
+ if (!toolsCommand.action) throw new Error('Action not defined');
+ await toolsCommand.action(mockContext, '');
+
+ const message = (mockContext.ui.addItem as vi.Mock).mock.calls[0][0].text;
+ expect(message).not.toContain('Reads files from the local system.');
+ expect(message).toContain('File Reader');
+ expect(message).toContain('Code Editor');
+ });
+
+ it('should list tools with descriptions when "desc" arg is passed', async () => {
+ const mockContext = createMockCommandContext({
+ services: {
+ config: {
+ getToolRegistry: () =>
+ Promise.resolve({ getAllTools: () => mockTools }),
+ },
+ },
+ });
+
+ if (!toolsCommand.action) throw new Error('Action not defined');
+ await toolsCommand.action(mockContext, 'desc');
+
+ const message = (mockContext.ui.addItem as vi.Mock).mock.calls[0][0].text;
+ expect(message).toContain('Reads files from the local system.');
+ expect(message).toContain('Edits code files.');
+ });
+});
diff --git a/packages/cli/src/ui/commands/toolsCommand.ts b/packages/cli/src/ui/commands/toolsCommand.ts
new file mode 100644
index 00000000..f65edd07
--- /dev/null
+++ b/packages/cli/src/ui/commands/toolsCommand.ts
@@ -0,0 +1,66 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { type CommandContext, type SlashCommand } from './types.js';
+import { MessageType } from '../types.js';
+
+export const toolsCommand: SlashCommand = {
+ name: 'tools',
+ description: 'list available Gemini CLI tools',
+ action: async (context: CommandContext, args?: string): Promise<void> => {
+ const subCommand = args?.trim();
+
+ // Default to NOT showing descriptions. The user must opt in with an argument.
+ let useShowDescriptions = false;
+ if (subCommand === 'desc' || subCommand === 'descriptions') {
+ useShowDescriptions = true;
+ }
+
+ const toolRegistry = await context.services.config?.getToolRegistry();
+ if (!toolRegistry) {
+ context.ui.addItem(
+ {
+ type: MessageType.ERROR,
+ text: 'Could not retrieve tool registry.',
+ },
+ Date.now(),
+ );
+ return;
+ }
+
+ const tools = toolRegistry.getAllTools();
+ // Filter out MCP tools by checking for the absence of a serverName property
+ const geminiTools = tools.filter((tool) => !('serverName' in tool));
+
+ let message = 'Available Gemini CLI tools:\n\n';
+
+ if (geminiTools.length > 0) {
+ geminiTools.forEach((tool) => {
+ if (useShowDescriptions && tool.description) {
+ message += ` - \u001b[36m${tool.displayName} (${tool.name})\u001b[0m:\n`;
+
+ const greenColor = '\u001b[32m';
+ const resetColor = '\u001b[0m';
+
+ // Handle multi-line descriptions
+ const descLines = tool.description.trim().split('\n');
+ for (const descLine of descLines) {
+ message += ` ${greenColor}${descLine}${resetColor}\n`;
+ }
+ } else {
+ message += ` - \u001b[36m${tool.displayName}\u001b[0m\n`;
+ }
+ });
+ } else {
+ message += ' No tools available\n';
+ }
+ message += '\n';
+
+ message += '\u001b[0m';
+
+ context.ui.addItem({ type: MessageType.INFO, text: message }, Date.now());
+ },
+};