Add NPM distribution support with hybrid installation approach

Major changes:
- Add package.json with NPM packaging configuration
- Create Node.js CLI interface (bin/claude-hooks.js) with full command set
- Convert bash scripts to Python for better npm integration
- Add npm postinstall/preuninstall hooks for automatic setup
- Update bootstrap prompt to recommend NPM method with git fallback
- Enhance README with NPM-first documentation
- Maintain backward compatibility with existing git installation

Features:
- npm install -g claude-hooks for easy distribution
- claude-hooks init/status/test/backup/uninstall commands
- Automatic Python dependency installation
- Conflict detection and prevention
- Hybrid approach supporting both npm and git workflows

This resolves installation complexity while maintaining developer flexibility.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ryan Malloy 2025-07-19 21:03:13 -06:00
parent 1e169a5d45
commit 9445e09c48
13 changed files with 1055 additions and 107 deletions

133
scripts/install.py Normal file
View file

@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Claude Hooks Installation Script for NPM Distribution"""
import os
import sys
import json
import shutil
import subprocess
from pathlib import Path
# Colors for terminal output
class Colors:
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m' # No Color
def print_colored(message, color):
print(f"{color}{message}{Colors.NC}")
def print_status(message, status="INFO"):
colors = {
"SUCCESS": Colors.GREEN,
"ERROR": Colors.RED,
"WARNING": Colors.YELLOW,
"INFO": Colors.BLUE
}
print_colored(f"[{status}] {message}", colors.get(status, Colors.NC))
def main():
print_colored("Claude Code Hooks Initialization", Colors.BLUE)
print("=" * 40)
# Get package root (npm package location)
package_root = Path(__file__).parent.parent
claude_config_dir = Path.home() / ".config" / "claude"
hooks_config_file = claude_config_dir / "hooks.json"
# Check for existing installation
print("Checking for existing Claude Hooks installation...")
if hooks_config_file.exists():
print_status("Claude Hooks already configured!", "WARNING")
response = input("Overwrite existing configuration? (y/N): ").lower()
if response != 'y':
print("Installation cancelled.")
return 1
# Check Python version
print("Checking Python version...")
if sys.version_info < (3, 8):
print_status(f"Python 3.8+ required, found {sys.version_info.major}.{sys.version_info.minor}", "ERROR")
return 1
print_status(f"Python {sys.version_info.major}.{sys.version_info.minor} found", "SUCCESS")
# Create Claude config directory
print("Creating Claude config directory...")
claude_config_dir.mkdir(parents=True, exist_ok=True)
print_status("Claude config directory ready", "SUCCESS")
# Generate hooks configuration
print("Generating hooks configuration...")
hooks_template = package_root / "config" / "hooks.json.template"
if hooks_template.exists():
with open(hooks_template, 'r') as f:
config_content = f.read()
# Replace template variables
config_content = config_content.replace("{{INSTALL_PATH}}", str(package_root))
with open(hooks_config_file, 'w') as f:
f.write(config_content)
print_status("Hooks configuration generated", "SUCCESS")
else:
# Fallback: create basic configuration
hooks_config = {
"hooks": {
"UserPromptSubmit": f"python3 {package_root}/hooks/context_monitor.py",
"PreToolUse": {
"Bash": f"python3 {package_root}/hooks/command_validator.py"
},
"PostToolUse": {
"*": f"python3 {package_root}/hooks/session_logger.py"
},
"Stop": f"python3 {package_root}/hooks/session_finalizer.py"
}
}
with open(hooks_config_file, 'w') as f:
json.dump(hooks_config, f, indent=2)
print_status("Basic hooks configuration created", "SUCCESS")
# Test hook scripts
print("Testing hook scripts...")
test_hook = package_root / "hooks" / "context_monitor.py"
if test_hook.exists():
try:
result = subprocess.run([
sys.executable, str(test_hook)
], input='{"prompt": "test"}', text=True, capture_output=True, timeout=10)
if result.returncode == 0:
print_status("Hook scripts working", "SUCCESS")
else:
print_status("Hook scripts may have issues", "WARNING")
except subprocess.TimeoutExpired:
print_status("Hook script test timeout", "WARNING")
except Exception as e:
print_status(f"Hook test error: {e}", "WARNING")
else:
print_status("Hook scripts not found", "WARNING")
print()
print_colored("Initialization Complete!", Colors.GREEN)
print()
print_colored("Next Steps:", Colors.BLUE)
print("1. Restart Claude Code to activate the hooks")
print("2. Test with: claude-hooks test")
print("3. Try commands that commonly fail (pip vs pip3) to see learning in action")
print()
print(f"📁 Installation directory: {package_root}")
print(f"⚙️ Configuration file: {hooks_config_file}")
print()
print_colored("The hooks will start learning from your usage patterns automatically.", Colors.YELLOW)
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -18,6 +18,24 @@ HOOKS_CONFIG_FILE="$CLAUDE_CONFIG_DIR/hooks.json"
echo -e "${BLUE}Claude Code Hooks Installation${NC}"
echo "=================================="
# Check for existing installation
echo -n "Checking for existing Claude Hooks installation... "
if [ -f "$HOOKS_CONFIG_FILE" ] || command -v claude-hooks >/dev/null 2>&1; then
echo -e "${RED}CONFLICT DETECTED${NC}"
echo ""
echo -e "${YELLOW}⚠️ Claude Hooks is already installed!${NC}"
echo " This would create conflicts since hooks are user-wide."
echo ""
echo -e "${BLUE}To proceed:${NC}"
echo "1. Uninstall existing hooks: claude-hooks uninstall"
echo "2. Or manually remove: rm -rf ~/.config/claude/hooks.json ~/.local/bin/claude-hooks"
echo "3. Then run this installer again"
echo ""
exit 1
else
echo -e "${GREEN}No conflicts found${NC}"
fi
# Check Python version
echo -n "Checking Python version... "
if ! python3 --version >/dev/null 2>&1; then
@ -67,6 +85,70 @@ echo -n "Creating runtime directories... "
mkdir -p "$CLAUDE_HOOKS_DIR/.claude_hooks/"{backups,logs,patterns}
echo -e "${GREEN}SUCCESS${NC}"
# Create claude-hooks command
echo -n "Creating claude-hooks command... "
mkdir -p "$HOME/.local/bin"
cat > "$HOME/.local/bin/claude-hooks" << 'EOF'
#!/bin/bash
# Claude Hooks Command Line Interface
CLAUDE_HOOKS_DIR="$(dirname "$(readlink -f "$0")")/../.."
if [ ! -d "$CLAUDE_HOOKS_DIR/claude-hooks" ]; then
# Fallback: look for installation in common locations
for dir in "/opt/claude-hooks" "$HOME/claude-hooks" "$HOME/.local/share/claude-hooks"; do
if [ -d "$dir" ]; then
CLAUDE_HOOKS_DIR="$dir"
break
fi
done
fi
case "$1" in
"status")
echo "Claude Hooks Status:"
if [ -f "$HOME/.config/claude/hooks.json" ]; then
echo "✓ Hooks configuration installed"
else
echo "✗ No hooks configuration found"
fi
if command -v python3 >/dev/null 2>&1; then
echo "✓ Python 3 available"
else
echo "✗ Python 3 not found"
fi
;;
"uninstall")
if [ -f "$CLAUDE_HOOKS_DIR/claude-hooks/scripts/uninstall.sh" ]; then
exec "$CLAUDE_HOOKS_DIR/claude-hooks/scripts/uninstall.sh"
else
echo "Error: Uninstall script not found"
echo "Manual removal: rm -rf ~/.config/claude/hooks.json ~/.local/bin/claude-hooks"
exit 1
fi
;;
"test")
if [ -f "$CLAUDE_HOOKS_DIR/claude-hooks/scripts/test.sh" ]; then
exec "$CLAUDE_HOOKS_DIR/claude-hooks/scripts/test.sh"
else
echo "Error: Test script not found"
exit 1
fi
;;
*)
echo "Claude Hooks CLI"
echo "Usage: claude-hooks {status|uninstall|test}"
echo ""
echo "Commands:"
echo " status - Show installation status"
echo " uninstall - Remove Claude Hooks"
echo " test - Run hook tests"
;;
esac
EOF
chmod +x "$HOME/.local/bin/claude-hooks"
echo -e "${GREEN}SUCCESS${NC}"
# Test hook scripts
echo -n "Testing hook scripts... "
if python3 "$CLAUDE_HOOKS_DIR/hooks/context_monitor.py" <<< '{"prompt": "test"}' >/dev/null 2>&1; then

114
scripts/manual-backup.py Normal file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Manual Backup Script for Claude Hooks"""
import os
import sys
import json
from pathlib import Path
from datetime import datetime
# Add lib directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
try:
from backup_manager import BackupManager
from session_state import SessionState
except ImportError as e:
print(f"Error importing backup modules: {e}")
print("Please ensure Claude Hooks is properly installed.")
sys.exit(1)
# Colors for terminal output
class Colors:
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m' # No Color
def print_colored(message, color):
print(f"{color}{message}{Colors.NC}")
def print_status(message, status="INFO"):
colors = {
"SUCCESS": Colors.GREEN,
"ERROR": Colors.RED,
"WARNING": Colors.YELLOW,
"INFO": Colors.BLUE
}
print_colored(f"[{status}] {message}", colors.get(status, Colors.NC))
def main():
print_colored("Claude Hooks Manual Backup", Colors.BLUE)
print("=" * 30)
# Get package root and setup paths
package_root = Path(__file__).parent.parent
runtime_dir = package_root / ".claude_hooks"
try:
# Initialize backup manager
backup_manager = BackupManager(str(runtime_dir / "backups"))
session_state = SessionState(str(runtime_dir))
print("Creating manual backup...")
# Get current working directory (where user is working)
cwd = Path.cwd()
print(f"Backing up from: {cwd}")
# Create backup context
backup_context = {
"trigger": "manual",
"timestamp": datetime.now().isoformat(),
"cwd": str(cwd),
"session_info": session_state.get_session_summary()
}
# Perform backup
backup_id = backup_manager.create_backup(
project_path=str(cwd),
context=backup_context,
force=True # Allow manual backup even if recent backup exists
)
if backup_id:
print_status(f"Backup created successfully: {backup_id}", "SUCCESS")
# Show backup details
backup_info = backup_manager.get_backup_info(backup_id)
if backup_info:
print()
print_colored("Backup Details:", Colors.BLUE)
print(f"📁 Backup ID: {backup_id}")
print(f"📅 Created: {backup_info.get('timestamp', 'Unknown')}")
print(f"📂 Location: {backup_info.get('path', 'Unknown')}")
if 'files_backed_up' in backup_info:
file_count = len(backup_info['files_backed_up'])
print(f"📄 Files: {file_count}")
if file_count > 0 and file_count <= 10:
print(" Files backed up:")
for file_path in backup_info['files_backed_up'][:10]:
print(f" - {file_path}")
elif file_count > 10:
print(" Sample files backed up:")
for file_path in backup_info['files_backed_up'][:5]:
print(f" - {file_path}")
print(f" ... and {file_count - 5} more")
print()
print_colored("Backup completed successfully! 🎉", Colors.GREEN)
else:
print_status("Backup failed - check logs for details", "ERROR")
return 1
except Exception as e:
print_status(f"Backup error: {e}", "ERROR")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,93 @@
#!/usr/bin/env node
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
console.log('📦 Claude Hooks post-install setup...');
// Check Python availability
try {
execSync('python3 --version', { stdio: 'pipe' });
console.log('✓ Python 3 found');
} catch {
console.error('❌ Python 3 is required but not found');
console.error('Please install Python 3.8+ and try again');
process.exit(1);
}
// Install Python dependencies
console.log('📋 Installing Python dependencies...');
const packageRoot = path.dirname(__dirname);
const requirementsPath = path.join(packageRoot, 'requirements.txt');
if (fs.existsSync(requirementsPath)) {
try {
execSync(`python3 -m pip install -r "${requirementsPath}" --user --quiet`, {
stdio: 'pipe',
cwd: packageRoot
});
console.log('✓ Python dependencies installed');
} catch (error) {
console.log('⚠️ Some Python dependencies may not have installed');
console.log('You can install them manually: pip3 install -r requirements.txt');
}
} else {
console.log('⚠️ requirements.txt not found, skipping Python dependencies');
}
// Make scripts executable
console.log('🔧 Setting up permissions...');
const scriptsDir = path.join(packageRoot, 'scripts');
const hooksDir = path.join(packageRoot, 'hooks');
[scriptsDir, hooksDir].forEach(dir => {
if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir);
files.forEach(file => {
if (file.endsWith('.py') || file.endsWith('.sh')) {
const filePath = path.join(dir, file);
try {
fs.chmodSync(filePath, 0o755);
} catch (e) {
// Ignore chmod errors on systems that don't support it
}
}
});
}
});
console.log('✓ Permissions configured');
// Create runtime directories
console.log('📁 Creating runtime directories...');
const runtimeDir = path.join(packageRoot, '.claude_hooks');
const subdirs = ['backups', 'logs', 'patterns'];
try {
if (!fs.existsSync(runtimeDir)) {
fs.mkdirSync(runtimeDir, { recursive: true });
}
subdirs.forEach(subdir => {
const subdirPath = path.join(runtimeDir, subdir);
if (!fs.existsSync(subdirPath)) {
fs.mkdirSync(subdirPath, { recursive: true });
}
});
console.log('✓ Runtime directories created');
} catch (error) {
console.log('⚠️ Could not create runtime directories:', error.message);
}
console.log('');
console.log('🎉 Claude Hooks installed successfully!');
console.log('');
console.log('Next steps:');
console.log('1. Run: claude-hooks init');
console.log('2. Restart Claude Code to activate hooks');
console.log('3. Test with: claude-hooks test');
console.log('');
console.log('For help: claude-hooks --help');

