chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:22-alpine
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk --no-cache add \
|
||||
git \
|
||||
bash \
|
||||
python3 \
|
||||
py3-pip \
|
||||
curl \
|
||||
&& npm install -g @anthropic-ai/claude-agent-sdk
|
||||
|
||||
# Create non-root user for security
|
||||
RUN adduser -u 10001 -D -s /bin/bash sandboxuser
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Create output directory
|
||||
RUN mkdir -p /output && chown sandboxuser:sandboxuser /output
|
||||
|
||||
# Copy execution script
|
||||
COPY execute.js /app/execute.js
|
||||
COPY package.json /app/package.json
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --production && \
|
||||
chown -R sandboxuser:sandboxuser /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER sandboxuser
|
||||
|
||||
# Set environment
|
||||
ENV HOME=/home/sandboxuser
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Default command (overridden by launcher)
|
||||
CMD ["node", "/app/execute.js"]
|
||||
@@ -0,0 +1,453 @@
|
||||
# Docker Claude Code Sandbox
|
||||
|
||||
Execute Claude Code in isolated Docker containers with AI-powered code generation using the Claude Agent SDK.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install Docker
|
||||
|
||||
Ensure Docker is installed and running on your system:
|
||||
|
||||
```bash
|
||||
# Check Docker installation
|
||||
docker --version
|
||||
|
||||
# Verify Docker daemon is running
|
||||
docker ps
|
||||
```
|
||||
|
||||
If Docker is not installed, visit: https://docs.docker.com/get-docker/
|
||||
|
||||
### 2. Configure API Key
|
||||
|
||||
Set your Anthropic API key:
|
||||
|
||||
```bash
|
||||
# Set as environment variable
|
||||
export ANTHROPIC_API_KEY=sk-ant-your-api-key-here
|
||||
|
||||
# Or pass directly when using the CLI
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--agent development/frontend-developer \
|
||||
--prompt "Create a React component" \
|
||||
--anthropic-api-key sk-ant-your-key
|
||||
```
|
||||
|
||||
### 3. Run Your First Sandbox
|
||||
|
||||
```bash
|
||||
# Basic execution
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--prompt "Write a function to calculate factorial"
|
||||
|
||||
# With specific agent
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--agent development/python-developer \
|
||||
--prompt "Create a data validation script"
|
||||
|
||||
# With multiple components
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--agent development/fullstack-developer \
|
||||
--command development/setup-testing \
|
||||
--prompt "Set up a complete testing environment"
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
This sandbox combines two powerful technologies:
|
||||
|
||||
1. **Claude Agent SDK** - Provides programmatic access to Claude Code
|
||||
2. **Docker** - Provides isolated container execution
|
||||
|
||||
```
|
||||
User Prompt → Docker Launcher → Container Build → Execute Script → Claude Agent SDK → Output Files
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
```
|
||||
docker/
|
||||
├── docker-launcher.js # Node.js launcher that orchestrates Docker
|
||||
├── Dockerfile # Container definition with Claude Agent SDK
|
||||
├── execute.js # Script that runs inside container
|
||||
├── package.json # Dependencies (Claude Agent SDK)
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Launcher Phase (docker-launcher.js)
|
||||
- Checks Docker installation and daemon status
|
||||
- Builds container image if it doesn't exist
|
||||
- Prepares environment variables and volume mounts
|
||||
- Launches container with user prompt
|
||||
|
||||
### 2. Container Phase (execute.js)
|
||||
- Installs requested components (agents, commands, MCPs, etc.)
|
||||
- Executes Claude Agent SDK with the user's prompt
|
||||
- Auto-allows all tool uses (no permission prompts)
|
||||
- Captures output and generated files
|
||||
- Copies results to mounted output directory
|
||||
|
||||
### 3. Output Phase
|
||||
- Generated files are saved to `output/` directory
|
||||
- Files preserve directory structure
|
||||
- Accessible on host machine for inspection
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Simple Code Generation
|
||||
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--prompt "Create a REST API server with Express.js"
|
||||
```
|
||||
|
||||
### With Specific Agent
|
||||
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--agent security/security-auditor \
|
||||
--prompt "Audit this codebase for security vulnerabilities"
|
||||
```
|
||||
|
||||
### Multiple Components
|
||||
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--agent development/frontend-developer \
|
||||
--command testing/setup-testing \
|
||||
--setting performance/performance-optimization \
|
||||
--prompt "Create a React app with testing setup"
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# 1. Generate initial code
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--agent development/fullstack-developer \
|
||||
--prompt "Create a blog API with authentication"
|
||||
|
||||
# 2. Check output
|
||||
ls -la output/
|
||||
|
||||
# 3. Iterate on generated code
|
||||
npx claude-code-templates@latest --sandbox docker \
|
||||
--prompt "Add pagination to the blog API"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Required:**
|
||||
- `ANTHROPIC_API_KEY` - Your Anthropic API key
|
||||
|
||||
**Optional:**
|
||||
- `DOCKER_BUILDKIT=1` - Enable BuildKit for faster builds
|
||||
|
||||
### Docker Image Details
|
||||
|
||||
The Docker image (`claude-sandbox`) includes:
|
||||
|
||||
- **Base**: Node.js 22 Alpine Linux (minimal, secure)
|
||||
- **Runtime**: Git, Bash, Python3, Pip, Curl
|
||||
- **Claude SDK**: `@anthropic-ai/claude-agent-sdk` installed globally
|
||||
- **Security**: Runs as non-root user (UID 10001)
|
||||
- **Working Directory**: `/app`
|
||||
- **Output Directory**: `/output` (mounted as volume)
|
||||
|
||||
### Build Configuration
|
||||
|
||||
Edit `Dockerfile` to customize:
|
||||
|
||||
```dockerfile
|
||||
# Add additional system dependencies
|
||||
RUN apk --no-cache add postgresql-client redis
|
||||
|
||||
# Install additional global npm packages
|
||||
RUN npm install -g typescript tsx
|
||||
|
||||
# Set custom environment variables
|
||||
ENV CUSTOM_VAR=value
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Build Image Manually
|
||||
|
||||
```bash
|
||||
cd .claude/sandbox/docker
|
||||
docker build -t claude-sandbox .
|
||||
```
|
||||
|
||||
### Run Container Directly
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
|
||||
-v $(pwd)/output:/output \
|
||||
claude-sandbox \
|
||||
node /app/execute.js "Your prompt here" ""
|
||||
```
|
||||
|
||||
### Clean Up
|
||||
|
||||
```bash
|
||||
# Remove built image
|
||||
docker rmi claude-sandbox
|
||||
|
||||
# Remove all stopped containers
|
||||
docker container prune
|
||||
|
||||
# Remove dangling images
|
||||
docker image prune
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Docker Not Found
|
||||
|
||||
**Error:** `Docker is not installed`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Install Docker from official site
|
||||
# macOS: https://docs.docker.com/desktop/install/mac-install/
|
||||
# Linux: https://docs.docker.com/engine/install/
|
||||
# Windows: https://docs.docker.com/desktop/install/windows-install/
|
||||
```
|
||||
|
||||
### Docker Daemon Not Running
|
||||
|
||||
**Error:** `Docker daemon is not running`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# macOS/Windows: Start Docker Desktop application
|
||||
# Linux: sudo systemctl start docker
|
||||
```
|
||||
|
||||
### API Key Not Set
|
||||
|
||||
**Error:** `ANTHROPIC_API_KEY environment variable is required`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY=sk-ant-your-key-here
|
||||
```
|
||||
|
||||
### Build Failures
|
||||
|
||||
**Error:** Failed to build Docker image
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check Docker logs
|
||||
docker logs <container-id>
|
||||
|
||||
# Rebuild from scratch
|
||||
docker build --no-cache -t claude-sandbox .
|
||||
|
||||
# Check disk space
|
||||
docker system df
|
||||
```
|
||||
|
||||
### Permission Issues
|
||||
|
||||
**Error:** Permission denied when accessing output files
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check output directory permissions
|
||||
ls -la output/
|
||||
|
||||
# Fix permissions (if needed)
|
||||
sudo chown -R $USER:$USER output/
|
||||
```
|
||||
|
||||
### Container Execution Failures
|
||||
|
||||
**Error:** Container failed with code 1
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Run container interactively for debugging
|
||||
docker run -it --rm \
|
||||
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
|
||||
claude-sandbox \
|
||||
/bin/bash
|
||||
|
||||
# Check container logs
|
||||
docker logs <container-id>
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Image Caching**: First build takes longer, subsequent builds are fast
|
||||
2. **Volume Mounts**: Use volumes instead of COPY for faster iteration
|
||||
3. **Layer Optimization**: Group RUN commands to reduce image layers
|
||||
4. **BuildKit**: Enable for parallel builds (`DOCKER_BUILDKIT=1`)
|
||||
5. **Prune Regularly**: Clean up unused images and containers
|
||||
|
||||
## Security
|
||||
|
||||
- **Isolation**: Containers are isolated from host system
|
||||
- **Non-root User**: Execution runs as `sandboxuser` (UID 10001)
|
||||
- **No Network**: Container has no internet access (except during build)
|
||||
- **Read-only**: Host filesystem is mounted read-only
|
||||
- **Resource Limits**: Docker enforces CPU and memory limits
|
||||
- **Secret Management**: API keys are passed as environment variables (not stored in image)
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
**Docker:**
|
||||
- Free and open-source
|
||||
- No cloud costs (runs locally)
|
||||
- Resource usage: ~500MB disk space, ~512MB RAM during execution
|
||||
|
||||
**Anthropic API:**
|
||||
- Claude Sonnet 4.5: ~$3 per million input tokens
|
||||
- Average request: ~200 tokens = $0.0006 per request
|
||||
|
||||
**Example costs for 100 executions:**
|
||||
- Docker: $0 (local execution)
|
||||
- Anthropic: ~$0.06 (avg 200 tokens/request)
|
||||
- **Total: ~$0.06**
|
||||
|
||||
## Comparison with Other Providers
|
||||
|
||||
| Feature | Docker | E2B | Cloudflare |
|
||||
|---------|--------|-----|------------|
|
||||
| Execution Location | 🏠 Local | ☁️ Cloud | 🌍 Edge |
|
||||
| Setup Complexity | Medium | Easy | Easy |
|
||||
| Internet Required | Setup only | Yes | Yes |
|
||||
| Cost | Free | Paid | Paid |
|
||||
| Privacy | Full control | Third-party | Third-party |
|
||||
| Offline Support | Yes | No | No |
|
||||
| Best For | Local dev, privacy, offline | Full stack projects | Serverless, global APIs |
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
docker/
|
||||
├── docker-launcher.js # Orchestrates container lifecycle
|
||||
│ ├── checkDockerInstalled()
|
||||
│ ├── checkDockerRunning()
|
||||
│ ├── buildDockerImage()
|
||||
│ └── runDockerContainer()
|
||||
├── Dockerfile # Container definition
|
||||
│ ├── Base image (Node 22 Alpine)
|
||||
│ ├── System dependencies
|
||||
│ ├── Claude Agent SDK
|
||||
│ └── Security (non-root user)
|
||||
├── execute.js # Execution script (runs in container)
|
||||
│ ├── installComponents()
|
||||
│ ├── executeQuery()
|
||||
│ └── copyGeneratedFiles()
|
||||
├── package.json # NPM dependencies
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
### Scripts
|
||||
|
||||
```bash
|
||||
# Build image
|
||||
npm run build
|
||||
|
||||
# Clean image
|
||||
npm run clean
|
||||
```
|
||||
|
||||
### Extending the Image
|
||||
|
||||
Add custom tools to the Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
# Install Python packages
|
||||
RUN pip install --no-cache-dir pandas numpy matplotlib
|
||||
|
||||
# Install Node.js packages globally
|
||||
RUN npm install -g typescript eslint prettier
|
||||
|
||||
# Add custom scripts
|
||||
COPY scripts/ /app/scripts/
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Dockerfile
|
||||
|
||||
Create a custom Dockerfile for specialized environments:
|
||||
|
||||
```dockerfile
|
||||
FROM node:22-alpine
|
||||
|
||||
# Install database clients
|
||||
RUN apk add --no-cache postgresql-client mysql-client
|
||||
|
||||
# Install development tools
|
||||
RUN apk add --no-cache vim nano tmux
|
||||
|
||||
# Install Claude Agent SDK
|
||||
RUN npm install -g @anthropic-ai/claude-agent-sdk
|
||||
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
### Multi-stage Builds
|
||||
|
||||
Optimize image size with multi-stage builds:
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /build
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Runtime stage
|
||||
FROM node:22-alpine
|
||||
COPY --from=builder /build/node_modules ./node_modules
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
### Persistent Storage
|
||||
|
||||
Mount additional volumes for persistent data:
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
|
||||
-v $(pwd)/output:/output \
|
||||
-v $(pwd)/cache:/cache \
|
||||
claude-sandbox
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Docker Documentation](https://docs.docker.com/)
|
||||
- [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk)
|
||||
- [Anthropic API Documentation](https://docs.anthropic.com/)
|
||||
- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/)
|
||||
- [Container Security](https://docs.docker.com/engine/security/)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
1. Check Docker installation: `docker --version && docker ps`
|
||||
2. Verify API key: `echo $ANTHROPIC_API_KEY`
|
||||
3. Check container logs: `docker logs <container-id>`
|
||||
4. Review output directory: `ls -la output/`
|
||||
5. Open an issue on GitHub
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ using Docker, Node.js, and Claude Agent SDK
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Docker Sandbox Launcher
|
||||
* Orchestrates Docker container execution for Claude Code
|
||||
*/
|
||||
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// 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 Launcher');
|
||||
console.log('═══════════════════════════════════════\n');
|
||||
|
||||
/**
|
||||
* Check if Docker is installed
|
||||
*/
|
||||
function checkDockerInstalled() {
|
||||
try {
|
||||
execSync('docker --version', { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Error: Docker is not installed');
|
||||
console.error(' Please install Docker: https://docs.docker.com/get-docker/\n');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Docker daemon is running
|
||||
*/
|
||||
function checkDockerRunning() {
|
||||
try {
|
||||
execSync('docker ps', { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Error: Docker daemon is not running');
|
||||
console.error(' Please start Docker and try again\n');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Docker image if it doesn't exist
|
||||
*/
|
||||
async function buildDockerImage() {
|
||||
console.log('🔨 Checking Docker image...');
|
||||
|
||||
// Check if image exists
|
||||
try {
|
||||
execSync('docker image inspect claude-sandbox', { stdio: 'pipe' });
|
||||
console.log(' ✅ Image already exists\n');
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Image doesn't exist, build it
|
||||
console.log(' 📦 Building Docker image (this may take a few minutes)...\n');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const build = spawn('docker', [
|
||||
'build',
|
||||
'-t', 'claude-sandbox',
|
||||
'.'
|
||||
], {
|
||||
cwd: __dirname,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
build.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('\n✅ Docker image built successfully\n');
|
||||
resolve(true);
|
||||
} else {
|
||||
console.error('\n❌ Failed to build Docker image');
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
build.on('error', (error) => {
|
||||
console.error('❌ Build error:', error.message);
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Docker container
|
||||
*/
|
||||
async function runDockerContainer() {
|
||||
console.log('🚀 Starting Docker container...\n');
|
||||
|
||||
// Create output directory
|
||||
const outputDir = path.join(process.cwd(), 'output');
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const dockerArgs = [
|
||||
'run',
|
||||
'--rm',
|
||||
'-e', `ANTHROPIC_API_KEY=${anthropicApiKey}`,
|
||||
'-v', `${outputDir}:/output`,
|
||||
'claude-sandbox',
|
||||
'node', '/app/execute.js',
|
||||
prompt,
|
||||
componentsToInstall
|
||||
];
|
||||
|
||||
const container = spawn('docker', dockerArgs, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
container.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('\n✅ Docker container completed successfully');
|
||||
|
||||
// Show output directory
|
||||
console.log(`\n📂 Output files saved to: ${outputDir}`);
|
||||
resolve(true);
|
||||
} else {
|
||||
console.error('\n❌ Docker container failed with code:', code);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
container.on('error', (error) => {
|
||||
console.error('❌ Container error:', error.message);
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main execution flow
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
// Step 1: Check Docker installation
|
||||
if (!checkDockerInstalled()) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 2: Check Docker daemon
|
||||
if (!checkDockerRunning()) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 3: Build Docker image
|
||||
const buildSuccess = await buildDockerImage();
|
||||
if (!buildSuccess) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 4: Run container
|
||||
const runSuccess = await runDockerContainer();
|
||||
if (!runSuccess) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\n🎉 Docker sandbox execution completed!');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Fatal error:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run main function
|
||||
main();
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/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();
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "claude-code-docker-sandbox",
|
||||
"version": "1.0.0",
|
||||
"description": "Docker sandbox for Claude Code execution with Claude Agent SDK",
|
||||
"main": "docker-launcher.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "docker build -t claude-sandbox .",
|
||||
"clean": "docker rmi claude-sandbox || true"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.30"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"claude",
|
||||
"docker",
|
||||
"sandbox",
|
||||
"ai",
|
||||
"agent"
|
||||
],
|
||||
"author": "Claude Code Templates",
|
||||
"license": "MIT"
|
||||
}
|
||||
Reference in New Issue
Block a user