UNPKG

claude-buddy

Version:

Claude Buddy installation and setup script

375 lines (339 loc) 11.7 kB
/** * Configuration Prompts for Claude Buddy Installation * * Interactive prompting system for hooks.json configuration during installation. * Supports both fresh installations and updates with intelligent defaults. */ const prompts = require('prompts'); const chalk = require('chalk'); /** * Default hooks.json configuration * These values match the framework defaults in dist/.claude/hooks.json */ function getDefaultConfig() { return { file_protection: { enabled: true, additional_patterns: [], whitelist_patterns: [], strict_mode: false }, command_validation: { enabled: true, block_dangerous: true, warn_performance: true, suggest_best_practices: true, additional_dangerous_patterns: [], whitelist_patterns: [], strict_mode: false }, auto_formatting: { enabled: true, extensions: ['.py', '.js', '.ts', '.tsx', '.jsx', '.json', '.css', '.scss', '.md'], tools: {}, exclude_patterns: ['node_modules/', '.git/', 'dist/', 'build/', '__pycache__/', '.venv/'], create_backup: false }, git: { auto_push: true, branch_protection: ['main', 'master'], commit_validation: true, conventional_commits: true, sign_commits: false }, features: { auto_commit: true, safety_hooks: true, auto_formatting: true, commit_templates: 'conventional', documentation_generation: true, code_review: true, personas: true }, logging: { enabled: true, level: 'info', file_operations: true, command_executions: true, hook_activities: true }, notifications: { desktop_alerts: false, protection_events: true, formatting_results: false, commit_summaries: true } }; } /** * Core features configuration questions */ async function promptCoreFeatures(existingConfig) { const defaults = existingConfig || getDefaultConfig(); console.log(chalk.bold.cyan('\n━━━ CORE FEATURES ━━━')); console.log(chalk.dim('Configure the main features of Claude Buddy\n')); const questions = [ { type: 'confirm', name: 'safety_hooks', message: 'Enable safety hooks? (file protection + command validation)', initial: defaults.features?.safety_hooks ?? true }, { type: 'confirm', name: 'auto_formatting', message: 'Enable auto-formatting for code files?', initial: defaults.features?.auto_formatting ?? true }, { type: 'confirm', name: 'auto_commit', message: 'Enable auto-commit workflows?', initial: defaults.features?.auto_commit ?? true }, { type: 'confirm', name: 'code_review', message: 'Enable code review before commits?', initial: defaults.features?.code_review ?? true }, { type: 'confirm', name: 'documentation_generation', message: 'Enable documentation generation?', initial: defaults.features?.documentation_generation ?? true }, { type: 'confirm', name: 'personas', message: 'Enable persona system?', initial: defaults.features?.personas ?? true } ]; return await prompts(questions, { onCancel: () => process.exit(0) }); } /** * Git settings configuration questions */ async function promptGitSettings(existingConfig) { const defaults = existingConfig || getDefaultConfig(); console.log(chalk.bold.cyan('\n━━━ GIT SETTINGS ━━━')); console.log(chalk.dim('Configure git-related behaviors\n')); const questions = [ { type: 'confirm', name: 'auto_push', message: 'Auto-push after commits?', initial: defaults.git?.auto_push ?? true }, { type: 'list', name: 'branch_protection', message: 'Protected branches (comma-separated):', initial: defaults.git?.branch_protection?.join(',') || 'main,master', separator: ',' }, { type: 'confirm', name: 'conventional_commits', message: 'Enforce conventional commits?', initial: defaults.git?.conventional_commits ?? true }, { type: 'confirm', name: 'commit_validation', message: 'Enable commit message validation?', initial: defaults.git?.commit_validation ?? true }, { type: 'confirm', name: 'sign_commits', message: 'Sign commits with GPG?', initial: defaults.git?.sign_commits ?? false }, { type: 'select', name: 'commit_templates', message: 'Commit message template style:', choices: [ { title: 'Conventional (type(scope): message)', value: 'conventional' }, { title: 'Simple (short message)', value: 'simple' }, { title: 'Detailed (with body and footer)', value: 'detailed' } ], initial: defaults.features?.commit_templates === 'conventional' ? 0 : defaults.features?.commit_templates === 'simple' ? 1 : 2 } ]; return await prompts(questions, { onCancel: () => process.exit(0) }); } /** * Advanced settings configuration questions */ async function promptAdvancedSettings(existingConfig) { const defaults = existingConfig || getDefaultConfig(); console.log(chalk.bold.cyan('\n━━━ ADVANCED SETTINGS ━━━')); console.log(chalk.dim('Optional advanced configuration\n')); const questions = [ { type: 'select', name: 'logging_level', message: 'Logging level:', choices: [ { title: 'Error (only errors)', value: 'error' }, { title: 'Warn (errors and warnings)', value: 'warn' }, { title: 'Info (normal operation)', value: 'info' }, { title: 'Debug (detailed info)', value: 'debug' } ], initial: defaults.logging?.level === 'error' ? 0 : defaults.logging?.level === 'warn' ? 1 : defaults.logging?.level === 'info' ? 2 : 3 }, { type: 'confirm', name: 'desktop_alerts', message: 'Enable desktop notifications?', initial: defaults.notifications?.desktop_alerts ?? false }, { type: 'confirm', name: 'file_protection_strict', message: 'Enable strict mode for file protection?', initial: defaults.file_protection?.strict_mode ?? false }, { type: 'confirm', name: 'formatting_backup', message: 'Create backups before auto-formatting?', initial: defaults.auto_formatting?.create_backup ?? false } ]; return await prompts(questions, { onCancel: () => process.exit(0) }); } /** * Prompt user for complete configuration * * @param {string} mode - 'install' or 'update' * @param {Object|null} existingConfig - Existing hooks.json config (for updates) * @returns {Promise<Object>} Complete configuration object */ async function promptConfiguration(mode, existingConfig = null) { console.log(chalk.bold.green('\n🎯 Claude Buddy Configuration')); console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')); if (mode === 'update' && existingConfig) { console.log(chalk.dim('Updating existing configuration.')); console.log(chalk.dim('Press Enter to keep current values shown in [brackets].\n')); } else { console.log(chalk.dim('Configuring your Claude Buddy installation.')); console.log(chalk.dim('Press Enter to accept defaults shown in [brackets].\n')); } // Core features const coreAnswers = await promptCoreFeatures(existingConfig); // Git settings const gitAnswers = await promptGitSettings(existingConfig); // Ask if user wants advanced settings const wantAdvanced = await prompts({ type: 'confirm', name: 'value', message: 'Configure advanced settings?', initial: false }, { onCancel: () => process.exit(0) }); let advancedAnswers = {}; if (wantAdvanced.value) { advancedAnswers = await promptAdvancedSettings(existingConfig); } // Merge all answers into config structure return mergeConfigAnswers(coreAnswers, gitAnswers, advancedAnswers, existingConfig); } /** * Merge user answers into complete config structure * * @param {Object} coreAnswers - Core features answers * @param {Object} gitAnswers - Git settings answers * @param {Object} advancedAnswers - Advanced settings answers * @param {Object|null} existingConfig - Existing config to merge with * @returns {Object} Complete merged configuration */ function mergeConfigAnswers(coreAnswers, gitAnswers, advancedAnswers, existingConfig) { const base = existingConfig || getDefaultConfig(); // Merge core features base.features = { ...base.features, safety_hooks: coreAnswers.safety_hooks, auto_formatting: coreAnswers.auto_formatting, auto_commit: coreAnswers.auto_commit, code_review: coreAnswers.code_review, documentation_generation: coreAnswers.documentation_generation, personas: coreAnswers.personas, commit_templates: gitAnswers.commit_templates || base.features.commit_templates }; // Merge git settings base.git = { ...base.git, auto_push: gitAnswers.auto_push, branch_protection: Array.isArray(gitAnswers.branch_protection) ? gitAnswers.branch_protection : gitAnswers.branch_protection.split(',').map(b => b.trim()), conventional_commits: gitAnswers.conventional_commits, commit_validation: gitAnswers.commit_validation, sign_commits: gitAnswers.sign_commits }; // Apply safety hooks setting to individual components base.file_protection.enabled = coreAnswers.safety_hooks; base.command_validation.enabled = coreAnswers.safety_hooks; // Apply auto formatting setting base.auto_formatting.enabled = coreAnswers.auto_formatting; // Merge advanced settings if provided if (advancedAnswers.logging_level) { base.logging.level = advancedAnswers.logging_level; } if (advancedAnswers.desktop_alerts !== undefined) { base.notifications.desktop_alerts = advancedAnswers.desktop_alerts; } if (advancedAnswers.file_protection_strict !== undefined) { base.file_protection.strict_mode = advancedAnswers.file_protection_strict; base.command_validation.strict_mode = advancedAnswers.file_protection_strict; } if (advancedAnswers.formatting_backup !== undefined) { base.auto_formatting.create_backup = advancedAnswers.formatting_backup; } return base; } /** * Validate configuration object * * @param {Object} config - Configuration to validate * @throws {Error} If configuration is invalid */ function validateConfigAnswers(config) { // Basic structure validation const requiredSections = [ 'file_protection', 'command_validation', 'auto_formatting', 'git', 'features', 'logging', 'notifications' ]; for (const section of requiredSections) { if (!config[section]) { throw new Error(`Missing required configuration section: ${section}`); } } // Validate git branch protection if (!Array.isArray(config.git.branch_protection)) { throw new Error('git.branch_protection must be an array'); } // Validate logging level const validLevels = ['error', 'warn', 'info', 'debug']; if (!validLevels.includes(config.logging.level)) { throw new Error(`Invalid logging level: ${config.logging.level}`); } // Validate commit template const validTemplates = ['conventional', 'simple', 'detailed']; if (!validTemplates.includes(config.features.commit_templates)) { throw new Error(`Invalid commit template: ${config.features.commit_templates}`); } return true; } module.exports = { promptConfiguration, getDefaultConfig, mergeConfigAnswers, validateConfigAnswers };