View file

@ -0,0 +1,33 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
console.log('🗑️ Claude Hooks pre-uninstall cleanup...');
// Check if hooks are configured
const hooksConfig = path.join(os.homedir(), '.config', 'claude', 'hooks.json');
if (fs.existsSync(hooksConfig)) {
console.log('⚠️ Claude Hooks configuration detected');
console.log('');
console.log('IMPORTANT: This will remove the NPM package but leave hooks active!');
console.log('');
console.log('To properly uninstall:');
console.log('1. Run: claude-hooks uninstall');
console.log('2. Then run: npm uninstall -g claude-hooks');
console.log('');
console.log('Or to force removal of hooks configuration:');
console.log(`rm -f "${hooksConfig}"`);
console.log('');
// Give user a moment to see the message
setTimeout(() => {
console.log('Continuing with NPM package removal...');
}, 2000);
} else {
console.log('✓ No active hooks configuration found');
}
console.log('✓ Pre-uninstall check complete');

172
scripts/test.py Normal file
View file

@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""Claude Hooks Test Script for NPM Distribution"""
import os
import sys
import json
import subprocess
import tempfile
from pathlib import Path
# Colors for terminal output
class Colors:
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m' # No Color
def print_colored(message, color):
print(f"{color}{message}{Colors.NC}")
def print_status(message, status="INFO"):
colors = {
"SUCCESS": Colors.GREEN,
"ERROR": Colors.RED,
"WARNING": Colors.YELLOW,
"INFO": Colors.BLUE
}
print_colored(f"[{status}] {message}", colors.get(status, Colors.NC))
def test_hook_script(hook_path, test_input=None):
"""Test a single hook script"""
if not hook_path.exists():
return False, "Script not found"
try:
cmd = [sys.executable, str(hook_path)]
result = subprocess.run(
cmd,
input=test_input or "",
text=True,
capture_output=True,
timeout=10
)
if result.returncode == 0:
return True, "Success"
else:
return False, f"Exit code {result.returncode}: {result.stderr.strip()}"
except subprocess.TimeoutExpired:
return False, "Timeout"
except Exception as e:
return False, str(e)
def main():
print_colored("Claude Hooks Test Suite", Colors.BLUE)
print("=" * 30)
# Get package root
package_root = Path(__file__).parent.parent
hooks_dir = package_root / "hooks"
claude_config_dir = Path.home() / ".config" / "claude"
hooks_config_file = claude_config_dir / "hooks.json"
tests_passed = 0
tests_total = 0
# Test 1: Check configuration exists
print("Testing configuration...")
tests_total += 1
if hooks_config_file.exists():
print_status("✓ Hooks configuration found", "SUCCESS")
tests_passed += 1
else:
print_status("✗ Hooks configuration missing", "ERROR")
print(" Run: claude-hooks init")
# Test 2: Check Python version
print("Testing Python compatibility...")
tests_total += 1
if sys.version_info >= (3, 8):
print_status(f"✓ Python {sys.version_info.major}.{sys.version_info.minor} compatible", "SUCCESS")
tests_passed += 1
else:
print_status(f"✗ Python 3.8+ required, found {sys.version_info.major}.{sys.version_info.minor}", "ERROR")
# Test 3: Test hook scripts
hook_tests = [
("context_monitor.py", '{"prompt": "test prompt", "context_size": 1000}'),
("command_validator.py", '{"command": "pip install requests", "cwd": "/tmp"}'),
("session_logger.py", '{"tool": "Bash", "args": {"command": "echo test"}}'),
("session_finalizer.py", '{"session_duration": 300, "tools_used": ["Bash", "Edit"]}')
]
for script_name, test_input in hook_tests:
print(f"Testing {script_name}...")
tests_total += 1
hook_path = hooks_dir / script_name
success, message = test_hook_script(hook_path, test_input)
if success:
print_status(f"{script_name} working", "SUCCESS")
tests_passed += 1
else:
print_status(f"{script_name} failed: {message}", "ERROR")
# Test 4: Check runtime directories
print("Testing runtime directories...")
tests_total += 1
runtime_dir = package_root / ".claude_hooks"
required_dirs = ["backups", "logs", "patterns"]
missing_dirs = []
for dirname in required_dirs:
if not (runtime_dir / dirname).exists():
missing_dirs.append(dirname)
if not missing_dirs:
print_status("✓ Runtime directories present", "SUCCESS")
tests_passed += 1
else:
print_status(f"✗ Missing directories: {', '.join(missing_dirs)}", "ERROR")
# Test 5: Test shadow learner
print("Testing shadow learner...")
tests_total += 1
try:
# Import test
sys.path.insert(0, str(package_root))
from lib.shadow_learner import ShadowLearner
learner = ShadowLearner(str(package_root / ".claude_hooks" / "patterns"))
# Basic functionality test
learner.learn_command_failure("pip", "pip3", 0.9)
suggestion = learner.get_suggestion("pip")
if suggestion and suggestion.get('suggestion') == 'pip3':
print_status("✓ Shadow learner functional", "SUCCESS")
tests_passed += 1
else:
print_status("✗ Shadow learner not learning correctly", "ERROR")
except Exception as e:
print_status(f"✗ Shadow learner error: {e}", "ERROR")
# Summary
print()
print_colored("Test Results", Colors.BLUE)
print("-" * 20)
if tests_passed == tests_total:
print_status(f"All {tests_total} tests passed! 🎉", "SUCCESS")
print()
print_colored("Claude Hooks is ready to use!", Colors.GREEN)
print("Try running some commands to see the learning in action:")
print("- pip install requests (should suggest pip3)")
print("- python script.py (should suggest python3)")
return 0
else:
print_status(f"{tests_passed}/{tests_total} tests passed", "WARNING")
print()
print_colored("Some issues detected. Check installation:", Colors.YELLOW)
print("1. Run: claude-hooks init")
print("2. Restart Claude Code")
print("3. Run tests again")
return 1
if __name__ == "__main__":
sys.exit(main())

110
scripts/uninstall.py Normal file
View file

@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Claude Hooks Uninstallation Script for NPM Distribution"""
import os
import sys
import json
import shutil
from pathlib import Path
# Colors for terminal output
class Colors:
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m' # No Color
def print_colored(message, color):
print(f"{color}{message}{Colors.NC}")
def print_status(message, status="INFO"):
colors = {
"SUCCESS": Colors.GREEN,
"ERROR": Colors.RED,
"WARNING": Colors.YELLOW,
"INFO": Colors.BLUE
}
print_colored(f"[{status}] {message}", colors.get(status, Colors.NC))
def main():
print_colored("Claude Code Hooks Uninstallation", Colors.BLUE)
print("=" * 40)
# Configuration paths
claude_config_dir = Path.home() / ".config" / "claude"
hooks_config_file = claude_config_dir / "hooks.json"
settings_file = claude_config_dir / "settings.json"
# Check if hooks are installed
if not hooks_config_file.exists():
print_status("No Claude Hooks configuration found", "WARNING")
print("Nothing to uninstall.")
return 0
print_colored("This will remove Claude Hooks configuration:", Colors.YELLOW)
print(f"- Remove hooks configuration: {hooks_config_file}")
print("- Remove hooks from Claude settings (if present)")
print("- Preserve hook data and backups")
print()
response = input("Continue with uninstallation? (y/n): ").lower()
if response != 'y':
print("Uninstallation cancelled.")
return 0
# Remove hooks configuration
print("Removing hooks configuration...")
try:
hooks_config_file.unlink()
print_status("Hooks configuration removed", "SUCCESS")
except FileNotFoundError:
print_status("Hooks configuration not found", "WARNING")
except Exception as e:
print_status(f"Error removing hooks configuration: {e}", "ERROR")
return 1
# Remove from Claude settings if it exists
if settings_file.exists():
print("Removing hooks from Claude settings...")
try:
with open(settings_file, 'r') as f:
settings = json.load(f)
if 'hooks' in settings:
# Backup settings
backup_file = settings_file.with_suffix('.json.backup')
shutil.copy2(settings_file, backup_file)
# Remove hooks key
del settings['hooks']
# Write back
with open(settings_file, 'w') as f:
json.dump(settings, f, indent=2)
print_status("Hooks removed from Claude settings", "SUCCESS")
else:
print_status("No hooks found in Claude settings", "WARNING")
except json.JSONDecodeError:
print_status("Could not parse Claude settings file", "WARNING")
except Exception as e:
print_status(f"Error updating Claude settings: {e}", "WARNING")
else:
print_status("No Claude settings file found", "WARNING")
print()
print_colored("Uninstallation Complete!", Colors.GREEN)
print()
print_colored("Next Steps:", Colors.BLUE)
print("1. Restart Claude Code to deactivate hooks")
print("2. Hook data and backups are preserved in case you reinstall")
print("3. To completely remove the package: npm uninstall -g claude-hooks")
print()
print_colored("Note: To completely remove all data, delete hook directories manually", Colors.YELLOW)
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -10,103 +10,94 @@ YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
CLAUDE_CONFIG_DIR="$HOME/.config/claude"
HOOKS_CONFIG_FILE="$CLAUDE_CONFIG_DIR/hooks.json"
CLAUDE_HOOKS_CMD="$HOME/.local/bin/claude-hooks"
echo -e "${BLUE}Claude Code Hooks Uninstallation${NC}"
echo "===================================="
# Warn user about data loss
echo -e "${YELLOW}WARNING:${NC} This will remove:"
echo "- Hook configuration from Claude Code"
echo "- All learned patterns and session data"
echo "- Backup files (if in project directory)"
# Check if hooks are installed
if [ ! -f "$HOOKS_CONFIG_FILE" ] && [ ! -f "$CLAUDE_HOOKS_CMD" ]; then
echo -e "${YELLOW}No Claude Hooks installation found${NC}"
echo "Nothing to uninstall."
exit 0
fi
echo -e "${YELLOW}This will remove Claude Hooks from your system:${NC}"
echo "- Remove hooks configuration: $HOOKS_CONFIG_FILE"
echo "- Remove claude-hooks command: $CLAUDE_HOOKS_CMD"
echo "- Preserve hook data and backups"
echo ""
read -p "Are you sure you want to continue? (y/N): " -n 1 -r
read -p "Continue with uninstallation? (y/n): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Uninstallation cancelled."
exit 0
fi
# Remove hooks from Claude settings
# Remove hooks configuration
echo -n "Removing hooks configuration... "
if [ -f "$HOOKS_CONFIG_FILE" ]; then
rm -f "$HOOKS_CONFIG_FILE"
echo -e "${GREEN}SUCCESS${NC}"
else
echo -e "${YELLOW}Not found${NC}"
fi
# Remove claude-hooks command
echo -n "Removing claude-hooks command... "
if [ -f "$CLAUDE_HOOKS_CMD" ]; then
rm -f "$CLAUDE_HOOKS_CMD"
echo -e "${GREEN}SUCCESS${NC}"
else
echo -e "${YELLOW}Not found${NC}"
fi
# Remove from Claude settings if it exists
if [ -f "$HOME/.config/claude/settings.json" ]; then
echo -n "Removing hooks from Claude settings... "
# Backup settings
cp "$HOME/.config/claude/settings.json" "$HOME/.config/claude/settings.json.backup"
# Remove hooks configuration
python3 << 'EOF'
if grep -q "hooks" "$HOME/.config/claude/settings.json" 2>/dev/null; then
# Backup settings
cp "$HOME/.config/claude/settings.json" "$HOME/.config/claude/settings.json.backup"
# Remove hooks from settings
python3 << EOF
import json
import sys
import os
try:
with open('/home/usr/.config/claude/settings.json', 'r') as f:
settings_path = os.path.expanduser('~/.config/claude/settings.json')
with open(settings_path, 'r') as f:
settings = json.load(f)
# Remove hooks section
# Remove hooks key if it exists
if 'hooks' in settings:
del settings['hooks']
with open('/home/usr/.config/claude/settings.json', 'w') as f:
with open(settings_path, 'w') as f:
json.dump(settings, f, indent=2)
print("SUCCESS")
else:
print("NO HOOKS FOUND")
print("Not found in settings")
except Exception as e:
print(f"ERROR: {e}")
sys.exit(1)
EOF
echo -e "${GREEN}Hooks removed from Claude settings${NC}"
else
echo -e "${YELLOW}No Claude settings file found${NC}"
fi
# Remove hooks configuration file
if [ -f "$HOOKS_CONFIG_FILE" ]; then
echo -n "Removing hooks configuration file... "
rm -f "$HOOKS_CONFIG_FILE"
echo -e "${GREEN}SUCCESS${NC}"
fi
# Ask about removing data
echo ""
read -p "Remove learned patterns and session data? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -n "Removing hook data... "
rm -rf ".claude_hooks"
rm -f "LAST_SESSION.md" "ACTIVE_TODOS.md" "RECOVERY_GUIDE.md"
echo -e "${GREEN}SUCCESS${NC}"
fi
# Ask about removing project files
echo ""
read -p "Remove project files (hooks, scripts, etc.)? This cannot be undone! (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
CLAUDE_HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
echo -n "Removing project files... "
cd ..
rm -rf "$CLAUDE_HOOKS_DIR"
echo -e "${GREEN}SUCCESS${NC}"
echo ""
echo -e "${GREEN}Complete uninstallation finished!${NC}"
echo "All files have been removed."
else
echo ""
echo -e "${GREEN}Partial uninstallation finished!${NC}"
echo "Hooks disabled, but project files remain."
echo -e "${GREEN}SUCCESS${NC}"
else
echo -e "${YELLOW}Not found in settings${NC}"
fi
fi
echo ""
echo -e "${BLUE}Post-uninstallation:${NC}"
echo "1. Restart Claude Code to ensure hooks are disabled"
echo "2. Check that no hook-related errors appear"
echo "3. Your backup files (if any) remain in git history"
echo -e "${GREEN}Uninstallation Complete!${NC}"
echo ""
echo -e "${GREEN}Claude Code Hooks successfully removed.${NC}"
echo -e "${BLUE}Next Steps:${NC}"
echo "1. Restart Claude Code to deactivate hooks"
echo "2. Hook data and backups are preserved in case you reinstall"
echo ""
echo -e "${YELLOW}Note:${NC} To completely remove all data, delete the claude-hooks directory manually"