diff --git a/hooks/caveman-activate.js b/hooks/caveman-activate.js index ac080aa..0becf06 100644 --- a/hooks/caveman-activate.js +++ b/hooks/caveman-activate.js @@ -9,6 +9,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const { getDefaultMode } = require('./caveman-config'); const claudeDir = path.join(os.homedir(), '.claude'); const flagPath = path.join(claudeDir, '.caveman-active'); @@ -17,7 +18,7 @@ const settingsPath = path.join(claudeDir, 'settings.json'); // 1. Write flag file try { fs.mkdirSync(path.dirname(flagPath), { recursive: true }); - fs.writeFileSync(flagPath, 'full'); + fs.writeFileSync(flagPath, getDefaultMode()); } catch (e) { // Silent fail -- flag is best-effort, don't block the hook } diff --git a/hooks/caveman-config.js b/hooks/caveman-config.js new file mode 100644 index 0000000..15abdc8 --- /dev/null +++ b/hooks/caveman-config.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node +// caveman — shared configuration resolver +// +// Resolution order for default mode: +// 1. CAVEMAN_DEFAULT_MODE environment variable +// 2. Config file defaultMode field: +// - $XDG_CONFIG_HOME/caveman/config.json (any platform, if set) +// - ~/.config/caveman/config.json (macOS / Linux fallback) +// - %APPDATA%\caveman\config.json (Windows fallback) +// 3. 'full' + +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const VALID_MODES = [ + 'lite', 'full', 'ultra', + 'wenyan-lite', 'wenyan', 'wenyan-full', 'wenyan-ultra', + 'commit', 'review', 'compress' +]; + +function getConfigDir() { + if (process.env.XDG_CONFIG_HOME) { + return path.join(process.env.XDG_CONFIG_HOME, 'caveman'); + } + if (process.platform === 'win32') { + return path.join( + process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), + 'caveman' + ); + } + return path.join(os.homedir(), '.config', 'caveman'); +} + +function getConfigPath() { + return path.join(getConfigDir(), 'config.json'); +} + +function getDefaultMode() { + // 1. Environment variable (highest priority) + const envMode = process.env.CAVEMAN_DEFAULT_MODE; + if (envMode && VALID_MODES.includes(envMode.toLowerCase())) { + return envMode.toLowerCase(); + } + + // 2. Config file + try { + const configPath = getConfigPath(); + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + if (config.defaultMode && VALID_MODES.includes(config.defaultMode.toLowerCase())) { + return config.defaultMode.toLowerCase(); + } + } catch (e) { + // Config file doesn't exist or is invalid — fall through + } + + // 3. Default + return 'full'; +} + +module.exports = { getDefaultMode, getConfigDir, getConfigPath, VALID_MODES }; diff --git a/hooks/caveman-mode-tracker.js b/hooks/caveman-mode-tracker.js index da708d7..e74491c 100644 --- a/hooks/caveman-mode-tracker.js +++ b/hooks/caveman-mode-tracker.js @@ -5,6 +5,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const { getDefaultMode } = require('./caveman-config'); const flagPath = path.join(os.homedir(), '.claude', '.caveman-active'); @@ -35,7 +36,7 @@ process.stdin.on('end', () => { else if (arg === 'wenyan-lite') mode = 'wenyan-lite'; else if (arg === 'wenyan' || arg === 'wenyan-full') mode = 'wenyan'; else if (arg === 'wenyan-ultra') mode = 'wenyan-ultra'; - else mode = 'full'; + else mode = getDefaultMode(); } if (mode) { diff --git a/hooks/install.ps1 b/hooks/install.ps1 index 255e4fd..00ec4ea 100644 --- a/hooks/install.ps1 +++ b/hooks/install.ps1 @@ -24,7 +24,7 @@ $HooksDir = Join-Path $ClaudeDir "hooks" $Settings = Join-Path $ClaudeDir "settings.json" $RepoUrl = "https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks" -$HookFiles = @("caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") +$HookFiles = @("caveman-config.js", "caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") # Resolve source — works from repo clone or remote $ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { $null } diff --git a/hooks/install.sh b/hooks/install.sh index c7b6a66..ba235e0 100755 --- a/hooks/install.sh +++ b/hooks/install.sh @@ -37,7 +37,7 @@ HOOKS_DIR="$CLAUDE_DIR/hooks" SETTINGS="$CLAUDE_DIR/settings.json" REPO_URL="https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks" -HOOK_FILES=("caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") +HOOK_FILES=("caveman-config.js" "caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") # Resolve source — works from repo clone or curl pipe SCRIPT_DIR="" diff --git a/hooks/uninstall.ps1 b/hooks/uninstall.ps1 index c734ad6..a00ec83 100644 --- a/hooks/uninstall.ps1 +++ b/hooks/uninstall.ps1 @@ -11,7 +11,7 @@ $HooksDir = Join-Path $ClaudeDir "hooks" $Settings = Join-Path $ClaudeDir "settings.json" $FlagFile = Join-Path $ClaudeDir ".caveman-active" -$HookFiles = @("caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") +$HookFiles = @("caveman-config.js", "caveman-activate.js", "caveman-mode-tracker.js", "caveman-statusline.sh", "caveman-statusline.ps1") # Detect if caveman is installed as a plugin $PluginInstalled = $false diff --git a/hooks/uninstall.sh b/hooks/uninstall.sh index b602fcf..fec9acd 100755 --- a/hooks/uninstall.sh +++ b/hooks/uninstall.sh @@ -10,7 +10,7 @@ HOOKS_DIR="$CLAUDE_DIR/hooks" SETTINGS="$CLAUDE_DIR/settings.json" FLAG_FILE="$CLAUDE_DIR/.caveman-active" -HOOK_FILES=("caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") +HOOK_FILES=("caveman-config.js" "caveman-activate.js" "caveman-mode-tracker.js" "caveman-statusline.sh") # Detect if caveman is installed as a plugin (check plugin cache) PLUGIN_INSTALLED=0 diff --git a/tests/verify_repo.py b/tests/verify_repo.py index c3ee7ab..73def57 100644 --- a/tests/verify_repo.py +++ b/tests/verify_repo.py @@ -105,12 +105,19 @@ def verify_manifests_and_syntax() -> None: for path in manifest_paths: read_json(path) + run(["node", "--check", "hooks/caveman-config.js"]) run(["node", "--check", "hooks/caveman-activate.js"]) run(["node", "--check", "hooks/caveman-mode-tracker.js"]) run(["bash", "-n", "hooks/install.sh"]) run(["bash", "-n", "hooks/uninstall.sh"]) run(["bash", "-n", "hooks/caveman-statusline.sh"]) + # Ensure install/uninstall scripts include caveman-config.js + install_sh = (ROOT / "hooks/install.sh").read_text() + uninstall_sh = (ROOT / "hooks/uninstall.sh").read_text() + ensure("caveman-config.js" in install_sh, "install.sh missing caveman-config.js") + ensure("caveman-config.js" in uninstall_sh, "uninstall.sh missing caveman-config.js") + print("JSON manifests and JS/bash syntax OK") @@ -120,6 +127,8 @@ def verify_powershell_static() -> None: uninstall_text = (ROOT / "hooks/uninstall.ps1").read_text() statusline_text = (ROOT / "hooks/caveman-statusline.ps1").read_text() + ensure("caveman-config.js" in install_text, "install.ps1 missing caveman-config.js") + ensure("caveman-config.js" in uninstall_text, "uninstall.ps1 missing caveman-config.js") ensure("caveman-statusline.ps1" in install_text, "install.ps1 missing statusline.ps1") ensure("caveman-statusline.ps1" in uninstall_text, "uninstall.ps1 missing statusline.ps1") ensure("-AsHashtable" not in install_text, "install.ps1 should stay compatible with Windows PowerShell 5.1") @@ -220,6 +229,16 @@ def verify_hook_install_flow() -> None: ensure("STATUSLINE SETUP NEEDED" not in activate.stdout, "activation should stay quiet when custom statusline exists") ensure((claude_dir / ".caveman-active").read_text() == "full", "activation flag should default to full") + # Test configurable default mode via CAVEMAN_DEFAULT_MODE env var + activate_custom = run( + ["node", "hooks/caveman-activate.js"], + env={"HOME": str(home), "CAVEMAN_DEFAULT_MODE": "ultra"}, + ) + ensure("CAVEMAN MODE ACTIVE." in activate_custom.stdout, "activation with custom default missing banner") + ensure((claude_dir / ".caveman-active").read_text() == "ultra", "CAVEMAN_DEFAULT_MODE=ultra should set flag to ultra") + # Reset back to full for subsequent tests + (claude_dir / ".caveman-active").write_text("full") + run( ["node", "hooks/caveman-mode-tracker.js"], env={"HOME": str(home)},