diff --git a/packages/cli/src/services/CommandService.test.ts b/packages/cli/src/services/CommandService.test.ts
index 362e4b8b62..e2d5b9f585 100644
--- a/packages/cli/src/services/CommandService.test.ts
+++ b/packages/cli/src/services/CommandService.test.ts
@@ -349,36 +349,4 @@ 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 808ab61bf5..5f1e09d50d 100644
--- a/packages/cli/src/services/CommandService.ts
+++ b/packages/cli/src/services/CommandService.ts
@@ -85,9 +85,7 @@ export class CommandService {
});
}
- const finalCommands = Object.freeze(
- Array.from(commandMap.values()).filter((cmd) => !cmd.hidden),
- );
+ const finalCommands = Object.freeze(Array.from(commandMap.values()));
return new CommandService(finalCommands);
}
diff --git a/packages/cli/src/ui/commands/corgiCommand.ts b/packages/cli/src/ui/commands/corgiCommand.ts
index bdbcc05f56..2da6ad3ed1 100644
--- a/packages/cli/src/ui/commands/corgiCommand.ts
+++ b/packages/cli/src/ui/commands/corgiCommand.ts
@@ -9,8 +9,8 @@ import { CommandKind, type SlashCommand } from './types.js';
export const corgiCommand: SlashCommand = {
name: 'corgi',
description: 'Toggles corgi mode.',
- kind: CommandKind.BUILT_IN,
hidden: true,
+ kind: CommandKind.BUILT_IN,
action: (context, _args) => {
context.ui.toggleCorgiMode();
},
diff --git a/packages/cli/src/ui/components/Help.test.tsx b/packages/cli/src/ui/components/Help.test.tsx
new file mode 100644
index 0000000000..ff749643ba
--- /dev/null
+++ b/packages/cli/src/ui/components/Help.test.tsx
@@ -0,0 +1,63 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/** @vitest-environment jsdom */
+
+import { render } from 'ink-testing-library';
+import { describe, it, expect } from 'vitest';
+import { Help } from './Help.js';
+import type { SlashCommand } from '../commands/types.js';
+import { CommandKind } from '../commands/types.js';
+
+const mockCommands: readonly SlashCommand[] = [
+ {
+ name: 'test',
+ description: 'A test command',
+ kind: CommandKind.BUILT_IN,
+ },
+ {
+ name: 'hidden',
+ description: 'A hidden command',
+ hidden: true,
+ kind: CommandKind.BUILT_IN,
+ },
+ {
+ name: 'parent',
+ description: 'A parent command',
+ kind: CommandKind.BUILT_IN,
+ subCommands: [
+ {
+ name: 'visible-child',
+ description: 'A visible child command',
+ kind: CommandKind.BUILT_IN,
+ },
+ {
+ name: 'hidden-child',
+ description: 'A hidden child command',
+ hidden: true,
+ kind: CommandKind.BUILT_IN,
+ },
+ ],
+ },
+];
+
+describe('Help Component', () => {
+ it('should not render hidden commands', () => {
+ const { lastFrame } = render();
+ const output = lastFrame();
+
+ expect(output).toContain('/test');
+ expect(output).not.toContain('/hidden');
+ });
+
+ it('should not render hidden subcommands', () => {
+ const { lastFrame } = render();
+ const output = lastFrame();
+
+ expect(output).toContain('visible-child');
+ expect(output).not.toContain('hidden-child');
+ });
+});
diff --git a/packages/cli/src/ui/components/Help.tsx b/packages/cli/src/ui/components/Help.tsx
index 124ab26081..b2a8a4400a 100644
--- a/packages/cli/src/ui/components/Help.tsx
+++ b/packages/cli/src/ui/components/Help.tsx
@@ -65,7 +65,7 @@ export const Help: React.FC = ({ commands }) => (
Commands:
{commands
- .filter((command) => command.description)
+ .filter((command) => command.description && !command.hidden)
.map((command: SlashCommand) => (
@@ -79,15 +79,17 @@ export const Help: React.FC = ({ commands }) => (
{command.description && ' - ' + command.description}
{command.subCommands &&
- command.subCommands.map((subCommand) => (
-
-
- {' '}
- {subCommand.name}
+ command.subCommands
+ .filter((subCommand) => !subCommand.hidden)
+ .map((subCommand) => (
+
+
+ {' '}
+ {subCommand.name}
+
+ {subCommand.description && ' - ' + subCommand.description}
- {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 7e855403e2..1d164445e9 100644
--- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
+++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
@@ -223,18 +223,6 @@ 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.test.ts b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
index 6b8ce9682b..b5568ce9b4 100644
--- a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
+++ b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
@@ -347,6 +347,31 @@ describe('useSlashCompletion', () => {
expect(result.current.suggestions).toHaveLength(0);
});
+
+ it('should not suggest hidden commands', async () => {
+ const slashCommands = [
+ createTestCommand({
+ name: 'visible',
+ description: 'A visible command',
+ }),
+ createTestCommand({
+ name: 'hidden',
+ description: 'A hidden command',
+ hidden: true,
+ }),
+ ];
+ const { result } = renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ expect(result.current.suggestions.length).toBe(1);
+ expect(result.current.suggestions[0].label).toBe('visible');
+ });
});
describe('Sub-Commands', () => {
diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.ts b/packages/cli/src/ui/hooks/useSlashCompletion.ts
index 87288090fe..a284e0bc6e 100644
--- a/packages/cli/src/ui/hooks/useSlashCompletion.ts
+++ b/packages/cli/src/ui/hooks/useSlashCompletion.ts
@@ -225,7 +225,7 @@ function useCommandSuggestions(
if (partial === '') {
// If no partial query, show all available commands
potentialSuggestions = commandsToSearch.filter(
- (cmd) => cmd.description,
+ (cmd) => cmd.description && !cmd.hidden,
);
} else {
// Use fuzzy search for non-empty partial queries with fallback
@@ -400,7 +400,7 @@ export function useSlashCompletion(props: UseSlashCompletionProps): {
const commandMap = new Map();
commands.forEach((cmd) => {
- if (cmd.description) {
+ if (cmd.description && !cmd.hidden) {
commandItems.push(cmd.name);
commandMap.set(cmd.name, cmd);