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:
parent
1e169a5d45
commit
9445e09c48
13 changed files with 1055 additions and 107 deletions
133
scripts/install.py
Normal file
133
scripts/install.py
Normal 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())
|
||||
|
|
@ -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
114
scripts/manual-backup.py
Normal 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())
|
||||
93
scripts/npm-postinstall.js
Normal file
93
scripts/npm-postinstall.js
Normal 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');
|
||||
33
scripts/npm-preuninstall.js
Normal file
33
scripts/npm-preuninstall.js
Normal 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
172
scripts/test.py
Normal 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
110
scripts/uninstall.py
Normal 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())
|
||||
|
|
@ -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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue