Claude hooks auto-backup: manual (backup_20250720_091250)
This commit is contained in:
parent
9445e09c48
commit
392833187e
135 changed files with 16151 additions and 3439 deletions
273
bin/claude-hooks.js
Normal file → Executable file
273
bin/claude-hooks.js
Normal file → Executable file
|
|
@ -2,41 +2,107 @@
|
|||
|
||||
const { Command } = require('commander');
|
||||
const chalk = require('chalk');
|
||||
const { execSync, spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fs = require('fs-extra');
|
||||
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 create hooks configuration
|
||||
async function createHooksConfig() {
|
||||
const claudeConfigDir = path.join(os.homedir(), '.config', 'claude');
|
||||
const hooksConfigFile = path.join(claudeConfigDir, 'hooks.json');
|
||||
|
||||
await fs.ensureDir(claudeConfigDir);
|
||||
|
||||
const hooksConfig = {
|
||||
hooks: {
|
||||
UserPromptSubmit: `node ${path.join(packageRoot, 'hooks', 'context-monitor.js')}`,
|
||||
PreToolUse: {
|
||||
Bash: `node ${path.join(packageRoot, 'hooks', 'command-validator.js')}`
|
||||
},
|
||||
PostToolUse: {
|
||||
'*': `node ${path.join(packageRoot, 'hooks', 'session-logger.js')}`
|
||||
},
|
||||
Stop: `node ${path.join(packageRoot, 'hooks', 'session-finalizer.js')}`
|
||||
}
|
||||
};
|
||||
|
||||
await fs.writeJson(hooksConfigFile, hooksConfig, { spaces: 2 });
|
||||
return hooksConfigFile;
|
||||
}
|
||||
|
||||
// Helper to test a hook script
|
||||
async function testHookScript(scriptName, testInput) {
|
||||
return new Promise((resolve) => {
|
||||
const { spawn } = require('child_process');
|
||||
const scriptPath = path.join(packageRoot, 'hooks', scriptName);
|
||||
|
||||
const child = spawn('node', [scriptPath], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
cwd: packageRoot
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.stdin.write(JSON.stringify(testInput));
|
||||
child.stdin.end();
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
child.kill();
|
||||
resolve({ success: false, error: 'Timeout' });
|
||||
}, 10000);
|
||||
|
||||
child.on('close', (code) => {
|
||||
clearTimeout(timeout);
|
||||
resolve({
|
||||
success: code === 0,
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim()
|
||||
});
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
resolve({ success: false, error: error.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to check installation status
|
||||
function checkStatus() {
|
||||
async function checkStatus() {
|
||||
const hooksConfig = path.join(os.homedir(), '.config', 'claude', 'hooks.json');
|
||||
const hasHooks = fs.existsSync(hooksConfig);
|
||||
const hasHooks = await fs.pathExists(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'));
|
||||
}
|
||||
console.log(chalk.green('✓ Node.js runtime available'));
|
||||
|
||||
return hasHooks;
|
||||
}
|
||||
|
||||
// Helper to remove hooks configuration
|
||||
async function removeHooksConfig() {
|
||||
const hooksConfig = path.join(os.homedir(), '.config', 'claude', 'hooks.json');
|
||||
|
||||
if (await fs.pathExists(hooksConfig)) {
|
||||
await fs.remove(hooksConfig);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
program
|
||||
.name('claude-hooks')
|
||||
.description('Intelligent hooks system for Claude Code')
|
||||
|
|
@ -46,82 +112,173 @@ 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;
|
||||
.option('--auto-setup', 'Auto-setup during npm postinstall')
|
||||
.option('--quiet', 'Minimal output')
|
||||
.action(async (options) => {
|
||||
if (!options.quiet) {
|
||||
console.log(chalk.blue('Initializing Claude Hooks...'));
|
||||
}
|
||||
|
||||
const initScript = runPythonScript('install.py');
|
||||
initScript.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log(chalk.green('\n🎉 Claude Hooks initialized successfully!'));
|
||||
try {
|
||||
// Check for existing installation
|
||||
if (!options.force && await checkStatus()) {
|
||||
if (!options.quiet) {
|
||||
console.log(chalk.yellow('⚠️ Claude Hooks already configured. Use --force to reinstall.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create runtime directories
|
||||
const runtimeDir = path.join(packageRoot, '.claude_hooks');
|
||||
await fs.ensureDir(path.join(runtimeDir, 'backups'));
|
||||
await fs.ensureDir(path.join(runtimeDir, 'logs'));
|
||||
await fs.ensureDir(path.join(runtimeDir, 'patterns'));
|
||||
|
||||
// Create hooks configuration
|
||||
const configFile = await createHooksConfig();
|
||||
|
||||
if (!options.quiet) {
|
||||
console.log(chalk.green('✅ Claude Hooks initialized successfully!'));
|
||||
console.log(chalk.blue('Configuration file:'), configFile);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red('❌ Initialization failed:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('status')
|
||||
.description('Show Claude Hooks installation status')
|
||||
.action(() => {
|
||||
checkStatus();
|
||||
.action(async () => {
|
||||
await checkStatus();
|
||||
});
|
||||
|
||||
program
|
||||
.command('test')
|
||||
.description('Run Claude Hooks tests')
|
||||
.action(() => {
|
||||
.action(async () => {
|
||||
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);
|
||||
|
||||
let passed = 0;
|
||||
let total = 0;
|
||||
|
||||
// Test 1: Check configuration
|
||||
total++;
|
||||
console.log('Testing configuration...');
|
||||
if (await checkStatus()) {
|
||||
console.log(chalk.green('✓ Configuration exists'));
|
||||
passed++;
|
||||
} else {
|
||||
console.log(chalk.red('✗ Configuration missing'));
|
||||
}
|
||||
|
||||
// Test 2: Test hook scripts
|
||||
const hookTests = [
|
||||
{
|
||||
name: 'context-monitor.js',
|
||||
input: { prompt: 'test prompt', context_size: 1000 }
|
||||
},
|
||||
{
|
||||
name: 'command-validator.js',
|
||||
input: { tool: 'Bash', parameters: { command: 'pip install requests' } }
|
||||
},
|
||||
{
|
||||
name: 'session-logger.js',
|
||||
input: { tool: 'Bash', parameters: { command: 'echo test' }, success: true }
|
||||
},
|
||||
{
|
||||
name: 'session-finalizer.js',
|
||||
input: {}
|
||||
}
|
||||
});
|
||||
];
|
||||
|
||||
for (const test of hookTests) {
|
||||
total++;
|
||||
console.log(`Testing ${test.name}...`);
|
||||
|
||||
const result = await testHookScript(test.name, test.input);
|
||||
|
||||
if (result.success) {
|
||||
console.log(chalk.green(`✓ ${test.name} working`));
|
||||
passed++;
|
||||
} else {
|
||||
console.log(chalk.red(`✗ ${test.name} failed: ${result.error || result.stderr}`));
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log();
|
||||
if (passed === total) {
|
||||
console.log(chalk.green(`✅ All ${total} tests passed!`));
|
||||
console.log(chalk.green('Claude Hooks is ready to use!'));
|
||||
} else {
|
||||
console.log(chalk.yellow(`⚠️ ${passed}/${total} tests passed`));
|
||||
console.log(chalk.blue('Try: claude-hooks init --force'));
|
||||
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'));
|
||||
.option('--quiet', 'Minimal output')
|
||||
.action(async (options) => {
|
||||
if (!options.quiet) {
|
||||
console.log(chalk.blue('Uninstalling Claude Hooks...'));
|
||||
}
|
||||
|
||||
try {
|
||||
const removed = await removeHooksConfig();
|
||||
|
||||
if (removed) {
|
||||
if (!options.quiet) {
|
||||
console.log(chalk.green('✅ Claude Hooks configuration removed'));
|
||||
console.log(chalk.yellow('Note: To completely remove the package: npm uninstall -g claude-hooks'));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.red('❌ Uninstall failed'));
|
||||
process.exit(1);
|
||||
if (!options.quiet) {
|
||||
console.log(chalk.yellow('⚠️ No Claude Hooks configuration found'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red('❌ Uninstall failed:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('backup')
|
||||
.description('Manually trigger a backup')
|
||||
.action(() => {
|
||||
.action(async () => {
|
||||
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'));
|
||||
|
||||
try {
|
||||
// Import backup manager
|
||||
const { BackupManager } = require(path.join(packageRoot, 'lib', 'backup-manager'));
|
||||
const backupManager = new BackupManager();
|
||||
|
||||
const backupId = await backupManager.createBackup(process.cwd(), {
|
||||
trigger: 'manual',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
if (backupId) {
|
||||
console.log(chalk.green(`✅ Backup created: ${backupId}`));
|
||||
} else {
|
||||
console.log(chalk.red('❌ Backup failed'));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red('❌ Backup failed:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program.parse();
|
||||
Loading…
Add table
Add a link
Reference in a new issue