@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
139 lines • 6.08 kB
JavaScript
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