Files
oh-my-openagent/src/hooks/claude-code-hooks/config.ts
Sisyphus cad6425a4a fix: honor CLAUDE_CONFIG_DIR environment variable (#261)
Fixes #255

- Add getClaudeConfigDir() utility function that respects CLAUDE_CONFIG_DIR env var
- Update all hardcoded ~/.claude paths to use the new utility
- Add comprehensive tests for getClaudeConfigDir()
- Maintain backward compatibility with default ~/.claude when env var is not set

Files updated:
- src/shared/claude-config-dir.ts (new utility)
- src/shared/claude-config-dir.test.ts (tests)
- src/hooks/claude-code-hooks/config.ts
- src/hooks/claude-code-hooks/todo.ts
- src/hooks/claude-code-hooks/transcript.ts
- src/features/claude-code-command-loader/loader.ts
- src/features/claude-code-agent-loader/loader.ts
- src/features/claude-code-skill-loader/loader.ts
- src/features/claude-code-mcp-loader/loader.ts
- src/tools/session-manager/constants.ts
- src/tools/slashcommand/tools.ts

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
2025-12-26 23:28:33 +09:00

104 lines
2.6 KiB
TypeScript

import { join } from "path"
import { existsSync } from "fs"
import { getClaudeConfigDir } from "../../shared"
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 claudeConfigDir = getClaudeConfigDir()
const paths = [
join(claudeConfigDir, "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
}