diff options
Diffstat (limited to 'packages/cli/src/ui/hooks')
| -rw-r--r-- | packages/cli/src/ui/hooks/slashCommandProcessor.test.ts | 90 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/slashCommandProcessor.ts | 82 |
2 files changed, 158 insertions, 14 deletions
diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index c279da8c..221893a2 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -49,7 +49,7 @@ vi.mock('node:fs/promises', () => ({ })); import { act, renderHook } from '@testing-library/react'; -import { vi, describe, it, expect, beforeEach, Mock } from 'vitest'; +import { vi, describe, it, expect, beforeEach, afterEach, Mock } from 'vitest'; import open from 'open'; import { useSlashCommandProcessor, @@ -107,7 +107,7 @@ describe('useSlashCommandProcessor', () => { process.env = { ...globalThis.process.env }; }); - const getProcessor = () => { + const getProcessor = (showToolDescriptions: boolean = false) => { const { result } = renderHook(() => useSlashCommandProcessor( mockConfig, @@ -119,6 +119,7 @@ describe('useSlashCommandProcessor', () => { mockOpenThemeDialog, mockPerformMemoryRefresh, mockCorgiMode, + showToolDescriptions, ), ); return result.current; @@ -571,17 +572,82 @@ Add any other context about the problem here. // Check that the message contains details about both servers and their tools const message = mockAddItem.mock.calls[1][0].text; // Server 1 - Connected (green dot) - expect(message).toContain('🟢 server1 (2 tools):'); - expect(message).toContain('server1_tool1'); - expect(message).toContain('server1_tool2'); + expect(message).toContain('🟢 \u001b[1mserver1\u001b[0m (2 tools)'); + expect(message).toContain('\u001b[36mserver1_tool1\u001b[0m'); + expect(message).toContain('\u001b[36mserver1_tool2\u001b[0m'); // Server 2 - Connecting (yellow dot) - expect(message).toContain('🟡 server2 (1 tools):'); - expect(message).toContain('server2_tool1'); + expect(message).toContain('🟡 \u001b[1mserver2\u001b[0m (1 tools)'); + expect(message).toContain('\u001b[36mserver2_tool1\u001b[0m'); // Server 3 - No status, should default to Disconnected (red dot) - expect(message).toContain('🔴 server3 (1 tools):'); - expect(message).toContain('server3_tool1'); + expect(message).toContain('🔴 \u001b[1mserver3\u001b[0m (1 tools)'); + expect(message).toContain('\u001b[36mserver3_tool1\u001b[0m'); + + expect(commandResult).toBe(true); + }); + + it('should display tool descriptions when showToolDescriptions is true', async () => { + // Mock MCP servers configuration with server description + const mockMcpServers = { + server1: { + command: 'cmd1', + description: 'This is a server description', + }, + }; + + // Setup getMCPServerStatus mock implementation + vi.mocked(getMCPServerStatus).mockImplementation((serverName) => { + if (serverName === 'server1') return MCPServerStatus.CONNECTED; + return MCPServerStatus.DISCONNECTED; + }); + + // Mock tools from server with descriptions + const mockServerTools = [ + { name: 'tool1', description: 'This is tool 1 description' }, + { name: 'tool2', description: 'This is tool 2 description' }, + ]; + + mockConfig = { + ...mockConfig, + getToolRegistry: vi.fn().mockResolvedValue({ + getToolsByServer: vi.fn().mockReturnValue(mockServerTools), + }), + getMcpServers: vi.fn().mockReturnValue(mockMcpServers), + } as unknown as Config; + + const { handleSlashCommand } = getProcessor(true); + let commandResult: SlashCommandActionReturn | boolean = false; + await act(async () => { + commandResult = handleSlashCommand('/mcp'); + }); + + expect(mockAddItem).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + type: MessageType.INFO, + text: expect.stringContaining('Configured MCP servers and tools:'), + }), + expect.any(Number), + ); + + const message = mockAddItem.mock.calls[1][0].text; + + // Check that server description is included (with ANSI color codes) + expect(message).toContain('\u001b[1mserver1\u001b[0m (2 tools)'); + expect(message).toContain( + '\u001b[32mThis is a server description\u001b[0m', + ); + + // Check that tool descriptions are included (with ANSI color codes) + expect(message).toContain('\u001b[36mtool1\u001b[0m'); + expect(message).toContain( + '\u001b[32mThis is tool 1 description\u001b[0m', + ); + expect(message).toContain('\u001b[36mtool2\u001b[0m'); + expect(message).toContain( + '\u001b[32mThis is tool 2 description\u001b[0m', + ); expect(commandResult).toBe(true); }); @@ -636,9 +702,9 @@ Add any other context about the problem here. // Check that the message contains details about both servers and their tools const message = mockAddItem.mock.calls[1][0].text; - expect(message).toContain('🟢 server1 (1 tools):'); - expect(message).toContain('server1_tool1'); - expect(message).toContain('🔴 server2 (0 tools):'); + expect(message).toContain('🟢 \u001b[1mserver1\u001b[0m (1 tools)'); + expect(message).toContain('\u001b[36mserver1_tool1\u001b[0m'); + expect(message).toContain('🔴 \u001b[1mserver2\u001b[0m (0 tools)'); expect(message).toContain('No tools available'); expect(commandResult).toBe(true); diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index 39ccf0e8..38fdddba 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -47,6 +47,7 @@ export const useSlashCommandProcessor = ( openThemeDialog: () => void, performMemoryRefresh: () => Promise<void>, toggleCorgiMode: () => void, + showToolDescriptions: boolean = false, ) => { const addMessage = useCallback( (message: Message) => { @@ -141,6 +142,21 @@ export const useSlashCommandProcessor = ( name: 'mcp', description: 'list configured MCP servers and tools', action: async (_mainCommand, _subCommand, _args) => { + // Check if the _subCommand includes a specific flag to control description visibility + let useShowDescriptions = showToolDescriptions; + if (_subCommand === 'desc' || _subCommand === 'descriptions') { + useShowDescriptions = true; + } else if ( + _subCommand === 'nodesc' || + _subCommand === 'nodescriptions' + ) { + useShowDescriptions = false; + } else if (_args === 'desc' || _args === 'descriptions') { + useShowDescriptions = true; + } else if (_args === 'nodesc' || _args === 'nodescriptions') { + useShowDescriptions = false; + } + const toolRegistry = await config?.getToolRegistry(); if (!toolRegistry) { addMessage({ @@ -184,10 +200,68 @@ export const useSlashCommandProcessor = ( break; } - message += `${statusDot} ${serverName} (${serverTools.length} tools):\n`; + // Get server description if available + const server = mcpServers[serverName]; + + // Format server header with bold formatting + message += `${statusDot} \u001b[1m${serverName}\u001b[0m (${serverTools.length} tools)`; + + // Add server description with proper handling of multi-line descriptions + if (useShowDescriptions && server?.description) { + const greenColor = '\u001b[32m'; + const resetColor = '\u001b[0m'; + + const descLines = server.description.split('\n'); + message += `: ${greenColor}${descLines[0]}${resetColor}`; + message += '\n'; + + // If there are multiple lines, add proper indentation for each line + if (descLines.length > 1) { + for (let i = 1; i < descLines.length; i++) { + // Skip empty lines at the end + if (i === descLines.length - 1 && descLines[i].trim() === '') + continue; + message += ` ${greenColor}${descLines[i]}${resetColor}\n`; + } + } + } else { + message += '\n'; + } + + // Reset formatting after server entry + message += '\u001b[0m'; + if (serverTools.length > 0) { serverTools.forEach((tool) => { - message += ` - ${tool.name}\n`; + if (useShowDescriptions && tool.description) { + // Format tool name in cyan using simple ANSI cyan color + message += ` - \u001b[36m${tool.name}\u001b[0m: `; + + // Apply green color to the description text + const greenColor = '\u001b[32m'; + const resetColor = '\u001b[0m'; + + // Handle multi-line descriptions by properly indenting and preserving formatting + const descLines = tool.description.split('\n'); + message += `${greenColor}${descLines[0]}${resetColor}\n`; + + // If there are multiple lines, add proper indentation for each line + if (descLines.length > 1) { + for (let i = 1; i < descLines.length; i++) { + // Skip empty lines at the end + if ( + i === descLines.length - 1 && + descLines[i].trim() === '' + ) + continue; + message += ` ${greenColor}${descLines[i]}${resetColor}\n`; + } + } + // Reset is handled inline with each line now + } else { + // Use cyan color for the tool name even when not showing descriptions + message += ` - \u001b[36m${tool.name}\u001b[0m\n`; + } }); } else { message += ' No tools available\n'; @@ -195,6 +269,9 @@ export const useSlashCommandProcessor = ( message += '\n'; } + // Make sure to reset any ANSI formatting at the end to prevent it from affecting the terminal + message += '\u001b[0m'; + addMessage({ type: MessageType.INFO, content: message, @@ -369,6 +446,7 @@ Add any other context about the problem here. addMessage, toggleCorgiMode, config, + showToolDescriptions, ], ); |
