Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit 9cf8fe4

Browse files
committed
Tool suggestions
1 parent 9bd0710 commit 9cf8fe4

File tree

3 files changed

+105
-59
lines changed

3 files changed

+105
-59
lines changed

source/app.tsx

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
import React, {useState, useEffect, useCallback} from 'react';
2-
import {Box, Text, useInput, useStdout} from 'ink';
3-
import {cliService} from './services/cli-service.js';
4-
import {Logger} from './logger.js';
5-
import {InputPrompt} from './components/InputPrompt.js';
1+
import React, { useState, useEffect, useCallback } from 'react';
2+
import { Box, Text, useInput, useStdout } from 'ink';
3+
import { cliService } from './services/cli-service.js';
4+
import { Logger } from './logger.js';
5+
import { InputPrompt } from './components/InputPrompt.js';
66
import Spinner from './components/Spinner.js';
7-
import {AsciiLogo} from './components/AsciiLogo.js';
8-
import {CommandMessage} from './types.js';
9-
import {ToolCall} from './types.js';
10-
import {Message} from './types.js';
7+
import { AsciiLogo } from './components/AsciiLogo.js';
8+
import { CommandMessage } from './types.js';
9+
import { ToolCall } from './types.js';
10+
import { Message } from './types.js';
1111
import type {
1212
PromptServerConfigData,
1313
ServerActionData,
1414
LLMConfigData,
1515
PromptApiKeyData,
1616
} from './types.js';
17-
import {MessageRenderer} from './components/Messages.js';
18-
import {Footer} from './components/Footer.js';
19-
import type {MCPServerConfig} from './services/mcp-config-service.js';
17+
import { MessageRenderer } from './components/Messages.js';
18+
import { Footer } from './components/Footer.js';
19+
import type { MCPServerConfig } from './services/mcp-config-service.js';
20+
import type { CommandRegistryEntry } from './services/cli-service.js';
21+
import { CommandSuggestions } from './components/CommandSuggestions.js';
2022
import Gradient from 'ink-gradient';
2123

