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

View File

@ -1,58 +1,84 @@
# Claude Code Hooks # Claude Hooks
Intelligent hooks system that makes Claude Code smarter, safer, and more reliable through learning and automation. Intelligent hooks system for Claude Code that provides command validation, session continuity, and learning capabilities using Claude Code's native hooks architecture.
## What Is This? ## Quick Start
Claude Code Hooks transforms your Claude experience by adding:
- **🧠 Learning Intelligence** - Remembers what works in your environment and suggests alternatives for commands that typically fail
- **🛡️ Safety Protection** - Blocks dangerous operations and warns about risky commands before they execute
- **💾 Automatic Backups** - Preserves your work before context limits are reached, with seamless session restoration
- **🔄 Session Continuity** - Maintains context across Claude restarts with detailed session summaries
## Quick Install
```bash ```bash
git clone https://github.com/your-org/claude-hooks.git # Install globally via npm
cd claude-hooks npm install -g claude-hooks
./scripts/install.sh
# Initialize hooks
claude-hooks init
# Test installation
claude-hooks test
# Restart Claude Code to activate
``` ```
The installer automatically configures Claude Code. Restart Claude and the hooks will start working immediately. ## Features
## See It in Action 🧠 **Shadow Learning** - Learns from command failures and suggests working alternatives
📊 **Context Monitoring** - Automatically backs up work before context limits
**Smart Validation** - Blocks dangerous commands with intelligent suggestions
🔄 **Session Continuity** - Maintains history and state across Claude restarts
## Usage
After installation, Claude Hooks runs automatically:
- **Command Learning**: Try `pip install requests` → suggests `pip3 install requests`
- **Auto Backup**: Triggers before context limits or manually with `claude-hooks backup`
- **Status Check**: Run `claude-hooks status` to verify installation
- **Manual Test**: Use `claude-hooks test` to verify all components
## Commands
Try a command that often fails:
```bash ```bash
pip install requests claude-hooks init # Initialize after npm install
claude-hooks status # Check installation status
claude-hooks test # Run test suite
claude-hooks backup # Create manual backup
claude-hooks uninstall # Remove hooks configuration
claude-hooks --help # Show all commands
``` ```
With hooks enabled, you'll see: ## How It Works
```
⚠️ Warning: pip commands often fail (confidence: 88%) Claude Hooks uses Claude Code's native hook system with four key integration points:
💡 Suggestion: Use "pip3 install requests"
- **UserPromptSubmit**: Monitors context usage and triggers backups
- **PreToolUse[Bash]**: Validates commands and suggests alternatives
- **PostToolUse[*]**: Logs activity and learns from patterns
- **Stop**: Finalizes sessions and creates continuation docs
## Installation Methods
### NPM (Recommended)
```bash
npm install -g claude-hooks
claude-hooks init
``` ```
The system learned this pattern from your environment and is preventing you from repeating a known failure. ### Git (Development)
```bash
git clone https://git.supported.systems/rsp2k/claude-hooks.git
cd claude-hooks && ./scripts/install.sh
```
## Documentation ## Documentation
**📚 [Complete Documentation](docs/README.md)** - Organized by your current needs - [Getting Started Tutorial](docs/tutorial/getting-started.md)
- [Architecture Explanation](docs/explanation/architecture.md)
**🎓 New to Claude Hooks?** → [Tutorial](docs/tutorial/getting-started.md) (30 minutes) - [How-to Guides](docs/how-to/)
- [Reference Documentation](docs/reference/)
**🛠️ Need to solve a problem?** → [How-To Guides](docs/README.md#-how-to-guides)
**📖 Looking up commands?** → [Reference](docs/README.md#-reference)
**💡 Want to understand how it works?** → [Explanations](docs/README.md#-explanation)
## Requirements ## Requirements
- Python 3.8+ - **Node.js** 16+ (for npm installation)
- Claude Code - **Python** 3.8+ (for hook scripts)
- Git (optional, for enhanced backup features) - **Claude Code** (hooks integrate with Claude's native system)
## License ## License

127
bin/claude-hooks.js Normal file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env node
const { Command } = require('commander');
const chalk = require('chalk');
const { execSync, spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const os = require('os');
const program = new Command();
const packageRoot = path.dirname(__dirname);
// Helper to run Python scripts
function runPythonScript(scriptName, args = []) {
const scriptPath = path.join(packageRoot, 'scripts', scriptName);
return spawn('python3', [scriptPath, ...args], {
stdio: 'inherit',
cwd: packageRoot
});
}
// Helper to check installation status
function checkStatus() {
const hooksConfig = path.join(os.homedir(), '.config', 'claude', 'hooks.json');
const hasHooks = fs.existsSync(hooksConfig);
console.log(chalk.blue('Claude Hooks Status:'));
console.log(hasHooks ? chalk.green('✓ Hooks configuration installed') : chalk.red('✗ No hooks configuration found'));
try {
execSync('python3 --version', { stdio: 'pipe' });
console.log(chalk.green('✓ Python 3 available'));
} catch {
console.log(chalk.red('✗ Python 3 not found'));
}
return hasHooks;
}
program
.name('claude-hooks')
.description('Intelligent hooks system for Claude Code')
.version(require('../package.json').version);
program
.command('init')
.description('Initialize Claude Hooks (run after npm install)')
.option('--force', 'Force reinstallation even if already configured')
.action((options) => {
console.log(chalk.blue('Initializing Claude Hooks...'));
if (!options.force && checkStatus()) {
console.log(chalk.yellow('⚠️ Claude Hooks already configured. Use --force to reinstall.'));
return;
}
const initScript = runPythonScript('install.py');
initScript.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('\n🎉 Claude Hooks initialized successfully!'));
console.log(chalk.blue('Next steps:'));
console.log('1. Restart Claude Code to activate hooks');
console.log('2. Try: claude-hooks test');
} else {
console.log(chalk.red('❌ Initialization failed'));
process.exit(1);
}
});
});
program
.command('status')
.description('Show Claude Hooks installation status')
.action(() => {
checkStatus();
});
program
.command('test')
.description('Run Claude Hooks tests')
.action(() => {
console.log(chalk.blue('Running Claude Hooks tests...'));
const testScript = runPythonScript('test.py');
testScript.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('✅ All tests passed!'));
} else {
console.log(chalk.red('❌ Some tests failed'));
process.exit(1);
}
});
});
program
.command('uninstall')
.description('Remove Claude Hooks configuration')
.action(() => {
console.log(chalk.blue('Uninstalling Claude Hooks...'));
const uninstallScript = runPythonScript('uninstall.py');
uninstallScript.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('✅ Claude Hooks uninstalled successfully'));
console.log(chalk.yellow('Note: To completely remove, run: npm uninstall -g claude-hooks'));
} else {
console.log(chalk.red('❌ Uninstall failed'));
process.exit(1);
}
});
});
program
.command('backup')
.description('Manually trigger a backup')
.action(() => {
console.log(chalk.blue('Creating manual backup...'));
const backupScript = runPythonScript('manual-backup.py');
backupScript.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('✅ Backup created successfully'));
} else {
console.log(chalk.red('❌ Backup failed'));
process.exit(1);
}
});
});
program.parse();

