UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

264 lines (221 loc) 9.25 kB
#!/usr/bin/env node /** * CFN Post-install Script * * This script runs after npm install to properly set up CFN structure. * It handles: * 1. Preserving existing custom agents in the target project * 2. Only updating/replacing the cfn-dev-team folder * 3. Setting up local-ruvector for semantic code search * 4. Installing git hooks for auto-indexing on commit (handles deleted/renamed files) */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.resolve(__dirname, '..'); // Colors for output const colors = { reset: '\x1b[0m', green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', white: '\x1b[37m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function copyCFNDevTeam(targetDir) { const sourceAgentsDir = path.join(projectRoot, '.claude', 'agents'); const targetAgentsDir = path.join(targetDir, '.claude', 'agents'); // Skip if we're in the npm package itself if (targetDir === projectRoot) { log('Skipping CFN setup in npm package directory', 'yellow'); return; } // Ensure .claude/agents directory exists fs.mkdirSync(targetAgentsDir, { recursive: true }); const sourceCFNDir = path.join(sourceAgentsDir, 'cfn-dev-team'); const targetCFNDir = path.join(targetAgentsDir, 'cfn-dev-team'); // Remove existing cfn-dev-team if it exists if (fs.existsSync(targetCFNDir)) { log(`Removing existing cfn-dev-team in ${targetDir}`, 'yellow'); fs.rmSync(targetCFNDir, { recursive: true, force: true }); } // Copy new cfn-dev-team if (fs.existsSync(sourceCFNDir)) { log(`Copying cfn-dev-team to ${targetDir}`, 'cyan'); fs.cpSync(sourceCFNDir, targetCFNDir, { recursive: true }); log('✓ cfn-dev-team agents updated successfully', 'green'); } else { log(`Warning: cfn-dev-team not found in ${sourceCFNDir}`, 'yellow'); } } function updateSkillsAndCommands(targetDir) { const sourceDir = path.join(projectRoot, '.claude'); const targetClaudeDir = path.join(targetDir, '.claude'); // Update skills (preserve existing) const sourceSkillsDir = path.join(sourceDir, 'skills'); const targetSkillsDir = path.join(targetClaudeDir, 'skills'); if (fs.existsSync(sourceSkillsDir)) { if (!fs.existsSync(targetSkillsDir)) { fs.mkdirSync(targetSkillsDir, { recursive: true }); } // Copy new/updated skills const skills = fs.readdirSync(sourceSkillsDir); for (const skill of skills) { const sourceSkill = path.join(sourceSkillsDir, skill); const targetSkill = path.join(targetSkillsDir, skill); if (fs.statSync(sourceSkill).isDirectory()) { if (fs.existsSync(targetSkill)) { fs.rmSync(targetSkill, { recursive: true, force: true }); } fs.cpSync(sourceSkill, targetSkill, { recursive: true }); } } log('✓ CFN skills updated', 'green'); } // Update commands const sourceCommandsDir = path.join(sourceDir, 'commands'); const targetCommandsDir = path.join(targetClaudeDir, 'commands'); if (fs.existsSync(sourceCommandsDir)) { if (fs.existsSync(targetCommandsDir)) { fs.rmSync(targetCommandsDir, { recursive: true, force: true }); } fs.cpSync(sourceCommandsDir, targetCommandsDir, { recursive: true }); log('✓ CFN commands updated', 'green'); } } function installGitHooks(targetDir) { const gitDir = path.join(targetDir, '.git'); const hooksDir = path.join(gitDir, 'hooks'); // Only install if this is a git repo if (!fs.existsSync(gitDir)) { log('Not a git repository - skipping hook installation', 'yellow'); return; } // Ensure hooks directory exists fs.mkdirSync(hooksDir, { recursive: true }); // Install post-commit hook for RuVector indexing const hookSource = path.join(targetDir, '.claude', 'hooks', 'post-commit-codebase-index'); const hookDest = path.join(hooksDir, 'post-commit'); if (fs.existsSync(hookSource)) { // Check if existing hook is not ours if (fs.existsSync(hookDest)) { const existingContent = fs.readFileSync(hookDest, 'utf-8'); if (!existingContent.includes('RuVector')) { // Backup existing hook and append ours const backupPath = path.join(hooksDir, 'post-commit.backup'); fs.copyFileSync(hookDest, backupPath); log(`Backed up existing post-commit hook to ${backupPath}`, 'yellow'); // Append our hook call to existing const appendContent = `\n\n# CFN RuVector indexing (added by claude-flow-novice)\n${hookSource}\n`; fs.appendFileSync(hookDest, appendContent); log('✓ Appended RuVector hook to existing post-commit', 'green'); } else { // Already has our hook, update it fs.copyFileSync(hookSource, hookDest); fs.chmodSync(hookDest, 0o755); log('✓ Updated post-commit hook', 'green'); } } else { // No existing hook, install ours fs.copyFileSync(hookSource, hookDest); fs.chmodSync(hookDest, 0o755); log('✓ Installed post-commit hook for RuVector indexing', 'green'); } } } function setupLocalRuvector(targetDir) { const ruvectorSkillDir = path.join(targetDir, '.claude', 'skills', 'cfn-local-ruvector-accelerator'); // Check if local-ruvector binary is available in PATH or ~/.local/bin const homeBin = process.env.HOME ? path.join(process.env.HOME, '.local', 'bin', 'local-ruvector') : null; // Check if binary exists in PATH let binaryInPath = false; try { execSync('which local-ruvector', { stdio: 'ignore' }); binaryInPath = true; } catch { binaryInPath = false; } if (binaryInPath || (homeBin && fs.existsSync(homeBin))) { log('✓ local-ruvector binary found', 'green'); return; } // Check if Rust is available for building let hasRust = false; try { execSync('cargo --version', { stdio: 'ignore' }); hasRust = true; } catch { hasRust = false; } if (hasRust && fs.existsSync(ruvectorSkillDir)) { log('Building local-ruvector binary (this may take a minute)...', 'cyan'); try { execSync('cargo build --release', { cwd: ruvectorSkillDir, stdio: 'inherit' }); // Install to ~/.local/bin const targetBinary = path.join(ruvectorSkillDir, 'target', 'release', 'local-ruvector'); if (fs.existsSync(targetBinary) && process.env.HOME) { const installDir = path.join(process.env.HOME, '.local', 'bin'); fs.mkdirSync(installDir, { recursive: true }); fs.copyFileSync(targetBinary, path.join(installDir, 'local-ruvector')); fs.chmodSync(path.join(installDir, 'local-ruvector'), 0o755); log('✓ local-ruvector installed to ~/.local/bin/', 'green'); log(' Ensure ~/.local/bin is in your PATH', 'cyan'); } } catch (err) { log('Warning: Failed to build local-ruvector: ' + err.message, 'yellow'); } } else { log('Note: local-ruvector binary not found.', 'yellow'); log('To enable fast semantic code search:', 'cyan'); log(' 1. Install Rust: curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh', 'white'); log(' 2. Build ruvector: npm run ruvector:local:build', 'white'); log(' 3. Install binary: cp .claude/skills/cfn-local-ruvector-accelerator/target/release/local-ruvector ~/.local/bin/', 'white'); } } // Main execution function main() { log('CFN Post-install Script', 'cyan'); log('========================', 'cyan'); // Check if we're in a project that needs CFN setup const packageJsonPath = path.join(process.cwd(), 'package.json'); // Only run if not in the CFN package itself if (process.cwd() !== projectRoot && fs.existsSync(packageJsonPath)) { log(`Setting up CFN in: ${process.cwd()}`, 'cyan'); // Initialize .claude directory if it doesn't exist const claudeDir = path.join(process.cwd(), '.claude'); if (!fs.existsSync(claudeDir)) { fs.mkdirSync(claudeDir, { recursive: true }); } // Copy cfn-dev-team agents copyCFNDevTeam(process.cwd()); // Update skills and commands updateSkillsAndCommands(process.cwd()); // Setup local-ruvector binary setupLocalRuvector(process.cwd()); // Install git hooks for auto-indexing installGitHooks(process.cwd()); // Create .cfn-initialized marker fs.writeFileSync(path.join(claudeDir, '.cfn-initialized'), new Date().toISOString()); log('\nCFN setup complete! 🎉', 'green'); log('You can now use CFN commands like:', 'cyan'); log(' /cfn-loop-cli "Task description" --mode=standard', 'white'); log(' /cfn-loop-task "Task description" --mode=standard', 'white'); } } // Run the script try { main(); } catch (error) { console.error('CFN post-install failed:', error); process.exit(1); }