/**
* @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]) => (
))}
);
};