UNPKG

ailock

Version:

AI-Proof File Guard - Protect sensitive files from accidental AI modifications

335 lines (323 loc) • 13.6 kB
import { Command } from 'commander'; import { render } from 'ink'; import React from 'react'; import chalk from 'chalk'; import { existsSync } from 'fs'; import { writeFile, mkdir } from 'fs/promises'; import { InitWizard } from '../ui/components/InitWizard.js'; import { isGitRepository, installPreCommitHook, getRepoRoot } from '../core/git.js'; import { loadConfig, findProtectedFiles } from '../core/config.js'; import { getPlatformAdapter } from '../core/platform.js'; import { HooksService } from '../services/HooksService.js'; import { homedir } from 'os'; import path from 'path'; /** * Detect project type based on existing files */ function detectProjectType() { const detections = [ { type: 'Node.js', files: ['package.json'], patterns: [ '.env', '.env.*', '!.env.example', 'config/*.json', 'config/*.yaml', '**/*.key', '**/*.pem', '**/secrets.json' ] }, { type: 'Docker', files: ['docker-compose.yml', 'docker-compose.yaml', 'Dockerfile'], patterns: [ '.env', '.env.*', '!.env.example', 'docker-compose.yml', 'docker-compose.*.yml', 'Dockerfile.prod', 'k8s/**/*.yaml', 'config/*.yaml', '**/*.key', '**/*.pem' ] }, { type: 'Python', files: ['requirements.txt', 'pyproject.toml', 'setup.py'], patterns: [ '.env', '.env.*', '!.env.example', 'config/*.json', 'config/*.yaml', '**/*.key', '**/*.pem', '**/secrets.json', '*.db', '*.sqlite' ] } ]; for (const detection of detections) { if (detection.files.some(file => existsSync(file))) { return { type: detection.type, patterns: detection.patterns }; } } // Default generic project return { type: 'Generic', patterns: [ '.env', '.env.*', '!.env.example', '**/*.key', '**/*.pem', '**/secrets.json' ] }; } /** * Generate .ailock configuration content */ function generateAilockConfig(projectType, patterns) { return `# AI-Proof File Guard Configuration # Generated for: ${projectType} project # Protects sensitive files from accidental AI modifications ${patterns.map(pattern => pattern.startsWith('!') ? pattern : pattern).join('\n')} # Add your own patterns below: `; } // Initialize HooksService for DRY principle const hooksService = new HooksService(); /** * Setup AI tool documentation (CLAUDE.md) */ async function setupAIDocumentation() { const claudeDir = path.join(homedir(), '.claude'); if (!existsSync(claudeDir)) { await mkdir(claudeDir, { recursive: true }); } const claudeMdPath = path.join(claudeDir, 'CLAUDE.md'); const protectionInstructions = ` ## AILock File Protection IMPORTANT: Before modifying any file, check if it's protected by ailock. ### Protection Check Process 1. Before any file modification, run: \`\`\`bash ailock status <file-path> \`\`\` 2. If the file is locked (shows šŸ”’), you MUST NOT modify it directly. 3. If modification is necessary, inform the user: - The file is protected by ailock - They need to explicitly unlock it: \`ailock unlock <file-path>\` - After modifications, lock it again: \`ailock lock <file-path>\` ### Why This Matters Files are locked to prevent accidental modifications of: - Production configuration files - Security-sensitive files (private keys, certificates) - Critical system files - Files that should remain stable ### Error Handling If you encounter permission errors when trying to modify a file: 1. Check if it's protected: \`ailock status <file-path>\` 2. If protected, inform the user and ask for explicit permission to unlock 3. Never try to force modifications or change file permissions without user consent Remember: Protected files can be read but not written. This is by design. `; if (existsSync(claudeMdPath)) { const existingContent = await import('fs').then(fs => fs.promises.readFile(claudeMdPath, 'utf-8')); if (!existingContent.includes('AILock File Protection')) { await writeFile(claudeMdPath, existingContent + '\n' + protectionInstructions, 'utf-8'); return; } } else { await writeFile(claudeMdPath, protectionInstructions.trim(), 'utf-8'); } } /** * Complete project security setup */ async function performCompleteSetup(options) { console.log(chalk.blue.bold('šŸš€ AI-Proof File Guard - Complete Project Setup\n')); // Step 1: Detect project type console.log(chalk.cyan('šŸ“‹ Step 1: Project Detection')); const { type, patterns } = detectProjectType(); console.log(chalk.green(` āœ… Detected: ${type} project`)); console.log(chalk.gray(` šŸ“¦ Found ${patterns.length} protection patterns`)); // Step 2: Generate .ailock config console.log(chalk.cyan('\nāš™ļø Step 2: Configuration')); const configContent = generateAilockConfig(type, patterns); await writeFile('.ailock', configContent); console.log(chalk.green(' āœ… Created .ailock configuration')); // Step 3: Install Git hooks (if applicable) console.log(chalk.cyan('\nšŸŖ Step 3: Git Integration')); const isGitRepo = await isGitRepository(); if (isGitRepo) { try { const repoRoot = await getRepoRoot(); if (repoRoot) { await installPreCommitHook(repoRoot, options.force); console.log(chalk.green(' āœ… Installed Git pre-commit hooks')); } } catch (error) { console.log(chalk.yellow(' āš ļø Git hooks installation skipped')); console.log(chalk.gray(` šŸ’” Run: ailock install-hooks (later)`)); } } else { console.log(chalk.gray(' ā„¹ļø Not a Git repository - hooks skipped')); } // Step 4: Execute first protection console.log(chalk.cyan('\nšŸ”’ Step 4: Initial Protection')); try { const config = await loadConfig(undefined, { includeGitignored: true }); const filesToLock = await findProtectedFiles(config); if (filesToLock.length > 0) { const adapter = getPlatformAdapter(); let lockedCount = 0; for (const file of filesToLock) { try { await adapter.lockFile(file); lockedCount++; } catch (error) { console.log(chalk.yellow(` āš ļø Could not lock: ${file}`)); } } console.log(chalk.green(` āœ… Protected ${lockedCount} sensitive files`)); } else { console.log(chalk.gray(' ā„¹ļø No sensitive files found to protect')); } } catch (error) { console.log(chalk.yellow(' āš ļø Initial protection skipped')); console.log(chalk.gray(' šŸ’” Run: ailock lock (later)')); } // Step 5: Claude Code Integration (if detected and not disabled) let claudeHooksInstalled = false; if (options.aiHooks !== false) { console.log(chalk.cyan('\nšŸ¤– Step 5: AI Tool Integration')); const claudeInfo = hooksService.detectClaudeCode(); if (claudeInfo.detected) { try { await hooksService.installClaudeHooks(claudeInfo); claudeHooksInstalled = true; console.log(chalk.green(' āœ… Installed Claude Code protection hooks')); console.log(chalk.gray(' šŸ“ Protected against accidental AI modifications')); if (claudeInfo.isProjectLevel) { console.log(chalk.gray(' šŸ“ Project-level: .claude/settings.json')); } else { console.log(chalk.gray(' šŸ‘¤ User-level: ~/.claude/settings.json')); } } catch (error) { console.log(chalk.yellow(' āš ļø Claude Code hook installation failed')); if (error instanceof Error) { console.log(chalk.gray(` šŸ’” ${error.message}`)); } console.log(chalk.gray(' šŸ’” Run manually: ailock hooks install claude')); } } else { console.log(chalk.gray(' ā„¹ļø Claude Code not detected - skipped')); console.log(chalk.gray(' šŸ’” Install hooks later: ailock hooks install claude')); } } // Step 6: Setup AI documentation (if requested) if (options.withAiDocs) { console.log(chalk.cyan(`\nšŸ“– Step ${options.noAiHooks ? '5' : '6'}: AI Tool Documentation`)); try { await setupAIDocumentation(); console.log(chalk.green(' āœ… Created AI tool protection documentation')); console.log(chalk.gray(' šŸ“ Location: ~/.claude/CLAUDE.md')); } catch (error) { console.log(chalk.yellow(' āš ļø Could not create AI documentation')); } } // Step 7: Show status and next steps const finalStep = options.withAiDocs ? (options.noAiHooks ? '6' : '7') : (options.noAiHooks ? '5' : '6'); console.log(chalk.cyan(`\nšŸ“Š Step ${finalStep}: Project Status`)); console.log(chalk.green('šŸŽ‰ Setup Complete! Your project is now AI-proof.')); // Quick start guide for first-time users console.log(chalk.cyan('\nšŸ“š Quick Start Guide:')); console.log(chalk.gray(' 1. Lock sensitive files: ') + chalk.white('ailock lock .env')); console.log(chalk.gray(' 2. Check protection status: ') + chalk.white('ailock status')); console.log(chalk.gray(' 3. Unlock when needed: ') + chalk.white('ailock unlock .env')); console.log(chalk.gray('\n Learn more: ') + chalk.blue('https://github.com/qpd-v/ailock')); console.log(chalk.blue.bold('\nšŸ’” Quick Commands:')); console.log(chalk.gray(' ailock status # Check protection status')); console.log(chalk.gray(' ailock lock # Protect more files')); console.log(chalk.gray(' ailock unlock [file] # Unlock for editing')); console.log(chalk.blue.bold('\nšŸ” What happened:')); console.log(chalk.gray(' • Detected your project type and created .ailock config')); console.log(chalk.gray(' • Installed Git hooks to prevent accidental commits')); console.log(chalk.gray(' • Protected sensitive files with OS-level locks')); if (claudeHooksInstalled) { console.log(chalk.gray(' • Installed Claude Code hooks for AI protection')); } console.log(chalk.gray(' • AI tools can read these files but cannot modify them')); } export const initCommand = new Command('init') .description('Initialize project protection and configuration') .option('-f, --force', 'Overwrite existing configuration and hooks') .option('--interactive', 'Use detailed interactive wizard for custom setup') .option('--config-only', 'Only create .ailock configuration file') .option('--with-ai-docs', 'Create AI tool documentation (CLAUDE.md) for enhanced protection') .option('--no-ai-hooks', 'Skip automatic AI tool hook installation') .action(async (options) => { try { // First-run welcome message const isFirstRun = !existsSync('.ailock'); if (isFirstRun && !options.force) { console.log(chalk.cyan('\nšŸ”’ Welcome to AILock!')); console.log(chalk.gray(' Protecting your sensitive files from AI modifications\n')); } // Check if .ailock already exists if (existsSync('.ailock') && !options.force) { console.log(chalk.yellow('āš ļø .ailock file already exists')); console.log(chalk.gray('Use --force to overwrite, or run: ailock status')); return; } if (options.interactive) { // Use the existing detailed wizard const { waitUntilExit } = render(React.createElement(InitWizard, { onComplete: () => { console.log(chalk.green('\nāœ… Interactive setup complete!')); process.exit(0); }, onCancel: () => { console.log(chalk.gray('\nSetup cancelled')); process.exit(0); } })); await waitUntilExit(); } else if (options.configOnly) { // Just create config file const { type, patterns } = detectProjectType(); const configContent = generateAilockConfig(type, patterns); await writeFile('.ailock', configContent); console.log(chalk.green(`āœ… Created .ailock configuration for ${type} project`)); console.log(chalk.gray('Run: ailock lock (to start protection)')); } else { // Default: Complete setup await performCompleteSetup(options); } } catch (error) { console.error(chalk.red('Error during setup:'), error instanceof Error ? error.message : String(error)); process.exit(1); } }); //# sourceMappingURL=init.js.map