feat: add disable all hook toggle (#726)

This commit is contained in:
jnjpng
2026-01-28 15:40:29 -08:00
committed by GitHub
parent 52c8d6f7b4
commit 5b75244476
6 changed files with 178 additions and 18 deletions

View File

@@ -2,7 +2,7 @@
// Interactive TUI for managing hooks configuration
import { Box, Text, useInput } from "ink";
import { memo, useCallback, useEffect, useState } from "react";
import { memo, useCallback, useEffect, useRef, useState } from "react";
import {
type HookEvent,
type HookMatcher,
@@ -18,11 +18,14 @@ import {
countTotalHooks,
type HookMatcherWithSource,
type HookWithSource,
isUserHooksDisabled,
loadMatchersWithSource,
loadSimpleMatchersWithSource,
removeHook,
type SaveLocation,
setHooksDisabled,
} from "../../hooks/writer";
import { settingsManager } from "../../settings-manager";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
import { PasteAwareTextInput } from "./PasteAwareTextInput";
@@ -145,6 +148,9 @@ export const HooksManager = memo(function HooksManager({
// Dynamic tool names from agent
const [toolNames, setToolNames] = useState<string[]>(FALLBACK_TOOL_NAMES);
// Track whether all hooks are disabled
const [hooksDisabled, setHooksDisabledState] = useState(isUserHooksDisabled);
// Fetch agent tools on mount
useEffect(() => {
if (!agentId) return;
@@ -188,9 +194,30 @@ export const HooksManager = memo(function HooksManager({
// Refresh counts - called when hooks change
const refreshCounts = useCallback(() => {
setTotalHooks(countTotalHooks());
setHooksDisabledState(isUserHooksDisabled());
}, []);
// Load total hooks count on mount and when returning to events screen
// Track if initial settings load has been done
const initialLoadDone = useRef(false);
// Ensure settings are loaded before counting hooks (runs once on mount)
useEffect(() => {
if (initialLoadDone.current) return;
initialLoadDone.current = true;
const loadSettings = async () => {
try {
await settingsManager.loadProjectSettings();
await settingsManager.loadLocalProjectSettings();
} catch {
// Settings may already be loaded or not available
}
refreshCounts();
};
loadSettings();
}, [refreshCounts]);
// Refresh counts when returning to events screen
useEffect(() => {
if (screen === "events") {
refreshCounts();
@@ -267,6 +294,13 @@ export const HooksManager = memo(function HooksManager({
setSelectedIndex(0);
}, [deleteHookIndex, selectedEvent, hooks, loadHooks, refreshCounts]);
// Handle toggling the "disable all hooks" setting
const handleToggleDisableAll = useCallback(() => {
const newValue = !hooksDisabled;
setHooksDisabled(newValue);
setHooksDisabledState(newValue);
}, [hooksDisabled]);
useInput((input, key) => {
// CTRL-C: immediately cancel
if (key.ctrl && input === "c") {
@@ -276,17 +310,26 @@ export const HooksManager = memo(function HooksManager({
// Handle each screen
if (screen === "events") {
// Total items: 1 (disable toggle) + HOOK_EVENTS.length
const totalItems = 1 + HOOK_EVENTS.length;
if (key.upArrow) {
setSelectedIndex((prev) => Math.max(0, prev - 1));
} else if (key.downArrow) {
setSelectedIndex((prev) => Math.min(HOOK_EVENTS.length - 1, prev + 1));
setSelectedIndex((prev) => Math.min(totalItems - 1, prev + 1));
} else if (key.return) {
const selected = HOOK_EVENTS[selectedIndex];
if (selected) {
setSelectedEvent(selected.event);
loadHooks(selected.event);
setScreen("hooks-list");
setSelectedIndex(0);
if (selectedIndex === 0) {
// Toggle "disable all hooks"
handleToggleDisableAll();
} else {
// Select a hook event (index is shifted by 1)
const selected = HOOK_EVENTS[selectedIndex - 1];
if (selected) {
setSelectedEvent(selected.event);
loadHooks(selected.event);
setScreen("hooks-list");
setSelectedIndex(0);
}
}
} else if (key.escape) {
onClose();
@@ -372,20 +415,45 @@ export const HooksManager = memo(function HooksManager({
// Render Events List
if (screen === "events") {
const disableToggleSelected = selectedIndex === 0;
const disableToggleLabel = hooksDisabled
? "Enable all hooks"
: "Disable all hooks";
const titleBase = " Hooks";
const titleSuffix = hooksDisabled ? " (disabled)" : "";
const hooksCountText = `${totalHooks} hooks `;
const titlePadding =
boxWidth -
titleBase.length -
titleSuffix.length -
hooksCountText.length -
2;
return (
<Box flexDirection="column" paddingX={1}>
<Text>{boxTop(boxWidth)}</Text>
<Text>
{boxLine(
` Hooks${" ".repeat(boxWidth - 20)}${totalHooks} hooks `,
boxWidth,
)}
{BOX_VERTICAL}
{titleBase}
<Text color="red">{titleSuffix}</Text>
{" ".repeat(Math.max(0, titlePadding))}
{hooksCountText}
{BOX_VERTICAL}
</Text>
<Text>{boxBottom(boxWidth)}</Text>
<Text> </Text>
{/* Disable all hooks toggle - first item */}
<Text>
<Text color={disableToggleSelected ? colors.input.prompt : undefined}>
{disableToggleSelected ? "" : " "} 1.
</Text>
<Text dimColor> {disableToggleLabel}</Text>
</Text>
{/* Hook events */}
{HOOK_EVENTS.map((item, index) => {
const isSelected = index === selectedIndex;
const isSelected = index + 1 === selectedIndex;
const hookCount = countHooksForEvent(item.event);
const prefix = isSelected ? "" : " ";
const countStr = hookCount > 0 ? ` (${hookCount})` : "";
@@ -393,7 +461,7 @@ export const HooksManager = memo(function HooksManager({
return (
<Text key={item.event}>
<Text color={isSelected ? colors.input.prompt : undefined}>
{prefix} {index + 1}. {item.event}
{prefix} {index + 2}. {item.event}
</Text>
<Text dimColor> - {item.description}</Text>
<Text color="yellow">{countStr}</Text>