From 9159aa52cbe99c0820c30a19843043ea141c1106 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 25 Jan 2026 20:03:52 -0500 Subject: [PATCH] feat(websearch): add removeHookConfig function - add removeHookConfig() to remove CCS hook from settings.json - only removes hooks matching CCS pattern (.ccs/hooks/websearch-transformer) - preserves user-defined WebSearch hooks - cleans up empty hooks object after removal - export from barrel file Closes #317 --- src/utils/websearch/hook-config.ts | 63 ++++++++++++++++++++++++++++++ src/utils/websearch/index.ts | 3 ++ 2 files changed, 66 insertions(+) diff --git a/src/utils/websearch/hook-config.ts b/src/utils/websearch/hook-config.ts index 5a806783..954e843b 100644 --- a/src/utils/websearch/hook-config.ts +++ b/src/utils/websearch/hook-config.ts @@ -178,3 +178,66 @@ export function ensureHookConfig(): boolean { return false; } } + +/** + * Remove CCS WebSearch hook from ~/.claude/settings.json + * Only removes hooks matching: matcher='WebSearch' AND command contains '.ccs/hooks/websearch-transformer' + * Preserves user-defined WebSearch hooks + */ +export function removeHookConfig(): boolean { + try { + if (!fs.existsSync(CLAUDE_SETTINGS_PATH)) { + return true; // Nothing to remove + } + + const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8'); + let settings: Record; + try { + settings = JSON.parse(content); + } catch { + return false; // Malformed JSON, don't touch + } + + const hooks = settings.hooks as Record | undefined; + if (!hooks?.PreToolUse) { + return true; // No hooks to remove + } + + const originalLength = hooks.PreToolUse.length; + hooks.PreToolUse = hooks.PreToolUse.filter((h: unknown) => { + const hook = h as Record; + if (hook.matcher !== 'WebSearch') return true; // Keep non-WebSearch hooks + + const hookArray = hook.hooks as Array> | undefined; + if (!hookArray?.[0]?.command) return true; // Keep malformed entries + + const command = hookArray[0].command as string; + return !command.includes('.ccs/hooks/websearch-transformer'); // Remove if CCS hook + }); + + if (hooks.PreToolUse.length === originalLength) { + return true; // Nothing changed + } + + // Clean up empty hooks object + if (hooks.PreToolUse.length === 0) { + delete hooks.PreToolUse; + } + if (Object.keys(hooks).length === 0) { + delete settings.hooks; + } + + fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8'); + + if (process.env.CCS_DEBUG) { + console.error(info('Removed WebSearch hook from settings.json')); + } + + return true; + } catch (error) { + if (process.env.CCS_DEBUG) { + console.error(warn(`Failed to remove hook config: ${(error as Error).message}`)); + } + return false; + } +} diff --git a/src/utils/websearch/index.ts b/src/utils/websearch/index.ts index 8c8826f1..b9a9dfa4 100644 --- a/src/utils/websearch/index.ts +++ b/src/utils/websearch/index.ts @@ -41,6 +41,9 @@ export { uninstallWebSearchHook, } from './hook-installer'; +// Hook Config (removal) +export { removeHookConfig } from './hook-config'; + // Hook Environment export { getWebSearchHookEnv } from './hook-env';