feat(hooks): add support for friendly names and descriptions (#15174)

This commit is contained in:
Abhi
2025-12-18 11:09:24 -05:00
committed by GitHub
parent 7da060c149
commit 54466a3ea8
14 changed files with 300 additions and 17 deletions
@@ -356,6 +356,18 @@ describe('SettingsSchema', () => {
'Enable local and remote subagents. Warning: Experimental feature, uses YOLO mode for subagents',
);
});
it('should have name and description in hook definitions', () => {
const hookDef = SETTINGS_SCHEMA_DEFINITIONS['HookDefinitionArray'];
expect(hookDef).toBeDefined();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hookItemProperties = (hookDef as any).items.properties.hooks.items
.properties;
expect(hookItemProperties.name).toBeDefined();
expect(hookItemProperties.name.type).toBe('string');
expect(hookItemProperties.description).toBeDefined();
expect(hookItemProperties.description.type).toBe('string');
});
});
it('has JSON schema definitions for every referenced ref', () => {
@@ -1891,6 +1891,10 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
type: 'object',
description: 'Individual hook configuration.',
properties: {
name: {
type: 'string',
description: 'Unique identifier for the hook.',
},
type: {
type: 'string',
description:
@@ -1901,6 +1905,10 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
description:
'Shell command to execute. Receives JSON input via stdin and returns JSON output via stdout.',
},
description: {
type: 'string',
description: 'A description of the hook.',
},
timeout: {
type: 'number',
description: 'Timeout in milliseconds for hook execution.',
@@ -309,6 +309,24 @@ describe('hooksCommand', () => {
content: 'Failed to enable hook: Failed to save settings',
});
});
it('should complete hook names using friendly names', () => {
const enableCmd = hooksCommand.subCommands!.find(
(cmd) => cmd.name === 'enable',
)!;
const hookEntry = createMockHook(
'./hooks/test.sh',
HookEventName.BeforeTool,
true,
);
hookEntry.config.name = 'friendly-name';
mockHookSystem.getAllHooks.mockReturnValue([hookEntry]);
const completions = enableCmd.completion!(mockContext, 'frie');
expect(completions).toContain('friendly-name');
});
});
describe('disable subcommand', () => {
+1 -1
View File
@@ -213,7 +213,7 @@ function completeHookNames(
* Get a display name for a hook
*/
function getHookDisplayName(hook: HookRegistryEntry): string {
return hook.config.command || 'unknown-hook';
return hook.config.name || hook.config.command || 'unknown-hook';
}
const panelCommand: SlashCommand = {
@@ -9,7 +9,13 @@ import { Box, Text } from 'ink';
interface HooksListProps {
hooks: ReadonlyArray<{
config: { command?: string; type: string; timeout?: number };
config: {
command?: string;
type: string;
name?: string;
description?: string;
timeout?: number;
};
source: string;
eventName: string;
matcher?: string;
@@ -50,7 +56,8 @@ export const HooksList: React.FC<HooksListProps> = ({ hooks }) => {
</Text>
<Box flexDirection="column" paddingLeft={2}>
{eventHooks.map((hook, index) => {
const hookName = hook.config.command || 'unknown';
const hookName =
hook.config.name || hook.config.command || 'unknown';
const statusColor = hook.enabled ? 'green' : 'gray';
const statusText = hook.enabled ? 'enabled' : 'disabled';
@@ -63,8 +70,14 @@ export const HooksList: React.FC<HooksListProps> = ({ hooks }) => {
</Text>
</Box>
<Box paddingLeft={2} flexDirection="column">
{hook.config.description && (
<Text italic>{hook.config.description}</Text>
)}
<Text dimColor>
Source: {hook.source}
{hook.config.name &&
hook.config.command &&
` | Command: ${hook.config.command}`}
{hook.matcher && ` | Matcher: ${hook.matcher}`}
{hook.sequential && ` | Sequential`}
{hook.config.timeout &&