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>
172 lines
5.6 KiB
Python
172 lines
5.6 KiB
Python
#!/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()) |