From c1b8708ef581310fd5861a09cbe40b265b6942bb Mon Sep 17 00:00:00 2001 From: DeWitt Clinton Date: Fri, 5 Sep 2025 13:38:36 -0700 Subject: [PATCH] Add hidden property to slash commands (#7797) --- .../cli/src/services/CommandService.test.ts | 32 +++++++++++++++ packages/cli/src/services/CommandService.ts | 4 +- packages/cli/src/ui/commands/corgiCommand.ts | 1 + packages/cli/src/ui/commands/types.ts | 1 + packages/cli/src/ui/components/Help.tsx | 40 +++++++++---------- .../ui/hooks/slashCommandProcessor.test.ts | 12 ++++++ .../cli/src/ui/hooks/useSlashCompletion.ts | 1 + .../core/src/core/contentGenerator.test.ts | 1 + 8 files changed, 70 insertions(+), 22 deletions(-) diff --git a/packages/cli/src/services/CommandService.test.ts b/packages/cli/src/services/CommandService.test.ts index e2d5b9f585..362e4b8b62 100644 --- a/packages/cli/src/services/CommandService.test.ts +++ b/packages/cli/src/services/CommandService.test.ts @@ -349,4 +349,36 @@ describe('CommandService', () => { expect(deployExtension).toBeDefined(); expect(deployExtension?.description).toBe('[gcp] Deploy to Google Cloud'); }); + + it('should filter out hidden commands', async () => { + const visibleCommand = createMockCommand('visible', CommandKind.BUILT_IN); + const hiddenCommand = { + ...createMockCommand('hidden', CommandKind.BUILT_IN), + hidden: true, + }; + const initiallyVisibleCommand = createMockCommand( + 'initially-visible', + CommandKind.BUILT_IN, + ); + const hiddenOverrideCommand = { + ...createMockCommand('initially-visible', CommandKind.FILE), + hidden: true, + }; + + const mockLoader = new MockCommandLoader([ + visibleCommand, + hiddenCommand, + initiallyVisibleCommand, + hiddenOverrideCommand, + ]); + + const service = await CommandService.create( + [mockLoader], + new AbortController().signal, + ); + + const commands = service.getCommands(); + expect(commands).toHaveLength(1); + expect(commands[0].name).toBe('visible'); + }); }); diff --git a/packages/cli/src/services/CommandService.ts b/packages/cli/src/services/CommandService.ts index 5f1e09d50d..808ab61bf5 100644 --- a/packages/cli/src/services/CommandService.ts +++ b/packages/cli/src/services/CommandService.ts @@ -85,7 +85,9 @@ export class CommandService { }); } - const finalCommands = Object.freeze(Array.from(commandMap.values())); + const finalCommands = Object.freeze( + Array.from(commandMap.values()).filter((cmd) => !cmd.hidden), + ); return new CommandService(finalCommands); } diff --git a/packages/cli/src/ui/commands/corgiCommand.ts b/packages/cli/src/ui/commands/corgiCommand.ts index cb3ecd1c8b..bdbcc05f56 100644 --- a/packages/cli/src/ui/commands/corgiCommand.ts +++ b/packages/cli/src/ui/commands/corgiCommand.ts @@ -10,6 +10,7 @@ export const corgiCommand: SlashCommand = { name: 'corgi', description: 'Toggles corgi mode.', kind: CommandKind.BUILT_IN, + hidden: true, action: (context, _args) => { context.ui.toggleCorgiMode(); }, diff --git a/packages/cli/src/ui/commands/types.ts b/packages/cli/src/ui/commands/types.ts index 32f52a1260..c6cc5f1d5e 100644 --- a/packages/cli/src/ui/commands/types.ts +++ b/packages/cli/src/ui/commands/types.ts @@ -170,6 +170,7 @@ export interface SlashCommand { name: string; altNames?: string[]; description: string; + hidden?: boolean; kind: CommandKind; diff --git a/packages/cli/src/ui/components/Help.tsx b/packages/cli/src/ui/components/Help.tsx index 6d06a41043..f823d2d2d7 100644 --- a/packages/cli/src/ui/components/Help.tsx +++ b/packages/cli/src/ui/components/Help.tsx @@ -64,29 +64,27 @@ export const Help: React.FC = ({ commands }) => ( Commands: - {commands - .filter((command) => command.description) - .map((command: SlashCommand) => ( - - - - {' '} - /{command.name} - - {command.description && ' - ' + command.description} + {commands.map((command: SlashCommand) => ( + + + + {' '} + /{command.name} - {command.subCommands && - command.subCommands.map((subCommand) => ( - - - {' '} - {subCommand.name} - - {subCommand.description && ' - ' + subCommand.description} + {command.description && ' - ' + command.description} + + {command.subCommands && + command.subCommands.map((subCommand) => ( + + + {' '} + {subCommand.name} - ))} - - ))} + {subCommand.description && ' - ' + subCommand.description} + + ))} + + ))} {' '} diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index 5210ed546c..07983b3662 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -220,6 +220,18 @@ describe('useSlashCommandProcessor', () => { expect(fileAction).toHaveBeenCalledTimes(1); expect(builtinAction).not.toHaveBeenCalled(); }); + + it('should not include hidden commands in the command list', async () => { + const visibleCommand = createTestCommand({ name: 'visible' }); + const hiddenCommand = createTestCommand({ name: 'hidden', hidden: true }); + const result = setupProcessorHook([visibleCommand, hiddenCommand]); + + await waitFor(() => { + expect(result.current.slashCommands).toHaveLength(1); + }); + + expect(result.current.slashCommands[0].name).toBe('visible'); + }); }); describe('Command Execution Logic', () => { diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.ts b/packages/cli/src/ui/hooks/useSlashCompletion.ts index 62bad6a3ca..b3d0976ba8 100644 --- a/packages/cli/src/ui/hooks/useSlashCompletion.ts +++ b/packages/cli/src/ui/hooks/useSlashCompletion.ts @@ -443,6 +443,7 @@ export function useSlashCompletion(props: UseSlashCompletionProps): { commands.filter( (cmd) => cmd.description && + !cmd.hidden && (cmd.name.toLowerCase().startsWith(partial.toLowerCase()) || cmd.altNames?.some((alt) => alt.toLowerCase().startsWith(partial.toLowerCase()), diff --git a/packages/core/src/core/contentGenerator.test.ts b/packages/core/src/core/contentGenerator.test.ts index acbc04815c..eba9d353ec 100644 --- a/packages/core/src/core/contentGenerator.test.ts +++ b/packages/core/src/core/contentGenerator.test.ts @@ -158,6 +158,7 @@ describe('createContentGeneratorConfig', () => { }); it('should configure for Vertex AI using GCP project and location when set', async () => { + vi.stubEnv('GOOGLE_API_KEY', undefined); vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'env-gcp-project'); vi.stubEnv('GOOGLE_CLOUD_LOCATION', 'env-gcp-location'); const config = await createContentGeneratorConfig(