From b67806ae9a99e5a3c449c60457933b47d14ba66c Mon Sep 17 00:00:00 2001 From: Billy Biggs Date: Sun, 15 Jun 2025 11:40:39 -0700 Subject: [PATCH] Support completion of checkpoint names in /resume (#1063) --- .../cli/src/ui/components/InputPrompt.tsx | 23 ++++++++++++--- .../cli/src/ui/hooks/slashCommandProcessor.ts | 20 +++++++++++++ packages/cli/src/ui/hooks/useCompletion.ts | 28 +++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index c4177c00..d31b7106 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -109,11 +109,20 @@ export const InputPrompt: React.FC = ({ const selectedSuggestion = completionSuggestions[indexToUse]; if (query.trimStart().startsWith('/')) { + const parts = query.trimStart().substring(1).split(' '); + const commandName = parts[0]; const slashIndex = query.indexOf('/'); const base = query.substring(0, slashIndex + 1); - const newValue = base + selectedSuggestion.value; - buffer.setText(newValue); - handleSubmitAndClear(newValue); + + const command = slashCommands.find((cmd) => cmd.name === commandName); + if (command && command.completion) { + const newValue = `${base}${commandName} ${selectedSuggestion.value}`; + buffer.setText(newValue); + } else { + const newValue = base + selectedSuggestion.value; + buffer.setText(newValue); + handleSubmitAndClear(newValue); + } } else { const atIndex = query.lastIndexOf('@'); if (atIndex === -1) return; @@ -131,7 +140,13 @@ export const InputPrompt: React.FC = ({ } resetCompletionState(); }, - [resetCompletionState, handleSubmitAndClear, buffer, completionSuggestions], + [ + resetCompletionState, + handleSubmitAndClear, + buffer, + completionSuggestions, + slashCommands, + ], ); useInput( diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index a4b13d0a..dfefc562 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -44,6 +44,7 @@ export interface SlashCommand { name: string; altName?: string; description?: string; + completion?: () => Promise; action: ( mainCommand: string, subCommand?: string, @@ -643,6 +644,25 @@ Add any other context about the problem here. name: 'resume', description: 'resume from conversation checkpoint. Usage: /resume [tag]', + completion: async () => { + const geminiDir = config?.getGeminiDir(); + if (!geminiDir) { + return []; + } + try { + const files = await fs.readdir(geminiDir); + return files + .filter( + (file) => + file.startsWith('checkpoint-') && file.endsWith('.json'), + ) + .map((file) => + file.replace('checkpoint-', '').replace('.json', ''), + ); + } catch (_err) { + return []; + } + }, action: async (_mainCommand, subCommand, _args) => { const tag = (subCommand || '').trim(); const logger = new Logger(config?.getSessionId() || ''); diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts index b7603720..5217e60c 100644 --- a/packages/cli/src/ui/hooks/useCompletion.ts +++ b/packages/cli/src/ui/hooks/useCompletion.ts @@ -127,6 +127,34 @@ export function useCompletion( // --- Handle Slash Command Completion --- if (trimmedQuery.startsWith('/')) { + const parts = trimmedQuery.substring(1).split(' '); + const commandName = parts[0]; + const subCommand = parts.slice(1).join(' '); + + const command = slashCommands.find( + (cmd) => cmd.name === commandName || cmd.altName === commandName, + ); + + if (command && command.completion) { + const fetchAndSetSuggestions = async () => { + setIsLoadingSuggestions(true); + if (command.completion) { + const results = await command.completion(); + const filtered = results.filter((r) => r.startsWith(subCommand)); + const newSuggestions = filtered.map((s) => ({ + label: s, + value: s, + })); + setSuggestions(newSuggestions); + setShowSuggestions(newSuggestions.length > 0); + setActiveSuggestionIndex(newSuggestions.length > 0 ? 0 : -1); + } + setIsLoadingSuggestions(false); + }; + fetchAndSetSuggestions(); + return; + } + const partialCommand = trimmedQuery.substring(1); const filteredSuggestions = slashCommands .filter(