chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
# Claude Code Sandbox Components
|
||||
|
||||
Execute Claude Code in isolated cloud environments for secure code generation and development.
|
||||
|
||||
## Available Sandbox Providers
|
||||
|
||||
### E2B Sandbox (`e2b`)
|
||||
Run Claude Code in E2B's secure cloud environment with pre-configured development tools.
|
||||
|
||||
**Component**: `e2b/claude-code-sandbox.md`
|
||||
|
||||
**Files Created**:
|
||||
- `.claude/sandbox/e2b-launcher.py` - Python launcher script
|
||||
- `.claude/sandbox/requirements.txt` - Python dependencies
|
||||
- `.claude/sandbox/.env.example` - Environment variables template
|
||||
|
||||
### Cloudflare Sandbox (`cloudflare`)
|
||||
Execute AI-powered code in Cloudflare Workers with global edge deployment and sub-second cold starts.
|
||||
|
||||
**Component**: `cloudflare/claude-code-sandbox.md`
|
||||
|
||||
**Files Created**:
|
||||
- `.claude/sandbox/cloudflare/src/index.ts` - Cloudflare Worker source
|
||||
- `.claude/sandbox/cloudflare/launcher.ts` - TypeScript launcher
|
||||
- `.claude/sandbox/cloudflare/monitor.ts` - Monitoring tool
|
||||
- `.claude/sandbox/cloudflare/wrangler.toml` - Cloudflare configuration
|
||||
- `.claude/sandbox/cloudflare/package.json` - Dependencies
|
||||
- `.claude/sandbox/cloudflare/README.md` - Complete documentation
|
||||
|
||||
## Quick Start
|
||||
|
||||
### E2B Sandbox
|
||||
```bash
|
||||
# Simple execution with API keys as parameters (recommended)
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--e2b-api-key your_e2b_key \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--prompt "Create a React todo app"
|
||||
|
||||
# With components installation
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--e2b-api-key your_e2b_key \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--agent frontend-developer \
|
||||
--command setup-react \
|
||||
--prompt "Create a modern todo app with TypeScript"
|
||||
|
||||
# Or use environment variables (set E2B_API_KEY and ANTHROPIC_API_KEY)
|
||||
npx claude-code-templates@latest --sandbox e2b --prompt "Create a React todo app"
|
||||
```
|
||||
|
||||
### Cloudflare Sandbox
|
||||
```bash
|
||||
# Execute via deployed Cloudflare Worker
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--prompt "Calculate the 10th Fibonacci number"
|
||||
|
||||
# Local development and deployment
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
npx wrangler deploy
|
||||
|
||||
# Test your deployment
|
||||
curl -X POST https://your-worker.workers.dev/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Calculate factorial of 5"}'
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
1. **Get API Keys**:
|
||||
- E2B API Key: https://e2b.dev/dashboard
|
||||
- Anthropic API Key: https://console.anthropic.com
|
||||
|
||||
2. **Create Environment File**:
|
||||
```bash
|
||||
# In your project/.claude/sandbox/.env
|
||||
E2B_API_KEY=your_e2b_api_key_here
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
```
|
||||
|
||||
3. **Install Python Requirements** (handled automatically):
|
||||
- Python 3.11+
|
||||
- E2B Python SDK
|
||||
- python-dotenv
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Component Download**: Downloads sandbox launcher and requirements
|
||||
2. **Environment Check**: Validates Python 3.11+ installation
|
||||
3. **Dependencies**: Installs E2B Python SDK automatically
|
||||
4. **Sandbox Creation**: Creates E2B sandbox with Claude Code template
|
||||
5. **Component Installation**: Installs any specified agents/commands/mcps/settings/hooks inside sandbox
|
||||
6. **Prompt Execution**: Runs your prompt through Claude Code in the isolated environment
|
||||
7. **Result Display**: Shows complete output and generated files
|
||||
8. **Cleanup**: Automatically destroys sandbox after execution
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Web Development
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox e2b --prompt "Create an HTML page with modern CSS animations"
|
||||
```
|
||||
|
||||
### Full Stack with Components
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--agent fullstack-developer \
|
||||
--command setup-node \
|
||||
--prompt "Create a Node.js API with JWT authentication"
|
||||
```
|
||||
|
||||
### Data Analysis
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--agent data-scientist \
|
||||
--prompt "Analyze this CSV data and create visualizations"
|
||||
```
|
||||
|
||||
### Security Audit
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--agent security-auditor \
|
||||
--command security-audit \
|
||||
--prompt "Review this codebase for security vulnerabilities"
|
||||
```
|
||||
|
||||
## Security Benefits
|
||||
|
||||
- ✅ **Complete Isolation**: Code runs in separate cloud environment
|
||||
- ✅ **No Local Impact**: Zero risk to your local system or files
|
||||
- ✅ **Temporary Environment**: Sandbox destroyed after execution
|
||||
- ✅ **Controlled Access**: Only specified components are installed
|
||||
- ✅ **API Key Security**: Keys never leave your local environment
|
||||
|
||||
## Future Sandbox Providers
|
||||
|
||||
## ✅ Production Ready Features (v1.20.3+)
|
||||
|
||||
### Enhanced E2B Integration
|
||||
- **Automatic File Download**: Generated files are automatically downloaded to local `./e2b-output/` directory
|
||||
- **Extended Timeouts**: 15-minute sandbox lifetime with intelligent timeout management
|
||||
- **Detailed Logging**: Step-by-step execution monitoring with debugging information
|
||||
- **Environment Verification**: Automatic checks for Claude Code installation and permissions
|
||||
- **Error Recovery**: Retry logic for connection issues and comprehensive error handling
|
||||
|
||||
### Advanced Debugging Tools
|
||||
- **Real-time Monitor** (`e2b-monitor.py`): System resource monitoring and performance analysis
|
||||
- **Debug Guide** (`SANDBOX_DEBUGGING.md`): Comprehensive troubleshooting documentation
|
||||
- **Sandbox State Tracking**: Live monitoring of file system changes and process execution
|
||||
|
||||
The system is designed to support multiple sandbox providers:
|
||||
|
||||
- **E2B** (`--sandbox e2b`) - ✅ **Fully Implemented** - Cloud-based isolated execution environment with full Linux access
|
||||
- **Cloudflare** (`--sandbox cloudflare`) - ✅ **Fully Implemented** - Edge-based sandbox with global deployment and AI code execution
|
||||
- **Docker** (`--sandbox docker`) - 🔄 Future - Local containerized execution
|
||||
- **AWS CodeBuild** (`--sandbox aws`) - 🔄 Future - AWS-based sandbox environment
|
||||
- **GitHub Codespaces** (`--sandbox github`) - 🔄 Future - GitHub's cloud development environment
|
||||
- **Custom** (`--sandbox custom`) - 🔄 Future - User-defined sandbox configurations
|
||||
|
||||
## Sandbox Comparison
|
||||
|
||||
| Feature | E2B | Cloudflare | Best For |
|
||||
|---------|-----|------------|----------|
|
||||
| **Cold Start** | 2-3 seconds | ~100ms | Cloudflare for speed |
|
||||
| **Max Duration** | Hours | 30 seconds (Workers) | E2B for long tasks |
|
||||
| **Environment** | Full Linux VM | V8 isolates + containers | E2B for flexibility |
|
||||
| **Languages** | Any (full OS) | Python, Node.js | E2B for variety |
|
||||
| **Global Distribution** | Single region | Edge network | Cloudflare for latency |
|
||||
| **Pricing Model** | Usage-based | $5/month flat | Depends on volume |
|
||||
| **Setup Complexity** | Low | Medium | E2B for simplicity |
|
||||
| **Local Development** | Cloud only | Docker required | E2B for quick start |
|
||||
| **Claude Integration** | Native template | API-based | E2B for turnkey |
|
||||
| **File Downloads** | Automatic | API-based | E2B for ease |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Python Not Found
|
||||
```bash
|
||||
# Install Python 3.11+
|
||||
brew install python3 # macOS
|
||||
# or visit https://python.org/downloads
|
||||
```
|
||||
|
||||
### API Keys Not Set
|
||||
```bash
|
||||
# Create .env file in .claude/sandbox/
|
||||
echo "E2B_API_KEY=your_key_here" >> .claude/sandbox/.env
|
||||
echo "ANTHROPIC_API_KEY=your_key_here" >> .claude/sandbox/.env
|
||||
```
|
||||
|
||||
### Dependencies Installation Failed
|
||||
```bash
|
||||
# Manual installation
|
||||
cd .claude/sandbox
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
## Component Architecture
|
||||
|
||||
```
|
||||
claude-code-templates/
|
||||
└── cli-tool/
|
||||
└── components/
|
||||
└── sandbox/
|
||||
├── e2b/ # E2B provider
|
||||
│ ├── claude-code-sandbox.md # Component documentation
|
||||
│ ├── e2b-launcher.py # Python launcher script
|
||||
│ ├── e2b-monitor.py # Monitoring tool
|
||||
│ ├── requirements.txt # Python dependencies
|
||||
│ ├── SANDBOX_DEBUGGING.md # Debug guide
|
||||
│ └── .env.example # Environment template
|
||||
├── cloudflare/ # Cloudflare provider
|
||||
│ ├── claude-code-sandbox.md # Component documentation
|
||||
│ ├── src/
|
||||
│ │ └── index.ts # Worker source code
|
||||
│ ├── launcher.ts # TypeScript launcher
|
||||
│ ├── monitor.ts # Monitoring tool
|
||||
│ ├── wrangler.toml # Cloudflare config
|
||||
│ ├── package.json # Dependencies
|
||||
│ ├── tsconfig.json # TypeScript config
|
||||
│ ├── README.md # Documentation
|
||||
│ ├── QUICKSTART.md # Quick start guide
|
||||
│ ├── SANDBOX_DEBUGGING.md # Debug guide
|
||||
│ └── .dev.vars.example # Environment template
|
||||
├── docker/ # Future: Docker provider
|
||||
├── aws/ # Future: AWS provider
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
The sandbox system integrates seamlessly with the existing Claude Code Templates component architecture, allowing any combination of agents, commands, MCPs, settings, and hooks to be installed and used within the secure sandbox environment.
|
||||
|
||||
## Choosing the Right Sandbox
|
||||
|
||||
### Use E2B when you need:
|
||||
- ✅ Long-running operations (hours)
|
||||
- ✅ Full Linux environment access
|
||||
- ✅ Quick setup with minimal configuration
|
||||
- ✅ Multiple programming languages
|
||||
- ✅ Automatic file downloads
|
||||
- ✅ Native Claude Code integration
|
||||
|
||||
### Use Cloudflare when you need:
|
||||
- ✅ Sub-second cold starts
|
||||
- ✅ Global edge distribution
|
||||
- ✅ Predictable flat-rate pricing
|
||||
- ✅ High request volume
|
||||
- ✅ Python/Node.js execution
|
||||
- ✅ Production-grade reliability
|
||||
@@ -0,0 +1,13 @@
|
||||
# Local Development Environment Variables
|
||||
# Copy this file to .dev.vars and fill in your actual values
|
||||
|
||||
# Anthropic API Key (required)
|
||||
# Get your key from: https://console.anthropic.com/
|
||||
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here
|
||||
|
||||
# Optional: Debug mode
|
||||
# DEBUG=true
|
||||
|
||||
# Optional: Custom settings
|
||||
# MAX_EXECUTION_TIME=30000
|
||||
# MAX_CODE_TOKENS=2048
|
||||
@@ -0,0 +1,45 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Wrangler
|
||||
.wrangler/
|
||||
.dev.vars
|
||||
.mf/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
.cache/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
|
||||
# Cloudflare Workers
|
||||
worker/
|
||||
@@ -0,0 +1,359 @@
|
||||
# Cloudflare Sandbox Implementation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
Complete implementation of Cloudflare Workers sandbox for executing Claude Code with AI-powered code generation. This sandbox leverages Cloudflare's global edge network to provide ultra-fast, isolated code execution.
|
||||
|
||||
## What Was Built
|
||||
|
||||
### Core Infrastructure
|
||||
1. **Cloudflare Worker** (`src/index.ts`)
|
||||
- RESTful API with `/execute`, `/health`, and root endpoints
|
||||
- Integration with Anthropic's Claude AI for code generation
|
||||
- Cloudflare Sandbox SDK integration for isolated execution
|
||||
- Support for both Python and JavaScript/Node.js
|
||||
- CORS support for browser access
|
||||
- Comprehensive error handling
|
||||
|
||||
2. **TypeScript Launcher** (`launcher.ts`)
|
||||
- Command-line tool for executing prompts
|
||||
- Worker availability detection
|
||||
- Fallback to direct execution if worker unavailable
|
||||
- Component extraction and agent support
|
||||
- Colored terminal output
|
||||
- API key management
|
||||
|
||||
3. **Monitoring Tool** (`monitor.ts`)
|
||||
- Real-time performance metrics
|
||||
- Worker health monitoring
|
||||
- Code generation time tracking
|
||||
- Sandbox execution monitoring
|
||||
- System information display
|
||||
- Memory usage tracking
|
||||
|
||||
### Documentation Suite
|
||||
1. **Main Documentation** (`claude-code-sandbox.md`)
|
||||
- Component overview and features
|
||||
- Architecture diagrams
|
||||
- Usage examples
|
||||
- API key configuration
|
||||
- Deployment guide
|
||||
- Security benefits
|
||||
- Comparison with E2B
|
||||
|
||||
2. **Quick Start Guide** (`QUICKSTART.md`)
|
||||
- Three deployment paths (production, local, CLI)
|
||||
- Step-by-step instructions
|
||||
- Common issues and quick fixes
|
||||
- Next steps and resources
|
||||
- Complete troubleshooting section
|
||||
|
||||
3. **Debugging Guide** (`SANDBOX_DEBUGGING.md`)
|
||||
- Available monitoring tools
|
||||
- Common troubleshooting scenarios
|
||||
- Advanced configuration
|
||||
- Performance optimization tips
|
||||
- Complete command reference
|
||||
- Best practices
|
||||
|
||||
4. **README** (`README.md`)
|
||||
- Quick start instructions
|
||||
- Complete API reference
|
||||
- Command-line tool documentation
|
||||
- Configuration examples
|
||||
- Security information
|
||||
- Cost estimation
|
||||
- Development guide
|
||||
|
||||
### Configuration Files
|
||||
1. **Package Configuration** (`package.json`)
|
||||
- All required dependencies
|
||||
- Development scripts
|
||||
- Testing setup
|
||||
- Build configuration
|
||||
|
||||
2. **Wrangler Configuration** (`wrangler.toml`)
|
||||
- Cloudflare Workers settings
|
||||
- Durable Objects configuration
|
||||
- Environment variables
|
||||
- Resource limits
|
||||
|
||||
3. **TypeScript Configuration** (`tsconfig.json`)
|
||||
- Strict type checking
|
||||
- ES2022 target
|
||||
- Path aliases
|
||||
- Worker types
|
||||
|
||||
4. **Environment Templates** (`.dev.vars.example`)
|
||||
- Local development variables
|
||||
- API key placeholders
|
||||
- Configuration examples
|
||||
|
||||
5. **Git Ignore** (`.gitignore`)
|
||||
- Node modules
|
||||
- Wrangler artifacts
|
||||
- Environment files
|
||||
- Build output
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ CLI / HTTP Client │
|
||||
└──────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Cloudflare Worker @ Edge │
|
||||
│ • Receives questions │
|
||||
│ • Manages secrets │
|
||||
│ • Handles CORS │
|
||||
└──────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────────────┐
|
||||
│ Claude AI │ │ Sandbox SDK │
|
||||
│ Code Gen │ │ Isolated Exec │
|
||||
└──────┬───────┘ └────────┬─────────┘
|
||||
│ │
|
||||
│ Generated Code │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Results │
|
||||
│ • Code │
|
||||
│ • Output │
|
||||
│ • Errors │
|
||||
│ • Metrics │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. AI-Powered Code Execution
|
||||
- Natural language to executable code via Claude Sonnet 4.5
|
||||
- Automatic code cleanup and formatting
|
||||
- Support for Python and JavaScript/Node.js
|
||||
- Error handling and timeout management
|
||||
|
||||
### 2. Global Edge Distribution
|
||||
- Deployed on Cloudflare's edge network
|
||||
- Sub-100ms cold starts
|
||||
- Automatic global replication
|
||||
- Low-latency execution worldwide
|
||||
|
||||
### 3. Secure Isolation
|
||||
- Container-based sandbox execution
|
||||
- No network access from sandboxes
|
||||
- CPU and memory limits enforced
|
||||
- Automatic cleanup after execution
|
||||
|
||||
### 4. Developer Experience
|
||||
- Comprehensive CLI tools
|
||||
- Real-time monitoring and metrics
|
||||
- Detailed debugging guides
|
||||
- Local development support with Docker
|
||||
|
||||
### 5. Production Ready
|
||||
- Health check endpoints
|
||||
- Structured error handling
|
||||
- Performance metrics
|
||||
- Cost-effective pricing model
|
||||
|
||||
## Comparison: Cloudflare vs E2B
|
||||
|
||||
| Aspect | Cloudflare | E2B | Winner |
|
||||
|--------|-----------|-----|--------|
|
||||
| **Speed** | ~100ms cold start | 2-3s cold start | ⚡ Cloudflare |
|
||||
| **Global** | Edge network | Single region | 🌍 Cloudflare |
|
||||
| **Duration** | 30s max (Workers) | Hours | ⏱️ E2B |
|
||||
| **Environment** | Python/Node.js | Full Linux | 🖥️ E2B |
|
||||
| **Pricing** | $5/month flat | Usage-based | 💰 Depends |
|
||||
| **Setup** | Medium complexity | Low complexity | 🔧 E2B |
|
||||
| **Integration** | API-based | Native template | 🔌 E2B |
|
||||
| **Use Case** | High volume, fast | Long operations | 🎯 Different |
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
cloudflare/
|
||||
├── src/
|
||||
│ └── index.ts # Cloudflare Worker source (253 lines)
|
||||
├── launcher.ts # CLI launcher tool (254 lines)
|
||||
├── monitor.ts # Monitoring tool (372 lines)
|
||||
├── claude-code-sandbox.md # Main component doc (358 lines)
|
||||
├── README.md # Complete guide (435 lines)
|
||||
├── QUICKSTART.md # Quick start (315 lines)
|
||||
├── SANDBOX_DEBUGGING.md # Debug guide (523 lines)
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── wrangler.toml # Cloudflare config
|
||||
├── .gitignore # Git ignore rules
|
||||
└── .dev.vars.example # Environment template
|
||||
```
|
||||
|
||||
**Total Lines of Code**: ~2,500+ lines
|
||||
**Total Files**: 12 files
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Deploy to Production
|
||||
```bash
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Test Locally
|
||||
```bash
|
||||
npm run dev
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-d '{"question": "What is 2^10?"}'
|
||||
```
|
||||
|
||||
### Monitor Execution
|
||||
```bash
|
||||
node monitor.ts "Calculate factorial of 5" your_api_key
|
||||
```
|
||||
|
||||
### Check Health
|
||||
```bash
|
||||
curl https://your-worker.workers.dev/health
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### With Claude Code Templates CLI
|
||||
The sandbox integrates seamlessly with the main CLI:
|
||||
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--anthropic-api-key your_key \
|
||||
--prompt "Your prompt"
|
||||
```
|
||||
|
||||
### With Existing Components
|
||||
Can be combined with agents, commands, and settings:
|
||||
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--agent frontend-developer \
|
||||
--command setup-react \
|
||||
--prompt "Create a todo app"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **API Key Storage**: Encrypted Wrangler secrets
|
||||
2. **Sandbox Isolation**: Container-based execution
|
||||
3. **No Network Access**: Sandboxes can't make external requests
|
||||
4. **Resource Limits**: CPU time and memory caps
|
||||
5. **CORS Configuration**: Configurable for production use
|
||||
|
||||
## Cost Analysis
|
||||
|
||||
### Cloudflare Workers
|
||||
- **Free Tier**: 100,000 requests/day (limited Durable Objects)
|
||||
- **Paid Plan**: $5/month (10M requests + unlimited Durable Objects)
|
||||
|
||||
### Anthropic API
|
||||
- **Claude Sonnet 4.5**: ~$3 per million input tokens
|
||||
- **Typical Request**: 200 tokens ≈ $0.0006 per execution
|
||||
|
||||
### Example Monthly Cost (10,000 executions)
|
||||
- Cloudflare: $5/month
|
||||
- Anthropic: ~$6/month
|
||||
- **Total**: ~$11/month
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
### Typical Execution Times
|
||||
- Worker response: 50-150ms
|
||||
- Code generation: 1-3 seconds
|
||||
- Sandbox execution: 100-500ms
|
||||
- **Total**: 1.5-4 seconds end-to-end
|
||||
|
||||
### Global Latency
|
||||
- North America: 10-50ms
|
||||
- Europe: 15-60ms
|
||||
- Asia: 20-80ms
|
||||
- **Average**: <100ms cold start
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate Improvements
|
||||
1. Add caching for common code patterns
|
||||
2. Implement streaming output
|
||||
3. Add support for more languages
|
||||
4. Create browser-based UI
|
||||
|
||||
### Future Enhancements
|
||||
1. Multi-step code execution
|
||||
2. Persistent session state
|
||||
3. File upload/download
|
||||
4. Collaborative debugging
|
||||
5. Rate limiting per user
|
||||
6. Usage analytics dashboard
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
### What Worked Well
|
||||
- TypeScript for type safety
|
||||
- Comprehensive documentation
|
||||
- CLI tools for debugging
|
||||
- Modular architecture
|
||||
|
||||
### Challenges Addressed
|
||||
- Container provisioning delay (2-3 min wait)
|
||||
- API key management (Wrangler secrets)
|
||||
- Local development requiring Docker
|
||||
- Timeout limitations (30s for Workers)
|
||||
|
||||
## Resources Created
|
||||
|
||||
### Documentation
|
||||
- 4 comprehensive markdown files
|
||||
- 1,631+ lines of documentation
|
||||
- Step-by-step guides
|
||||
- Troubleshooting sections
|
||||
|
||||
### Code
|
||||
- 3 TypeScript files
|
||||
- 879+ lines of production code
|
||||
- Full type coverage
|
||||
- Error handling throughout
|
||||
|
||||
### Configuration
|
||||
- 5 configuration files
|
||||
- Development and production environments
|
||||
- Docker support
|
||||
- Git integration
|
||||
|
||||
## Success Metrics
|
||||
|
||||
✅ Complete Cloudflare Worker implementation
|
||||
✅ Full CLI tooling (launcher + monitor)
|
||||
✅ Comprehensive documentation suite
|
||||
✅ Local development support
|
||||
✅ Production deployment guide
|
||||
✅ Debugging and troubleshooting guides
|
||||
✅ Integration with Claude Code Templates
|
||||
✅ Security best practices implemented
|
||||
✅ Performance optimizations included
|
||||
✅ Cost analysis provided
|
||||
|
||||
## Conclusion
|
||||
|
||||
This implementation provides a production-ready, globally-distributed sandbox solution for executing AI-generated code. It complements the existing E2B sandbox by offering ultra-fast cold starts and predictable pricing, making it ideal for high-volume, latency-sensitive applications.
|
||||
|
||||
The comprehensive documentation and tooling ensure developers can quickly get started and effectively debug any issues that arise. The modular architecture allows for easy extension and customization based on specific use cases.
|
||||
|
||||
---
|
||||
|
||||
**Implementation Date**: October 19, 2025
|
||||
**Version**: 1.0.0
|
||||
**Status**: Production Ready ✅
|
||||
@@ -0,0 +1,267 @@
|
||||
# Cloudflare Sandbox Quick Start Guide
|
||||
|
||||
Get your Cloudflare Claude Code Sandbox running in under 5 minutes.
|
||||
|
||||
## Prerequisites Checklist
|
||||
|
||||
- [ ] Cloudflare account (sign up at https://dash.cloudflare.com/sign-up)
|
||||
- [ ] Anthropic API key (get from https://console.anthropic.com/)
|
||||
- [ ] Node.js 16.17.0+ installed
|
||||
- [ ] Docker installed and running (for local development)
|
||||
|
||||
## Option 1: Deploy to Production (Fastest)
|
||||
|
||||
Perfect if you want to skip local testing and deploy directly.
|
||||
|
||||
### Step 1: Install Dependencies
|
||||
```bash
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
```
|
||||
|
||||
### Step 2: Set API Key
|
||||
```bash
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
# Paste your Anthropic API key when prompted
|
||||
```
|
||||
|
||||
### Step 3: Deploy
|
||||
```bash
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Step 4: Wait for Container Provisioning
|
||||
```bash
|
||||
# Wait 2-3 minutes, then check:
|
||||
npx wrangler containers list
|
||||
# You should see: ✓ Container ready
|
||||
```
|
||||
|
||||
### Step 5: Test Your Deployment
|
||||
```bash
|
||||
# Get your worker URL from the deploy output, then:
|
||||
curl -X POST https://YOUR-WORKER.YOUR-SUBDOMAIN.workers.dev/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "What is the 10th Fibonacci number?"}'
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"question": "What is the 10th Fibonacci number?",
|
||||
"code": "def fibonacci(n):\n ...",
|
||||
"output": "55\n",
|
||||
"error": "",
|
||||
"executionTime": 1234
|
||||
}
|
||||
```
|
||||
|
||||
**Done!** Your sandbox is live at the edge.
|
||||
|
||||
---
|
||||
|
||||
## Option 2: Local Development First
|
||||
|
||||
Perfect if you want to test locally before deploying.
|
||||
|
||||
### Step 1: Install Dependencies
|
||||
```bash
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
```
|
||||
|
||||
### Step 2: Create Local Environment File
|
||||
```bash
|
||||
cp .dev.vars.example .dev.vars
|
||||
# Edit .dev.vars and add your Anthropic API key
|
||||
```
|
||||
|
||||
### Step 3: Start Docker
|
||||
```bash
|
||||
# macOS: Open Docker Desktop
|
||||
# Linux: sudo systemctl start docker
|
||||
# Windows: Start Docker Desktop
|
||||
|
||||
# Verify Docker is running:
|
||||
docker ps
|
||||
```
|
||||
|
||||
### Step 4: Start Development Server
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Wait for:
|
||||
```
|
||||
⛅️ wrangler 3.78.12
|
||||
-------------------
|
||||
⎔ Starting local server...
|
||||
[wrangler:inf] Ready on http://localhost:8787
|
||||
```
|
||||
|
||||
### Step 5: Test Locally
|
||||
```bash
|
||||
# In a new terminal:
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Calculate factorial of 5"}'
|
||||
```
|
||||
|
||||
### Step 6: Deploy When Ready
|
||||
```bash
|
||||
# Stop the dev server (Ctrl+C)
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
**Done!** You've tested locally and deployed.
|
||||
|
||||
---
|
||||
|
||||
## Option 3: Using the CLI Tools
|
||||
|
||||
Perfect if you prefer command-line interaction.
|
||||
|
||||
### Step 1: Setup (same as above)
|
||||
```bash
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Step 2: Use the Launcher
|
||||
```bash
|
||||
# Execute a prompt
|
||||
node launcher.ts "What is 2 to the power of 10?" \
|
||||
"" \
|
||||
your_anthropic_key \
|
||||
https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
### Step 3: Use the Monitor (for debugging)
|
||||
```bash
|
||||
# Get detailed execution metrics
|
||||
node monitor.ts "Calculate factorial of 5" \
|
||||
your_anthropic_key \
|
||||
https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
**Done!** You're using the CLI tools.
|
||||
|
||||
---
|
||||
|
||||
## Common Issues & Quick Fixes
|
||||
|
||||
### "Container not ready"
|
||||
**Solution**: Wait 2-3 minutes after first deployment
|
||||
```bash
|
||||
npx wrangler containers list
|
||||
```
|
||||
|
||||
### "Docker daemon is not running"
|
||||
**Solution**: Start Docker Desktop or Docker service
|
||||
```bash
|
||||
docker ps # Should list containers
|
||||
```
|
||||
|
||||
### "ANTHROPIC_API_KEY not configured"
|
||||
**Solution**: Set the secret
|
||||
```bash
|
||||
# Production:
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
|
||||
# Local (.dev.vars):
|
||||
echo "ANTHROPIC_API_KEY=sk-ant-your-key" > .dev.vars
|
||||
```
|
||||
|
||||
### "Worker not found"
|
||||
**Solution**: Deploy the worker
|
||||
```bash
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### "Execution timeout"
|
||||
**Solution**: Increase timeout in request
|
||||
```json
|
||||
{
|
||||
"question": "Your question",
|
||||
"timeout": 60000
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### 1. Customize Your Worker
|
||||
Edit `src/index.ts` to add custom logic:
|
||||
- Add authentication
|
||||
- Implement rate limiting
|
||||
- Add custom error handling
|
||||
- Create specialized endpoints
|
||||
|
||||
### 2. Add Monitoring
|
||||
```bash
|
||||
# Watch logs in real-time
|
||||
npx wrangler tail
|
||||
|
||||
# Use the monitor tool
|
||||
node monitor.ts "your prompt" your_api_key
|
||||
```
|
||||
|
||||
### 3. Test Different Languages
|
||||
```bash
|
||||
# Python (default)
|
||||
curl -X POST https://your-worker.workers.dev/execute \
|
||||
-d '{"question": "Fibonacci", "language": "python"}'
|
||||
|
||||
# JavaScript
|
||||
curl -X POST https://your-worker.workers.dev/execute \
|
||||
-d '{"question": "Fibonacci", "language": "javascript"}'
|
||||
```
|
||||
|
||||
### 4. Integrate with Your App
|
||||
```javascript
|
||||
// Frontend integration
|
||||
const response = await fetch('https://your-worker.workers.dev/execute', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ question: 'Calculate factorial of 5' })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log(result.output);
|
||||
```
|
||||
|
||||
### 5. Enable Advanced Features
|
||||
See the main [README.md](./README.md) for:
|
||||
- Streaming output
|
||||
- Code Interpreter API
|
||||
- Caching strategies
|
||||
- Performance optimization
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **Documentation**: [README.md](./README.md)
|
||||
- **Debugging**: [SANDBOX_DEBUGGING.md](./SANDBOX_DEBUGGING.md)
|
||||
- **Component Info**: [claude-code-sandbox.md](./claude-code-sandbox.md)
|
||||
- **Cloudflare Docs**: https://developers.cloudflare.com/sandbox/
|
||||
- **Anthropic Docs**: https://docs.anthropic.com/
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. **Check logs**: `npx wrangler tail`
|
||||
2. **Use monitor**: `node monitor.ts "test" your_key`
|
||||
3. **Read debugging guide**: [SANDBOX_DEBUGGING.md](./SANDBOX_DEBUGGING.md)
|
||||
4. **Check container status**: `npx wrangler containers list`
|
||||
5. **Test health**: `curl https://your-worker.workers.dev/health`
|
||||
|
||||
---
|
||||
|
||||
**You're all set! Start executing code with Claude AI on Cloudflare's edge network.**
|
||||
@@ -0,0 +1,301 @@
|
||||
# Cloudflare Claude Code Sandbox
|
||||
|
||||
Execute Claude Code in isolated Cloudflare Workers sandboxes with AI-powered code generation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install Dependencies
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. Configure API Key
|
||||
```bash
|
||||
# For local development, create .dev.vars:
|
||||
echo "ANTHROPIC_API_KEY=your-api-key-here" > .dev.vars
|
||||
|
||||
# For production, use wrangler secrets:
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### 3. Local Development
|
||||
```bash
|
||||
# Start development server (requires Docker)
|
||||
npm run dev
|
||||
|
||||
# In another terminal, test the endpoint:
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "What is the 10th Fibonacci number?"}'
|
||||
```
|
||||
|
||||
### 4. Deploy to Cloudflare
|
||||
```bash
|
||||
# Deploy worker
|
||||
npx wrangler deploy
|
||||
|
||||
# Wait 2-3 minutes for container provisioning
|
||||
npx wrangler containers list
|
||||
|
||||
# Test production endpoint
|
||||
curl -X POST https://your-worker.your-subdomain.workers.dev/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Calculate factorial of 5"}'
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
This sandbox combines three powerful technologies:
|
||||
|
||||
1. **Claude AI** (Anthropic) - Generates executable code from natural language
|
||||
2. **Cloudflare Workers** - Runs at the edge for global low-latency access
|
||||
3. **Sandbox SDK** - Provides isolated container execution
|
||||
|
||||
```
|
||||
User Question → Cloudflare Worker → Claude AI → Generated Code → Sandbox → Results
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### POST /execute
|
||||
|
||||
Execute a natural language question as code.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"question": "What is the 100th Fibonacci number?",
|
||||
"maxTokens": 2048, // Optional: Max tokens for code generation
|
||||
"timeout": 30000, // Optional: Execution timeout in ms
|
||||
"language": "python" // Optional: "python" or "javascript"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"question": "What is the 100th Fibonacci number?",
|
||||
"code": "def fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)\n\nprint(fibonacci(100))",
|
||||
"output": "354224848179261915075\n",
|
||||
"error": "",
|
||||
"sandboxId": "user-1234567890-abc123",
|
||||
"executionTime": 2147
|
||||
}
|
||||
```
|
||||
|
||||
### GET /health
|
||||
|
||||
Check worker health status.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-10-19T12:00:00.000Z",
|
||||
"worker": "cloudflare-claude-sandbox"
|
||||
}
|
||||
```
|
||||
|
||||
## Command Line Tools
|
||||
|
||||
### Launcher
|
||||
Execute prompts directly from command line:
|
||||
|
||||
```bash
|
||||
# Basic usage
|
||||
node launcher.ts "Calculate factorial of 5"
|
||||
|
||||
# With custom worker URL
|
||||
node launcher.ts "Fibonacci 10" "" your_api_key https://your-worker.workers.dev
|
||||
|
||||
# With components
|
||||
node launcher.ts "Create a React app" "--agent frontend-developer" your_api_key
|
||||
```
|
||||
|
||||
### Monitor
|
||||
Monitor execution with detailed metrics:
|
||||
|
||||
```bash
|
||||
# Monitor execution
|
||||
node monitor.ts "Calculate factorial of 5" your_api_key
|
||||
|
||||
# Monitor production worker
|
||||
node monitor.ts "Sum array" your_api_key https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Mathematical Calculations
|
||||
```bash
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "What is the factorial of 20?"}'
|
||||
```
|
||||
|
||||
### String Manipulation
|
||||
```bash
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Reverse the string Hello World"}'
|
||||
```
|
||||
|
||||
### Data Analysis
|
||||
```bash
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Calculate the mean of [10, 20, 30, 40, 50]"}'
|
||||
```
|
||||
|
||||
### JavaScript Execution
|
||||
```bash
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"question": "Sort an array of numbers",
|
||||
"language": "javascript"
|
||||
}'
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Local Development (.dev.vars):**
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=sk-ant-your-key-here
|
||||
```
|
||||
|
||||
**Production (Wrangler Secrets):**
|
||||
```bash
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### Wrangler Configuration
|
||||
|
||||
Edit `wrangler.toml` to customize:
|
||||
|
||||
```toml
|
||||
# Worker name (must be unique)
|
||||
name = "my-custom-sandbox"
|
||||
|
||||
# Resource limits
|
||||
[limits]
|
||||
cpu_ms = 100 # CPU time per request
|
||||
|
||||
# Environment-specific configuration
|
||||
[env.production]
|
||||
vars = { ENVIRONMENT = "production" }
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container Not Ready
|
||||
After first deployment, wait 2-3 minutes:
|
||||
```bash
|
||||
npx wrangler containers list
|
||||
```
|
||||
|
||||
### Docker Issues (Local Development)
|
||||
Ensure Docker is running:
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
### API Key Not Set
|
||||
For local development:
|
||||
```bash
|
||||
echo "ANTHROPIC_API_KEY=your-key" > .dev.vars
|
||||
```
|
||||
|
||||
For production:
|
||||
```bash
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# Real-time logs
|
||||
npx wrangler tail
|
||||
|
||||
# Pretty formatted
|
||||
npx wrangler tail --format=pretty
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use specific prompts**: More specific questions generate faster code
|
||||
2. **Implement caching**: Cache generated code for common questions
|
||||
3. **Stream output**: Use streaming for long-running operations
|
||||
4. **Set appropriate timeouts**: Balance between UX and resource usage
|
||||
5. **Monitor metrics**: Use the monitor tool to identify bottlenecks
|
||||
|
||||
## Security
|
||||
|
||||
- Sandboxes are isolated containers with no network access
|
||||
- Code execution is limited by timeout (default 30s)
|
||||
- CPU and memory limits are enforced by Cloudflare
|
||||
- API keys are stored as encrypted Wrangler secrets
|
||||
- CORS is enabled for browser access (configure as needed)
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
**Cloudflare Workers:**
|
||||
- Free tier: 100,000 requests/day (limited Durable Objects)
|
||||
- Paid plan ($5/month): 10M requests/month + unlimited Durable Objects
|
||||
|
||||
**Anthropic API:**
|
||||
- Claude Sonnet 4.5: ~$3 per million input tokens
|
||||
- Average request: ~200 tokens = $0.0006 per request
|
||||
|
||||
**Example costs for 10,000 requests/month:**
|
||||
- Cloudflare: $5/month (paid plan)
|
||||
- Anthropic: ~$6/month (avg 200 tokens/request)
|
||||
- **Total: ~$11/month**
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
cloudflare-claude-sandbox/
|
||||
├── src/
|
||||
│ └── index.ts # Worker source code
|
||||
├── launcher.ts # CLI launcher tool
|
||||
├── monitor.ts # Monitoring tool
|
||||
├── wrangler.toml # Cloudflare configuration
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript config
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### Scripts
|
||||
- `npm run dev` - Start local development server
|
||||
- `npm run deploy` - Deploy to Cloudflare
|
||||
- `npm run tail` - View real-time logs
|
||||
- `npm run launch` - Run launcher tool
|
||||
- `npm run monitor` - Run monitoring tool
|
||||
- `npm run type-check` - Check TypeScript types
|
||||
- `npm test` - Run tests
|
||||
|
||||
## Resources
|
||||
|
||||
- [Cloudflare Sandbox SDK](https://developers.cloudflare.com/sandbox/)
|
||||
- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/)
|
||||
- [Anthropic API Documentation](https://docs.anthropic.com/)
|
||||
- [Wrangler CLI Reference](https://developers.cloudflare.com/workers/wrangler/)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
1. Check the [debugging guide](./SANDBOX_DEBUGGING.md)
|
||||
2. Run the monitor tool for detailed metrics
|
||||
3. Check Cloudflare worker logs: `npx wrangler tail`
|
||||
4. Open an issue on GitHub
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ using Cloudflare Workers, Claude AI, and the Sandbox SDK
|
||||
@@ -0,0 +1,442 @@
|
||||
# Cloudflare Sandbox Debugging Guide
|
||||
|
||||
## 🔍 Available Monitoring Tools
|
||||
|
||||
### 1. Launcher with Enhanced Logging
|
||||
**File**: `launcher.ts`
|
||||
- Detailed logging of each execution step
|
||||
- Worker availability checks
|
||||
- Code generation monitoring
|
||||
- Fallback to direct execution if worker unavailable
|
||||
- Colored terminal output for better readability
|
||||
|
||||
### 2. Real-time Monitor
|
||||
**File**: `monitor.ts`
|
||||
- Real-time performance metrics tracking
|
||||
- Worker health monitoring
|
||||
- Code generation time analysis
|
||||
- Sandbox execution monitoring
|
||||
- Memory usage tracking
|
||||
- Comprehensive error reporting
|
||||
|
||||
### 3. Wrangler CLI Tools
|
||||
**Built-in Cloudflare debugging tools**:
|
||||
- `npx wrangler tail` - Real-time log streaming
|
||||
- `npx wrangler containers list` - Container status
|
||||
- `npx wrangler deployments list` - Deployment history
|
||||
- `npx wrangler dev` - Local development server
|
||||
|
||||
## 🚨 Common Troubleshooting
|
||||
|
||||
### Problem: "Container not ready"
|
||||
**Symptoms**:
|
||||
```
|
||||
Error: Container not ready. Please wait 2-3 minutes after deployment.
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
1. **Wait for provisioning**:
|
||||
```bash
|
||||
# Check container status
|
||||
npx wrangler containers list
|
||||
|
||||
# Expected output after provisioning:
|
||||
# ✓ Container ready for sandbox execution
|
||||
```
|
||||
|
||||
2. **Verify deployment**:
|
||||
```bash
|
||||
npx wrangler deployments list
|
||||
# Check deployment status and timestamp
|
||||
```
|
||||
|
||||
3. **Check worker logs**:
|
||||
```bash
|
||||
npx wrangler tail
|
||||
# Look for initialization errors
|
||||
```
|
||||
|
||||
### Problem: "Worker not responding"
|
||||
**Symptoms**:
|
||||
```
|
||||
❌ Worker health check failed: fetch failed
|
||||
```
|
||||
|
||||
**Debugging Steps**:
|
||||
1. **Verify worker is deployed**:
|
||||
```bash
|
||||
npx wrangler deploy
|
||||
# Should return worker URL
|
||||
```
|
||||
|
||||
2. **Test worker endpoint**:
|
||||
```bash
|
||||
curl https://your-worker.your-subdomain.workers.dev
|
||||
# Should return usage instructions
|
||||
```
|
||||
|
||||
3. **Check local development**:
|
||||
```bash
|
||||
# For local testing
|
||||
npm run dev
|
||||
|
||||
# Test local endpoint
|
||||
curl http://localhost:8787
|
||||
```
|
||||
|
||||
### Problem: "Anthropic API key not set"
|
||||
**Symptoms**:
|
||||
```
|
||||
Error: ANTHROPIC_API_KEY is required
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
1. **Set as Wrangler secret (Production)**:
|
||||
```bash
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
# Paste your key when prompted
|
||||
```
|
||||
|
||||
2. **Set in .dev.vars (Local Development)**:
|
||||
```bash
|
||||
# Create .dev.vars file:
|
||||
echo "ANTHROPIC_API_KEY=sk-ant-your-key-here" > .dev.vars
|
||||
```
|
||||
|
||||
3. **Verify secret is set**:
|
||||
```bash
|
||||
npx wrangler secret list
|
||||
# Should show ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### Problem: "Sandbox execution timeout"
|
||||
**Symptoms**:
|
||||
```
|
||||
Error: Sandbox execution exceeded 30 second timeout
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
1. **Use Durable Objects for longer operations**:
|
||||
```typescript
|
||||
// In wrangler.toml, ensure Durable Objects are configured
|
||||
[[durable_objects.bindings]]
|
||||
name = "Sandbox"
|
||||
class_name = "Sandbox"
|
||||
```
|
||||
|
||||
2. **Optimize code generation**:
|
||||
```typescript
|
||||
// Request more concise code
|
||||
const prompt = `Generate SIMPLE Python code...`;
|
||||
```
|
||||
|
||||
3. **Break into smaller tasks**:
|
||||
```bash
|
||||
# Instead of complex operations, break into steps
|
||||
npx claude-code-templates --sandbox cloudflare \
|
||||
--prompt "Step 1: Create data structure"
|
||||
```
|
||||
|
||||
### Problem: "Docker not running" (Local Development)
|
||||
**Symptoms**:
|
||||
```
|
||||
Error: Docker daemon is not running
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
1. **Start Docker Desktop**:
|
||||
- macOS: Open Docker Desktop application
|
||||
- Linux: `sudo systemctl start docker`
|
||||
- Windows: Start Docker Desktop
|
||||
|
||||
2. **Verify Docker is running**:
|
||||
```bash
|
||||
docker ps
|
||||
# Should list running containers
|
||||
```
|
||||
|
||||
3. **Alternative: Deploy directly to Cloudflare**:
|
||||
```bash
|
||||
# Skip local testing, deploy directly
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
## 📊 Using the Monitor for Debugging
|
||||
|
||||
### Basic Monitoring Command:
|
||||
```bash
|
||||
# Monitor a simple operation
|
||||
node monitor.ts "Calculate factorial of 5" your_api_key
|
||||
|
||||
# Monitor with custom worker URL
|
||||
node monitor.ts "Fibonacci 10" your_api_key https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
### Monitor Output Example:
|
||||
```
|
||||
[14:32:15] ℹ 🚀 Starting enhanced Cloudflare sandbox monitoring
|
||||
============================================================
|
||||
🖥️ SYSTEM INFORMATION
|
||||
============================================================
|
||||
|
||||
Node.js Version: v20.11.0
|
||||
Platform: darwin
|
||||
Architecture: arm64
|
||||
Memory Usage: 45MB / 128MB
|
||||
|
||||
============================================================
|
||||
|
||||
[14:32:16] ℹ 🔍 Checking Cloudflare Worker health...
|
||||
[14:32:16] ✓ Worker is responding
|
||||
[14:32:16] ℹ Status: 200 OK
|
||||
|
||||
[14:32:17] ℹ 🤖 Starting code generation with Claude...
|
||||
[14:32:19] ✓ Code generated in 2147ms
|
||||
[14:32:19] ℹ Model: claude-sonnet-4-5-20250929
|
||||
[14:32:19] ℹ Tokens used: 156 in, 89 out
|
||||
[14:32:19] ℹ Code length: 234 characters
|
||||
|
||||
[14:32:19] ℹ ⚙️ Executing in Cloudflare Sandbox...
|
||||
[14:32:21] ✓ Sandbox execution completed in 1856ms
|
||||
[14:32:21] ℹ Exit code: 0 (success)
|
||||
[14:32:21] ℹ Output length: 3 characters
|
||||
|
||||
============================================================
|
||||
📊 PERFORMANCE METRICS
|
||||
============================================================
|
||||
|
||||
Total Execution Time: 4123ms
|
||||
├─ Code Generation: 2147ms
|
||||
└─ Sandbox Execution: 1856ms
|
||||
Memory Usage: 48MB
|
||||
|
||||
Status: Success ✓
|
||||
============================================================
|
||||
```
|
||||
|
||||
## 🎯 Debugging Specific Scenarios
|
||||
|
||||
### 1. Code Generation Issues
|
||||
```bash
|
||||
# Use monitor to see exact Claude API interaction
|
||||
node monitor.ts "Complex prompt that might fail"
|
||||
|
||||
# Look for:
|
||||
# - Token usage (may hit limits)
|
||||
# - Generated code preview
|
||||
# - Model used (should be claude-sonnet-4-5)
|
||||
```
|
||||
|
||||
### 2. Sandbox Execution Problems
|
||||
```bash
|
||||
# Check worker logs while testing
|
||||
npx wrangler tail &
|
||||
node launcher.ts "Test prompt"
|
||||
|
||||
# Look for:
|
||||
# - Sandbox creation errors
|
||||
# - File write failures
|
||||
# - Python execution errors
|
||||
```
|
||||
|
||||
### 3. Performance Issues
|
||||
```bash
|
||||
# Use monitor to identify bottlenecks
|
||||
node monitor.ts "Your prompt"
|
||||
|
||||
# Compare metrics:
|
||||
# - Code Generation Time (Claude API)
|
||||
# - Sandbox Execution Time (Cloudflare)
|
||||
# - Total Round Trip Time
|
||||
```
|
||||
|
||||
### 4. Network/Deployment Issues
|
||||
```bash
|
||||
# Check deployments
|
||||
npx wrangler deployments list
|
||||
|
||||
# View recent logs
|
||||
npx wrangler tail --format=pretty
|
||||
|
||||
# Test worker health
|
||||
curl -v https://your-worker.workers.dev
|
||||
```
|
||||
|
||||
## 🛠 Advanced Configuration
|
||||
|
||||
### Enable Debug Mode:
|
||||
```bash
|
||||
# In wrangler.toml
|
||||
[env.development]
|
||||
vars = { DEBUG = "true" }
|
||||
|
||||
# Or in .dev.vars for local development
|
||||
DEBUG=true
|
||||
ANTHROPIC_API_KEY=your_key
|
||||
```
|
||||
|
||||
### Custom Timeouts:
|
||||
```typescript
|
||||
// In src/index.ts
|
||||
const result = await sandbox.exec('python /tmp/code.py', {
|
||||
timeout: 60000, // 60 seconds
|
||||
});
|
||||
```
|
||||
|
||||
### Verbose Logging:
|
||||
```bash
|
||||
# Set log level
|
||||
export WRANGLER_LOG=debug
|
||||
|
||||
# Run with verbose output
|
||||
npx wrangler deploy --verbose
|
||||
```
|
||||
|
||||
## 📋 Debugging Checklist
|
||||
|
||||
### Before Reporting an Issue:
|
||||
- [ ] Cloudflare Workers account active (Paid plan if using Durable Objects)
|
||||
- [ ] Anthropic API key valid and has credits
|
||||
- [ ] Worker deployed successfully (`npx wrangler deploy`)
|
||||
- [ ] Waited 2-3 minutes after first deployment
|
||||
- [ ] Containers provisioned (`npx wrangler containers list`)
|
||||
- [ ] Secrets configured (`npx wrangler secret list`)
|
||||
- [ ] Docker running (for local development)
|
||||
- [ ] Used monitor tool for detailed metrics
|
||||
- [ ] Checked worker logs (`npx wrangler tail`)
|
||||
- [ ] Tested with simple prompt first
|
||||
|
||||
### Information to Include in Bug Reports:
|
||||
- Full monitor output showing timestamps and metrics
|
||||
- Worker URL or local development environment
|
||||
- Exact prompt that caused the issue
|
||||
- Components installed (if applicable)
|
||||
- Worker logs from `npx wrangler tail`
|
||||
- Container status from `npx wrangler containers list`
|
||||
- Error messages with full stack traces
|
||||
- Node.js and Wrangler versions
|
||||
|
||||
## 🚀 Performance Optimization Tips
|
||||
|
||||
### 1. Minimize Code Generation Time
|
||||
```typescript
|
||||
// Be specific to reduce Claude's thinking time
|
||||
const prompt = `Generate a single Python function to calculate factorial.
|
||||
Use recursion. Include only the function, no tests.`;
|
||||
```
|
||||
|
||||
### 2. Use Code Interpreter API
|
||||
```typescript
|
||||
// Faster than exec for Python
|
||||
import { getCodeInterpreter } from '@cloudflare/sandbox';
|
||||
const interpreter = getCodeInterpreter(env.Sandbox, userId);
|
||||
const result = await interpreter.notebook.execCell(pythonCode);
|
||||
```
|
||||
|
||||
### 3. Implement Caching
|
||||
```typescript
|
||||
// Cache generated code for common prompts
|
||||
const cacheKey = `code:${hashPrompt(prompt)}`;
|
||||
let code = await env.CACHE.get(cacheKey);
|
||||
if (!code) {
|
||||
code = await generateCode(prompt);
|
||||
await env.CACHE.put(cacheKey, code, { expirationTtl: 3600 });
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Stream Responses
|
||||
```typescript
|
||||
// Stream output for better perceived performance
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
const result = await sandbox.exec(command, {
|
||||
onStdout: (data) => controller.enqueue(encoder.encode(data)),
|
||||
});
|
||||
controller.close();
|
||||
},
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## 🔗 Useful Commands Reference
|
||||
|
||||
### Deployment & Management
|
||||
```bash
|
||||
# Deploy worker
|
||||
npx wrangler deploy
|
||||
|
||||
# Deploy to specific environment
|
||||
npx wrangler deploy --env production
|
||||
|
||||
# Rollback deployment
|
||||
npx wrangler rollback
|
||||
|
||||
# Delete deployment
|
||||
npx wrangler delete
|
||||
```
|
||||
|
||||
### Secrets Management
|
||||
```bash
|
||||
# Add secret
|
||||
npx wrangler secret put SECRET_NAME
|
||||
|
||||
# List secrets
|
||||
npx wrangler secret list
|
||||
|
||||
# Delete secret
|
||||
npx wrangler secret delete SECRET_NAME
|
||||
```
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
# Start dev server
|
||||
npm run dev
|
||||
|
||||
# Start with specific port
|
||||
npx wrangler dev --port 3000
|
||||
|
||||
# Start with remote Durable Objects
|
||||
npx wrangler dev --remote
|
||||
```
|
||||
|
||||
### Monitoring & Logs
|
||||
```bash
|
||||
# Tail logs in real-time
|
||||
npx wrangler tail
|
||||
|
||||
# Tail with pretty formatting
|
||||
npx wrangler tail --format=pretty
|
||||
|
||||
# Tail specific deployment
|
||||
npx wrangler tail --deployment-id <id>
|
||||
|
||||
# Filter logs
|
||||
npx wrangler tail --status error
|
||||
```
|
||||
|
||||
### Container Management
|
||||
```bash
|
||||
# List containers
|
||||
npx wrangler containers list
|
||||
|
||||
# Get container details
|
||||
npx wrangler containers describe <container-id>
|
||||
```
|
||||
|
||||
## 💡 Tips & Best Practices
|
||||
|
||||
1. **Always test locally first**: Use `npm run dev` before deploying
|
||||
2. **Monitor metrics**: Use the monitor tool to track performance
|
||||
3. **Check logs regularly**: Set up `npx wrangler tail` during testing
|
||||
4. **Use environment-specific configs**: Separate dev/prod in wrangler.toml
|
||||
5. **Implement error handling**: Catch and log all errors properly
|
||||
6. **Set appropriate timeouts**: Balance between user experience and resource usage
|
||||
7. **Use Durable Objects wisely**: Only for stateful operations
|
||||
8. **Cache aggressively**: Reduce API calls with smart caching
|
||||
9. **Stream when possible**: Better UX for long operations
|
||||
10. **Version your deployments**: Tag releases for easy rollback
|
||||
|
||||
---
|
||||
|
||||
**With these tools and techniques, you can effectively debug and optimize your Cloudflare sandbox implementation.**
|
||||
@@ -0,0 +1,314 @@
|
||||
# Cloudflare Claude Code Sandbox
|
||||
|
||||
Execute Claude Code in an isolated Cloudflare Workers sandbox environment with AI-powered code execution.
|
||||
|
||||
## Description
|
||||
|
||||
This component sets up Cloudflare Sandbox SDK integration to run Claude Code in a secure, isolated cloud environment. Built on Cloudflare's container-based sandboxes with Durable Objects for persistent execution.
|
||||
|
||||
## Features
|
||||
|
||||
- **Isolated Execution**: Run Claude Code in secure Cloudflare Workers sandboxes
|
||||
- **AI Code Executor**: Turn natural language into executable Python/Node.js code
|
||||
- **Real-time Streaming**: Stream execution output as it happens
|
||||
- **Persistent Storage**: Use Durable Objects for stateful sandbox sessions
|
||||
- **Global Distribution**: Leverage Cloudflare's edge network for low latency
|
||||
- **Component Installation**: Automatically install agents and commands in sandbox
|
||||
|
||||
## Requirements
|
||||
|
||||
- Cloudflare Account (Workers Paid plan for Durable Objects)
|
||||
- Anthropic API Key
|
||||
- Node.js 16.17.0+
|
||||
- Docker (for local development)
|
||||
- Wrangler CLI
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ User Request (Natural Language) │
|
||||
└──────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Cloudflare Worker (API Endpoint) │
|
||||
│ • POST /execute │
|
||||
│ • Receives question/prompt │
|
||||
└──────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Claude AI (via Anthropic SDK) │
|
||||
│ • Generates Python/TypeScript code │
|
||||
│ • Returns executable implementation │
|
||||
└──────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Cloudflare Sandbox (Durable Object) │
|
||||
│ • Isolated container execution │
|
||||
│ • Python/Node.js runtime │
|
||||
│ • File system access │
|
||||
│ • Real-time streaming output │
|
||||
└──────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Results │
|
||||
│ • Generated code │
|
||||
│ • Execution output │
|
||||
│ • Error messages (if any) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Execute a prompt in Cloudflare sandbox
|
||||
npx claude-code-templates@latest --sandbox cloudflare --prompt "Calculate the 10th Fibonacci number"
|
||||
|
||||
# Pass API keys directly
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--prompt "Create a web scraper"
|
||||
|
||||
# Install components and execute
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--agent frontend-developer \
|
||||
--command setup-react \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--prompt "Create a modern todo app"
|
||||
|
||||
# Deploy your own Cloudflare Worker sandbox
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
The component creates:
|
||||
- `.claude/sandbox/cloudflare/src/index.ts` - Worker with sandbox logic
|
||||
- `.claude/sandbox/cloudflare/wrangler.toml` - Cloudflare configuration
|
||||
- `.claude/sandbox/cloudflare/package.json` - Node.js dependencies
|
||||
- `.claude/sandbox/cloudflare/launcher.ts` - TypeScript launcher script
|
||||
- `.claude/sandbox/cloudflare/monitor.ts` - Real-time monitoring tool
|
||||
|
||||
## API Key Configuration
|
||||
|
||||
### Option 1: CLI Parameters (Recommended)
|
||||
```bash
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--anthropic-api-key your_anthropic_api_key \
|
||||
--prompt "Your prompt here"
|
||||
```
|
||||
|
||||
### Option 2: Wrangler Secrets
|
||||
```bash
|
||||
cd .claude/sandbox/cloudflare
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
# Paste your API key when prompted
|
||||
```
|
||||
|
||||
### Option 3: Environment Variables
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# Or create .dev.vars file:
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
```
|
||||
|
||||
**Note**: Wrangler secrets are required for production deployment. CLI parameters work for local execution only.
|
||||
|
||||
## How it Works
|
||||
|
||||
1. User sends natural language request (e.g., "What's the factorial of 5?")
|
||||
2. Cloudflare Worker receives request via POST /execute
|
||||
3. Claude generates executable Python/TypeScript code via Anthropic API
|
||||
4. Code is written to sandbox file system
|
||||
5. Sandbox executes code in isolated container
|
||||
6. Results stream back in real-time
|
||||
7. Worker returns both code and execution output
|
||||
|
||||
## Deployment
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
cd .claude/sandbox/cloudflare
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# Test locally
|
||||
curl -X POST http://localhost:8787/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "What is 2^10?"}'
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
```bash
|
||||
# Set API key secret
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
|
||||
# Deploy to Cloudflare Workers
|
||||
npx wrangler deploy
|
||||
|
||||
# Wait 2-3 minutes for container provisioning
|
||||
npx wrangler containers list
|
||||
|
||||
# Test deployment
|
||||
curl -X POST https://your-worker.your-subdomain.workers.dev/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Calculate factorial of 5"}'
|
||||
```
|
||||
|
||||
## Security Benefits
|
||||
|
||||
- **Container Isolation**: Each execution runs in isolated Cloudflare container
|
||||
- **No Local Access**: Sandboxes have no access to your local system
|
||||
- **Resource Limits**: Automatic CPU time and memory constraints
|
||||
- **Temporary Execution**: Containers destroyed after execution
|
||||
- **Edge Security**: Cloudflare's security infrastructure built-in
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Code Interpreter API
|
||||
```typescript
|
||||
// Use built-in code interpreter instead of exec
|
||||
import { getCodeInterpreter } from '@cloudflare/sandbox';
|
||||
|
||||
const interpreter = getCodeInterpreter(env.Sandbox, 'user-id');
|
||||
const result = await interpreter.notebook.execCell('print(2**10)');
|
||||
```
|
||||
|
||||
### Streaming Output
|
||||
```typescript
|
||||
// Stream execution results in real-time
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
const result = await sandbox.exec('python script.py', {
|
||||
onStdout: (data) => controller.enqueue(data),
|
||||
onStderr: (data) => controller.enqueue(data)
|
||||
});
|
||||
controller.close();
|
||||
}
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Persistent Sessions
|
||||
```typescript
|
||||
// Maintain sandbox state across requests
|
||||
const sandbox = getSandbox(env.Sandbox, userId);
|
||||
await sandbox.writeFile('/data/state.json', JSON.stringify(state));
|
||||
// Later...
|
||||
const state = await sandbox.readFile('/data/state.json');
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Mathematical computation
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--prompt "Calculate the 100th Fibonacci number"
|
||||
|
||||
# Data analysis
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--prompt "What is the mean of [10, 20, 30, 40, 50]?"
|
||||
|
||||
# String manipulation
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--prompt "Reverse the string 'Hello World'"
|
||||
|
||||
# Web development
|
||||
npx claude-code-templates@latest --sandbox cloudflare \
|
||||
--agent frontend-developer \
|
||||
--prompt "Create a responsive navigation bar"
|
||||
```
|
||||
|
||||
## Comparison with E2B
|
||||
|
||||
| Feature | Cloudflare Sandbox | E2B Sandbox |
|
||||
|---------|-------------------|-------------|
|
||||
| **Provider** | Cloudflare Workers | E2B.dev |
|
||||
| **Infrastructure** | Cloudflare Edge Network | Cloud VMs |
|
||||
| **Pricing** | $5/month (Workers Paid) | Usage-based |
|
||||
| **Cold Start** | ~100ms | ~2-3 seconds |
|
||||
| **Max Duration** | 30 seconds (Workers) | Up to hours |
|
||||
| **Languages** | Python, Node.js | Full Linux environment |
|
||||
| **Global** | Yes (edge network) | Single region |
|
||||
| **Best For** | Fast, lightweight tasks | Long-running operations |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container Not Ready
|
||||
```bash
|
||||
# After first deployment, wait 2-3 minutes
|
||||
npx wrangler containers list
|
||||
|
||||
# Check container status
|
||||
npx wrangler tail
|
||||
```
|
||||
|
||||
### API Key Issues
|
||||
```bash
|
||||
# Verify secret is set
|
||||
npx wrangler secret list
|
||||
|
||||
# Update secret
|
||||
npx wrangler secret put ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### Local Development Issues
|
||||
```bash
|
||||
# Ensure Docker is running
|
||||
docker ps
|
||||
|
||||
# Clear wrangler cache
|
||||
rm -rf .wrangler
|
||||
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Use Code Interpreter API** for better Python performance
|
||||
2. **Implement caching** for frequently used code patterns
|
||||
3. **Stream output** for long-running operations
|
||||
4. **Use Durable Objects** for session persistence
|
||||
5. **Deploy to multiple regions** (automatic with Workers)
|
||||
|
||||
## Template Information
|
||||
|
||||
- **Provider**: Cloudflare Workers + Sandbox SDK
|
||||
- **Runtime**: V8 isolates with container sandboxes
|
||||
- **Languages**: Python 3.x, Node.js
|
||||
- **Timeout**: 30 seconds (Workers), configurable for Durable Objects
|
||||
- **Memory**: 128MB default
|
||||
- **Storage**: Ephemeral (use Durable Objects for persistence)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Cloudflare Sandbox SDK Docs](https://developers.cloudflare.com/sandbox/)
|
||||
- [Workers Documentation](https://developers.cloudflare.com/workers/)
|
||||
- [Durable Objects Guide](https://developers.cloudflare.com/durable-objects/)
|
||||
- [Wrangler CLI Reference](https://developers.cloudflare.com/workers/wrangler/)
|
||||
- [Anthropic API Documentation](https://docs.anthropic.com/)
|
||||
|
||||
## Next Steps
|
||||
|
||||
After installation:
|
||||
1. Set up Cloudflare account and get API credentials
|
||||
2. Install Wrangler CLI: `npm install -g wrangler`
|
||||
3. Configure secrets: `npx wrangler secret put ANTHROPIC_API_KEY`
|
||||
4. Deploy your worker: `npx wrangler deploy`
|
||||
5. Test with example requests
|
||||
6. Customize sandbox configuration for your use case
|
||||
|
||||
## License
|
||||
|
||||
Uses Cloudflare Sandbox SDK (open source) and requires Cloudflare Workers Paid plan ($5/month) for Durable Objects support.
|
||||
@@ -0,0 +1,504 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Cloudflare Sandbox Launcher
|
||||
* Executes Claude Code prompts using Cloudflare Workers and Sandbox SDK
|
||||
*/
|
||||
|
||||
import { query, ClaudeAgentOptions } from '@anthropic-ai/claude-agent-sdk';
|
||||
import fetch from 'node-fetch';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
interface ExecutionResult {
|
||||
success: boolean;
|
||||
question: string;
|
||||
code: string;
|
||||
output: string;
|
||||
error: string;
|
||||
sandboxId?: string;
|
||||
files?: { path: string; content: string }[];
|
||||
}
|
||||
|
||||
interface LauncherConfig {
|
||||
prompt: string;
|
||||
componentsToInstall: string;
|
||||
anthropicApiKey: string;
|
||||
workerUrl?: string;
|
||||
useLocalWorker?: boolean;
|
||||
targetDir?: string;
|
||||
}
|
||||
|
||||
// ANSI color codes for terminal output
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
function log(message: string, level: 'info' | 'success' | 'error' | 'warning' = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const prefix = {
|
||||
info: `${colors.blue}ℹ${colors.reset}`,
|
||||
success: `${colors.green}✓${colors.reset}`,
|
||||
error: `${colors.red}✗${colors.reset}`,
|
||||
warning: `${colors.yellow}⚠${colors.reset}`,
|
||||
}[level];
|
||||
|
||||
console.log(`[${timestamp}] ${prefix} ${message}`);
|
||||
}
|
||||
|
||||
function printSeparator(char: string = '=', length: number = 60) {
|
||||
console.log(char.repeat(length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract files from generated code (handles multiple code blocks)
|
||||
*/
|
||||
function extractFilesFromCode(code: string): { path: string; content: string }[] {
|
||||
const files: { path: string; content: string }[] = [];
|
||||
|
||||
// Pattern to match code blocks with file names
|
||||
// Matches: ```html:filename.html or ```javascript:script.js or ```css:styles.css
|
||||
const fileBlockPattern = /```(\w+):([^\n]+)\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
|
||||
while ((match = fileBlockPattern.exec(code)) !== null) {
|
||||
const [, _language, filename, content] = match;
|
||||
files.push({
|
||||
path: filename.trim(),
|
||||
content: content.trim()
|
||||
});
|
||||
}
|
||||
|
||||
// If no explicit filenames, try to detect from code blocks
|
||||
if (files.length === 0) {
|
||||
const codeBlockPattern = /```(\w+)\n([\s\S]*?)```/g;
|
||||
const languageExtensions: Record<string, string> = {
|
||||
html: 'index.html',
|
||||
css: 'styles.css',
|
||||
javascript: 'script.js',
|
||||
js: 'script.js',
|
||||
typescript: 'index.ts',
|
||||
ts: 'index.ts',
|
||||
python: 'main.py',
|
||||
py: 'main.py'
|
||||
};
|
||||
|
||||
const detectedFiles = new Map<string, string>();
|
||||
|
||||
while ((match = codeBlockPattern.exec(code)) !== null) {
|
||||
const [, language, content] = match;
|
||||
const ext = language.toLowerCase();
|
||||
const filename = languageExtensions[ext] || `code.${ext}`;
|
||||
|
||||
// If we already have this type of file, append a number
|
||||
let finalFilename = filename;
|
||||
let counter = 1;
|
||||
while (detectedFiles.has(finalFilename)) {
|
||||
const parts = filename.split('.');
|
||||
const extension = parts.pop();
|
||||
const base = parts.join('.');
|
||||
finalFilename = `${base}${counter}.${extension}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
detectedFiles.set(finalFilename, content.trim());
|
||||
}
|
||||
|
||||
detectedFiles.forEach((content, filename) => {
|
||||
files.push({ path: filename, content });
|
||||
});
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save files to local directory
|
||||
*/
|
||||
function saveFilesToDirectory(files: { path: string; content: string }[], baseDir: string): void {
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
if (!fs.existsSync(baseDir)) {
|
||||
fs.mkdirSync(baseDir, { recursive: true });
|
||||
}
|
||||
|
||||
files.forEach(file => {
|
||||
const fullPath = path.join(baseDir, file.path);
|
||||
const dir = path.dirname(fullPath);
|
||||
|
||||
// Create directory if needed
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write file
|
||||
fs.writeFileSync(fullPath, file.content, 'utf-8');
|
||||
log(`✓ ${file.path}`, 'success');
|
||||
});
|
||||
|
||||
console.log('');
|
||||
console.log(`${colors.green}✓${colors.reset} All files saved to: ${colors.cyan}${path.resolve(baseDir)}${colors.reset}`);
|
||||
printSeparator();
|
||||
}
|
||||
|
||||
async function executeViaWorker(
|
||||
config: LauncherConfig
|
||||
): Promise<ExecutionResult> {
|
||||
const workerUrl = config.workerUrl || 'http://localhost:8787';
|
||||
const endpoint = `${workerUrl}/execute`;
|
||||
|
||||
log(`Sending request to Cloudflare Worker: ${endpoint}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
question: config.prompt,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Worker returned ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const result = (await response.json()) as ExecutionResult;
|
||||
return result;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Failed to execute via worker: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function executeDirectly(config: LauncherConfig): Promise<ExecutionResult> {
|
||||
log('Executing with Claude Agent SDK...');
|
||||
|
||||
// Extract agent names for context
|
||||
const agents = config.componentsToInstall ? extractAgents(config.componentsToInstall) : [];
|
||||
|
||||
try {
|
||||
log('Generating code with Claude Agent SDK...');
|
||||
log(`Working directory: ${process.cwd()}`);
|
||||
|
||||
// Detect if this is a web development request
|
||||
const isWebRequest = /html|css|javascript|webpage|website|form|ui|interface|frontend/i.test(config.prompt);
|
||||
|
||||
const promptContent = isWebRequest
|
||||
? `Create a complete web application for: "${config.prompt}"
|
||||
|
||||
IMPORTANT FORMAT REQUIREMENTS:
|
||||
- Provide complete, working code for ALL files needed
|
||||
- Use this EXACT format for each file:
|
||||
|
||||
\`\`\`html:index.html
|
||||
[your HTML code here]
|
||||
\`\`\`
|
||||
|
||||
\`\`\`css:styles.css
|
||||
[your CSS code here]
|
||||
\`\`\`
|
||||
|
||||
\`\`\`javascript:script.js
|
||||
[your JavaScript code here]
|
||||
\`\`\`
|
||||
|
||||
Requirements:
|
||||
- Create a complete, functional web application
|
||||
- Include all necessary HTML, CSS, and JavaScript
|
||||
- Use modern, responsive design
|
||||
- Add proper comments
|
||||
- Ensure code is ready to run
|
||||
- Do NOT include any explanations, ONLY code blocks with filenames`
|
||||
: config.prompt;
|
||||
|
||||
// Configure Claude Agent SDK options
|
||||
// Using settingSources: ['project'] to automatically load agents from .claude/ directory
|
||||
// This is supported in SDK version ^0.1.23 and later
|
||||
const options: ClaudeAgentOptions = {
|
||||
model: 'claude-sonnet-4-5',
|
||||
apiKey: config.anthropicApiKey,
|
||||
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
||||
// Automatically load agents, settings, and configurations from .claude/ directory
|
||||
settingSources: ['project'],
|
||||
};
|
||||
|
||||
if (agents.length > 0) {
|
||||
log(`Using agents from .claude/agents/ directory via settingSources`, 'success');
|
||||
}
|
||||
|
||||
// Collect the full response
|
||||
let generatedCode = '';
|
||||
|
||||
try {
|
||||
for await (const message of query({ prompt: promptContent, options })) {
|
||||
// The Agent SDK returns different message types:
|
||||
// - 'system': Initialization info
|
||||
// - 'assistant': Individual API responses
|
||||
// - 'result': Final aggregated result (THIS is what we need!)
|
||||
if (message.type === 'result' && message.result) {
|
||||
generatedCode = message.result;
|
||||
log(`Received result (${message.result.length} chars)`, 'success');
|
||||
} else if (message.type === 'text' && message.text) {
|
||||
// Fallback for older SDK versions
|
||||
generatedCode += message.text;
|
||||
}
|
||||
}
|
||||
} catch (queryError) {
|
||||
const err = queryError as Error;
|
||||
log(`Query error: ${err.message}`, 'error');
|
||||
log(`Stack: ${err.stack}`, 'error');
|
||||
throw new Error(`Claude Agent SDK query failed: ${err.message}`);
|
||||
}
|
||||
|
||||
if (!generatedCode) {
|
||||
throw new Error('Failed to generate code from Claude Agent SDK (empty response)');
|
||||
}
|
||||
|
||||
log('Code generated successfully', 'success');
|
||||
|
||||
// Note: Direct execution would require local Python runtime
|
||||
// For now, we return the code for manual execution or deployment
|
||||
return {
|
||||
success: true,
|
||||
question: config.prompt,
|
||||
code: generatedCode,
|
||||
output: 'Code generated. Deploy to Cloudflare Worker to execute.',
|
||||
error: '',
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Direct execution failed: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
function extractAgents(componentsString: string): string[] {
|
||||
const agents: string[] = [];
|
||||
const parts = componentsString.split('--');
|
||||
|
||||
for (const part of parts) {
|
||||
const trimmed = part.trim();
|
||||
if (trimmed.startsWith('agent ')) {
|
||||
const agentNames = trimmed.substring(6).trim();
|
||||
if (agentNames) {
|
||||
agents.push(...agentNames.split(',').map((a) => a.trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return agents;
|
||||
}
|
||||
|
||||
async function installAgents(agents: string[]): Promise<void> {
|
||||
if (agents.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Installing ${agents.length} agent(s)...`);
|
||||
|
||||
// Create .claude/agents directory
|
||||
const claudeDir = path.join(process.cwd(), '.claude');
|
||||
const agentsDir = path.join(claudeDir, 'agents');
|
||||
|
||||
if (!fs.existsSync(agentsDir)) {
|
||||
fs.mkdirSync(agentsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Download each agent from GitHub
|
||||
const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents';
|
||||
|
||||
for (const agent of agents) {
|
||||
try {
|
||||
log(`Downloading agent: ${agent}...`);
|
||||
|
||||
// Construct the GitHub URL for the agent
|
||||
const agentUrl = `${GITHUB_RAW_BASE}/${agent}.md`;
|
||||
|
||||
// Download the agent file
|
||||
const response = await fetch(agentUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
log(`Failed to download agent ${agent}: ${response.statusText}`, 'warning');
|
||||
continue;
|
||||
}
|
||||
|
||||
const agentContent = await response.text();
|
||||
|
||||
// Save to .claude/agents/
|
||||
const agentFileName = agent.replace(/\//g, '-') + '.md';
|
||||
const agentPath = path.join(agentsDir, agentFileName);
|
||||
|
||||
fs.writeFileSync(agentPath, agentContent, 'utf-8');
|
||||
log(`Installed agent: ${agent}`, 'success');
|
||||
} catch (error) {
|
||||
log(`Error installing agent ${agent}: ${error instanceof Error ? error.message : String(error)}`, 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
// Create settings.json to reference the agents
|
||||
const settingsPath = path.join(claudeDir, 'settings.json');
|
||||
const settings = {
|
||||
agents: agents.map(agent => ({
|
||||
name: agent.split('/').pop() || agent,
|
||||
path: `.claude/agents/${agent.replace(/\//g, '-')}.md`
|
||||
}))
|
||||
};
|
||||
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
||||
log('Created .claude/settings.json', 'success');
|
||||
}
|
||||
|
||||
function displayResults(result: ExecutionResult, targetDir?: string) {
|
||||
console.log('');
|
||||
|
||||
if (result.error) {
|
||||
console.log(`${colors.red}❌ Error:${colors.reset} ${result.error}`);
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.sandboxId) {
|
||||
log(`Sandbox ID: ${result.sandboxId}`);
|
||||
}
|
||||
|
||||
// Extract and save files
|
||||
const files = extractFilesFromCode(result.code);
|
||||
if (files.length > 0) {
|
||||
// Generate unique directory name with timestamp (similar to E2B's sandbox-xxxxxxxx)
|
||||
const timestamp = Date.now().toString(36);
|
||||
const baseDir = targetDir || process.cwd();
|
||||
const outputDir = path.join(baseDir, `cloudflare-${timestamp}`);
|
||||
|
||||
console.log('');
|
||||
printSeparator();
|
||||
log(`Downloading ${files.length} file(s)...`);
|
||||
console.log('');
|
||||
saveFilesToDirectory(files, outputDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkWorkerAvailability(url: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' });
|
||||
return response.ok || response.status === 405; // 405 is fine, means worker is up
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 1) {
|
||||
console.log('Cloudflare Sandbox Launcher');
|
||||
console.log('');
|
||||
console.log('Usage:');
|
||||
console.log(' node launcher.ts <prompt> [components] [anthropic_api_key] [worker_url]');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' node launcher.ts "Calculate factorial of 5"');
|
||||
console.log(' node launcher.ts "Create a React app" "--agent frontend-developer" YOUR_KEY');
|
||||
console.log(' node launcher.ts "Fibonacci" "" YOUR_KEY https://your-worker.workers.dev');
|
||||
console.log('');
|
||||
console.log('Environment Variables:');
|
||||
console.log(' ANTHROPIC_API_KEY - Anthropic API key');
|
||||
console.log(' CLOUDFLARE_WORKER_URL - Cloudflare Worker endpoint');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config: LauncherConfig = {
|
||||
prompt: args[0],
|
||||
componentsToInstall: args[1] || '',
|
||||
anthropicApiKey: args[2] || process.env.ANTHROPIC_API_KEY || '',
|
||||
workerUrl: args[3] || process.env.CLOUDFLARE_WORKER_URL || 'http://localhost:8787',
|
||||
targetDir: args[4] || process.cwd(),
|
||||
useLocalWorker: true,
|
||||
};
|
||||
|
||||
if (!config.anthropicApiKey) {
|
||||
log('Error: Anthropic API key is required', 'error');
|
||||
console.log('Provide via command line argument or ANTHROPIC_API_KEY environment variable');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
printSeparator();
|
||||
console.log(`${colors.bright}☁️ CLOUDFLARE SANDBOX LAUNCHER${colors.reset}`);
|
||||
printSeparator();
|
||||
console.log('');
|
||||
|
||||
log(`Prompt: "${config.prompt.substring(0, 100)}${config.prompt.length > 100 ? '...' : ''}"`);
|
||||
|
||||
if (config.componentsToInstall) {
|
||||
const agents = extractAgents(config.componentsToInstall);
|
||||
if (agents.length > 0) {
|
||||
log(`Agents: ${agents.join(', ')}`);
|
||||
|
||||
// Install agents before execution
|
||||
console.log('');
|
||||
await installAgents(agents);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
try {
|
||||
// Check if worker is available
|
||||
log('Checking Cloudflare Worker availability...');
|
||||
const workerAvailable = await checkWorkerAvailability(config.workerUrl || 'http://localhost:8787');
|
||||
|
||||
let result: ExecutionResult;
|
||||
|
||||
if (workerAvailable) {
|
||||
log('Cloudflare Worker is available', 'success');
|
||||
log('Executing via Cloudflare Sandbox...');
|
||||
result = await executeViaWorker(config);
|
||||
} else {
|
||||
log('Cloudflare Worker not available, using direct execution', 'warning');
|
||||
log('For full sandbox execution, deploy worker with: npx wrangler deploy', 'warning');
|
||||
result = await executeDirectly(config);
|
||||
}
|
||||
|
||||
displayResults(result, config.targetDir);
|
||||
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('');
|
||||
log(`Execution failed: ${error instanceof Error ? error.message : String(error)}`, 'error');
|
||||
console.log('');
|
||||
|
||||
log('Troubleshooting:', 'info');
|
||||
console.log('1. Ensure Cloudflare Worker is deployed: npx wrangler deploy');
|
||||
console.log('2. Check API key is set: npx wrangler secret put ANTHROPIC_API_KEY');
|
||||
console.log('3. Wait 2-3 minutes after first deployment for container provisioning');
|
||||
console.log('4. Check container status: npx wrangler containers list');
|
||||
console.log('5. For local testing: npm run dev');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export { executeViaWorker, executeDirectly, type LauncherConfig, type ExecutionResult };
|
||||
|
||||
// Run if executed directly (ES modules compatible)
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Cloudflare Sandbox Real-time Monitor
|
||||
* Provides real-time monitoring and debugging of Cloudflare sandbox operations
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
interface MonitoringMetrics {
|
||||
executionTime: number;
|
||||
codeGenerationTime: number;
|
||||
sandboxExecutionTime: number;
|
||||
memoryUsage?: number;
|
||||
success: boolean;
|
||||
errorDetails?: string;
|
||||
}
|
||||
|
||||
interface SandboxState {
|
||||
status: 'idle' | 'generating' | 'executing' | 'completed' | 'failed';
|
||||
currentStep: string;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
// ANSI color codes
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
function logWithTimestamp(message: string, level: 'INFO' | 'SUCCESS' | 'ERROR' | 'WARNING' = 'INFO') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const icon = {
|
||||
INFO: `${colors.blue}ℹ${colors.reset}`,
|
||||
SUCCESS: `${colors.green}✓${colors.reset}`,
|
||||
ERROR: `${colors.red}✗${colors.reset}`,
|
||||
WARNING: `${colors.yellow}⚠${colors.reset}`,
|
||||
}[level];
|
||||
|
||||
const colorCode = {
|
||||
INFO: colors.blue,
|
||||
SUCCESS: colors.green,
|
||||
ERROR: colors.red,
|
||||
WARNING: colors.yellow,
|
||||
}[level];
|
||||
|
||||
console.log(`${colors.dim}[${timestamp}]${colors.reset} ${icon} ${colorCode}${message}${colors.reset}`);
|
||||
}
|
||||
|
||||
function printSeparator(char: string = '=', length: number = 60) {
|
||||
console.log(colors.dim + char.repeat(length) + colors.reset);
|
||||
}
|
||||
|
||||
async function monitorWorkerHealth(workerUrl: string): Promise<boolean> {
|
||||
logWithTimestamp('🔍 Checking Cloudflare Worker health...');
|
||||
|
||||
try {
|
||||
const response = await fetch(workerUrl, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(5000), // 5 second timeout
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 405) {
|
||||
logWithTimestamp('✅ Worker is responding', 'SUCCESS');
|
||||
logWithTimestamp(` Status: ${response.status} ${response.statusText}`);
|
||||
return true;
|
||||
} else {
|
||||
logWithTimestamp(`⚠️ Worker returned: ${response.status} ${response.statusText}`, 'WARNING');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logWithTimestamp(`❌ Worker health check failed: ${errorMsg}`, 'ERROR');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function monitorCodeGeneration(
|
||||
anthropic: Anthropic,
|
||||
prompt: string
|
||||
): Promise<{ code: string; duration: number }> {
|
||||
logWithTimestamp('🤖 Starting code generation with Claude...');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await anthropic.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 4096,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `Generate Python code to answer: "${prompt}"
|
||||
|
||||
Requirements:
|
||||
- Use only Python standard library
|
||||
- Print the result using print()
|
||||
- Keep code simple and safe
|
||||
- Include proper error handling
|
||||
|
||||
Return ONLY the code, no explanations.`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
const code = response.content[0]?.type === 'text' ? response.content[0].text : '';
|
||||
|
||||
if (!code) {
|
||||
throw new Error('No code generated');
|
||||
}
|
||||
|
||||
logWithTimestamp(`✅ Code generated in ${duration}ms`, 'SUCCESS');
|
||||
logWithTimestamp(` Model: ${response.model}`);
|
||||
logWithTimestamp(` Tokens used: ${response.usage.input_tokens} in, ${response.usage.output_tokens} out`);
|
||||
logWithTimestamp(` Code length: ${code.length} characters`);
|
||||
|
||||
if (code.length < 500) {
|
||||
console.log('');
|
||||
logWithTimestamp('📝 Generated Code Preview:');
|
||||
printSeparator('-', 60);
|
||||
console.log(colors.cyan + code + colors.reset);
|
||||
printSeparator('-', 60);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
return { code, duration };
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logWithTimestamp(`❌ Code generation failed: ${errorMsg}`, 'ERROR');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function monitorSandboxExecution(
|
||||
workerUrl: string,
|
||||
question: string
|
||||
): Promise<{ result: any; duration: number }> {
|
||||
logWithTimestamp('⚙️ Executing in Cloudflare Sandbox...');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${workerUrl}/execute`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ question }),
|
||||
signal: AbortSignal.timeout(60000), // 60 second timeout
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Sandbox returned ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
logWithTimestamp(`✅ Sandbox execution completed in ${duration}ms`, 'SUCCESS');
|
||||
logWithTimestamp(` Exit code: ${result.success ? '0 (success)' : '1 (failed)'}`);
|
||||
logWithTimestamp(` Output length: ${result.output?.length || 0} characters`);
|
||||
|
||||
if (result.error) {
|
||||
logWithTimestamp(` Error output: ${result.error.length} characters`, 'WARNING');
|
||||
}
|
||||
|
||||
return { result, duration };
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logWithTimestamp(`❌ Sandbox execution failed after ${duration}ms: ${errorMsg}`, 'ERROR');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function displayMetrics(metrics: MonitoringMetrics) {
|
||||
console.log('');
|
||||
printSeparator();
|
||||
console.log(`${colors.bright}📊 PERFORMANCE METRICS${colors.reset}`);
|
||||
printSeparator();
|
||||
console.log('');
|
||||
|
||||
console.log(`${colors.cyan}Total Execution Time:${colors.reset} ${metrics.executionTime}ms`);
|
||||
console.log(`${colors.cyan} ├─ Code Generation:${colors.reset} ${metrics.codeGenerationTime}ms`);
|
||||
console.log(`${colors.cyan} └─ Sandbox Execution:${colors.reset} ${metrics.sandboxExecutionTime}ms`);
|
||||
|
||||
if (metrics.memoryUsage) {
|
||||
console.log(`${colors.cyan}Memory Usage:${colors.reset} ${metrics.memoryUsage}MB`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
`${colors.cyan}Status:${colors.reset} ${metrics.success ? `${colors.green}Success ✓${colors.reset}` : `${colors.red}Failed ✗${colors.reset}`}`
|
||||
);
|
||||
|
||||
if (metrics.errorDetails) {
|
||||
console.log(`${colors.red}Error Details:${colors.reset} ${metrics.errorDetails}`);
|
||||
}
|
||||
|
||||
printSeparator();
|
||||
}
|
||||
|
||||
function displaySystemInfo() {
|
||||
console.log('');
|
||||
printSeparator();
|
||||
console.log(`${colors.bright}🖥️ SYSTEM INFORMATION${colors.reset}`);
|
||||
printSeparator();
|
||||
console.log('');
|
||||
|
||||
console.log(`${colors.cyan}Node.js Version:${colors.reset} ${process.version}`);
|
||||
console.log(`${colors.cyan}Platform:${colors.reset} ${process.platform}`);
|
||||
console.log(`${colors.cyan}Architecture:${colors.reset} ${process.arch}`);
|
||||
console.log(
|
||||
`${colors.cyan}Memory Usage:${colors.reset} ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB / ${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)}MB`
|
||||
);
|
||||
|
||||
printSeparator();
|
||||
console.log('');
|
||||
}
|
||||
|
||||
async function enhancedSandboxMonitoring(
|
||||
prompt: string,
|
||||
anthropicApiKey: string,
|
||||
workerUrl: string = 'http://localhost:8787'
|
||||
): Promise<boolean> {
|
||||
logWithTimestamp('🚀 Starting enhanced Cloudflare sandbox monitoring');
|
||||
printSeparator();
|
||||
|
||||
displaySystemInfo();
|
||||
|
||||
const metrics: MonitoringMetrics = {
|
||||
executionTime: 0,
|
||||
codeGenerationTime: 0,
|
||||
sandboxExecutionTime: 0,
|
||||
success: false,
|
||||
};
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Step 1: Check worker health
|
||||
const workerHealthy = await monitorWorkerHealth(workerUrl);
|
||||
if (!workerHealthy) {
|
||||
logWithTimestamp('⚠️ Worker is not healthy, attempting to continue...', 'WARNING');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Step 2: Generate code with monitoring
|
||||
const anthropic = new Anthropic({ apiKey: anthropicApiKey });
|
||||
const { code, duration: codeGenDuration } = await monitorCodeGeneration(anthropic, prompt);
|
||||
metrics.codeGenerationTime = codeGenDuration;
|
||||
|
||||
console.log('');
|
||||
|
||||
// Step 3: Execute in sandbox with monitoring
|
||||
const { result, duration: execDuration } = await monitorSandboxExecution(workerUrl, prompt);
|
||||
metrics.sandboxExecutionTime = execDuration;
|
||||
|
||||
console.log('');
|
||||
|
||||
// Display results
|
||||
printSeparator();
|
||||
console.log(`${colors.bright}🎯 EXECUTION RESULTS${colors.reset}`);
|
||||
printSeparator();
|
||||
console.log('');
|
||||
|
||||
console.log(`${colors.cyan}Question:${colors.reset} ${result.question}`);
|
||||
console.log('');
|
||||
|
||||
console.log(`${colors.cyan}Generated Code:${colors.reset}`);
|
||||
printSeparator('-', 60);
|
||||
console.log(colors.green + result.code + colors.reset);
|
||||
printSeparator('-', 60);
|
||||
console.log('');
|
||||
|
||||
if (result.output) {
|
||||
console.log(`${colors.cyan}Output:${colors.reset}`);
|
||||
console.log(colors.bright + result.output + colors.reset);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
console.log(`${colors.red}Error Output:${colors.reset}`);
|
||||
console.log(result.error);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
metrics.executionTime = Date.now() - startTime;
|
||||
metrics.success = result.success;
|
||||
metrics.memoryUsage = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
||||
|
||||
displayMetrics(metrics);
|
||||
|
||||
logWithTimestamp('✅ Monitoring session completed successfully', 'SUCCESS');
|
||||
return true;
|
||||
} catch (error) {
|
||||
metrics.executionTime = Date.now() - startTime;
|
||||
metrics.success = false;
|
||||
metrics.errorDetails = error instanceof Error ? error.message : String(error);
|
||||
metrics.memoryUsage = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
||||
|
||||
console.log('');
|
||||
displayMetrics(metrics);
|
||||
|
||||
logWithTimestamp(`❌ Monitoring session failed: ${metrics.errorDetails}`, 'ERROR');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 1) {
|
||||
console.log('Cloudflare Sandbox Monitor');
|
||||
console.log('');
|
||||
console.log('Usage:');
|
||||
console.log(' node monitor.ts <prompt> [anthropic_api_key] [worker_url]');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' node monitor.ts "Calculate factorial of 5"');
|
||||
console.log(' node monitor.ts "Fibonacci 10" YOUR_API_KEY');
|
||||
console.log(' node monitor.ts "Sum array" YOUR_KEY https://your-worker.workers.dev');
|
||||
console.log('');
|
||||
console.log('Environment Variables:');
|
||||
console.log(' ANTHROPIC_API_KEY - Anthropic API key');
|
||||
console.log(' CLOUDFLARE_WORKER_URL - Worker endpoint (default: http://localhost:8787)');
|
||||
console.log('');
|
||||
console.log('This tool provides enhanced monitoring and debugging for Cloudflare sandbox operations.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prompt = args[0];
|
||||
const anthropicApiKey = args[1] || process.env.ANTHROPIC_API_KEY || '';
|
||||
const workerUrl = args[2] || process.env.CLOUDFLARE_WORKER_URL || 'http://localhost:8787';
|
||||
|
||||
if (!anthropicApiKey) {
|
||||
logWithTimestamp('❌ Anthropic API key is required', 'ERROR');
|
||||
console.log('Provide via command line argument or ANTHROPIC_API_KEY environment variable');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
printSeparator('=', 70);
|
||||
console.log(`${colors.bright}${colors.cyan} 🎬 CLOUDFLARE SANDBOX MONITOR${colors.reset}`);
|
||||
printSeparator('=', 70);
|
||||
console.log('');
|
||||
|
||||
const success = await enhancedSandboxMonitoring(prompt, anthropicApiKey, workerUrl);
|
||||
|
||||
console.log('');
|
||||
|
||||
if (success) {
|
||||
logWithTimestamp('🎉 Monitoring completed successfully', 'SUCCESS');
|
||||
process.exit(0);
|
||||
} else {
|
||||
logWithTimestamp('💔 Monitoring failed', 'ERROR');
|
||||
console.log('');
|
||||
console.log('Troubleshooting:');
|
||||
console.log('1. Ensure worker is deployed: npx wrangler deploy');
|
||||
console.log('2. Check API key is set: npx wrangler secret put ANTHROPIC_API_KEY');
|
||||
console.log('3. Verify worker URL is correct');
|
||||
console.log('4. Check worker logs: npx wrangler tail');
|
||||
console.log('5. Test locally: npm run dev');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (require.main === module) {
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { monitorWorkerHealth, monitorCodeGeneration, monitorSandboxExecution };
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "cloudflare-claude-sandbox",
|
||||
"version": "1.0.0",
|
||||
"description": "Cloudflare Workers sandbox for executing Claude Code with AI-powered code generation",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"deploy": "wrangler deploy",
|
||||
"tail": "wrangler tail",
|
||||
"launch": "tsx launcher.ts",
|
||||
"monitor": "tsx monitor.ts",
|
||||
"test": "vitest",
|
||||
"test:watch": "vitest --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.23",
|
||||
"@cloudflare/sandbox": "^0.1.0",
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
||||
"@cloudflare/workers-types": "^4.20240129.0",
|
||||
"@types/node": "^20.11.0",
|
||||
"prettier": "^3.2.4",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^1.2.0",
|
||||
"wrangler": "^3.78.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.0"
|
||||
},
|
||||
"keywords": [
|
||||
"cloudflare",
|
||||
"workers",
|
||||
"sandbox",
|
||||
"claude",
|
||||
"anthropic",
|
||||
"ai",
|
||||
"code-execution",
|
||||
"durable-objects",
|
||||
"edge-computing"
|
||||
],
|
||||
"author": "Claude Code Templates",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/anthropics/claude-code-templates"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
import { getSandbox, type Sandbox } from '@cloudflare/sandbox';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
export { Sandbox } from '@cloudflare/sandbox';
|
||||
|
||||
interface Env {
|
||||
Sandbox: DurableObjectNamespace<Sandbox>;
|
||||
ANTHROPIC_API_KEY: string;
|
||||
}
|
||||
|
||||
interface ExecuteRequest {
|
||||
question: string;
|
||||
maxTokens?: number;
|
||||
timeout?: number;
|
||||
language?: 'python' | 'javascript';
|
||||
}
|
||||
|
||||
interface ExecuteResponse {
|
||||
success: boolean;
|
||||
question: string;
|
||||
code: string;
|
||||
output: string;
|
||||
error: string;
|
||||
sandboxId?: string;
|
||||
executionTime?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Worker handler
|
||||
* Receives code execution requests and orchestrates Claude AI + Cloudflare Sandbox
|
||||
*/
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// CORS headers for browser access
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
};
|
||||
|
||||
// Handle OPTIONS request for CORS
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
// Root endpoint - return usage instructions
|
||||
if (request.method === 'GET' && url.pathname === '/') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
name: 'Cloudflare Claude Code Sandbox',
|
||||
version: '1.0.0',
|
||||
endpoints: {
|
||||
execute: 'POST /execute - Execute code via Claude AI',
|
||||
health: 'GET /health - Check worker health',
|
||||
},
|
||||
usage: {
|
||||
example: {
|
||||
method: 'POST',
|
||||
url: '/execute',
|
||||
body: {
|
||||
question: 'What is the 10th Fibonacci number?',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Health check endpoint
|
||||
if (request.method === 'GET' && url.pathname === '/health') {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
worker: 'cloudflare-claude-sandbox',
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Execute endpoint
|
||||
if (request.method === 'POST' && url.pathname === '/execute') {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Parse request body
|
||||
const body = (await request.json()) as ExecuteRequest;
|
||||
|
||||
if (!body.question) {
|
||||
return Response.json(
|
||||
{ error: 'Question is required' },
|
||||
{ status: 400, headers: corsHeaders }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate API key
|
||||
if (!env.ANTHROPIC_API_KEY) {
|
||||
return Response.json(
|
||||
{
|
||||
error: 'ANTHROPIC_API_KEY not configured',
|
||||
message: 'Set the API key using: npx wrangler secret put ANTHROPIC_API_KEY',
|
||||
},
|
||||
{ status: 500, headers: corsHeaders }
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize Anthropic client
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
|
||||
// Generate code using Claude
|
||||
console.log('Generating code with Claude for:', body.question.substring(0, 100));
|
||||
|
||||
const language = body.language || 'python';
|
||||
const codePrompt =
|
||||
language === 'python'
|
||||
? `Generate Python code to answer: "${body.question}"
|
||||
|
||||
Requirements:
|
||||
- Use only Python standard library
|
||||
- Print the result using print()
|
||||
- Keep code simple and safe
|
||||
- Include proper error handling
|
||||
- Use descriptive variable names
|
||||
|
||||
Return ONLY the code, no explanations or markdown formatting.`
|
||||
: `Generate JavaScript code to answer: "${body.question}"
|
||||
|
||||
Requirements:
|
||||
- Use only Node.js standard library
|
||||
- Print the result using console.log()
|
||||
- Keep code simple and safe
|
||||
- Include proper error handling
|
||||
- Use descriptive variable names
|
||||
|
||||
Return ONLY the code, no explanations or markdown formatting.`;
|
||||
|
||||
const codeGeneration = await anthropic.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: body.maxTokens || 2048,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: codePrompt,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const generatedCode =
|
||||
codeGeneration.content[0]?.type === 'text' ? codeGeneration.content[0].text : '';
|
||||
|
||||
if (!generatedCode) {
|
||||
return Response.json(
|
||||
{ error: 'Failed to generate code from Claude' },
|
||||
{ status: 500, headers: corsHeaders }
|
||||
);
|
||||
}
|
||||
|
||||
// Clean up code (remove markdown formatting if present)
|
||||
const cleanCode = generatedCode
|
||||
.replace(/```(?:python|javascript|js)?\n?/g, '')
|
||||
.replace(/```\n?$/g, '')
|
||||
.trim();
|
||||
|
||||
console.log('Code generated, executing in sandbox...');
|
||||
|
||||
// Execute the code in a sandbox
|
||||
// Use a unique ID per request to avoid conflicts
|
||||
const sandboxId = `user-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
||||
const sandbox = getSandbox(env.Sandbox, sandboxId);
|
||||
|
||||
// Determine execution command based on language
|
||||
const fileName = language === 'python' ? '/tmp/code.py' : '/tmp/code.js';
|
||||
const execCommand =
|
||||
language === 'python' ? 'python /tmp/code.py' : 'node /tmp/code.js';
|
||||
|
||||
// Write code to sandbox and execute
|
||||
await sandbox.writeFile(fileName, cleanCode);
|
||||
|
||||
const result = await sandbox.exec(execCommand, {
|
||||
timeout: body.timeout || 30000, // 30 seconds default
|
||||
});
|
||||
|
||||
const executionTime = Date.now() - startTime;
|
||||
|
||||
const response: ExecuteResponse = {
|
||||
success: result.success,
|
||||
question: body.question,
|
||||
code: cleanCode,
|
||||
output: result.stdout || '',
|
||||
error: result.stderr || '',
|
||||
sandboxId: sandboxId,
|
||||
executionTime: executionTime,
|
||||
};
|
||||
|
||||
console.log(
|
||||
`Execution completed in ${executionTime}ms. Success: ${result.success}`
|
||||
);
|
||||
|
||||
return Response.json(response, {
|
||||
headers: corsHeaders,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Execution error:', error);
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
const executionTime = Date.now() - startTime;
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
error: 'Internal server error',
|
||||
message: errorMessage,
|
||||
executionTime: executionTime,
|
||||
},
|
||||
{ status: 500, headers: corsHeaders }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown endpoint
|
||||
return new Response(
|
||||
'POST /execute with { "question": "your question" }\nGET /health for health check\nGET / for API information',
|
||||
{ status: 404, headers: corsHeaders }
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["@cloudflare/workers-types", "node"],
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"launcher.ts",
|
||||
"monitor.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
".wrangler"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#:schema node_modules/wrangler/config-schema.json
|
||||
name = "cloudflare-claude-sandbox"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-01-01"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
|
||||
# Durable Objects configuration for sandbox persistence
|
||||
[[durable_objects.bindings]]
|
||||
name = "Sandbox"
|
||||
class_name = "Sandbox"
|
||||
|
||||
# Durable Object migration (required for first deployment)
|
||||
[[migrations]]
|
||||
tag = "v1"
|
||||
new_classes = ["Sandbox"]
|
||||
|
||||
# Environment variables (non-secret)
|
||||
[vars]
|
||||
ENVIRONMENT = "production"
|
||||
|
||||
# Development environment configuration
|
||||
[env.development]
|
||||
vars = { ENVIRONMENT = "development" }
|
||||
|
||||
# Staging environment configuration
|
||||
[env.staging]
|
||||
vars = { ENVIRONMENT = "staging" }
|
||||
|
||||
# Resource limits
|
||||
[limits]
|
||||
# CPU time limit per request (milliseconds)
|
||||
cpu_ms = 50
|
||||
|
||||
# Build configuration
|
||||
[build]
|
||||
command = ""
|
||||
|
||||
# Observability
|
||||
[observability]
|
||||
enabled = true
|
||||
|
||||
# Usage model - required for Durable Objects
|
||||
[usage_model]
|
||||
# Use 'bundled' for Workers Paid plan ($5/month)
|
||||
# Includes 10M requests/month and unlimited Durable Objects
|
||||
# For free tier, comment this out (limited functionality)
|
||||
# usage_model = "bundled"
|
||||
|
||||
# Note: Uncomment the line above after setting up Workers Paid plan
|
||||
# Or remove this section entirely for the free tier (sandbox will be limited)
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# E2B API Configuration
|
||||
# Get your API key from: https://e2b.dev/dashboard
|
||||
E2B_API_KEY=your_e2b_api_key_here
|
||||
|
||||
# Anthropic API Configuration
|
||||
# Get your API key from: https://console.anthropic.com
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# Optional: Sandbox timeout in seconds (default: 300)
|
||||
# E2B_TIMEOUT=300
|
||||
@@ -0,0 +1,203 @@
|
||||
# E2B Sandbox Debugging Guide
|
||||
|
||||
## 🔍 Herramientas de Monitoreo Disponibles
|
||||
|
||||
### 1. Launcher Principal con Logging Mejorado
|
||||
**Archivo**: `e2b-launcher.py`
|
||||
- Logging detallado de cada paso
|
||||
- Verificación de instalación de Claude Code
|
||||
- Monitoreo de permisos y ambiente
|
||||
- Timeouts extendidos para operaciones largas
|
||||
- Descarga automática de archivos generados
|
||||
|
||||
### 2. Monitor de Sandbox en Tiempo Real
|
||||
**Archivo**: `e2b-monitor.py`
|
||||
- Monitoreo de recursos del sistema
|
||||
- Tracking de file system en tiempo real
|
||||
- Análisis de performance y memory usage
|
||||
- Logging con timestamps detallados
|
||||
|
||||
### 3. Simulador Demo
|
||||
Para testing sin API keys válidos, crea un archivo demo que simule el flujo completo.
|
||||
|
||||
## 🚨 Troubleshooting Común
|
||||
|
||||
### Problema: "Sandbox timeout"
|
||||
**Síntomas**:
|
||||
```
|
||||
❌ Error: The sandbox was not found: This error is likely due to sandbox timeout
|
||||
```
|
||||
|
||||
**Soluciones**:
|
||||
1. **Aumentar timeout del sandbox**:
|
||||
```python
|
||||
sbx = Sandbox.create(timeout=600) # 10 minutos
|
||||
sbx.set_timeout(900) # Extender a 15 minutos
|
||||
```
|
||||
|
||||
2. **Usar el monitor para ver qué consume tiempo**:
|
||||
```bash
|
||||
python e2b-monitor.py "Your prompt here" "" your_e2b_key your_anthropic_key
|
||||
```
|
||||
|
||||
### Problema: "Claude not found"
|
||||
**Síntomas**:
|
||||
```
|
||||
❌ Claude not found, checking PATH...
|
||||
```
|
||||
|
||||
**Debugging Steps**:
|
||||
1. **Verificar template correcto**:
|
||||
```python
|
||||
template="anthropic-claude-code" # Debe ser exactamente este
|
||||
```
|
||||
|
||||
2. **Verificar instalación en sandbox**:
|
||||
```bash
|
||||
# El launcher ejecuta automáticamente:
|
||||
which claude
|
||||
claude --version
|
||||
echo $PATH
|
||||
```
|
||||
|
||||
### Problema: "Permission denied"
|
||||
**Síntomas**:
|
||||
```
|
||||
❌ Write permission issue
|
||||
```
|
||||
|
||||
**Soluciones**:
|
||||
1. **Verificar directorio de trabajo**:
|
||||
```bash
|
||||
pwd
|
||||
whoami
|
||||
ls -la
|
||||
```
|
||||
|
||||
2. **Cambiar a directorio con permisos**:
|
||||
```python
|
||||
sbx.commands.run("cd /home/user && mkdir workspace && cd workspace")
|
||||
```
|
||||
|
||||
### Problema: API Key Issues
|
||||
**Síntomas**:
|
||||
```
|
||||
❌ Error: 401: Invalid API key
|
||||
```
|
||||
|
||||
**Debugging**:
|
||||
1. **Verificar formato de API key**:
|
||||
- E2B keys: formato específico de E2B
|
||||
- Anthropic keys: empiezan con "sk-ant-"
|
||||
|
||||
2. **Verificar permisos**:
|
||||
- Verificar que la key tenga permisos de sandbox
|
||||
- Verificar quota/límites de la cuenta
|
||||
|
||||
## 📊 Usando el Monitor para Debugging
|
||||
|
||||
### Comando Básico:
|
||||
```bash
|
||||
python e2b-monitor.py "Create a React app" "" your_e2b_key your_anthropic_key
|
||||
```
|
||||
|
||||
### Output del Monitor:
|
||||
```
|
||||
[14:32:15] INFO: 🚀 Starting enhanced E2B sandbox with monitoring
|
||||
[14:32:16] INFO: ✅ Sandbox created: abc123xyz
|
||||
[14:32:17] INFO: 🔍 System resources check
|
||||
[14:32:17] INFO: Memory usage:
|
||||
[14:32:17] INFO: total used free
|
||||
[14:32:17] INFO: Mem: 2.0Gi 512Mi 1.5Gi
|
||||
[14:32:18] INFO: 📁 Initial file system state
|
||||
[14:32:18] INFO: Current directory: /home/user
|
||||
[14:32:19] INFO: 🤖 Executing Claude Code with monitoring
|
||||
[14:32:19] INFO: Starting monitored execution: echo 'Create a React app'...
|
||||
[14:32:22] INFO: Command completed in 3.45 seconds
|
||||
[14:32:22] INFO: Exit code: 0
|
||||
[14:32:22] INFO: STDOUT length: 2847 characters
|
||||
```
|
||||
|
||||
## 🎯 Casos de Uso Específicos
|
||||
|
||||
### 1. **Debugging Timeouts**
|
||||
```bash
|
||||
# Usar el monitor para ver exactamente dónde se cuelga
|
||||
python e2b-monitor.py "Complex prompt that times out"
|
||||
```
|
||||
|
||||
### 2. **Verificar Generación de Archivos**
|
||||
El launcher automáticamente descarga archivos generados:
|
||||
```
|
||||
💾 DOWNLOADING FILES TO LOCAL MACHINE:
|
||||
✅ Downloaded: ./index.html → ./e2b-output/index.html
|
||||
✅ Downloaded: ./styles.css → ./e2b-output/styles.css
|
||||
|
||||
📁 All files downloaded to: /path/to/project/e2b-output
|
||||
```
|
||||
|
||||
### 3. **Monitoreo de Performance**
|
||||
```
|
||||
[14:33:20] INFO: Top processes:
|
||||
[14:33:20] INFO: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||
[14:33:20] INFO: user 1234 5.2 2.1 98765 43210 pts/0 S+ 14:32 0:01 claude
|
||||
```
|
||||
|
||||
## 🛠 Configuración Avanzada
|
||||
|
||||
### Variables de Ambiente Útiles:
|
||||
```bash
|
||||
export E2B_DEBUG=1 # Debug mode
|
||||
export ANTHROPIC_API_KEY=your_key # Claude API key
|
||||
export E2B_API_KEY=your_key # E2B API key
|
||||
```
|
||||
|
||||
### Configuración de Timeout Personalizada:
|
||||
```python
|
||||
# Para operaciones muy largas (ej: compilación completa)
|
||||
sbx = Sandbox.create(timeout=1800) # 30 minutos
|
||||
sbx.set_timeout(3600) # 1 hora máximo
|
||||
```
|
||||
|
||||
## 📋 Checklist de Debugging
|
||||
|
||||
### Antes de Reportar un Issue:
|
||||
- [ ] API keys válidos y con permisos correctos
|
||||
- [ ] Template correcto: "anthropic-claude-code"
|
||||
- [ ] Timeout suficiente para la operación
|
||||
- [ ] Ejecutar con el monitor para logs detallados
|
||||
- [ ] Verificar que Claude Code esté instalado en sandbox
|
||||
- [ ] Revisar permisos de escritura en directorio
|
||||
- [ ] Comprobar memoria/recursos disponibles
|
||||
|
||||
### Información a Incluir en Reports:
|
||||
- Output completo del launcher o monitor
|
||||
- Sandbox ID si está disponible
|
||||
- Prompt exacto que causa el problema
|
||||
- Componentes instalados (si aplica)
|
||||
- Tiempo de ejecución antes del fallo
|
||||
|
||||
## 🚀 Funcionalidades del Sistema
|
||||
|
||||
### Descarga Automática de Archivos
|
||||
El launcher descarga automáticamente todos los archivos generados:
|
||||
- HTML, CSS, JS, TS, TSX, Python, JSON, Markdown
|
||||
- Se guardan en directorio local `./e2b-output/`
|
||||
- Excluye archivos internos de Claude Code
|
||||
- Preserva nombres de archivo originales
|
||||
|
||||
### Logging Detallado
|
||||
- Verificación de instalación de Claude Code
|
||||
- Monitoreo de permisos y ambiente del sandbox
|
||||
- Tracking de exit codes y output length
|
||||
- Timestamps para análisis de performance
|
||||
|
||||
### Timeouts Inteligentes
|
||||
- 10 minutos timeout inicial para creación
|
||||
- 15 minutos total extendido automáticamente
|
||||
- 5 minutos timeout para ejecución de Claude Code
|
||||
- Timeouts cortos para verificaciones (5-10 segundos)
|
||||
|
||||
---
|
||||
|
||||
**Con estas herramientas puedes monitorear exactamente qué está pasando dentro del sandbox E2B y debuggear cualquier problema que surja.**
|
||||
@@ -0,0 +1,110 @@
|
||||
# E2B Claude Code Sandbox
|
||||
|
||||
Execute Claude Code in an isolated E2B cloud sandbox environment.
|
||||
|
||||
## Description
|
||||
|
||||
This component sets up E2B (E2B.dev) integration to run Claude Code in a secure, isolated cloud environment. Perfect for executing code safely without affecting your local system.
|
||||
|
||||
## Features
|
||||
|
||||
- **Isolated Execution**: Run Claude Code in a secure cloud sandbox
|
||||
- **Pre-configured Environment**: Ships with Claude Code already installed
|
||||
- **API Integration**: Seamless connection to Anthropic's Claude API
|
||||
- **Safe Code Execution**: Execute prompts without local system risks
|
||||
- **Component Installation**: Automatically installs any components specified with CLI flags
|
||||
|
||||
## Requirements
|
||||
|
||||
- E2B API Key (get from https://e2b.dev/dashboard)
|
||||
- Anthropic API Key
|
||||
- Python 3.11+ (for E2B SDK)
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Execute a prompt in E2B sandbox (requires API keys as environment variables or CLI parameters)
|
||||
npx claude-code-templates@latest --sandbox e2b --prompt "Create a React todo app"
|
||||
|
||||
# Pass API keys directly as parameters
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--e2b-api-key your_e2b_key \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--prompt "Create a React todo app"
|
||||
|
||||
# Install components and execute in sandbox
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--agent frontend-developer \
|
||||
--command setup-react \
|
||||
--e2b-api-key your_e2b_key \
|
||||
--anthropic-api-key your_anthropic_key \
|
||||
--prompt "Create a modern todo app with TypeScript"
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
The component will create:
|
||||
- `.claude/sandbox/e2b-launcher.py` - Python script to launch E2B sandbox
|
||||
- `.claude/sandbox/requirements.txt` - Python dependencies
|
||||
- `.claude/sandbox/.env.example` - Environment variables template
|
||||
|
||||
## API Key Configuration
|
||||
|
||||
You can provide API keys in two ways:
|
||||
|
||||
### Option 1: CLI Parameters (Recommended)
|
||||
```bash
|
||||
# Pass keys directly as command parameters
|
||||
npx claude-code-templates@latest --sandbox e2b \
|
||||
--e2b-api-key your_e2b_api_key \
|
||||
--anthropic-api-key your_anthropic_api_key \
|
||||
--prompt "Your prompt here"
|
||||
```
|
||||
|
||||
### Option 2: Environment Variables
|
||||
Set these environment variables in your shell or `.env` file:
|
||||
```bash
|
||||
export E2B_API_KEY=your_e2b_api_key_here
|
||||
export ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# Or create .claude/sandbox/.env file:
|
||||
E2B_API_KEY=your_e2b_api_key_here
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
```
|
||||
|
||||
**Note**: CLI parameters take precedence over environment variables.
|
||||
|
||||
## How it Works
|
||||
|
||||
1. Creates E2B sandbox with `anthropic-claude-code` template
|
||||
2. Installs any specified components (agents, commands, etc.)
|
||||
3. Executes your prompt using Claude Code inside the sandbox
|
||||
4. Returns the complete output and any generated files
|
||||
5. Automatically cleans up the sandbox after execution
|
||||
|
||||
## Security Benefits
|
||||
|
||||
- **Isolation**: Code runs in a separate cloud environment
|
||||
- **No Local Impact**: No risk to your local system or files
|
||||
- **Temporary**: Sandbox is destroyed after execution
|
||||
- **Controlled**: Only specified components and prompts are executed
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Simple web app creation
|
||||
npx claude-code-templates@latest --sandbox e2b --prompt "Create an HTML page with CSS animations"
|
||||
|
||||
# Full stack development
|
||||
npx claude-code-templates@latest --sandbox e2b --agent fullstack-developer --prompt "Create a Node.js API with authentication"
|
||||
|
||||
# Data analysis
|
||||
npx claude-code-templates@latest --sandbox e2b --agent data-scientist --prompt "Analyze this CSV data and create visualizations"
|
||||
```
|
||||
|
||||
## Template Information
|
||||
|
||||
- **Provider**: E2B (https://e2b.dev)
|
||||
- **Base Template**: anthropic-claude-code
|
||||
- **Timeout**: 5 minutes (configurable)
|
||||
- **Environment**: Ubuntu with Claude Code pre-installed
|
||||
@@ -0,0 +1,438 @@
|
||||
#!/usr/bin/env python3.11
|
||||
"""
|
||||
E2B Claude Code Sandbox Launcher
|
||||
Executes Claude Code prompts in isolated E2B cloud sandbox
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import datetime
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
# Debug: Print Python path information
|
||||
print(f"Python executable: {sys.executable}")
|
||||
print(f"Python version: {sys.version}")
|
||||
print(f"Python path: {sys.path[:3]}...") # Show first 3 paths
|
||||
|
||||
try:
|
||||
from e2b import Sandbox
|
||||
print("✓ E2B imported successfully")
|
||||
except ImportError as e:
|
||||
print(f"✗ E2B import failed: {e}")
|
||||
print("Trying to install E2B...")
|
||||
import subprocess
|
||||
# Try different installation methods for different Python environments
|
||||
install_commands = [
|
||||
[sys.executable, '-m', 'pip', 'install', '--user', 'e2b'], # User install first
|
||||
[sys.executable, '-m', 'pip', 'install', '--break-system-packages', 'e2b'], # System packages
|
||||
[sys.executable, '-m', 'pip', 'install', 'e2b'] # Default fallback
|
||||
]
|
||||
|
||||
result = None
|
||||
for cmd in install_commands:
|
||||
print(f"Trying: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print("✓ Installation successful")
|
||||
break
|
||||
else:
|
||||
print(f"✗ Failed: {result.stderr.strip()[:100]}...")
|
||||
|
||||
if result is None:
|
||||
result = subprocess.run([sys.executable, '-m', 'pip', 'install', 'e2b'],
|
||||
capture_output=True, text=True)
|
||||
print(f"Install result: {result.returncode}")
|
||||
if result.stdout:
|
||||
print(f"Install stdout: {result.stdout}")
|
||||
if result.stderr:
|
||||
print(f"Install stderr: {result.stderr}")
|
||||
|
||||
# Try importing again
|
||||
try:
|
||||
from e2b import Sandbox
|
||||
print("✓ E2B imported successfully after install")
|
||||
except ImportError as e2:
|
||||
print(f"✗ E2B still failed after install: {e2}")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to import and use dotenv if available, but don't fail if it's not
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
except ImportError:
|
||||
# dotenv is optional since we can get keys from command line arguments
|
||||
pass
|
||||
|
||||
def main():
|
||||
|
||||
# Parse command line arguments
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python e2b-launcher.py <prompt> [components_to_install] [e2b_api_key] [anthropic_api_key]")
|
||||
sys.exit(1)
|
||||
|
||||
prompt = sys.argv[1]
|
||||
components_to_install = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
|
||||
# Get API keys from command line arguments or environment variables
|
||||
e2b_api_key = sys.argv[3] if len(sys.argv) > 3 else os.getenv('E2B_API_KEY')
|
||||
anthropic_api_key = sys.argv[4] if len(sys.argv) > 4 else os.getenv('ANTHROPIC_API_KEY')
|
||||
|
||||
if not e2b_api_key:
|
||||
print("Error: E2B API key is required")
|
||||
print("Provide via command line argument or E2B_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
if not anthropic_api_key:
|
||||
print("Error: Anthropic API key is required")
|
||||
print("Provide via command line argument or ANTHROPIC_API_KEY environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# Create E2B sandbox with Claude Code template with retry logic
|
||||
print("🚀 Creating E2B sandbox with Claude Code...")
|
||||
|
||||
# Try creating sandbox with retries for WebSocket issues
|
||||
max_retries = 3
|
||||
retry_count = 0
|
||||
sbx = None
|
||||
|
||||
while retry_count < max_retries and sbx is None:
|
||||
try:
|
||||
if retry_count > 0:
|
||||
print(f"🔄 Retry {retry_count}/{max_retries - 1} - WebSocket connection...")
|
||||
|
||||
sbx = Sandbox.create(
|
||||
template="anthropic-claude-code",
|
||||
api_key=e2b_api_key,
|
||||
envs={
|
||||
'ANTHROPIC_API_KEY': anthropic_api_key,
|
||||
},
|
||||
timeout=600, # 10 minutes timeout for longer operations
|
||||
)
|
||||
|
||||
# Keep sandbox alive during operations
|
||||
print(f"🔄 Extending sandbox timeout to prevent early termination...")
|
||||
sbx.set_timeout(900) # 15 minutes total
|
||||
print(f"✅ Sandbox created: {sbx.sandbox_id}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e).lower()
|
||||
if "websocket" in error_msg or "connection" in error_msg or "timeout" in error_msg:
|
||||
retry_count += 1
|
||||
if retry_count < max_retries:
|
||||
print(f"⚠️ WebSocket connection failed (attempt {retry_count}), retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
continue
|
||||
else:
|
||||
print(f"❌ WebSocket connection failed after {max_retries} attempts")
|
||||
print("💡 This might be due to:")
|
||||
print(" • Network/firewall restrictions blocking WebSocket connections")
|
||||
print(" • Temporary E2B service issues")
|
||||
print(" • Corporate proxy blocking WebSocket traffic")
|
||||
print("💡 Try:")
|
||||
print(" • Running from a different network")
|
||||
print(" • Checking your firewall/proxy settings")
|
||||
print(" • Waiting a few minutes and trying again")
|
||||
raise e
|
||||
else:
|
||||
# Non-WebSocket error, don't retry
|
||||
raise e
|
||||
|
||||
if sbx is None:
|
||||
raise Exception("Failed to create sandbox after all retry attempts")
|
||||
|
||||
# Install components if specified
|
||||
if components_to_install:
|
||||
print("📦 Installing specified components...")
|
||||
install_result = sbx.commands.run(
|
||||
f"npx claude-code-templates@latest {components_to_install}",
|
||||
timeout=120, # 2 minutes for component installation
|
||||
)
|
||||
|
||||
if install_result.exit_code != 0:
|
||||
print(f"⚠️ Component installation warnings:")
|
||||
print(install_result.stderr)
|
||||
else:
|
||||
print("✅ Components installed successfully")
|
||||
|
||||
# Build enhanced prompt with instructions
|
||||
# Parse components to extract agents
|
||||
agents = []
|
||||
if components_to_install:
|
||||
# Split by '--' to get individual component types
|
||||
parts = components_to_install.split('--')
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if part.startswith('agent '):
|
||||
# Extract agent names after 'agent ' prefix
|
||||
agent_names = part[6:].strip() # Remove 'agent ' prefix
|
||||
if agent_names:
|
||||
# Split by comma if multiple agents
|
||||
agents.extend([a.strip() for a in agent_names.split(',')])
|
||||
|
||||
# Create enhanced prompt with proper instructions
|
||||
if agents:
|
||||
agent_list = ', '.join(agents)
|
||||
enhanced_prompt = f"""You are Claude Code, an AI assistant specialized in software development.
|
||||
|
||||
IMPORTANT INSTRUCTIONS:
|
||||
1. Execute the user's request immediately and create the requested code/files
|
||||
2. You have access to the following specialized agents: {agent_list}
|
||||
3. Use these agents in the order you deem most appropriate for completing the task
|
||||
4. Generate all necessary files and code to fulfill the request
|
||||
5. Be proactive and create a complete, working implementation
|
||||
|
||||
USER REQUEST: {prompt}
|
||||
|
||||
Now, please execute this request and create all necessary files."""
|
||||
else:
|
||||
enhanced_prompt = f"""You are Claude Code, an AI assistant specialized in software development.
|
||||
|
||||
IMPORTANT INSTRUCTIONS:
|
||||
1. Execute the user's request immediately and create the requested code/files
|
||||
2. Generate all necessary files and code to fulfill the request
|
||||
3. Be proactive and create a complete, working implementation
|
||||
4. Don't just acknowledge the request - actually create the implementation
|
||||
|
||||
USER REQUEST: {prompt}
|
||||
|
||||
Now, please execute this request and create all necessary files."""
|
||||
|
||||
# Execute Claude Code with the enhanced prompt
|
||||
print(f"🤖 Executing Claude Code with prompt: '{prompt[:50]}{'...' if len(prompt) > 50 else ''}'")
|
||||
if agents:
|
||||
print(f"🤝 Using agents: {', '.join(agents)}")
|
||||
|
||||
# First, check if Claude Code is installed and available
|
||||
print("🔍 Checking Claude Code installation...")
|
||||
check_result = sbx.commands.run("which claude", timeout=10)
|
||||
if check_result.exit_code == 0:
|
||||
print(f"✅ Claude found at: {check_result.stdout.strip()}")
|
||||
else:
|
||||
print("❌ Claude not found, checking PATH...")
|
||||
path_result = sbx.commands.run("echo $PATH", timeout=5)
|
||||
print(f"PATH: {path_result.stdout}")
|
||||
ls_result = sbx.commands.run("ls -la /usr/local/bin/ | grep claude", timeout=5)
|
||||
print(f"Claude binaries: {ls_result.stdout}")
|
||||
|
||||
# Check current directory and permissions
|
||||
print("🔍 Checking sandbox environment...")
|
||||
pwd_result = sbx.commands.run("pwd", timeout=5)
|
||||
print(f"Current directory: {pwd_result.stdout.strip()}")
|
||||
|
||||
whoami_result = sbx.commands.run("whoami", timeout=5)
|
||||
print(f"Current user: {whoami_result.stdout.strip()}")
|
||||
|
||||
# Check if we can write to current directory
|
||||
test_write = sbx.commands.run("touch test_write.tmp && rm test_write.tmp", timeout=5)
|
||||
if test_write.exit_code == 0:
|
||||
print("✅ Write permissions OK")
|
||||
else:
|
||||
print("❌ Write permission issue")
|
||||
|
||||
# Build Claude Code command with enhanced prompt and better error handling
|
||||
# Escape single quotes in the enhanced prompt
|
||||
escaped_prompt = enhanced_prompt.replace("'", "'\\''")
|
||||
claude_command = f"echo '{escaped_prompt}' | claude -p --dangerously-skip-permissions"
|
||||
|
||||
# Show the original user prompt in the command display (not the enhanced version)
|
||||
display_prompt = prompt[:100] + '...' if len(prompt) > 100 else prompt
|
||||
print(f"🚀 Running command: echo '{display_prompt}' | claude -p --dangerously-skip-permissions")
|
||||
|
||||
# Show loading message with visual separation
|
||||
print("")
|
||||
print("=" * 60)
|
||||
print("☁️ EXECUTING CLAUDE CODE IN SECURE CLOUD SANDBOX")
|
||||
print("=" * 60)
|
||||
print("")
|
||||
print(" ⏳ Starting execution...")
|
||||
print(" 🔒 Isolated E2B environment active")
|
||||
print(" 📡 Streaming real-time output below:")
|
||||
print("")
|
||||
print("-" * 60)
|
||||
print("📝 LIVE OUTPUT:")
|
||||
print("-" * 60)
|
||||
|
||||
# Collect output for later use
|
||||
stdout_buffer = []
|
||||
stderr_buffer = []
|
||||
|
||||
# Track if we've received any output and last activity time
|
||||
has_output = [False] # Use list to allow modification in nested function
|
||||
last_activity = [time.time()]
|
||||
execution_complete = [False]
|
||||
|
||||
# Progress indicator thread
|
||||
def show_progress():
|
||||
"""Show periodic progress updates if no output for a while"""
|
||||
progress_messages = [
|
||||
"⏳ Still processing...",
|
||||
"🔄 Claude Code is working on your request...",
|
||||
"⚙️ Analyzing requirements...",
|
||||
"🛠️ Building solution...",
|
||||
"📝 Generating code...",
|
||||
"🔍 Reviewing implementation..."
|
||||
]
|
||||
message_index = 0
|
||||
|
||||
while not execution_complete[0]:
|
||||
time.sleep(5) # Check every 5 seconds
|
||||
|
||||
# If no activity for 10 seconds and no output yet
|
||||
if not has_output[0] and (time.time() - last_activity[0]) > 10:
|
||||
print(f"\n {progress_messages[message_index % len(progress_messages)]}")
|
||||
message_index += 1
|
||||
last_activity[0] = time.time()
|
||||
|
||||
# Start progress thread
|
||||
progress_thread = threading.Thread(target=show_progress, daemon=True)
|
||||
progress_thread.start()
|
||||
|
||||
# Define callbacks for streaming output
|
||||
def on_stdout(data):
|
||||
"""Handle stdout output in real-time"""
|
||||
if data:
|
||||
# Mark that we've received output and update activity time
|
||||
if not has_output[0]:
|
||||
has_output[0] = True
|
||||
print("\n🎯 Claude Code started responding:\n")
|
||||
|
||||
last_activity[0] = time.time()
|
||||
|
||||
# Print the data as it comes
|
||||
print(data, end='', flush=True)
|
||||
stdout_buffer.append(data)
|
||||
|
||||
def on_stderr(data):
|
||||
"""Handle stderr output in real-time"""
|
||||
if data:
|
||||
# Mark that we've received output and update activity time
|
||||
if not has_output[0]:
|
||||
has_output[0] = True
|
||||
print("\n🎯 Claude Code started responding:\n")
|
||||
|
||||
last_activity[0] = time.time()
|
||||
|
||||
# Print stderr with warning prefix
|
||||
if data.strip():
|
||||
print(f"⚠️ {data}", end='', flush=True)
|
||||
stderr_buffer.append(data)
|
||||
|
||||
# Execute with streaming output and extended timeout
|
||||
try:
|
||||
result = sbx.commands.run(
|
||||
claude_command,
|
||||
timeout=600, # 10 minutes timeout for complex operations
|
||||
on_stdout=on_stdout,
|
||||
on_stderr=on_stderr
|
||||
)
|
||||
finally:
|
||||
# Mark execution as complete to stop progress thread
|
||||
execution_complete[0] = True
|
||||
|
||||
# Join collected output
|
||||
full_stdout = ''.join(stdout_buffer)
|
||||
full_stderr = ''.join(stderr_buffer)
|
||||
|
||||
# Print execution summary
|
||||
print("")
|
||||
print("-" * 60)
|
||||
print(f"🔍 Command exit code: {result.exit_code}")
|
||||
|
||||
# Since we already streamed the output, just show summary
|
||||
if full_stdout:
|
||||
print(f"📤 Total stdout: {len(full_stdout)} characters")
|
||||
if full_stderr:
|
||||
print(f"⚠️ Total stderr: {len(full_stderr)} characters")
|
||||
|
||||
# List generated files
|
||||
print("=" * 60)
|
||||
print("📁 GENERATED FILES:")
|
||||
print("=" * 60)
|
||||
|
||||
# More comprehensive file search - include jsx, tsx, and other common extensions
|
||||
files_result = sbx.commands.run("""find . -type f \\( \
|
||||
-name '*.html' -o -name '*.js' -o -name '*.jsx' -o \
|
||||
-name '*.ts' -o -name '*.tsx' -o \
|
||||
-name '*.css' -o -name '*.scss' -o -name '*.sass' -o \
|
||||
-name '*.py' -o -name '*.json' -o -name '*.md' -o \
|
||||
-name '*.vue' -o -name '*.svelte' -o \
|
||||
-name '*.yaml' -o -name '*.yml' -o \
|
||||
-name '*.xml' -o -name '*.txt' -o \
|
||||
-name '*.env' -o -name '*.env.example' -o \
|
||||
-name '*.sh' -o -name '*.bash' -o \
|
||||
-name '*.go' -o -name '*.rs' -o -name '*.java' -o \
|
||||
-name '*.php' -o -name '*.rb' -o -name '*.swift' \
|
||||
\\) ! -path '*/.npm/*' ! -path '*/.claude/*' ! -path '*/node_modules/*' | head -50""")
|
||||
if files_result.stdout.strip():
|
||||
print(files_result.stdout)
|
||||
|
||||
# Download important files to local machine
|
||||
print("\n" + "=" * 60)
|
||||
print("💾 DOWNLOADING FILES TO LOCAL MACHINE:")
|
||||
print("=" * 60)
|
||||
|
||||
# Create project directory with sandbox ID in current working directory
|
||||
project_dir = f"sandbox-{sbx.sandbox_id[:8]}" # Use first 8 chars of sandbox ID
|
||||
local_output_dir = os.path.join(os.getcwd(), project_dir) # Use current working directory
|
||||
|
||||
# Ensure the project directory exists
|
||||
os.makedirs(local_output_dir, exist_ok=True)
|
||||
|
||||
print(f"📂 Downloading files to project directory: {local_output_dir}")
|
||||
print(f"📍 Current working directory: {os.getcwd()}")
|
||||
|
||||
files_to_download = files_result.stdout.strip().split('\n')
|
||||
for file_path in files_to_download:
|
||||
file_path = file_path.strip()
|
||||
if file_path and not file_path.startswith('./.npm/'): # Skip npm cache files
|
||||
try:
|
||||
# Read file content from sandbox
|
||||
file_content = sbx.commands.run(f"cat '{file_path}'", timeout=30)
|
||||
if file_content.exit_code == 0:
|
||||
# Preserve directory structure by removing leading ./
|
||||
relative_path = file_path.lstrip('./')
|
||||
local_file = os.path.join(local_output_dir, relative_path)
|
||||
|
||||
# Create directory structure if needed
|
||||
os.makedirs(os.path.dirname(local_file), exist_ok=True)
|
||||
|
||||
# Write file locally
|
||||
with open(local_file, 'w', encoding='utf-8') as f:
|
||||
f.write(file_content.stdout)
|
||||
|
||||
print(f"✅ Downloaded: {file_path} → {local_file}")
|
||||
else:
|
||||
print(f"❌ Failed to read: {file_path}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error downloading {file_path}: {e}")
|
||||
|
||||
print(f"\n📁 All files downloaded to: {os.path.abspath(local_output_dir)}")
|
||||
|
||||
else:
|
||||
print("No common files generated")
|
||||
|
||||
print("=" * 60)
|
||||
print(f"✅ Execution completed successfully")
|
||||
print(f"🗂️ Sandbox ID: {sbx.sandbox_id}")
|
||||
print("💡 Note: Sandbox will be automatically destroyed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error executing Claude Code in sandbox: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
# Cleanup sandbox
|
||||
try:
|
||||
if 'sbx' in locals():
|
||||
sbx.kill()
|
||||
print("🧹 Sandbox cleaned up")
|
||||
except Exception as cleanup_error:
|
||||
print(f"⚠️ Cleanup warning: {cleanup_error}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python3.11
|
||||
"""
|
||||
E2B Sandbox Real-time Monitor
|
||||
Provides real-time monitoring and debugging of E2B sandbox operations
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def log_with_timestamp(message, level="INFO"):
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
print(f"[{timestamp}] {level}: {message}")
|
||||
|
||||
def monitor_sandbox_execution(sbx, command, timeout=600):
|
||||
"""
|
||||
Monitor sandbox command execution with real-time feedback
|
||||
"""
|
||||
log_with_timestamp(f"Starting monitored execution: {command[:100]}...")
|
||||
|
||||
# Start command execution
|
||||
start_time = time.time()
|
||||
result = None
|
||||
|
||||
try:
|
||||
# Execute command with monitoring
|
||||
log_with_timestamp("Command started, monitoring execution...")
|
||||
|
||||
# For real implementation, you would run the command and monitor
|
||||
# This is a template for when you have valid API keys
|
||||
result = sbx.commands.run(command, timeout=timeout)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
log_with_timestamp(f"Command completed in {elapsed:.2f} seconds")
|
||||
|
||||
# Log execution results
|
||||
log_with_timestamp(f"Exit code: {result.exit_code}")
|
||||
if result.stdout:
|
||||
log_with_timestamp(f"STDOUT length: {len(result.stdout)} characters")
|
||||
if len(result.stdout) < 500:
|
||||
log_with_timestamp(f"STDOUT preview: {result.stdout[:200]}...")
|
||||
if result.stderr:
|
||||
log_with_timestamp(f"STDERR length: {len(result.stderr)} characters", "WARNING")
|
||||
log_with_timestamp(f"STDERR: {result.stderr}", "ERROR")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
elapsed = time.time() - start_time
|
||||
log_with_timestamp(f"Command failed after {elapsed:.2f} seconds: {e}", "ERROR")
|
||||
raise e
|
||||
|
||||
def monitor_file_system(sbx, description="Monitoring file system"):
|
||||
"""
|
||||
Monitor sandbox file system state
|
||||
"""
|
||||
log_with_timestamp(f"📁 {description}")
|
||||
|
||||
try:
|
||||
# Check current directory
|
||||
pwd_result = sbx.commands.run("pwd", timeout=10)
|
||||
log_with_timestamp(f"Current directory: {pwd_result.stdout.strip()}")
|
||||
|
||||
# List files
|
||||
ls_result = sbx.commands.run("ls -la", timeout=10)
|
||||
log_with_timestamp("Directory contents:")
|
||||
for line in ls_result.stdout.split('\n')[:10]: # Show first 10 files
|
||||
if line.strip():
|
||||
log_with_timestamp(f" {line}")
|
||||
|
||||
# Check disk usage
|
||||
du_result = sbx.commands.run("du -sh .", timeout=10)
|
||||
log_with_timestamp(f"Directory size: {du_result.stdout.strip()}")
|
||||
|
||||
# Check for specific file types
|
||||
find_result = sbx.commands.run("find . -type f -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.json' | head -10", timeout=15)
|
||||
if find_result.stdout.strip():
|
||||
log_with_timestamp("Generated files found:")
|
||||
for file in find_result.stdout.split('\n')[:10]:
|
||||
if file.strip():
|
||||
log_with_timestamp(f" 📄 {file.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
log_with_timestamp(f"File system monitoring error: {e}", "ERROR")
|
||||
|
||||
def monitor_system_resources(sbx):
|
||||
"""
|
||||
Monitor sandbox system resources
|
||||
"""
|
||||
log_with_timestamp("🔍 System resources check")
|
||||
|
||||
try:
|
||||
# Memory usage
|
||||
mem_result = sbx.commands.run("free -h", timeout=10)
|
||||
log_with_timestamp("Memory usage:")
|
||||
for line in mem_result.stdout.split('\n')[:3]:
|
||||
if line.strip():
|
||||
log_with_timestamp(f" {line}")
|
||||
|
||||
# CPU load
|
||||
load_result = sbx.commands.run("uptime", timeout=10)
|
||||
log_with_timestamp(f"System load: {load_result.stdout.strip()}")
|
||||
|
||||
# Process list (top 5 processes)
|
||||
ps_result = sbx.commands.run("ps aux --sort=-%cpu | head -6", timeout=10)
|
||||
log_with_timestamp("Top processes:")
|
||||
lines = ps_result.stdout.split('\n')
|
||||
for line in lines[:6]: # Header + top 5
|
||||
if line.strip():
|
||||
log_with_timestamp(f" {line}")
|
||||
|
||||
except Exception as e:
|
||||
log_with_timestamp(f"System monitoring error: {e}", "ERROR")
|
||||
|
||||
def enhanced_sandbox_execution(prompt, components_to_install="", e2b_api_key=None, anthropic_api_key=None):
|
||||
"""
|
||||
Enhanced sandbox execution with full monitoring
|
||||
This would be called instead of the basic launcher when you have valid API keys
|
||||
"""
|
||||
|
||||
log_with_timestamp("🚀 Starting enhanced E2B sandbox with monitoring")
|
||||
log_with_timestamp("=" * 60)
|
||||
|
||||
try:
|
||||
from e2b import Sandbox
|
||||
log_with_timestamp("✅ E2B SDK imported successfully")
|
||||
except ImportError as e:
|
||||
log_with_timestamp(f"❌ E2B import failed: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
if not e2b_api_key or not anthropic_api_key:
|
||||
log_with_timestamp("❌ Missing API keys", "ERROR")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Create sandbox with monitoring
|
||||
log_with_timestamp("Creating E2B sandbox...")
|
||||
sbx = Sandbox.create(
|
||||
template="anthropic-claude-code",
|
||||
api_key=e2b_api_key,
|
||||
envs={'ANTHROPIC_API_KEY': anthropic_api_key},
|
||||
timeout=600
|
||||
)
|
||||
|
||||
log_with_timestamp(f"✅ Sandbox created: {sbx.sandbox_id}")
|
||||
sbx.set_timeout(900)
|
||||
log_with_timestamp("⏱️ Sandbox timeout extended to 15 minutes")
|
||||
|
||||
# Initial system check
|
||||
monitor_system_resources(sbx)
|
||||
monitor_file_system(sbx, "Initial file system state")
|
||||
|
||||
# Install components if specified
|
||||
if components_to_install:
|
||||
log_with_timestamp(f"📦 Installing components: {components_to_install}")
|
||||
install_command = f"npx claude-code-templates@latest {components_to_install}"
|
||||
monitor_sandbox_execution(sbx, install_command, timeout=120)
|
||||
monitor_file_system(sbx, "After components installation")
|
||||
|
||||
# Verify Claude Code installation
|
||||
log_with_timestamp("🔍 Verifying Claude Code installation")
|
||||
claude_check = monitor_sandbox_execution(sbx, "which claude", timeout=10)
|
||||
if claude_check.exit_code == 0:
|
||||
version_check = monitor_sandbox_execution(sbx, "claude --version", timeout=10)
|
||||
log_with_timestamp(f"Claude version: {version_check.stdout.strip()}")
|
||||
else:
|
||||
log_with_timestamp("❌ Claude Code not found in PATH", "ERROR")
|
||||
|
||||
# Execute main prompt with monitoring
|
||||
log_with_timestamp("🤖 Executing Claude Code with monitoring")
|
||||
claude_command = f"echo '{prompt}' | claude -p --dangerously-skip-permissions"
|
||||
|
||||
result = monitor_sandbox_execution(sbx, claude_command, timeout=600)
|
||||
|
||||
# Final file system check
|
||||
monitor_file_system(sbx, "Final file system state")
|
||||
|
||||
# Display results
|
||||
log_with_timestamp("=" * 60)
|
||||
log_with_timestamp("🎯 CLAUDE CODE RESULTS")
|
||||
log_with_timestamp("=" * 60)
|
||||
|
||||
if result.stdout:
|
||||
print(result.stdout)
|
||||
|
||||
if result.stderr:
|
||||
log_with_timestamp("⚠️ STDERR OUTPUT", "WARNING")
|
||||
print(result.stderr)
|
||||
|
||||
log_with_timestamp("✅ Execution completed successfully")
|
||||
|
||||
# Cleanup
|
||||
sbx.kill()
|
||||
log_with_timestamp("🧹 Sandbox cleaned up")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
log_with_timestamp(f"❌ Execution failed: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("E2B Sandbox Monitor")
|
||||
print("Usage: python e2b-monitor.py <prompt> [components] [e2b_key] [anthropic_key]")
|
||||
print()
|
||||
print("This tool provides enhanced monitoring and debugging for E2B sandbox operations.")
|
||||
print("Use this when you have valid API keys and want detailed insight into sandbox execution.")
|
||||
sys.exit(1)
|
||||
|
||||
prompt = sys.argv[1]
|
||||
components = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
e2b_key = sys.argv[3] if len(sys.argv) > 3 else os.getenv('E2B_API_KEY')
|
||||
anthropic_key = sys.argv[4] if len(sys.argv) > 4 else os.getenv('ANTHROPIC_API_KEY')
|
||||
|
||||
log_with_timestamp("🎬 E2B Sandbox Monitor Starting")
|
||||
log_with_timestamp("=" * 60)
|
||||
|
||||
success = enhanced_sandbox_execution(prompt, components, e2b_key, anthropic_key)
|
||||
|
||||
if success:
|
||||
log_with_timestamp("🎉 Monitoring session completed successfully")
|
||||
else:
|
||||
log_with_timestamp("❌ Monitoring session failed")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
e2b>=2.0.2
|
||||
Reference in New Issue
Block a user