feat(ui): improve discoverability of MCP slash commands (#6080)

Co-authored-by: Rinil Kunhiraman <rinilkunhiraman@users.noreply.github.com>
Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
Rinil Kunhiraman
2025-09-05 20:15:40 -07:00
committed by GitHub
parent a027010097
commit d2b5b4f129
4 changed files with 231 additions and 509 deletions

View File

@@ -7,7 +7,7 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import type { SlashCommand } from '../commands/types.js';
import { type SlashCommand, CommandKind } from '../commands/types.js';
interface Help {
commands: readonly SlashCommand[];
@@ -64,27 +64,32 @@ export const Help: React.FC<Help> = ({ commands }) => (
<Text bold color={Colors.Foreground}>
Commands:
</Text>
{commands.map((command: SlashCommand) => (
<Box key={command.name} flexDirection="column">
<Text color={Colors.Foreground}>
<Text bold color={Colors.AccentPurple}>
{' '}
/{command.name}
</Text>
{command.description && ' - ' + command.description}
</Text>
{command.subCommands &&
command.subCommands.map((subCommand) => (
<Text key={subCommand.name} color={Colors.Foreground}>
<Text bold color={Colors.AccentPurple}>
{' '}
{subCommand.name}
</Text>
{subCommand.description && ' - ' + subCommand.description}
{commands
.filter((command) => command.description)
.map((command: SlashCommand) => (
<Box key={command.name} flexDirection="column">
<Text color={Colors.Foreground}>
<Text bold color={Colors.AccentPurple}>
{' '}
/{command.name}
</Text>
))}
</Box>
))}
{command.kind === CommandKind.MCP_PROMPT && (
<Text color={Colors.Gray}> [MCP]</Text>
)}
{command.description && ' - ' + command.description}
</Text>
{command.subCommands &&
command.subCommands.map((subCommand) => (
<Text key={subCommand.name} color={Colors.Foreground}>
<Text bold color={Colors.AccentPurple}>
{' '}
{subCommand.name}
</Text>
{subCommand.description && ' - ' + subCommand.description}
</Text>
))}
</Box>
))}
<Text color={Colors.Foreground}>
<Text bold color={Colors.AccentPurple}>
{' '}
@@ -92,6 +97,10 @@ export const Help: React.FC<Help> = ({ commands }) => (
</Text>
- shell command
</Text>
<Text color={Colors.Foreground}>
<Text color={Colors.Gray}>[MCP]</Text> - Model Context Protocol command
(from external servers)
</Text>
<Box height={1} />

View File

@@ -7,12 +7,13 @@
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { PrepareLabel } from './PrepareLabel.js';
import { isSlashCommand } from '../utils/commandUtils.js';
import { CommandKind } from '../commands/types.js';
export interface Suggestion {
label: string;
value: string;
description?: string;
matchedIndex?: number;
commandKind?: CommandKind;
}
interface SuggestionsDisplayProps {
suggestions: Suggestion[];
@@ -53,21 +54,6 @@ export function SuggestionsDisplay({
);
const visibleSuggestions = suggestions.slice(startIndex, endIndex);
const isSlashCommandMode = isSlashCommand(userInput);
let commandNameWidth = 0;
if (isSlashCommandMode) {
const maxLabelLength = visibleSuggestions.length
? Math.max(...visibleSuggestions.map((s) => s.label.length))
: 0;
const maxAllowedWidth = Math.floor(width * 0.35);
commandNameWidth = Math.max(
15,
Math.min(maxLabelLength + 2, maxAllowedWidth),
);
}
return (
<Box flexDirection="column" paddingX={1} width={width}>
{scrollOffset > 0 && <Text color={Colors.Foreground}></Text>}
@@ -88,31 +74,33 @@ export function SuggestionsDisplay({
return (
<Box key={`${suggestion.value}-${originalIndex}`} width={width}>
<Box flexDirection="row">
{isSlashCommandMode ? (
<>
<Box width={commandNameWidth} flexShrink={0}>
{labelElement}
</Box>
{suggestion.description ? (
<Box flexGrow={1} marginLeft={1}>
<Text color={textColor} wrap="wrap">
{suggestion.description}
</Text>
</Box>
) : null}
</>
) : (
<>
{labelElement}
{suggestion.description ? (
<Box flexGrow={1} marginLeft={1}>
<Text color={textColor} wrap="wrap">
{suggestion.description}
</Text>
</Box>
) : null}
</>
)}
{(() => {
const isSlashCommand = userInput.startsWith('/');
return (
<>
{isSlashCommand ? (
<Box flexShrink={0} paddingRight={2}>
{labelElement}
{suggestion.commandKind === CommandKind.MCP_PROMPT && (
<Text color={Colors.Gray}> [MCP]</Text>
)}
</Box>
) : (
labelElement
)}
{suggestion.description && (
<Box
flexGrow={1}
paddingLeft={isSlashCommand ? undefined : 1}
>
<Text color={textColor} wrap="truncate">
{suggestion.description}
</Text>
</Box>
)}
</>
);
})()}
</Box>
</Box>
);