2224
export default function App() {
@@ -37,12 +39,15 @@ export default function App() {
3739
useState(false);
3840
const [serverConfigStep, setServerConfigStep] = useState<string>('');
3941
const [currentServerConfig, setCurrentServerConfig] = useState<
40-
(Partial<MCPServerConfig> & {name?: string}) | undefined
42+
(Partial<MCPServerConfig> & { name?: string }) | undefined
4143
>(undefined);
4244
const [inputHistory, setInputHistory] = useState<string[]>([]);
4345
const [historyIndex, setHistoryIndex] = useState<number>(-1);
4446
const [tempInput, setTempInput] = useState<string>('');
45-
const {stdout} = useStdout();
47+
const [commandSuggestions, setCommandSuggestions] = useState<
48+
Array<[string, CommandRegistryEntry]>
49+
>([]);
50+
const { stdout } = useStdout();
4651

4752
// Initialize MCP service on component mount
4853
useEffect(() => {
@@ -59,6 +64,7 @@ export default function App() {
5964
connectedServers: servers,
6065
});
6166

67+
setCommandSuggestions([...cliService.getCommandRegistry().entries()]);
6268
setCurrentModel(model);
6369
setConnectedServers(servers);
6470
setShowInput(true);
@@ -172,7 +178,7 @@ export default function App() {
172178

173179
// Check if we're waiting for server configuration input
174180
if (isWaitingForServerConfig) {
175-
Logger.debug('Processing server config input', {step: serverConfigStep});
181+
Logger.debug('Processing server config input', { step: serverConfigStep });
176182

177183
const userMessage: Message = {
178184
id: Date.now().toString(),
@@ -198,7 +204,7 @@ export default function App() {
198204
);
199205

200206
for await (const result of stream) {
201-
Logger.debug('Server config result received', {result});
207+
Logger.debug('Server config result received', { result });
202208

203209
if (result.commandResult) {
204210
// Check if we're continuing server config or done
@@ -248,9 +254,8 @@ export default function App() {
248254
const errorMessage: Message = {
249255
id: (Date.now() + 1).toString(),
250256
role: 'assistant',
251-
content: `Error: ${
252-
error instanceof Error ? error.message : 'Unknown error'
253-
}`,
257+
content: `Error: ${error instanceof Error ? error.message : 'Unknown error'
258+
}`,
254259
timestamp: new Date(),
255260
};
256261

@@ -337,9 +342,8 @@ export default function App() {
337342
const errorMessage: Message = {
338343
id: (Date.now() + 1).toString(),
339344
role: 'assistant',
340-
content: `Error: ${
341-
error instanceof Error ? error.message : 'Unknown error'
342-
}`,
345+
content: `Error: ${error instanceof Error ? error.message : 'Unknown error'
346+
}`,
343347
timestamp: new Date(),
344348
};
345349

@@ -499,9 +503,8 @@ export default function App() {
499503
const errorMessage: Message = {
500504
id: (Date.now() + 1).toString(),
501505
role: 'assistant',
502-
content: `Error: ${
503-
error instanceof Error ? error.message : 'Unknown error'
504-
}`,
506+
content: `Error: ${error instanceof Error ? error.message : 'Unknown error'
507+
}`,
505508
timestamp: new Date(),
506509
};
507510

@@ -584,6 +587,9 @@ export default function App() {
584587

585588
{showInput && !initializationError && (
586589
<Box flexDirection="column" marginTop={1}>
590+
{input.startsWith('/') && (
591+
<CommandSuggestions suggestions={commandSuggestions} query={input} />
592+
)}
587593
<Box
588594
borderStyle="round"
589595
borderColor={
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
import { Box, Text } from 'ink';
3+
import type { CommandRegistryEntry } from '../services/cli-service.js';
4+
5+
interface CommandSuggestionsProps {
6+
suggestions: Array<[string, CommandRegistryEntry]>;
7+
query: string;
8+
}
9+
10+
export function CommandSuggestions({
11+
suggestions,
12+
query,
13+
}: CommandSuggestionsProps) {
14+
const filteredSuggestions = suggestions.filter(([command]) =>
15+
command.startsWith(query),
16+
);
17+
18+
if (filteredSuggestions.length === 0) {
19+
return null;
20+
}
21+
22+
return (
23+
<Box flexDirection="column" marginTop={1}>
24+
<Box borderStyle="round" paddingX={1} flexDirection="column">
25+
<Text bold>COMMANDS</Text>
26+
{filteredSuggestions.map(([command, { description }]) => (
27+
<Box key={command} flexDirection="row">
28+
<Box width={20}>
29+
<Text>{command}</Text>
30+
</Box>
31+
<Text>{description}</Text>
32+
</Box>
33+
))}
34+
</Box>
35+
</Box>
36+
);
37+
}

source/services/cli-service.ts

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import {config} from 'dotenv';
2-
import {Logger} from '../logger.js';
3-
import type {CommandResult} from '../types.js';
4-
import type {ToolCall} from '../types.js';
5-
import type {Tool} from '@modelcontextprotocol/sdk/types.js';
6-
import type {LLMConfigData, ServerActionData} from '../types.js';
7-
import {AgentService} from './agent-service.js';
8-
import {LLMService} from './llm-service.js';
9-
import {MCPConfigService, type MCPServerConfig} from './mcp-config-service.js';
10-
import {UtilityService} from './utility-service.js';
1+
import { config } from 'dotenv';
2+
import { Logger } from '../logger.js';
3+
import type { CommandResult } from '../types.js';
4+
import type { ToolCall } from '../types.js';
5+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
6+
import type { LLMConfigData, ServerActionData } from '../types.js';
7+
import { AgentService } from './agent-service.js';
8+
import { LLMService } from './llm-service.js';
9+
import { MCPConfigService, type MCPServerConfig } from './mcp-config-service.js';
10+
import { UtilityService } from './utility-service.js';
1111

1212
// Load environment variables
1313
config();
@@ -27,7 +27,7 @@ type CommandHandler = (
2727
/**
2828
* Registry entry for a command, including its handler and metadata.
2929
*/
30-
interface CommandRegistryEntry {
30+
export interface CommandRegistryEntry {
3131
handler: CommandHandler;
3232
description: string;
3333
}
@@ -230,9 +230,8 @@ export class CLIService {
230230
});
231231
return {
232232
type: 'error',
233-
message: `Command failed: ${
234-
error instanceof Error ? error.message : 'Unknown error'
235-
}`,
233+
message: `Command failed: ${error instanceof Error ? error.message : 'Unknown error'
234+
}`,
236235
};
237236
}
238237
}
@@ -257,7 +256,7 @@ export class CLIService {
257256
pendingModel?: string,
258257
isServerConfigInput?: boolean,
259258
serverConfigStep?: string,
260-
serverConfig?: Partial<MCPServerConfig> & {name?: string},
259+
serverConfig?: Partial<MCPServerConfig> & { name?: string },
261260
): AsyncGenerator<{
262261
response?: string;
263262
toolCalls?: ToolCall[];
@@ -356,12 +355,11 @@ export class CLIService {
356355
return;
357356
} catch (error) {
358357
yield {
359-
response: `Command error: ${
360-
error instanceof Error ? error.message : 'Unknown error'
361-
}`,
358+
response: `Command error: ${error instanceof Error ? error.message : 'Unknown error'
359+
}`,
362360
toolCalls: [],
363361
isCommand: true,
364-
commandResult: {type: 'error', message: 'Command failed'},
362+
commandResult: { type: 'error', message: 'Command failed' },
365363
done: true,
366364
};
367365
return;
@@ -405,16 +403,15 @@ export class CLIService {
405403
done: false,
406404
};
407405
}
408-
yield {done: true};
406+
yield { done: true };
409407
} catch (error) {
410408
Logger.error('Error sending message via Agent service', {
411409
error: error instanceof Error ? error.message : 'Unknown error',
412410
stack: error instanceof Error ? error.stack : undefined,
413411
});
414412
yield {
415-
response: `Error: ${
416-
error instanceof Error ? error.message : 'Unknown error'
417-
}`,
413+
response: `Error: ${error instanceof Error ? error.message : 'Unknown error'
414+
}`,
418415
thought: undefined,
419416
done: true,
420417
};
@@ -483,7 +480,7 @@ export class CLIService {
483480
* Gets the list of available tools from the agent.
484481
* @returns A promise that resolves to an object containing the tools or an error.
485482
*/
486-
async getAvailableTools(): Promise<{tools: Tool[]; error?: string}> {
483+
async getAvailableTools(): Promise<{ tools: Tool[]; error?: string }> {
487484
return this.agentService.getAvailableTools();
488485
}
489486

@@ -512,7 +509,7 @@ export class CLIService {
512509
return {
513510
type: 'list_servers',
514511
message: 'MCP Server Status:',
515-
data: {servers: serversWithStatus},
512+
data: { servers: serversWithStatus },
516513
};
517514
}
518515

@@ -547,7 +544,7 @@ export class CLIService {
547544
type: 'prompt_server_config',
548545
message:
549546
'Let\'s configure a new MCP server!\n\nYou can either:\n1. Enter a server name for interactive setup\n2. Paste a complete JSON configuration\n\nExample JSON:\n{\n "mcpServers": {\n "myserver": {\n "command": "npx",\n "args": ["-y", "@example/server"]\n }\n }\n}\n\nEnter server name or paste JSON:',
550-
data: {step: 'name_or_json'},
547+
data: { step: 'name_or_json' },
551548
};
552549
}
553550

@@ -611,17 +608,16 @@ export class CLIService {
611608
return {
612609
type: 'success',
613610
message: `Connected to server "${serverName}"!`,
614-
data: {reinitializeAgent: true},
611+
data: { reinitializeAgent: true },
615612
};
616613
} catch (error) {
617614
Logger.error(`Failed to connect to server ${serverName}`, {
618615
error: error instanceof Error ? error.message : String(error),
619616
});
620617
return {
621618
type: 'error',
622-
message: `Failed to connect to server "${serverName}": ${
623-
error instanceof Error ? error.message : 'Unknown error'
624-
}`,
619+
message: `Failed to connect to server "${serverName}": ${error instanceof Error ? error.message : 'Unknown error'
620+
}`,
625621
};
626622
}
627623
}
@@ -675,20 +671,27 @@ export class CLIService {
675671
return {
676672
type: 'success',
677673
message: `✅ Disconnected from server "${serverName}".`,
678-
data: {reinitializeAgent: true},
674+
data: { reinitializeAgent: true },
679675
};
680676
} catch (error) {
681677
Logger.error(`Failed to disconnect from server ${serverName}`, {
682678
error: error instanceof Error ? error.message : String(error),
683679
});
684680
return {
685681
type: 'error',
686-
message: `Failed to disconnect from server "${serverName}": ${
687-
error instanceof Error ? error.message : 'Unknown error'
688-
}`,
682+
message: `Failed to disconnect from server "${serverName}": ${error instanceof Error ? error.message : 'Unknown error'
683+
}`,
689684
};
690685
}
691686
}
687+
688+
/**
689+
* Returns the command registry.
690+
* @returns The map of registered commands
691+
*/
692+
getCommandRegistry(): Map<string, CommandRegistryEntry> {
693+
return this.commandRegistry;
694+
}
692695
}
693696

694697
// Export a singleton instance

0 commit comments

Comments
 (0)