diff --git a/README.md b/README.md index 67fdd1b..59b222a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,11 @@ LLM providers like Anthropic and OpenAI cache prompts based on exact prefix matc ## Configuration -DCP uses its own config file (`~/.config/opencode/dcp.jsonc` or `.opencode/dcp.jsonc`), created automatically on first run. +DCP uses its own config file: + +- Global: `~/.config/opencode/dcp.jsonc` (or `dcp.json`), created automatically on first run +- Custom config directory: `$OPENCODE_CONFIG_DIR/dcp.jsonc` (or `dcp.json`), if `OPENCODE_CONFIG_DIR` is set +- Project: `.opencode/dcp.jsonc` (or `dcp.json`) in your project’s `.opencode` directory
Default Configuration (click to expand) @@ -104,7 +108,9 @@ The `protectedTools` arrays in each strategy add to this default list. ### Config Precedence -Settings are merged in order: **Defaults** → **Global** (`~/.config/opencode/dcp.jsonc`) → **Project** (`.opencode/dcp.jsonc`). Each level overrides the previous, so project settings take priority over global, which takes priority over defaults. +Settings are merged in order: +Defaults → Global (`~/.config/opencode/dcp.jsonc`) → Config Dir (`$OPENCODE_CONFIG_DIR/dcp.jsonc`) → Project (`.opencode/dcp.jsonc`). +Each level overrides the previous, so project settings take priority over config-dir and global, which take priority over defaults. Restart OpenCode after making config changes. diff --git a/lib/config.ts b/lib/config.ts index 2c14144..eb90adc 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -259,14 +259,30 @@ function findOpencodeDir(startDir: string): string | null { return null } -function getConfigPaths(ctx?: PluginInput): { global: string | null, project: string | null } { +function getConfigPaths(ctx?: PluginInput): { global: string | null, configDir: string | null, project: string | null} { + + // Global: ~/.config/opencode/dcp.jsonc|json let globalPath: string | null = null if (existsSync(GLOBAL_CONFIG_PATH_JSONC)) { globalPath = GLOBAL_CONFIG_PATH_JSONC } else if (existsSync(GLOBAL_CONFIG_PATH_JSON)) { globalPath = GLOBAL_CONFIG_PATH_JSON } - + + // Custom config directory: $OPENCODE_CONFIG_DIR/dcp.jsonc|json + let configDirPath: string | null = null + const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR + if (opencodeConfigDir) { + const configJsonc = join(opencodeConfigDir, 'dcp.jsonc') + const configJson = join(opencodeConfigDir, 'dcp.json') + if (existsSync(configJsonc)) { + configDirPath = configJsonc + } else if (existsSync(configJson)) { + configDirPath = configJson + } + } + + // Project: /.opencode/dcp.jsonc|json let projectPath: string | null = null if (ctx?.directory) { const opencodeDir = findOpencodeDir(ctx.directory) @@ -281,7 +297,7 @@ function getConfigPaths(ctx?: PluginInput): { global: string | null, project: st } } - return { global: globalPath, project: projectPath } + return { global: globalPath, configDir: configDirPath, project: projectPath } } function createDefaultConfig(): void { @@ -425,6 +441,7 @@ function deepCloneConfig(config: PluginConfig): PluginConfig { } } + export function getConfig(ctx: PluginInput): PluginConfig { let config = deepCloneConfig(defaultConfig) const configPaths = getConfigPaths(ctx) @@ -461,6 +478,35 @@ export function getConfig(ctx: PluginInput): PluginConfig { createDefaultConfig() } + // Load and merge $OPENCODE_CONFIG_DIR/dcp.jsonc|json (overrides global) + if (configPaths.configDir) { + const result = loadConfigFile(configPaths.configDir) + if (result.parseError) { + setTimeout(async () => { + try { + ctx.client.tui.showToast({ + body: { + title: "DCP: Invalid configDir config", + message: `${configPaths.configDir}\n${result.parseError}\nUsing global/default values`, + variant: "warning", + duration: 7000 + } + }) + } catch {} + }, 7000) + } else if (result.data) { + // Validate config keys and types + showConfigValidationWarnings(ctx, configPaths.configDir, result.data, true) + config = { + enabled: result.data.enabled ?? config.enabled, + debug: result.data.debug ?? config.debug, + showUpdateToasts: result.data.showUpdateToasts ?? config.showUpdateToasts, + pruningSummary: result.data.pruningSummary ?? config.pruningSummary, + strategies: mergeStrategies(config.strategies, result.data.strategies as any) + } + } + } + // Load and merge project config (overrides global) if (configPaths.project) { const result = loadConfigFile(configPaths.project)