UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

139 lines 6.08 kB
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs'; import { join, dirname, relative, extname } from 'path'; import chalk from 'chalk'; import { fileURLToPath } from 'url'; import { validatePath, RESOURCE_LIMITS } from './security.js'; import { logger } from './logger.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Allowed file extensions for safety const ALLOWED_EXTENSIONS = ['.md', '.json', '.yml', '.yaml', '.txt']; const MAX_FILE_SIZE = RESOURCE_LIMITS.MAX_OUTPUT_SIZE; /** * Copies all .claude files from the npm package to the user's project. * This includes both .claude/commands and .claude/agents directories. * @param options Configuration options * @param options.force If true, overwrites existing files * @param options.silent If true, suppresses output * @throws Error if source directory is not found or copy operation fails */ export async function copyClaudeFiles(options = {}) { try { // Get the package root directory const packageRoot = join(__dirname, '..', '..'); const sourceDir = join(packageRoot, '.claude'); // Verify source directory exists if (!existsSync(sourceDir)) { throw new Error('Source .claude directory not found in package'); } // Target directory in user's project const targetDir = join(process.cwd(), '.claude'); // Create target directory if it doesn't exist if (!existsSync(targetDir)) { mkdirSync(targetDir, { recursive: true }); if (!options.silent) { logger.info(chalk.green(`✓ Created ${relative(process.cwd(), targetDir)} directory`)); } } // Copy files recursively const copiedFiles = []; const skippedFiles = []; copyDirectory(sourceDir, targetDir, options.force || false, copiedFiles, skippedFiles); // Show summary only if not silent if (!options.silent) { if (copiedFiles.length > 0 || skippedFiles.length > 0) { logger.info(''); } if (copiedFiles.length > 0) { logger.info(chalk.green(`✓ Copied ${copiedFiles.length} .claude file(s)`)); if (options.force || copiedFiles.length <= 10) { copiedFiles.forEach(file => { logger.info(chalk.gray(` - ${file}`)); }); } } if (skippedFiles.length > 0) { if (copiedFiles.length > 0) { logger.info(''); } logger.info(chalk.yellow(`⚠ Skipped ${skippedFiles.length} existing file(s)`)); if (!options.force && skippedFiles.length <= 5) { skippedFiles.forEach(file => { logger.info(chalk.gray(` - ${file}`)); }); logger.info(chalk.gray('\nUse --force to overwrite existing files')); } } if (copiedFiles.length === 0 && skippedFiles.length === 0) { logger.info(chalk.yellow('No files to copy - all .claude files already exist')); } } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to copy .claude files: ${errorMessage}`); } } /** * Recursively copies a directory structure from source to target. * @param sourceDir Source directory path * @param targetDir Target directory path * @param force Whether to overwrite existing files * @param copiedFiles Array to track copied files * @param skippedFiles Array to track skipped files */ function copyDirectory(sourceDir, targetDir, force, copiedFiles, skippedFiles) { const entries = readdirSync(sourceDir); for (const entry of entries) { // Validate paths to prevent directory traversal validatePath(entry, sourceDir); const sourcePath = join(sourceDir, entry); const targetPath = join(targetDir, entry); const stat = statSync(sourcePath); // Skip symbolic links for security if (stat.isSymbolicLink()) { logger.warn(`Skipping symbolic link: ${sourcePath}`); continue; } if (stat.isDirectory()) { // Create directory if it doesn't exist if (!existsSync(targetPath)) { mkdirSync(targetPath, { recursive: true }); } // Recursively copy directory contents copyDirectory(sourcePath, targetPath, force, copiedFiles, skippedFiles); } else if (stat.isFile()) { // Check file extension const ext = extname(sourcePath).toLowerCase(); if (!ALLOWED_EXTENSIONS.includes(ext)) { logger.debug(`Skipping disallowed file type: ${sourcePath}`); continue; } // Check file size if (stat.size > MAX_FILE_SIZE) { logger.warn(`Skipping file too large (${stat.size} bytes): ${sourcePath}`); continue; } const relativePath = relative(process.cwd(), targetPath); if (existsSync(targetPath) && !force) { skippedFiles.push(relativePath); } else { // Copy file try { const content = readFileSync(sourcePath, 'utf-8'); mkdirSync(dirname(targetPath), { recursive: true }); writeFileSync(targetPath, content); copiedFiles.push(relativePath); } catch (error) { const errorMsg = error instanceof Error ? error.message : 'Unknown error'; logger.error(`Failed to copy file ${sourcePath}: ${errorMsg}`); throw error; } } } } } //# sourceMappingURL=copy-claude-files.js.map