/** * @license * Copyright 2025 Vybestack LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../../colors.js'; import type { HookRegistryEntry } from '@vybestack/llxprt-code-core'; import { firstNonEmptyString } from '../../../utils/coalesce.js'; import { getBorderStyle } from '../../contexts/UnicodeRenderingContext.js'; interface HooksListProps { hooks: readonly HookRegistryEntry[]; } const SecurityWarning: React.FC = () => ( Security Warning: Hooks can execute arbitrary commands on your system. Only use hooks from sources you trust. ); const buildHookDetails = (entry: HookRegistryEntry): string[] => { const details: string[] = []; details.push(entry.source); if (entry.config.name && entry.config.command) { details.push(`command: ${entry.config.command}`); } if (entry.matcher) { details.push(`matcher: ${entry.matcher}`); } if (entry.sequential === true) { details.push('sequential'); } if (entry.config.timeout != null && entry.config.timeout > 0) { details.push(`timeout: ${entry.config.timeout}ms`); } return details; }; interface HookEntryProps { entry: HookRegistryEntry; index: number; eventName: string; } const HookEntry: React.FC = ({ entry, index, eventName }) => { const commandName = firstNonEmptyString( entry.config.name, entry.config.command, `${entry.config.type} hook`, ); const statusColor = entry.enabled ? Colors.AccentGreen : Colors.DimComment; const statusText = entry.enabled ? 'enabled' : 'disabled'; const details = buildHookDetails(entry); return ( {commandName} [{statusText}] {entry.config.description && ( {entry.config.description} )} {details.join(' • ')} ); }; interface EventGroupProps { eventName: string; entries: HookRegistryEntry[]; } const EventGroup: React.FC = ({ eventName, entries }) => ( {eventName} {entries.map((entry, index) => ( ))} ); const HooksTip: React.FC = () => ( Tip: Use /hooks enable <name> or /hooks disable <name> to manage hooks ); const groupHooksByEvent = ( hooks: readonly HookRegistryEntry[], ): Map => { const byEvent = new Map(); for (const entry of hooks) { const eventName = entry.eventName; if (!byEvent.has(eventName)) { byEvent.set(eventName, []); } byEvent.get(eventName)!.push(entry); } return byEvent; }; export const HooksList: React.FC = ({ hooks }) => { if (hooks.length === 0) { return No hooks configured.; } const byEvent = groupHooksByEvent(hooks); return ( {Array.from(byEvent.entries()).map(([eventName, entries]) => ( ))} ); };