View File

@ -6,23 +6,42 @@
I want to install Claude Hooks - an intelligent system that will make you smarter and safer by learning from my environment and preventing common mistakes. I want to install Claude Hooks - an intelligent system that will make you smarter and safer by learning from my environment and preventing common mistakes.
**⚠️ IMPORTANT: Claude Hooks installs user-wide and affects ALL Claude Code instances. If you have other Claude sessions with hooks already installed, this will create conflicts. Check first!**
**What it does:** **What it does:**
- Learns which commands fail in my environment and suggests working alternatives - Learns which commands fail in my environment and suggests working alternatives
- Automatically backs up my work before context limits - Automatically backs up my work before context limits
- Blocks dangerous commands before they execute - Blocks dangerous commands before they execute
- Maintains session history across Claude restarts - Maintains session history across Claude restarts
**Installation steps:** **Pre-installation check:**
First, verify no hooks are already installed:
1. Check: `claude-hooks status 2>/dev/null || echo "No claude-hooks found - safe to proceed"`
2. Check: `ls ~/.config/claude/hooks.json 2>/dev/null || echo "No hooks config found - safe to proceed"`
If either check shows existing hooks, **STOP** and run the uninstall process first:
- `claude-hooks uninstall` (if available)
- Or manually remove: `rm -rf ~/.config/claude/hooks.json`
**Installation (choose one method):**
**Method 1 - NPM (Recommended):**
1. `npm install -g claude-hooks`
2. `claude-hooks init`
3. `claude-hooks test`
**Method 2 - Git (For developers):**
1. `git clone https://git.supported.systems/rsp2k/claude-hooks.git` 1. `git clone https://git.supported.systems/rsp2k/claude-hooks.git`
2. `cd claude-hooks` 2. `cd claude-hooks && ./scripts/install.sh`
3. `./scripts/install.sh` 3. `./scripts/test.sh`
4. `./scripts/test.sh`
**After installation:** **After installation:**
- Restart Claude Code to activate the hooks - Restart Claude Code to activate the hooks
- Try `claude-hooks status` to see it working - Try `claude-hooks status` to verify it's working
- Try `pip install requests` to see intelligent command suggestions - Try `pip install requests` to see intelligent command suggestions
Please guide me through this installation step-by-step. Handle any errors that come up and make sure everything works. After installation, help me test it by trying some commands that commonly fail on different systems (like `pip` vs `pip3`, `python` vs `python3`) so I can see the intelligence in action. **To uninstall later:** `claude-hooks uninstall` or `npm uninstall -g claude-hooks`
Please guide me through this installation step-by-step, including the pre-installation checks. Use the NPM method unless I specifically request git installation. Handle any errors that come up and make sure everything works. After installation, help me test it by trying some commands that commonly fail on different systems (like `pip` vs `pip3`, `python` vs `python3`) so I can see the intelligence in action.
The goal is to have a working Claude Hooks system that starts learning from my environment immediately. The goal is to have a working Claude Hooks system that starts learning from my environment immediately.

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "claude-hooks",
"version": "1.0.0",
"description": "Intelligent hooks system for Claude Code with shadow learning and automatic backup",
"keywords": ["claude", "hooks", "ai", "automation", "backup", "learning"],
"author": "Claude Hooks Contributors",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://git.supported.systems/rsp2k/claude-hooks.git"
},
"bugs": {
"url": "https://git.supported.systems/rsp2k/claude-hooks/issues"
},
"homepage": "https://git.supported.systems/rsp2k/claude-hooks",
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"bin": {
"claude-hooks": "./bin/claude-hooks.js"
},
"files": [
"bin/",
"hooks/",
"lib/",
"config/",
"scripts/",
"docs/",
"requirements.txt",
"README.md"
],
"scripts": {
"postinstall": "node scripts/npm-postinstall.js",
"preuninstall": "node scripts/npm-preuninstall.js",
"test": "python3 scripts/test.sh",
"start": "claude-hooks"
},
"dependencies": {
"commander": "^11.0.0",
"chalk": "^5.0.0"
},
"peerDependencies": {
"python3": ">=3.8.0"
},
"preferGlobal": true
}

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 -e "${BLUE}Claude Code Hooks Installation${NC}"
echo "==================================" 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 # Check Python version
echo -n "Checking Python version... " echo -n "Checking Python version... "
if ! python3 --version >/dev/null 2>&1; then 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} mkdir -p "$CLAUDE_HOOKS_DIR/.claude_hooks/"{backups,logs,patterns}
echo -e "${GREEN}SUCCESS${NC}" 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 # Test hook scripts
echo -n "Testing hook scripts... " echo -n "Testing hook scripts... "
if python3 "$CLAUDE_HOOKS_DIR/hooks/context_monitor.py" <<< '{"prompt": "test"}' >/dev/null 2>&1; then 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' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# Configuration
CLAUDE_CONFIG_DIR="$HOME/.config/claude" CLAUDE_CONFIG_DIR="$HOME/.config/claude"
HOOKS_CONFIG_FILE="$CLAUDE_CONFIG_DIR/hooks.json" 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 -e "${BLUE}Claude Code Hooks Uninstallation${NC}"
echo "====================================" echo "===================================="
# Warn user about data loss # Check if hooks are installed
echo -e "${YELLOW}WARNING:${NC} This will remove:" if [ ! -f "$HOOKS_CONFIG_FILE" ] && [ ! -f "$CLAUDE_HOOKS_CMD" ]; then
echo "- Hook configuration from Claude Code" echo -e "${YELLOW}No Claude Hooks installation found${NC}"
echo "- All learned patterns and session data" echo "Nothing to uninstall."
echo "- Backup files (if in project directory)" 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 "" 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 echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Uninstallation cancelled." echo "Uninstallation cancelled."
exit 0 exit 0
fi 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 if [ -f "$HOME/.config/claude/settings.json" ]; then
echo -n "Removing hooks from Claude settings... " echo -n "Removing hooks from Claude settings... "
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"
# Backup settings # Remove hooks from settings
cp "$HOME/.config/claude/settings.json" "$HOME/.config/claude/settings.json.backup" python3 << EOF
# Remove hooks configuration
python3 << 'EOF'
import json import json
import sys import sys
import os
try: 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) settings = json.load(f)
# Remove hooks section # Remove hooks key if it exists
if 'hooks' in settings: if 'hooks' in settings:
del settings['hooks'] 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) json.dump(settings, f, indent=2)
print("SUCCESS") print("SUCCESS")
else: else:
print("NO HOOKS FOUND") print("Not found in settings")
except Exception as e: except Exception as e:
print(f"ERROR: {e}") print(f"ERROR: {e}")
sys.exit(1) sys.exit(1)
EOF EOF
echo -e "${GREEN}SUCCESS${NC}"
echo -e "${GREEN}Hooks removed from Claude settings${NC}" else
else echo -e "${YELLOW}Not found in settings${NC}"
echo -e "${YELLOW}No Claude settings file found${NC}" fi
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."
fi fi
echo "" echo ""
echo -e "${BLUE}Post-uninstallation:${NC}" echo -e "${GREEN}Uninstallation Complete!${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 "" 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"

1
test-clone Submodule

@ -0,0 +1 @@
Subproject commit 1e169a5d459fe1c1ef42da0bffcb485059efe46a