diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js index 47312f9f2..da6f26899 100644 --- a/packages/cli/src/i18n/locales/de.js +++ b/packages/cli/src/i18n/locales/de.js @@ -614,6 +614,15 @@ export default { 'Für dieses Ereignis sind keine Hooks konfiguriert.', 'To add hooks, edit settings.json directly or ask Qwen.': 'Um Hooks hinzuzufügen, bearbeiten Sie settings.json direkt oder fragen Sie Qwen.', + 'Enter to select · Esc to go back': 'Enter zum Auswählen · Esc zum Zurück', + // Hooks - Config Detail Step + 'Hook details': 'Hook-Details', + 'Event:': 'Ereignis:', + 'Extension:': 'Erweiterung:', + 'Desc:': 'Beschreibung:', + 'No hook config selected': 'Keine Hook-Konfiguration ausgewählt', + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.': + 'Um diesen Hook zu ändern oder zu entfernen, bearbeiten Sie settings.json direkt oder fragen Sie Qwen.', // Hooks - Source Project: 'Projekt', User: 'Benutzer', diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index 0a5f21536..5a2299b2d 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -687,6 +687,15 @@ export default { 'No hooks configured for this event.': 'No hooks configured for this event.', 'To add hooks, edit settings.json directly or ask Qwen.': 'To add hooks, edit settings.json directly or ask Qwen.', + 'Enter to select · Esc to go back': 'Enter to select · Esc to go back', + // Hooks - Config Detail Step + 'Hook details': 'Hook details', + 'Event:': 'Event:', + 'Extension:': 'Extension:', + 'Desc:': 'Desc:', + 'No hook config selected': 'No hook config selected', + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.': + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.', // Hooks - Source Project: 'Project', User: 'User', diff --git a/packages/cli/src/i18n/locales/ja.js b/packages/cli/src/i18n/locales/ja.js index 906867911..0a5ed8403 100644 --- a/packages/cli/src/i18n/locales/ja.js +++ b/packages/cli/src/i18n/locales/ja.js @@ -400,6 +400,15 @@ export default { 'このイベントにはフックが設定されていません。', 'To add hooks, edit settings.json directly or ask Qwen.': 'フックを追加するには、settings.json を直接編集するか、Qwen に尋ねてください。', + 'Enter to select · Esc to go back': 'Enter で選択 · Esc で戻る', + // Hooks - Config Detail Step + 'Hook details': 'フック詳細', + 'Event:': 'イベント:', + 'Extension:': '拡張機能:', + 'Desc:': '説明:', + 'No hook config selected': 'フック設定が選択されていません', + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.': + 'このフックを変更または削除するには、settings.json を直接編集するか、Qwen に尋ねてください。', // Hooks - Source Project: 'プロジェクト', User: 'ユーザー', diff --git a/packages/cli/src/i18n/locales/pt.js b/packages/cli/src/i18n/locales/pt.js index c5110a2ce..e0a9afed4 100644 --- a/packages/cli/src/i18n/locales/pt.js +++ b/packages/cli/src/i18n/locales/pt.js @@ -620,6 +620,15 @@ export default { 'Nenhum hook configurado para este evento.', 'To add hooks, edit settings.json directly or ask Qwen.': 'Para adicionar hooks, edite settings.json diretamente ou pergunte ao Qwen.', + 'Enter to select · Esc to go back': 'Enter para selecionar · Esc para voltar', + // Hooks - Config Detail Step + 'Hook details': 'Detalhes do Hook', + 'Event:': 'Evento:', + 'Extension:': 'Extensão:', + 'Desc:': 'Descrição:', + 'No hook config selected': 'Nenhuma configuração de hook selecionada', + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.': + 'Para modificar ou remover este hook, edite settings.json diretamente ou pergunte ao Qwen.', // Hooks - Source Project: 'Projeto', User: 'Usuário', diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index f7a137f5d..28d42b450 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -625,6 +625,15 @@ export default { 'Для этого события нет настроенных хуков.', 'To add hooks, edit settings.json directly or ask Qwen.': 'Чтобы добавить хуки, отредактируйте settings.json напрямую или спросите Qwen.', + 'Enter to select · Esc to go back': 'Enter для выбора · Esc для возврата', + // Hooks - Config Detail Step + 'Hook details': 'Детали хука', + 'Event:': 'Событие:', + 'Extension:': 'Расширение:', + 'Desc:': 'Описание:', + 'No hook config selected': 'Конфигурация хука не выбрана', + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.': + 'Чтобы изменить или удалить этот хук, отредактируйте settings.json напрямую или спросите Qwen.', // Hooks - Source Project: 'Проект', User: 'Пользователь', diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index 371e98b40..859ae6fc3 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -651,6 +651,15 @@ export default { 'No hooks configured for this event.': '此事件未配置 Hook。', 'To add hooks, edit settings.json directly or ask Qwen.': '要添加 Hook,请直接编辑 settings.json 或询问 Qwen。', + 'Enter to select · Esc to go back': 'Enter 选择 · Esc 返回', + // Hooks - Config Detail Step + 'Hook details': 'Hook 详情', + 'Event:': '事件:', + 'Extension:': '扩展:', + 'Desc:': '描述:', + 'No hook config selected': '未选择 Hook 配置', + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.': + '要修改或删除此 Hook,请直接编辑 settings.json 或询问 Qwen。', // Hooks - Source Project: '项目', User: '用户', diff --git a/packages/cli/src/ui/components/hooks/HookConfigDetailStep.test.tsx b/packages/cli/src/ui/components/hooks/HookConfigDetailStep.test.tsx new file mode 100644 index 000000000..2c7385215 --- /dev/null +++ b/packages/cli/src/ui/components/hooks/HookConfigDetailStep.test.tsx @@ -0,0 +1,343 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render } from 'ink-testing-library'; +import { + HookEventName, + HooksConfigSource, + HookType, +} from '@qwen-code/qwen-code-core'; +import { HookConfigDetailStep } from './HookConfigDetailStep.js'; +import type { HookEventDisplayInfo, HookConfigDisplayInfo } from './types.js'; + +// Mock i18n module +vi.mock('../../../i18n/index.js', () => ({ + t: vi.fn((key: string) => key), +})); + +// Mock useKeypress +vi.mock('../../hooks/useKeypress.js', () => ({ + useKeypress: vi.fn(), +})); + +// Mock useTerminalSize +vi.mock('../../hooks/useTerminalSize.js', () => ({ + useTerminalSize: vi.fn(() => ({ columns: 100, rows: 24 })), +})); + +// Mock semantic-colors +vi.mock('../../semantic-colors.js', () => ({ + theme: { + text: { + primary: 'white', + secondary: 'gray', + accent: 'cyan', + }, + border: { + default: 'gray', + }, + }, +})); + +describe('HookConfigDetailStep', () => { + const mockOnBack = vi.fn(); + + const createMockHookEvent = (): HookEventDisplayInfo => ({ + event: HookEventName.Stop, + shortDescription: 'Right before Qwen Code concludes its response', + description: '', + exitCodes: [ + { code: 0, description: 'stdout/stderr not shown' }, + { + code: 2, + description: 'show stderr to model and continue conversation', + }, + { code: 'Other', description: 'show stderr to user only' }, + ], + configs: [], + }); + + const createMockHookConfig = ( + source: HooksConfigSource = HooksConfigSource.User, + sourceDisplay = 'User Settings', + sourcePath?: string, + ): HookConfigDisplayInfo => ({ + config: { + type: HookType.Command, + command: '/path/to/hook.sh', + }, + source, + sourceDisplay, + sourcePath, + enabled: true, + }); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should render hook details title', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Hook details'); + }); + + it('should render event name', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Event:'); + expect(lastFrame()).toContain(HookEventName.Stop); + }); + + it('should render hook type', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Type:'); + expect(lastFrame()).toContain('command'); + }); + + it('should render source for User Settings', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(HooksConfigSource.User); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Source:'); + expect(lastFrame()).toContain('User Settings'); + }); + + it('should render source for Local Settings', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(HooksConfigSource.Project); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Local Settings'); + }); + + it('should render source for Extensions with path', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig( + HooksConfigSource.Extensions, + 'ralph-wiggum', + '/Users/test/.qwen/extensions/ralph-wiggum', + ); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Extensions'); + expect(lastFrame()).toContain('/Users/test/.qwen/extensions/ralph-wiggum'); + }); + + it('should render Extension field for extensions', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig( + HooksConfigSource.Extensions, + 'ralph-wiggum', + ); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Extension:'); + expect(lastFrame()).toContain('ralph-wiggum'); + }); + + it('should not render Extension field for non-extensions', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(HooksConfigSource.User); + + const { lastFrame } = render( + , + ); + + // Should not have Extension label for User Settings + const output = lastFrame(); + const extensionMatch = output?.match(/Extension:/g); + expect(extensionMatch).toBeNull(); + }); + + it('should render command', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Command:'); + expect(lastFrame()).toContain('/path/to/hook.sh'); + }); + + it('should render hook name if present', () => { + const hookEvent = createMockHookEvent(); + const hookConfig: HookConfigDisplayInfo = { + config: { + type: HookType.Command, + command: '/path/to/hook.sh', + name: 'My Hook', + }, + source: HooksConfigSource.User, + sourceDisplay: 'User Settings', + enabled: true, + }; + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Name:'); + expect(lastFrame()).toContain('My Hook'); + }); + + it('should render hook description if present', () => { + const hookEvent = createMockHookEvent(); + const hookConfig: HookConfigDisplayInfo = { + config: { + type: HookType.Command, + command: '/path/to/hook.sh', + description: 'A test hook', + }, + source: HooksConfigSource.User, + sourceDisplay: 'User Settings', + enabled: true, + }; + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Desc:'); + expect(lastFrame()).toContain('A test hook'); + }); + + it('should render help text', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('To modify or remove this hook'); + }); + + it('should render Esc hint', () => { + const hookEvent = createMockHookEvent(); + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain('Esc to go back'); + }); + + it('should handle different event types', () => { + const events = [ + HookEventName.PreToolUse, + HookEventName.PostToolUse, + HookEventName.UserPromptSubmit, + HookEventName.SessionStart, + ]; + + for (const event of events) { + const hookEvent: HookEventDisplayInfo = { + event, + shortDescription: 'Test', + description: '', + exitCodes: [], + configs: [], + }; + const hookConfig = createMockHookConfig(); + + const { lastFrame } = render( + , + ); + + expect(lastFrame()).toContain(event); + } + }); +}); diff --git a/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx b/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx new file mode 100644 index 000000000..e83345b43 --- /dev/null +++ b/packages/cli/src/ui/components/hooks/HookConfigDetailStep.tsx @@ -0,0 +1,179 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Box, Text } from 'ink'; +import { theme } from '../../semantic-colors.js'; +import { useKeypress } from '../../hooks/useKeypress.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; +import type { HookConfigDisplayInfo, HookEventDisplayInfo } from './types.js'; +import { HooksConfigSource } from '@qwen-code/qwen-code-core'; +import { t } from '../../../i18n/index.js'; + +interface HookConfigDetailStepProps { + hookEvent: HookEventDisplayInfo; + hookConfig: HookConfigDisplayInfo; + onBack: () => void; +} + +export function HookConfigDetailStep({ + hookEvent, + hookConfig, + onBack, +}: HookConfigDetailStepProps): React.JSX.Element { + const { columns: terminalWidth } = useTerminalSize(); + + useKeypress( + (key) => { + if (key.name === 'escape') { + onBack(); + } + }, + { isActive: true }, + ); + + // Get source display + const getSourceDisplay = (): string => { + switch (hookConfig.source) { + case HooksConfigSource.Project: + return t('Local Settings'); + case HooksConfigSource.User: + return t('User Settings'); + case HooksConfigSource.System: + return t('System Settings'); + case HooksConfigSource.Extensions: + return t('Extensions'); + default: + return hookConfig.source; + } + }; + + // Check if this is from an extension + const isFromExtension = hookConfig.source === HooksConfigSource.Extensions; + + // Get hook type display + const getHookTypeDisplay = (): string => { + switch (hookConfig.config.type) { + case 'command': + return 'command'; + default: + return hookConfig.config.type; + } + }; + + // Get command to display + const getCommand = (): string => { + if (hookConfig.config.type === 'command') { + return hookConfig.config.command; + } + return ''; + }; + + // Calculate box width for command display + const commandBoxWidth = Math.min(terminalWidth - 6, 80); + + // Label width for alignment (Extension: is the longest label) + const labelWidth = 12; + + return ( + + {/* Title */} + + + {t('Hook details')} + + + + {/* Event */} + + + {t('Event:')} + + {hookEvent.event} + + + {/* Type */} + + + {t('Type:')} + + {getHookTypeDisplay()} + + + {/* Source */} + + + {t('Source:')} + + {getSourceDisplay()} + {hookConfig.sourcePath && ( + ({hookConfig.sourcePath}) + )} + + + {/* Extension name (only for extensions) */} + {isFromExtension && hookConfig.sourceDisplay && ( + + + {t('Extension:')} + + {hookConfig.sourceDisplay} + + )} + + {/* Name (if exists) */} + {hookConfig.config.name && ( + + + {t('Name:')} + + {hookConfig.config.name} + + )} + + {/* Description (if exists) */} + {hookConfig.config.description && ( + + + {t('Desc:')} + + + {hookConfig.config.description} + + + )} + + {/* Command */} + + {t('Command:')} + + + {/* Command box */} + + {getCommand()} + + + {/* Help text */} + + + {t( + 'To modify or remove this hook, edit settings.json directly or ask Qwen to help.', + )} + + + + {/* Footer hint */} + + {t('Esc to go back')} + + + ); +} diff --git a/packages/cli/src/ui/components/hooks/HookDetailStep.test.tsx b/packages/cli/src/ui/components/hooks/HookDetailStep.test.tsx index 294a16952..4e53d0988 100644 --- a/packages/cli/src/ui/components/hooks/HookDetailStep.test.tsx +++ b/packages/cli/src/ui/components/hooks/HookDetailStep.test.tsx @@ -24,6 +24,11 @@ vi.mock('../../hooks/useKeypress.js', () => ({ useKeypress: vi.fn(), })); +// Mock useTerminalSize +vi.mock('../../hooks/useTerminalSize.js', () => ({ + useTerminalSize: vi.fn(() => ({ columns: 100, rows: 24 })), +})); + // Mock semantic-colors vi.mock('../../semantic-colors.js', () => ({ theme: { @@ -137,6 +142,7 @@ describe('HookDetailStep', () => { const output = lastFrame(); expect(output).toContain('Configured hooks'); + expect(output).toContain('[command]'); expect(output).toContain('hook-command-0'); expect(output).toContain('hook-command-1'); }); diff --git a/packages/cli/src/ui/components/hooks/HookDetailStep.tsx b/packages/cli/src/ui/components/hooks/HookDetailStep.tsx index d5078eb31..0a99a5cb7 100644 --- a/packages/cli/src/ui/components/hooks/HookDetailStep.tsx +++ b/packages/cli/src/ui/components/hooks/HookDetailStep.tsx @@ -8,25 +8,34 @@ import { useState } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../../semantic-colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import type { HookEventDisplayInfo } from './types.js'; +import { HooksConfigSource } from '@qwen-code/qwen-code-core'; import { getTranslatedSourceDisplayMap } from './constants.js'; import { t } from '../../../i18n/index.js'; interface HookDetailStepProps { hook: HookEventDisplayInfo; onBack: () => void; + onSelectConfig?: (index: number) => void; } export function HookDetailStep({ hook, onBack, + onSelectConfig, }: HookDetailStepProps): React.JSX.Element { const hasConfigs = hook.configs.length > 0; const [selectedIndex, setSelectedIndex] = useState(0); + const { columns: terminalWidth } = useTerminalSize(); // Get translated source display map const sourceDisplayMap = getTranslatedSourceDisplayMap(); + // Calculate column widths (command: 70%, source: 30%) + const commandWidth = Math.floor(terminalWidth * 0.65); + const sourceWidth = Math.floor(terminalWidth * 0.3); + // Handle keyboard navigation useKeypress( (key) => { @@ -39,12 +48,26 @@ export function HookDetailStep({ setSelectedIndex((prev) => Math.min(hook.configs.length - 1, prev + 1), ); + } else if (key.name === 'return' && onSelectConfig) { + onSelectConfig(selectedIndex); } } }, { isActive: true }, ); + // Get source display for config list + const getConfigSourceDisplay = (config: { + source: HooksConfigSource; + sourceDisplay: string; + }): string => { + if (config.source === HooksConfigSource.Extensions) { + // For extensions, sourceDisplay is the extension name + return `${sourceDisplayMap[HooksConfigSource.Extensions]} (${config.sourceDisplay})`; + } + return sourceDisplayMap[config.source] || config.source; + }; + return ( {/* Title */} @@ -87,31 +110,49 @@ export function HookDetailStep({ {hook.configs.map((config, index) => { const isSelected = index === selectedIndex; - const sourceDisplay = - sourceDisplayMap[config.source] || config.source; + const sourceDisplay = getConfigSourceDisplay(config); + const command = + config.config.type === 'command' ? config.config.command : ''; + const hookType = config.config.type; return ( - + {/* Left column: selector + command */} + + + + {isSelected ? '❯' : ' '} + + - {isSelected ? '❯' : ' '} + {`${index + 1}. [${hookType}] ${command}`} + + + {/* Right column: source */} + + + {sourceDisplay} - - {`${index + 1}. ${config.config.command}`} - - · - {sourceDisplay} ); })} - {t('Esc to go back')} + {onSelectConfig ? ( + + {t('Enter to select · Esc to go back')} + + ) : ( + {t('Esc to go back')} + )} ) : ( diff --git a/packages/cli/src/ui/components/hooks/HooksManagementDialog.tsx b/packages/cli/src/ui/components/hooks/HooksManagementDialog.tsx index 562cdfeb9..7d49e8e6a 100644 --- a/packages/cli/src/ui/components/hooks/HooksManagementDialog.tsx +++ b/packages/cli/src/ui/components/hooks/HooksManagementDialog.tsx @@ -22,6 +22,7 @@ import type { import { HOOKS_MANAGEMENT_STEPS } from './types.js'; import { HooksListStep } from './HooksListStep.js'; import { HookDetailStep } from './HookDetailStep.js'; +import { HookConfigDetailStep } from './HookConfigDetailStep.js'; import { DISPLAY_HOOK_EVENTS, getTranslatedSourceDisplayMap, @@ -42,6 +43,7 @@ export function HooksManagementDialog({ HOOKS_MANAGEMENT_STEPS.HOOKS_LIST, ]); const [selectedHookIndex, setSelectedHookIndex] = useState(-1); + const [selectedConfigIndex, setSelectedConfigIndex] = useState(-1); const [hooks, setHooks] = useState([]); const [isLoading, setIsLoading] = useState(true); const [loadError, setLoadError] = useState(null); @@ -107,7 +109,8 @@ export function HooksManagementDialog({ hookInfo.configs.push({ config: hookConfig, source: HooksConfigSource.Extensions, - sourceDisplay: sourceDisplayMap[HooksConfigSource.Extensions], + sourceDisplay: extension.name, + sourcePath: extension.path, enabled: true, }); } @@ -167,13 +170,23 @@ export function HooksManagementDialog({ }); }, [onClose]); - // Select hook + // Select hook event const handleSelectHook = useCallback((index: number) => { setSelectedHookIndex(index); + setSelectedConfigIndex(-1); setNavigationStack((prev) => [...prev, HOOKS_MANAGEMENT_STEPS.HOOK_DETAIL]); }, []); - // Selected hook + // Select hook config + const handleSelectConfig = useCallback((index: number) => { + setSelectedConfigIndex(index); + setNavigationStack((prev) => [ + ...prev, + HOOKS_MANAGEMENT_STEPS.HOOK_CONFIG_DETAIL, + ]); + }, []); + + // Selected hook event const selectedHook = useMemo(() => { if (selectedHookIndex >= 0 && selectedHookIndex < hooks.length) { return hooks[selectedHookIndex]; @@ -181,6 +194,18 @@ export function HooksManagementDialog({ return null; }, [hooks, selectedHookIndex]); + // Selected hook config + const selectedConfig = useMemo(() => { + if ( + selectedHook && + selectedConfigIndex >= 0 && + selectedConfigIndex < selectedHook.configs.length + ) { + return selectedHook.configs[selectedConfigIndex]; + } + return null; + }, [selectedHook, selectedConfigIndex]); + // Render based on current step const renderContent = () => { const currentStep = getCurrentStep(); @@ -220,7 +245,11 @@ export function HooksManagementDialog({ case HOOKS_MANAGEMENT_STEPS.HOOK_DETAIL: if (selectedHook) { return ( - + ); } return ( @@ -229,6 +258,24 @@ export function HooksManagementDialog({ ); + case HOOKS_MANAGEMENT_STEPS.HOOK_CONFIG_DETAIL: + if (selectedHook && selectedConfig) { + return ( + + ); + } + return ( + + + {t('No hook config selected')} + + + ); + default: return null; } diff --git a/packages/cli/src/ui/components/hooks/types.ts b/packages/cli/src/ui/components/hooks/types.ts index 821aa8af8..c4d3d92ee 100644 --- a/packages/cli/src/ui/components/hooks/types.ts +++ b/packages/cli/src/ui/components/hooks/types.ts @@ -36,6 +36,7 @@ export interface HookConfigDisplayInfo { config: HookConfig; source: HooksConfigSource; sourceDisplay: string; + sourcePath?: string; enabled: boolean; } @@ -45,6 +46,7 @@ export interface HookConfigDisplayInfo { export const HOOKS_MANAGEMENT_STEPS = { HOOKS_LIST: 'hooks_list', HOOK_DETAIL: 'hook_detail', + HOOK_CONFIG_DETAIL: 'hook_config_detail', } as const; export type HooksManagementStep =