104 lines
2.6 KiB
TypeScript
104 lines
2.6 KiB
TypeScript
import { homedir } from "os"
|
|
import { join } from "path"
|
|
import { existsSync } from "fs"
|
|
import type { ClaudeHooksConfig, HookMatcher, HookCommand } from "./types"
|
|
|
|
interface RawHookMatcher {
|
|
matcher?: string
|
|
pattern?: string
|
|
hooks: HookCommand[]
|
|
}
|
|
|
|
interface RawClaudeHooksConfig {
|
|
PreToolUse?: RawHookMatcher[]
|
|
PostToolUse?: RawHookMatcher[]
|
|
UserPromptSubmit?: RawHookMatcher[]
|
|
Stop?: RawHookMatcher[]
|
|
PreCompact?: RawHookMatcher[]
|
|
}
|
|
|
|
function normalizeHookMatcher(raw: RawHookMatcher): HookMatcher {
|
|
return {
|
|
matcher: raw.matcher ?? raw.pattern ?? "*",
|
|
hooks: raw.hooks,
|
|
}
|
|
}
|
|
|
|
function normalizeHooksConfig(raw: RawClaudeHooksConfig): ClaudeHooksConfig {
|
|
const result: ClaudeHooksConfig = {}
|
|
const eventTypes: (keyof RawClaudeHooksConfig)[] = [
|
|
"PreToolUse",
|
|
"PostToolUse",
|
|
"UserPromptSubmit",
|
|
"Stop",
|
|
"PreCompact",
|
|
]
|
|
|
|
for (const eventType of eventTypes) {
|
|
if (raw[eventType]) {
|
|
result[eventType] = raw[eventType].map(normalizeHookMatcher)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
export function getClaudeSettingsPaths(customPath?: string): string[] {
|
|
const home = homedir()
|
|
const paths = [
|
|
join(home, ".claude", "settings.json"),
|
|
join(process.cwd(), ".claude", "settings.json"),
|
|
join(process.cwd(), ".claude", "settings.local.json"),
|
|
]
|
|
|
|
if (customPath && existsSync(customPath)) {
|
|
paths.unshift(customPath)
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
function mergeHooksConfig(
|
|
base: ClaudeHooksConfig,
|
|
override: ClaudeHooksConfig
|
|
): ClaudeHooksConfig {
|
|
const result: ClaudeHooksConfig = { ...base }
|
|
const eventTypes: (keyof ClaudeHooksConfig)[] = [
|
|
"PreToolUse",
|
|
"PostToolUse",
|
|
"UserPromptSubmit",
|
|
"Stop",
|
|
"PreCompact",
|
|
]
|
|
for (const eventType of eventTypes) {
|
|
if (override[eventType]) {
|
|
result[eventType] = [...(base[eventType] || []), ...override[eventType]]
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
export async function loadClaudeHooksConfig(
|
|
customSettingsPath?: string
|
|
): Promise<ClaudeHooksConfig | null> {
|
|
const paths = getClaudeSettingsPaths(customSettingsPath)
|
|
let mergedConfig: ClaudeHooksConfig = {}
|
|
|
|
for (const settingsPath of paths) {
|
|
if (existsSync(settingsPath)) {
|
|
try {
|
|
const content = await Bun.file(settingsPath).text()
|
|
const settings = JSON.parse(content) as { hooks?: RawClaudeHooksConfig }
|
|
if (settings.hooks) {
|
|
const normalizedHooks = normalizeHooksConfig(settings.hooks)
|
|
mergedConfig = mergeHooksConfig(mergedConfig, normalizedHooks)
|
|
}
|
|
} catch {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
return Object.keys(mergedConfig).length > 0 ? mergedConfig : null
|
|
}
|