UNPKG

claudepoint

Version:

The safest way to experiment with Claude Code. Create instant checkpoints, experiment fearlessly, restore instantly.

1,210 lines (1,034 loc) 75.9 kB
/** * ClaudePoint CLI * Command line interface for checkpoint management */ import { program } from 'commander'; import CheckpointManager from './lib/checkpoint-manager.js'; import chalk from 'chalk'; import ora from 'ora'; import inquirer from 'inquirer'; import { initializeSlashCommands } from './lib/slash-commands.js'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { createRequire } from 'module'; const { promises: fsPromises } = fs; const require = createRequire(import.meta.url); const packageJson = require('../package.json'); // Progress bar animation function async function showProgressBar(message, steps) { const spinner = ora(message).start(); const totalSteps = steps.length; for (let i = 0; i < totalSteps; i++) { const step = steps[i]; spinner.text = `${message} [${i + 1}/${totalSteps}] ${step.text}`; if (step.action) { await step.action(); } await new Promise(resolve => setTimeout(resolve, step.delay || 300)); } return spinner; } // Configure MCP server based on scope async function configureMCPServer(scope = 'project') { try { // For project scope, we need to use Claude Code CLI if (scope === 'project') { // Check if we're in a Claude Code environment const { execSync } = await import('child_process'); try { // Try to add MCP server via Claude CLI execSync(`claude mcp add claudepoint --command claudepoint`, { stdio: 'inherit' }); return { success: true, scope: 'project' }; } catch (error) { // Fall back to manual instruction return { success: false, manual: true, instructions: 'Run in Claude Code: claude mcp add claudepoint --command claudepoint' }; } } // For user/global scope, modify config file const configPath = scope === 'user' ? path.join(os.homedir(), '.claude', 'mcp_servers.json') : path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'); let config = {}; try { const configData = await fsPromises.readFile(configPath, 'utf8'); config = JSON.parse(configData); } catch { // File doesn't exist, create new } if (!config.mcpServers) { config.mcpServers = {}; } // Check for existing claudepoint entries const existingKeys = Object.keys(config.mcpServers).filter(key => key === 'claudepoint' || key.startsWith('claudepoint_') ); if (existingKeys.length > 0) { return { alreadyConfigured: true, scope, existingKeys }; } // Get the full path to claudepoint binary const { execSync } = await import('child_process'); let claudepointPath; try { claudepointPath = execSync('which claudepoint', { encoding: 'utf8' }).trim(); } catch { // Fallback - try common paths const commonPaths = [ '/usr/local/bin/claudepoint', '/opt/homebrew/bin/claudepoint', path.join(os.homedir(), '.npm-global/bin/claudepoint') ]; claudepointPath = commonPaths.find(p => { try { require('fs').accessSync(p); return true; } catch { return false; } }) || 'claudepoint'; } config.mcpServers.claudepoint = { command: claudepointPath, args: [] }; await fsPromises.mkdir(path.dirname(configPath), { recursive: true }); await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2)); return { success: true, scope, configPath }; } catch (error) { return { success: false, error: error.message }; } } // Configure hooks based on scope async function configureHooks(scope = 'project', triggers = ['before_bulk_edit']) { try { const settingsPath = scope === 'project' ? path.join(process.cwd(), '.claude', 'settings.json') : path.join(os.homedir(), '.claude', 'settings.json'); let settings = {}; try { const data = await fsPromises.readFile(settingsPath, 'utf8'); settings = JSON.parse(data); } catch { // File doesn't exist } if (!settings.hooks) { settings.hooks = {}; } if (!Array.isArray(settings.hooks.PreToolUse)) { settings.hooks.PreToolUse = []; } // Remove existing claudepoint hooks settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(hook => !hook.hooks || !hook.hooks.some(h => h.command && h.command.includes('claudepoint-hook')) ); // Add hooks for specified triggers const toolMap = { 'before_bulk_edit': ['MultiEdit'], 'before_major_write': ['Write'], 'before_bash_commands': ['Bash'], 'before_file_operations': ['*'] }; triggers.forEach(trigger => { const tools = toolMap[trigger] || []; tools.forEach(tool => { settings.hooks.PreToolUse.push({ matcher: tool, hooks: [{ type: 'command', command: `claudepoint-hook --trigger ${trigger} --tool ${tool}` }] }); }); }); await fsPromises.mkdir(path.dirname(settingsPath), { recursive: true }); await fsPromises.writeFile(settingsPath, JSON.stringify(settings, null, 2)); return { success: true, scope, settingsPath }; } catch (error) { return { success: false, error: error.message }; } } // Original configureMCPServer function for backward compatibility async function configureMCPServerLegacy() { try { // Determine Claude Code config path based on platform const platform = os.platform(); let configPath; if (platform === 'darwin') { // macOS configPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'); } else if (platform === 'win32') { // Windows configPath = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json'); } else { // Linux or other configPath = path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json'); } // Check if config file exists let config = {}; let wasCreated = false; try { const configData = await fsPromises.readFile(configPath, 'utf8'); config = JSON.parse(configData); } catch (error) { // Config doesn't exist, create new one wasCreated = true; config = {}; } // Check if ANY claudepoint configuration already exists (prevent duplicates) if (config.mcpServers) { const claudepointKeys = Object.keys(config.mcpServers).filter(key => key === 'claudepoint' || key.startsWith('claudepoint_') ); if (claudepointKeys.length > 0) { return { alreadyConfigured: true, configPath, existingKeys: claudepointKeys }; } } // Add claudepoint configuration if (!config.mcpServers) { config.mcpServers = {}; } config.mcpServers.claudepoint = { command: 'claudepoint', args: [] }; // Ensure directory exists await fsPromises.mkdir(path.dirname(configPath), { recursive: true }); // Write updated config await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2)); return { success: true, configPath, wasCreated }; } catch (error) { return { success: false, error: error.message }; } } program .name('claudepoint') .description('🚀 The ultimate hacking companion for Claude Code // Break things beautifully') .version(packageJson.version) .action(async (options, command) => { // Default action when no command is specified - create a claudepoint! if (command.args.length === 0) { // Quick intro for deploy console.log(chalk.green(' ⣿⣿⠋⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⡀⠀⠋⣿⣿')); console.log(chalk.cyan(' >> CLAUDEPOINT DEPLOY SEQUENCE <<')); await new Promise(resolve => setTimeout(resolve, 200)); const spinner = ora('💾 Deploying claudepoint...').start(); try { const manager = new CheckpointManager(); const result = await manager.create(); if (result.success) { spinner.succeed(manager.getRandomMessage(manager.successMessages)); console.log(chalk.cyan(` Name: ${result.name}`)); console.log(chalk.gray(` Files: ${result.fileCount} | Size: ${result.size}`)); console.log(chalk.gray(` Description: ${result.description}`)); } else if (result.noChanges) { spinner.info(chalk.yellow('🤔 No changes detected since last claudepoint // Codebase is stable')); } else { spinner.fail(`🚨 Deploy failed: ${result.error}`); } } catch (error) { spinner.fail('🚨 Deploy error detected'); console.error(chalk.red('Error:'), error.message); } } }); program .command('setup') .description('🎆 Complete ClaudePoint setup // MCP + Hooks + Commands in one go') .option('--scope <scope>', 'Configuration scope: project, user, or global', 'project') .option('--no-interactive', 'Skip interactive setup prompts') .option('--force', 'Force reinstall even if already configured') .action(async (options) => { // Matrix animation console.log(chalk.green(' ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿')); console.log(chalk.green(' ⣿⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⣿⣿⣿⣿')); console.log(chalk.green(' ⣿⣿⣿⠿⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⣿⣿')); console.log(chalk.green(' ⣿⣿⠋⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⡀⠀⠋⣿⣿')); console.log(chalk.green(' ⣿⠋⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠋⣿')); console.log(chalk.green(' ⡟⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⢻')); console.log(chalk.green(' ⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀')); await new Promise(resolve => setTimeout(resolve, 500)); console.log(chalk.cyan.bold('\n ╔═══════════════════════════════════════╗')); console.log(chalk.cyan.bold(' ║ 🕶️ CLAUDEPOINT MATRIX v1.4.4 ║')); console.log(chalk.cyan.bold(' ║ >> INITIALIZING HACK MODE << ║')); console.log(chalk.cyan.bold(' ╚═══════════════════════════════════════╝')); await new Promise(resolve => setTimeout(resolve, 300)); // Loading animation const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; let frameIndex = 0; const loadingInterval = setInterval(() => { process.stdout.write(`\r${chalk.green(frames[frameIndex])} ${chalk.cyan('Accessing neural pathways...')} `); frameIndex = (frameIndex + 1) % frames.length; }, 100); await new Promise(resolve => setTimeout(resolve, 1500)); clearInterval(loadingInterval); process.stdout.write('\r \r'); console.log(chalk.green('✅ Neural pathways established')); console.log(chalk.green('✅ Matrix protocols loaded')); console.log(chalk.green('✅ Quantum tunnels active')); await new Promise(resolve => setTimeout(resolve, 400)); console.log(chalk.blue.bold('\n🕶️ Welcome to the ClaudePoint Matrix!\n')); try { const manager = new CheckpointManager(); // Determine configuration scope let configScope = options.scope; // Interactive setup by default if (options.interactive !== false) { console.log(chalk.gray('This wizard will set up ClaudePoint with MCP server, hooks, and commands.\n')); // Ask about scope const { scope } = await inquirer.prompt([{ type: 'list', name: 'scope', message: '🎯 Choose configuration scope:', choices: [ { name: 'Project (recommended) - Settings for this project only', value: 'project' }, { name: 'User - Available across all your projects', value: 'user' }, { name: 'Global - System-wide configuration', value: 'global' } ], default: 'project' }]); configScope = scope; console.log(chalk.gray('This digital wizard will hack your way to the perfect ClaudePoint setup.\n')); // Ask about gitignore const { updateGitignore } = await inquirer.prompt([{ type: 'confirm', name: 'updateGitignore', message: '🔒 Activate stealth mode (.claudepoint → .gitignore)?', default: true }]); // Ask about initial checkpoint const { createInitial } = await inquirer.prompt([{ type: 'confirm', name: 'createInitial', message: '💾 Deploy initial claudepoint to lock in your digital DNA?', default: true }]); // Ask about slash commands const { installCommands } = await inquirer.prompt([{ type: 'confirm', name: 'installCommands', message: '🚀 Install Claude Code command arsenal (/claudepoint, /undo, etc)?', default: true }]); // Ask about MCP configuration const { configureMCP } = await inquirer.prompt([{ type: 'confirm', name: 'configureMCP', message: '⚙️ Configure ClaudePoint as MCP server in Claude Code?', default: true }]); // Ask about hooks with better UX const { enableHooks } = await inquirer.prompt([{ type: 'confirm', name: 'enableHooks', message: '🪝 Enable automatic safety checkpoints before changes?', default: true }]); let selectedTriggers = []; if (enableHooks) { const { triggers } = await inquirer.prompt([{ type: 'checkbox', name: 'triggers', message: 'When should ClaudePoint create automatic checkpoints?', choices: [ { name: 'Before bulk edits (MultiEdit)', value: 'before_bulk_edit', checked: true }, { name: 'Before file writes (Write)', value: 'before_major_write', checked: false }, { name: 'Before bash commands', value: 'before_bash_commands', checked: false }, { name: 'Before any file changes', value: 'before_file_operations', checked: false } ] }]); selectedTriggers = triggers.length > 0 ? triggers : ['before_bulk_edit']; } // Now perform setup with chosen options using progress animation const steps = [ { text: 'Creating .claudepoint vault...', action: async () => {}, delay: 400 }, { text: 'Loading configuration...', delay: 300 }, { text: 'Setting up file patterns...', delay: 350 }, ]; if (updateGitignore) { steps.push({ text: 'Updating .gitignore...', delay: 300 }); } if (createInitial) { steps.push({ text: 'Creating initial checkpoint...', delay: 500 }); } const spinner = await showProgressBar('🚀 Initializing ClaudePoint', steps); // Perform actual setup const result = await manager.setup({ updateGitignore, createInitial }); if (result.success) { spinner.succeed('💾 ClaudePoint is ONLINE!'); console.log(chalk.green('✨ Created .claudepoint vault')); if (updateGitignore) { console.log(chalk.green('🔒 Updated .gitignore (stealth mode activated)')); } console.log(chalk.green('⚙️ Configuration loaded')); if (result.initialCheckpoint) { console.log(chalk.green(`✨ Deployed initial claudepoint: ${result.initialCheckpoint}`)); } // Install slash commands if requested if (installCommands) { spinner.start('🚀 Deploying Claude Code slash command arsenal...'); await initializeSlashCommands(); spinner.succeed('⚙️ Slash command arsenal deployed!'); console.log(chalk.green('✨ Created .claude/commands vault')); console.log(chalk.green('🚀 Added claudepoint command arsenal')); } // Configure MCP if requested if (configureMCP) { spinner.start(`Configuring MCP server (${configScope} scope)...`); const configResult = await configureMCPServer(configScope); if (configResult.success) { spinner.succeed(`MCP server configured (${configScope} scope)!`); if (configResult.configPath) { console.log(chalk.green(`✅ Updated: ${configResult.configPath}`)); } if (configScope === 'project') { console.log(chalk.blue('\n📍 MCP configured for this project only')); } else { console.log(chalk.blue(`\n📍 MCP configured at ${configScope} level`)); if (configScope === 'user' || configScope === 'global') { console.log(chalk.yellow(' For each project, you may still need to:')); console.log(chalk.cyan(' claude mcp add-from-claude-desktop')); } } } else if (configResult.alreadyConfigured) { spinner.info(`MCP server already configured (${configScope} scope)`); if (configResult.existingKeys) { console.log(chalk.gray(` Found: ${configResult.existingKeys.join(', ')}`)); } } else if (configResult.manual) { spinner.warn('Cannot auto-configure MCP in project scope'); console.log(chalk.yellow('\n📝 Run this command in Claude Code:')); console.log(chalk.cyan(configResult.instructions)); } else { spinner.warn(`Could not configure MCP: ${configResult.error}`); } } // Setup hooks if requested if (enableHooks && selectedTriggers.length > 0) { spinner.start(`Configuring hooks (${configScope} scope)...`); // Configure hooks with proper format const hooksResult = await configureHooks(configScope, selectedTriggers); if (hooksResult.success) { spinner.succeed(`Hooks configured (${configScope} scope)!`); console.log(chalk.green(`✅ Updated: ${hooksResult.settingsPath}`)); console.log(chalk.green(`✅ Enabled triggers: ${selectedTriggers.join(', ')}`)); // Also save local hook config for the hook binary const hooksManager = new CheckpointManager(); const hooksConfigData = await hooksManager.loadHooksConfig(); Object.keys(hooksConfigData.triggers).forEach(trigger => { hooksConfigData.triggers[trigger].enabled = selectedTriggers.includes(trigger); }); await hooksManager.saveHooksConfig(hooksConfigData); } else { spinner.warn(`Could not configure hooks: ${hooksResult.error}`); } } // Show summary console.log(chalk.blue('\n✨ Setup Summary:')); console.log(` 📁 Checkpoints: ${chalk.green('Ready')}`); console.log(` 🔧 Gitignore: ${updateGitignore ? chalk.green('Updated') : chalk.gray('Skipped')}`); console.log(` 📸 Initial checkpoint: ${createInitial && result.initialCheckpoint ? chalk.green('Created') : chalk.gray('Skipped')}`); console.log(` 📝 Slash commands: ${installCommands ? chalk.green('Installed') : chalk.gray('Skipped')}`); console.log(` ⚙️ MCP Server: ${configureMCP ? chalk.green('Configured') : chalk.gray('Skipped')}`); console.log(` 🪝 Hooks: ${enableHooks ? chalk.green('Configured & Installed to Claude Code') : chalk.gray('Skipped')}`); console.log(chalk.yellow('\n💡 Next steps:')); console.log(' 1. Restart Claude Code to activate hooks'); if (configScope !== 'project') { console.log(chalk.red(' 2. 🚨 IMPORTANT: Run this in your project terminal:')); console.log(' ' + chalk.cyan('claude mcp add-from-claude-desktop')); console.log(' 3. Test with /claudepoint command in Claude Code'); } else { console.log(' 2. Test with /claudepoint command in Claude Code'); } console.log(' 4. Create checkpoints before major changes'); console.log(' 5. Use "claudepoint --help" to see all commands'); console.log(chalk.gray('\n🔍 To verify MCP is working:')); console.log(chalk.gray(' - Type /claudepoint in Claude Code')); console.log(chalk.gray(' - You should see ClaudePoint tools available')); console.log(chalk.gray(' - If not working, make sure you did step 2 above!')); } else { spinner.fail(`Setup failed: ${result.error}`); process.exit(1); } } else { // Non-interactive mode with progress const steps = [ { text: 'Creating .claudepoint vault...', delay: 400 }, { text: 'Loading configuration...', delay: 300 }, { text: 'Updating .gitignore...', delay: 300 }, { text: 'Setting up patterns...', delay: 350 } ]; const spinner = await showProgressBar('🚀 Initializing ClaudePoint', steps); const result = await manager.setup(); if (result.success) { spinner.succeed('💾 ClaudePoint is ONLINE!'); console.log(chalk.green('✨ Created .claudepoint vault')); console.log(chalk.green('🔒 Updated .gitignore (stealth mode activated)')); console.log(chalk.green('⚙️ Configuration loaded')); if (result.initialCheckpoint) { console.log(chalk.green(`✨ Deployed initial claudepoint: ${result.initialCheckpoint}`)); } console.log(chalk.yellow('\n💡 Run "claudepoint setup" again for interactive configuration')); } else { spinner.fail(`Setup failed: ${result.error}`); process.exit(1); } } } catch (error) { console.error(chalk.red('Setup failed:'), error.message); process.exit(1); } }); program .command('create') .description('💾 Deploy a new claudepoint // Lock in your digital DNA') .option('-n, --name <n>', 'Custom checkpoint name') .option('-d, --description <description>', 'Checkpoint description') .option('--debug', 'Show debug information about file discovery') .action(async (options) => { const spinner = ora('💾 Deploying claudepoint...').start(); try { const manager = new CheckpointManager(); // Debug mode: Show file discovery info if (options.debug) { spinner.text = 'Discovering project files...'; const files = await manager.getProjectFiles(); spinner.stop(); console.log(chalk.blue(`\n🔍 Debug: Found ${files.length} files:`)); files.slice(0, 20).forEach(file => console.log(chalk.gray(` ${file}`))); if (files.length > 20) { console.log(chalk.gray(` ... and ${files.length - 20} more files`)); } console.log(''); spinner.start('Creating checkpoint...'); } const result = await manager.create(options.name, options.description); if (result.success) { spinner.succeed(manager.getRandomMessage(manager.successMessages)); console.log(chalk.cyan(` Name: ${result.name} ${chalk.green('[DEPLOYED]')}`)); console.log(chalk.gray(` Files: ${result.fileCount}`)); console.log(chalk.gray(` Size: ${result.size}`)); console.log(chalk.gray(` Description: ${result.description}`)); } else if (result.noChanges) { spinner.info(chalk.yellow('🤔 No changes detected since last claudepoint // Codebase is stable')); console.log('Make some changes and redeploy when ready'); } else { spinner.fail(`🚨 Deploy failed: ${result.error}`); process.exit(1); } } catch (error) { spinner.fail('Create failed'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); // 🚀 NEW: Quick undo command program .command('undo') .description('🔄 Instant time hack // Restore your last claudepoint') .action(async () => { const spinner = ora('🕰️ Initiating time hack...').start(); try { const manager = new CheckpointManager(); const result = await manager.undoLastClaudepoint(); if (result.success) { spinner.succeed(manager.getRandomMessage(manager.undoMessages)); console.log(chalk.green(` 🛡️ Emergency backup: ${result.emergencyBackup}`)); console.log(chalk.cyan(` 🔄 Restored: ${result.restored}`)); console.log(chalk.gray(` 📅 Back to the future: ${result.type || 'FULL'} claudepoint`)); } else if (result.noClaudepoints) { spinner.info(chalk.yellow('🤔 No claudepoints found to undo. Time to create your first safety net!')); } else { spinner.fail(`🚨 Time hack failed: ${result.error}`); process.exit(1); } } catch (error) { spinner.fail('🚨 Error during time hack'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); program .command('list') .description('🗂️ Browse your claudepoint vault // Digital artifact collection') .option('--show-chain', 'Show checkpoint chain information') .action(async (options) => { try { const manager = new CheckpointManager(); const checkpoints = await manager.getCheckpoints(); if (checkpoints.length === 0) { console.log(chalk.yellow('🤔 No claudepoints found in the vault.')); console.log('🚀 Deploy your first claudepoint with: claudepoint'); return; } console.log(chalk.blue(manager.getRandomMessage(manager.listMessages))); console.log(chalk.blue(`📦 Total claudepoints: ${checkpoints.length}`)); for (let index = 0; index < checkpoints.length; index++) { const cp = checkpoints[index]; const typeLabel = cp.type === 'FULL' ? chalk.green('[FULL]') : cp.type === 'INCREMENTAL' ? chalk.yellow('[INC]') : chalk.gray('[LEGACY]'); // Show chain structure with visual indicators let prefix = ' '; if (options.showChain && cp.type === 'INCREMENTAL') { // Find if this is part of a chain const hasSubsequent = checkpoints.slice(0, index).some(nextCp => nextCp.baseCheckpoint === cp.name); prefix = hasSubsequent ? ' ├─ ' : ' └─ '; } else if (options.showChain && cp.type === 'FULL') { prefix = ' '; } console.log(`${prefix}${chalk.cyan((index + 1) + '.')} ${chalk.bold(cp.name)} ${typeLabel}`); console.log(`${prefix} ${cp.description}`); let details = `${new Date(cp.timestamp).toLocaleString()} | ${cp.fileCount} files | ${manager.formatSize(cp.totalSize)}`; if (cp.type === 'INCREMENTAL' && cp.statistics) { details += ` | ${cp.statistics.filesChanged} changes`; } console.log(`${prefix} ${details}`); if (options.showChain && cp.baseCheckpoint) { console.log(`${prefix} ${chalk.gray('↳ based on:')} ${cp.baseCheckpoint}`); } console.log(); } if (!options.showChain) { console.log(chalk.gray('💡 Use --show-chain to see checkpoint relationships')); } } catch (error) { console.error(chalk.red('❌ List failed:'), error.message); process.exit(1); } }); // 🎯 NEW: Changes command - see what's different since last claudepoint program .command('changes') .description("🔍 Scan for changes // See what's different since your last claudepoint") .action(async () => { const spinner = ora('🔍 Scanning for changes...').start(); try { const manager = new CheckpointManager(); const changes = await manager.getChangedFilesSinceLastClaudepoint(); if (changes.error) { spinner.fail(`🚨 Scan error: ${changes.error}`); return; } if (!changes.hasLastClaudepoint) { spinner.info('🆕 No previous claudepoint found // Everything is new!'); console.log(chalk.blue(`📁 Total files in project: ${changes.totalChanges}`)); console.log(chalk.gray(' Deploy your first claudepoint to track changes')); return; } if (changes.totalChanges === 0) { spinner.succeed('✨ Codebase is stable // No changes detected'); console.log(chalk.green(`📍 Last claudepoint: ${changes.lastClaudepointName}`)); console.log(chalk.gray(` Created: ${changes.lastClaudepointDate}`)); console.log(chalk.blue("🎯 Perfect time to experiment - you're fully protected!")); return; } spinner.succeed(`🎯 Changes detected: ${changes.totalChanges} modifications found`); console.log(chalk.blue(`📍 Since claudepoint: ${changes.lastClaudepointName}`)); console.log(chalk.gray(` Created: ${changes.lastClaudepointDate}`)); if (changes.added.length > 0) { console.log(chalk.green(`\\n➕ Added files (${changes.added.length}):`)); changes.added.slice(0, 10).forEach(file => { console.log(chalk.green(` + ${file}`)); }); if (changes.added.length > 10) { console.log(chalk.gray(` ... and ${changes.added.length - 10} more`)); } } if (changes.modified.length > 0) { console.log(chalk.yellow(`\\n📝 Modified files (${changes.modified.length}):`)); changes.modified.slice(0, 10).forEach(file => { console.log(chalk.yellow(` ~ ${file}`)); }); if (changes.modified.length > 10) { console.log(chalk.gray(` ... and ${changes.modified.length - 10} more`)); } } if (changes.deleted.length > 0) { console.log(chalk.red(`\\n🗑️ Deleted files (${changes.deleted.length}):`)); changes.deleted.slice(0, 10).forEach(file => { console.log(chalk.red(` - ${file}`)); }); if (changes.deleted.length > 10) { console.log(chalk.gray(` ... and ${changes.deleted.length - 10} more`)); } } console.log(chalk.blue('\\n💡 Ready to lock in these changes? Run: claudepoint')); } catch (error) { spinner.fail('🚨 Scan error'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); // 🎛️ NEW: Configuration command program .command('config') .description('⚙️ Enter configuration mode // Tune your hacking rig') .action(async () => { const spinner = ora('🔧 Loading configuration...').start(); try { const manager = new CheckpointManager(); const status = await manager.getConfigurationStatus(); spinner.succeed(manager.getRandomMessage(manager.configMessages)); console.log(chalk.blue('\n🎛️ Current Configuration:')); console.log(chalk.cyan(` Max Claudepoints: ${status.maxClaudepoints}`)); console.log(chalk.cyan(` Current Claudepoints: ${status.currentClaudepoints}`)); console.log(chalk.cyan(` Max Age: ${status.maxAge} days ${status.maxAge === 0 ? '(unlimited)' : ''}`)); console.log(chalk.cyan(` Ignore Patterns: ${status.ignorePatterns} rules`)); console.log(chalk.cyan(` Auto Naming: ${status.autoName ? 'Enabled' : 'Disabled'}`)); console.log(chalk.gray(` Config File: ${status.configPath}`)); console.log(chalk.blue('\n🎨 Quick Config Commands:')); console.log(chalk.yellow(' • Edit config file directly with your favorite editor')); console.log(chalk.yellow(' • Or use the interactive setup: claudepoint setup')); const config = await manager.loadConfig(); if (config.additionalIgnores && config.additionalIgnores.length > 0) { console.log(chalk.blue('\n🚷 Additional Ignore Patterns:')); config.additionalIgnores.forEach(pattern => { console.log(chalk.gray(` • ${pattern}`)); }); } if (config.forceInclude && config.forceInclude.length > 0) { console.log(chalk.blue('\n⭐ Force Include Patterns:')); config.forceInclude.forEach(pattern => { console.log(chalk.green(` • ${pattern}`)); }); } } catch (error) { spinner.fail('🚨 Configuration error'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); program .command('restore <checkpoint>') .description('🔄 Time travel to a specific claudepoint // Precision restoration') .option('--dry-run', 'Show what would happen without making changes') .action(async (checkpoint, options) => { try { const manager = new CheckpointManager(); if (options.dryRun) { const result = await manager.restore(checkpoint, true); if (!result.success) { console.log(chalk.red(`❌ ${result.error}`)); const checkpoints = await manager.getCheckpoints(); console.log(chalk.blue('Available checkpoints:')); checkpoints.slice(0, 5).forEach(cp => { const typeLabel = cp.type === 'FULL' ? chalk.green('[FULL]') : cp.type === 'INCREMENTAL' ? chalk.yellow('[INC]') : chalk.gray('[LEGACY]'); console.log(` - ${cp.name} ${typeLabel}`); }); return; } const targetCheckpoint = result.checkpoint; const typeLabel = targetCheckpoint.type === 'FULL' ? chalk.green('[FULL]') : targetCheckpoint.type === 'INCREMENTAL' ? chalk.yellow('[INC]') : chalk.gray('[LEGACY]'); console.log(chalk.blue(`🔍 DRY RUN - Would restore: ${targetCheckpoint.name} ${typeLabel}`)); console.log(` Description: ${targetCheckpoint.description}`); console.log(` Date: ${new Date(targetCheckpoint.timestamp).toLocaleString()}`); console.log(` Files: ${targetCheckpoint.fileCount}`); console.log(` Strategy: ${result.restoreStrategy}`); if (result.chainLength > 1) { console.log(chalk.yellow(` Chain Length: ${result.chainLength} checkpoints (includes incremental history)`)); } const currentFiles = await manager.getProjectFiles(); const filesToDelete = currentFiles.filter(f => !targetCheckpoint.files.includes(f)); if (filesToDelete.length > 0) { console.log(chalk.yellow(` Would delete ${filesToDelete.length} files that didn't exist in checkpoint`)); } console.log('\nUse restore without --dry-run to proceed.'); return; } // Create emergency backup and confirm console.log(chalk.blue('🔒 Emergency backup protocol initiated...')); const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: `🔄 Restore claudepoint '${checkpoint}'? This will modify your codebase.`, default: false }]); if (!confirm) { console.log(chalk.red('❌ Time travel cancelled // Codebase remains stable')); return; } const spinner = ora('🔄 Initiating time travel sequence...').start(); const result = await manager.restore(checkpoint, false); if (result.success) { const typeLabel = result.type === 'FULL' ? '[FULL]' : result.type === 'INCREMENTAL' ? '[INC]' : ''; spinner.succeed(manager.getRandomMessage(manager.undoMessages)); console.log(chalk.green(` 🔒 Emergency backup: ${result.emergencyBackup}`)); console.log(chalk.cyan(` 🔄 Restored: ${result.restored} ${typeLabel}`)); if (result.type === 'INCREMENTAL') { console.log(chalk.yellow(` ⚡ Used incremental chain reconstruction`)); } console.log(chalk.blue(' 🎆 Welcome back to the past! Time travel complete.')); } else { spinner.fail(`🚨 Time travel failed: ${result.error}`); process.exit(1); } } catch (error) { console.error(chalk.red('❌ Restore failed:'), error.message); process.exit(1); } }); program .command('changelog') .description('Show development history and session log') .action(async () => { try { const manager = new CheckpointManager(); const changelog = await manager.getChangelog(); if (changelog.length === 0) { console.log(chalk.yellow('No development history found.')); return; } console.log(chalk.blue('📋 Development History:')); changelog.forEach((entry, index) => { console.log(`\n${chalk.cyan((index + 1) + '.')} ${chalk.bold(entry.action)} - ${chalk.green(entry.timestamp)}`); console.log(` ${entry.description}`); if (entry.details) { console.log(` ${chalk.gray(entry.details)}`); } }); } catch (error) { console.error(chalk.red('❌ Changelog failed:'), error.message); process.exit(1); } }); program .command('log <description>') .description('Add a custom entry to the development history') .option('-d, --details <details>', 'Detailed explanation of changes') .option('-t, --type <type>', 'Action type (CODE_CHANGE, REFACTOR, BUG_FIX, etc.)', 'CODE_CHANGE') .action(async (description, options) => { try { const manager = new CheckpointManager(); await manager.logToChangelog(options.type, description, options.details); console.log(chalk.green(`✅ Changelog entry added: ${description}`)); } catch (error) { console.error(chalk.red('❌ Log failed:'), error.message); process.exit(1); } }); program .command('init-commands') .description('Initialize Claude Code slash commands for ClaudePoint') .option('--force', 'Force regeneration even if commands already exist') .action(async (options) => { const spinner = ora('Checking Claude Code slash commands...').start(); try { // Check if commands already exist const commandsDir = path.join(process.cwd(), '.claude', 'commands'); const createCheckpointFile = path.join(commandsDir, 'create-checkpoint.md'); let commandsExist = false; try { await fsPromises.access(createCheckpointFile); commandsExist = true; } catch { commandsExist = false; } if (commandsExist && !options.force) { spinner.succeed('Slash commands already configured!'); console.log(chalk.green('✅ Claude Code slash commands are already set up')); console.log(chalk.blue('\n🚀 Available slash commands in Claude Code:')); console.log(' /create-checkpoint - Create a new checkpoint'); console.log(' /restore-checkpoint - Restore with interactive selection'); console.log(' /list-checkpoints - List all checkpoints'); console.log(' /checkpoint-status - Show current status'); console.log(' /claudepoint-init-hooks - Initialize hooks integration'); console.log(' /claudepoint-hooks-status - Show hooks status'); console.log(' /claudepoint-hooks-toggle-changelog - Toggle changelog'); console.log(chalk.yellow('\n💡 Use --force flag to regenerate commands')); console.log(chalk.gray(' Example: claudepoint init-commands --force')); return; } spinner.text = 'Creating Claude Code slash commands...'; await initializeSlashCommands(); spinner.succeed('Slash commands created successfully!'); console.log(chalk.green('✅ Created .claude/commands directory')); console.log(chalk.green('✅ Added /create-checkpoint command')); console.log(chalk.green('✅ Added /restore-checkpoint command')); console.log(chalk.green('✅ Added /list-checkpoints command')); console.log(chalk.green('✅ Added /checkpoint-status command')); console.log(chalk.green('✅ Added /claudepoint-init-hooks command')); console.log(chalk.green('✅ Added /claudepoint-hooks-status command')); console.log(chalk.green('✅ Added /claudepoint-hooks-toggle-changelog command')); console.log(chalk.blue('\n🚀 Available slash commands in Claude Code:')); console.log(' /create-checkpoint - Create a new checkpoint'); console.log(' /restore-checkpoint - Restore with interactive selection'); console.log(' /list-checkpoints - List all checkpoints'); console.log(' /checkpoint-status - Show current status'); console.log(' /claudepoint-init-hooks - Initialize hooks integration'); console.log(' /claudepoint-hooks-status - Show hooks status'); console.log(' /claudepoint-hooks-toggle-changelog - Toggle changelog'); console.log(chalk.yellow('\n💡 Tip: Type / in Claude Code to see available commands!')); } catch (error) { spinner.fail('Failed to create slash commands'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); program .command('init-hooks') .description('Initialize ClaudePoint hooks integration with Claude Code') .option('--install', 'Automatically install hooks to Claude Code settings') .action(async (options) => { const spinner = ora('Initializing ClaudePoint hooks...').start(); try { const manager = new CheckpointManager(); // Ensure directories exist await manager.ensureDirectories(); // Create hooks configuration file with proper defaults const defaultHooksConfig = await manager.loadHooksConfig(); defaultHooksConfig.enabled = true; defaultHooksConfig.auto_changelog = false; await manager.saveHooksConfig(defaultHooksConfig); spinner.text = 'Creating Claude Code hooks configuration...'; // Build Claude Code hooks configuration with CORRECT format const claudeHooksConfig = { hooks: { PreToolUse: [] } }; // Add hooks for each enabled trigger with proper structure Object.entries(defaultHooksConfig.triggers).forEach(([triggerName, triggerConfig]) => { if (triggerConfig.enabled && triggerConfig.tools) { triggerConfig.tools.forEach(tool => { claudeHooksConfig.hooks.PreToolUse.push({ matcher: tool, hooks: [{ type: "command", command: `claudepoint-hook --trigger ${triggerName} --tool ${tool}` }] }); }); } }); if (options.install) { // Install hooks to Claude Code settings automatically spinner.text = 'Installing hooks to Claude Code settings...'; const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json'); let existingSettings = {}; try { // Try to read existing settings const settingsData = await fsPromises.readFile(claudeSettingsPath, 'utf8'); existingSettings = JSON.parse(settingsData); } catch (error) { // File doesn't exist or is invalid, start with empty settings console.log(chalk.yellow(' Creating new Claude Code settings file')); } // Merge hooks with existing settings if (!existingSettings.hooks) { existingSettings.hooks = {}; } if (!existingSettings.hooks.PreToolUse) { existingSettings.hooks.PreToolUse = {}; } // Add ClaudePoint hooks with proper structure if (!Array.isArray(existingSettings.hooks.PreToolUse)) { existingSettings.hooks.PreToolUse = []; } // Remove existing claudepoint hooks to avoid duplicates existingSettings.hooks.PreToolUse = existingSettings.hooks.PreToolUse.filter(hook => !hook.hooks || !hook.hooks.some(h => h.command && h.command.includes('claudepoint-hook')) ); // Add new hooks existingSettings.hooks.PreToolUse.push(...claudeHooksConfig.hooks.PreToolUse); // Ensure .claude directory exists await fsPromises.mkdir(path.dirname(claudeSettingsPath), { recursive: true }); // Write updated settings await fsPromises.writeFile(claudeSettingsPath, JSON.stringify(existingSettings, null, 2)); spinner.succeed('ClaudePoint hooks installed!'); console.log(chalk.green('✅ Created .checkpoints/hooks.json')); console.log(chalk.green('✅ Configured default safety hooks')); console.log(chalk.green('✅ Installed hooks to ~/.claude/settings.json')); console.log(chalk.yellow('\n⚠️ Note: Hooks are installed globally in Claude Code')); console.log(chalk.gray(' Running setup in other projects will update the global hook configuration')); console.log(chalk.blue('\n🔄 Restart Claude Code to activate hooks')); } else { spinner.succeed('ClaudePoint hooks initialized!'); console.log(chalk.green('✅ Created .checkpoints/hooks.json')); console.log(chalk.green('✅ Configured default safety hooks')); console.log(chalk.blue('\n🔧 Add this to your Claude Code settings:')); console.log(chalk.gray('~/.claude/settings.json')); console.log(chalk.cyan(JSON.stringify(claudeHooksConfig, null, 2))); console.log(chalk.yellow('\n💡 Tip: Use --install flag to automatically add to settings!')); } console.log(chalk.blue('\n🚀 Available hook features:')); console.log(' • Safety checkpoints before bulk file edits'); console.log(' • Safety checkpoints before major file writes'); console.log(' • Optional automatic changelog entries'); console.log(chalk.blue('\n⚙️ Hook management commands:')); console.log(' claudepoint hooks status - Show hook configuration'); console.log(' claudepoint hooks enable - Enable hook triggers'); console.log(' claudepoint hooks disable - Disable hook triggers'); console.log(chalk.yellow('\n💡 Tip: Hooks create automatic safety checkpoints before major changes!')); } catch (error) { spinner.fail('Failed to initialize hooks'); console.error(chalk.red('Error:'), error.message); process.exit(1); } }); // Hooks management commands const hooksCommand = program .command('hooks') .description('Manage ClaudePoint hooks configuration') .addHelpText('after', ` Examples: $ claudepoint hooks status Show detailed configuration and available options $ claudepoint hooks configure Interactive configuration wizard $ claudepoint hooks enable Enable all hooks $ claudepoint hooks disable Disable all hooks $ claudepoint hooks set-changelog true Enable automatic changelog entries Available Triggers: before_bulk_edit Safety checkpoint before MultiEdit operations (default: enabled) before_major_write Safety checkpoint before Write operations (default: disabled) before_bash_commands Safety checkpoint before Bash commands (default: disabled) before_file_operations Safety checkpoint before any file changes (default: disabled) `); hooksCommand .command('status') .description('Show current hooks configuration and Claude Code installation status') .action(async () => { try { const manager = new CheckpointManager(); const config = await manager.loadHooksConfig(); // Check if hooks are installed in Claude Code settings const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json'); let installedInClaude = false; try { const settingsData = await fsPromises.readFile(claudeSettingsPath, 'utf8'); const settings = JSON.parse(settingsData); installedInClaude = settings.hooks?.PreToolUse?.MultiEdit?.includes('claudepoint-hook') || false; } catch (error) { // Settings file doesn't exist or can't be read installedInClaude = false; } console.log(chalk.blue('🔗 ClaudePoint Hooks Status')); console.log('================================'); const overallStatus = config.enabled ? (installedInClaude ? chalk.green('✅ CONFIGURED & INSTALLED') : chalk.yellow('⚠️ CONFIGURED BUT NOT INSTALLED')) : chalk.red('❌ DISABLED'); console.log(`Overall Status: ${overallStatus}`); console.log(`Auto Changelog: ${config.auto_changelog ? chalk.green('Enabled') : chalk.yellow('Disabled')}`); if (config.enabled && !installedInClaude) { console.log(chalk.red('\n⚠️ HOOKS NOT ACTIVE: Not installed in Claude Code settings')); console.log(chalk.yellow(' Run: claudepoint init-hooks --install')); } console.log('\n📋 Available Triggers:'); Object.entries(config.triggers || {}).forEach(([name, trigger]) => { const localStatus = trigger.enabled ? '✅ CONFIGURED' : '❌ DISABLED'; const claudeStatus = (trigger.enabled && installedInClaude) ? '✅ ACTIVE IN CLAUDE' : '❌ NOT IN CLAUDE CODE'; console.log(`\n ${chalk.bold(name)}`); console.log(` Local: ${trigger.enabled ? chalk.green(localStatus) : chalk.red(localStatus)}`); console.log(` Claude: ${(trigger.enabled && installedInClaude) ? chalk.green(claudeStatus) : chalk.red(claudeStatus)}`); console.log(` ${chalk.gray('Description:')} ${trigger.description}`); if (trigger.tools && trigger.too