UNPKG

claude-code-templates

Version:

CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects

252 lines (211 loc) • 7.66 kB
#!/usr/bin/env node /** * Docker Sandbox Executor * Runs inside Docker container using Claude Agent SDK */ import { query } from '@anthropic-ai/claude-agent-sdk'; import fs from 'fs/promises'; import path from 'path'; import { spawn } from 'child_process'; // Parse command line arguments const args = process.argv.slice(2); const prompt = args[0] || 'Hello, Claude!'; const componentsToInstall = args[1] || ''; const anthropicApiKey = process.env.ANTHROPIC_API_KEY; // Validate API key if (!anthropicApiKey) { console.error('āŒ Error: ANTHROPIC_API_KEY environment variable is required'); process.exit(1); } console.log('🐳 Docker Sandbox Executor'); console.log('═══════════════════════════════════════\n'); /** * Install Claude Code components if specified */ async function installComponents() { if (!componentsToInstall || componentsToInstall.trim() === '') { return true; } console.log('šŸ“¦ Installing components...'); console.log(` Components: ${componentsToInstall}\n`); return new Promise((resolve) => { const installCmd = `npx claude-code-templates@latest ${componentsToInstall} --yes`; const child = spawn('sh', ['-c', installCmd], { stdio: 'inherit', env: process.env }); child.on('close', (code) => { if (code === 0) { console.log('\nāœ… Components installed successfully\n'); resolve(true); } else { console.log('\nāš ļø Component installation had warnings (continuing...)\n'); resolve(true); // Continue even if installation has warnings } }); child.on('error', (error) => { console.error(`āŒ Installation error: ${error.message}`); resolve(false); }); }); } /** * Execute Claude Code query using Agent SDK */ async function executeQuery() { try { console.log('šŸ¤– Executing Claude Code...'); console.log(` Prompt: "${prompt.substring(0, 80)}${prompt.length > 80 ? '...' : ''}"\n`); console.log('─'.repeat(60)); console.log('šŸ“ CLAUDE OUTPUT:'); console.log('─'.repeat(60) + '\n'); // Enhance prompt with working directory context const enhancedPrompt = `${prompt}\n\nNote: Your current working directory is /app. When creating files, save them in the current directory (/app) so they can be captured in the output.`; // query() returns an async generator - we need to iterate it const generator = query({ prompt: enhancedPrompt, options: { apiKey: anthropicApiKey, model: 'claude-sonnet-4-5', permissionMode: 'bypassPermissions', // Auto-allow all tool uses } }); let assistantResponses = []; let messageCount = 0; // Iterate through the async generator for await (const message of generator) { messageCount++; if (message.type === 'assistant') { // Extract text from assistant message content if (message.message && message.message.content) { const content = Array.isArray(message.message.content) ? message.message.content : [message.message.content]; content.forEach(block => { if (block.type === 'text') { console.log(block.text); assistantResponses.push(block.text); } }); } } else if (message.type === 'result') { // Show final result metadata console.log('\n' + '─'.repeat(60)); console.log(`āœ… Execution completed (${message.num_turns} turn${message.num_turns > 1 ? 's' : ''})`); console.log(` Duration: ${message.duration_ms}ms`); console.log(` Cost: $${message.total_cost_usd.toFixed(5)}`); console.log('─'.repeat(60) + '\n'); } } // Show response summary const responseText = assistantResponses.join('\n'); if (responseText) { console.log('šŸ“„ Response Summary:'); console.log(` ${messageCount} message(s) received`); console.log(` ${assistantResponses.length} assistant response(s)`); console.log(` ${responseText.length} characters generated`); console.log(''); } return true; } catch (error) { console.error('\nāŒ Execution error:', error.message); if (error.stack) { console.error('\nStack trace:'); console.error(error.stack); } return false; } } /** * Find and copy generated files to output directory */ async function copyGeneratedFiles() { try { console.log('šŸ“ Searching for generated files...\n'); // Common file extensions to look for const extensions = [ 'js', 'jsx', 'ts', 'tsx', 'py', 'html', 'css', 'scss', 'json', 'md', 'yaml', 'yml', 'txt', 'sh', 'bash' ]; // Search for files in multiple directories const { execSync } = await import('child_process'); const findPattern = extensions.map(ext => `-name "*.${ext}"`).join(' -o '); // Search in /app and /tmp for generated files const searchPaths = ['/app', '/tmp']; let allFiles = []; for (const searchPath of searchPaths) { const findCmd = `find ${searchPath} -type f \\( ${findPattern} \\) ! -path "*/node_modules/*" ! -path "*/.npm/*" ! -path "/app/execute.js" ! -path "/app/package*.json" -newer /app/execute.js 2>/dev/null | head -50`; try { const output = execSync(findCmd, { encoding: 'utf8' }); const files = output.trim().split('\n').filter(f => f.trim()); allFiles = allFiles.concat(files); } catch (error) { // Continue to next search path } } if (allFiles.length === 0) { console.log('ā„¹ļø No generated files found\n'); return; } console.log(`šŸ“¦ Found ${allFiles.length} file(s):\n`); // Copy files to output directory preserving structure let copiedCount = 0; for (const file of allFiles) { try { // Determine relative path based on source directory let relativePath; if (file.startsWith('/app/')) { relativePath = file.replace('/app/', ''); } else if (file.startsWith('/tmp/')) { relativePath = file.replace('/tmp/', ''); } else { relativePath = path.basename(file); } const outputPath = path.join('/output', relativePath); // Create directory structure await fs.mkdir(path.dirname(outputPath), { recursive: true }); // Copy file await fs.copyFile(file, outputPath); console.log(` āœ… ${relativePath}`); copiedCount++; } catch (error) { console.log(` āš ļø Failed to copy: ${file}`); } } if (copiedCount > 0) { console.log(`\nāœ… Copied ${copiedCount} file(s) to output directory\n`); } } catch (error) { console.error('āŒ Error copying files:', error.message); } } /** * Main execution flow */ async function main() { try { // Step 1: Install components const installSuccess = await installComponents(); if (!installSuccess) { console.error('āŒ Component installation failed'); process.exit(1); } // Step 2: Execute Claude query const executeSuccess = await executeQuery(); if (!executeSuccess) { console.error('āŒ Query execution failed'); process.exit(1); } // Step 3: Copy generated files await copyGeneratedFiles(); console.log('šŸŽ‰ Docker sandbox execution completed successfully!'); process.exit(0); } catch (error) { console.error('āŒ Fatal error:', error.message); process.exit(1); } } // Run main function main();