Files
oh-my-opencode/src/hooks/claude-code-hooks/config-loader.ts
T

118 lines
3.1 KiB
TypeScript
Raw Normal View History

2025-12-09 17:48:28 +09:00
import { existsSync } from "fs"
import { homedir } from "os"
import { join } from "path"
import type { ClaudeHookEvent } from "./types"
import { log } from "../../shared/logger"
export interface DisabledHooksConfig {
Stop?: string[]
PreToolUse?: string[]
PostToolUse?: string[]
UserPromptSubmit?: string[]
}
export interface PluginExtendedConfig {
disabledHooks?: DisabledHooksConfig
}
const USER_CONFIG_PATH = join(homedir(), ".config", "opencode", "opencode-cc-plugin.json")
function getProjectConfigPath(): string {
return join(process.cwd(), ".opencode", "opencode-cc-plugin.json")
}
const DEFAULT_DISABLED_HOOKS: DisabledHooksConfig = {
PostToolUse: [
"inject_rules\\.py$",
"inject_readme\\.py$",
"inject_knowledge\\.py$",
"remind.*rules.*\\.py$",
],
}
2025-12-09 17:48:28 +09:00
async function loadConfigFromPath(path: string): Promise<PluginExtendedConfig | null> {
if (!existsSync(path)) {
return null
}
try {
const content = await Bun.file(path).text()
return JSON.parse(content) as PluginExtendedConfig
} catch (error) {
log("Failed to load config", { path, error })
return null
}
}
function mergeDisabledHooks(
base: DisabledHooksConfig | undefined,
override: DisabledHooksConfig | undefined
): DisabledHooksConfig {
if (!override) return base ?? {}
if (!base) return override
return {
Stop: [...(base.Stop ?? []), ...(override.Stop ?? [])],
PreToolUse: [...(base.PreToolUse ?? []), ...(override.PreToolUse ?? [])],
PostToolUse: [...(base.PostToolUse ?? []), ...(override.PostToolUse ?? [])],
UserPromptSubmit: [...(base.UserPromptSubmit ?? []), ...(override.UserPromptSubmit ?? [])],
2025-12-09 17:48:28 +09:00
}
}
export async function loadPluginExtendedConfig(): Promise<PluginExtendedConfig> {
const userConfig = await loadConfigFromPath(USER_CONFIG_PATH)
const projectConfig = await loadConfigFromPath(getProjectConfigPath())
const merged: PluginExtendedConfig = {
disabledHooks: mergeDisabledHooks(
DEFAULT_DISABLED_HOOKS,
mergeDisabledHooks(
userConfig?.disabledHooks,
projectConfig?.disabledHooks
)
2025-12-09 17:48:28 +09:00
),
}
if (userConfig || projectConfig) {
log("Plugin extended config loaded", {
userConfigExists: userConfig !== null,
projectConfigExists: projectConfig !== null,
mergedDisabledHooks: merged.disabledHooks,
})
}
return merged
}
const regexCache = new Map<string, RegExp>()
function getRegex(pattern: string): RegExp {
let regex = regexCache.get(pattern)
if (!regex) {
try {
regex = new RegExp(pattern)
regexCache.set(pattern, regex)
} catch {
regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
regexCache.set(pattern, regex)
}
}
return regex
}
export function isHookCommandDisabled(
eventType: ClaudeHookEvent,
command: string,
config: PluginExtendedConfig | null
): boolean {
if (!config?.disabledHooks) return false
const patterns = config.disabledHooks[eventType]
if (!patterns || patterns.length === 0) return false
return patterns.some((pattern) => {
const regex = getRegex(pattern)
return regex.test(command)
})
}