feat(hooks): add HTTP hook handler support

Add type: "http" hook support matching Claude Code's HTTP hook specification.
HTTP hooks send POST requests with JSON body, support env var interpolation
in headers via allowedEnvVars, and configurable timeout.

New files:
- execute-http-hook.ts: HTTP hook execution with env var interpolation
- dispatch-hook.ts: Unified dispatcher for command and HTTP hooks
- execute-http-hook.test.ts: 14 tests covering all HTTP hook scenarios

Modified files:
- types.ts: Added HookHttp interface, HookAction union type
- config.ts: Updated to accept HookAction in raw hook matchers
- pre-tool-use/post-tool-use/stop/user-prompt-submit/pre-compact:
  Updated all 5 executors to dispatch HTTP hooks via dispatchHook()
- plugin-loader/types.ts: Added "http" to HookEntry type union
This commit is contained in:
YeonGyu-Kim
2026-02-28 11:38:34 +09:00
parent 866bd50dca
commit 43dfdb2380
11 changed files with 397 additions and 57 deletions
+5 -10
View File
@@ -3,8 +3,8 @@ import type {
StopOutput,
ClaudeHooksConfig,
} from "./types"
import { findMatchingHooks, executeHookCommand, log } from "../../shared"
import { DEFAULT_CONFIG } from "./plugin-config"
import { findMatchingHooks, log } from "../../shared"
import { dispatchHook } from "./dispatch-hook"
import { getTodoPath } from "./todo"
import { isHookCommandDisabled, type PluginExtendedConfig } from "./config-loader"
@@ -68,19 +68,14 @@ export async function executeStopHooks(
for (const matcher of matchers) {
if (!matcher.hooks || matcher.hooks.length === 0) continue
for (const hook of matcher.hooks) {
if (hook.type !== "command") continue
if (hook.type !== "command" && hook.type !== "http") continue
if (isHookCommandDisabled("Stop", hook.command, extendedConfig ?? null)) {
if (hook.type === "command" && isHookCommandDisabled("Stop", hook.command, extendedConfig ?? null)) {
log("Stop hook command skipped (disabled by config)", { command: hook.command })
continue
}
const result = await executeHookCommand(
hook.command,
JSON.stringify(stdinData),
ctx.cwd,
{ forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath }
)
const result = await dispatchHook(hook, JSON.stringify(stdinData), ctx.cwd)
// Check exit code first - exit code 2 means block
if (result.exitCode === 2) {