claude-code-templates 1.26.4 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/sandbox/docker/Dockerfile +38 -0
- package/components/sandbox/docker/README.md +453 -0
- package/components/sandbox/docker/docker-launcher.js +184 -0
- package/components/sandbox/docker/execute.js +251 -0
- package/components/sandbox/docker/package.json +26 -0
- package/package.json +2 -1
- package/src/index.js +223 -13
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
FROM node:22-alpine
|
|
3
|
+
|
|
4
|
+
# Install runtime dependencies
|
|
5
|
+
RUN apk --no-cache add \
|
|
6
|
+
git \
|
|
7
|
+
bash \
|
|
8
|
+
python3 \
|
|
9
|
+
py3-pip \
|
|
10
|
+
curl \
|
|
11
|
+
&& npm install -g @anthropic-ai/claude-agent-sdk
|
|
12
|
+
|
|
13
|
+
# Create non-root user for security
|
|
14
|
+
RUN adduser -u 10001 -D -s /bin/bash sandboxuser
|
|
15
|
+
|
|
16
|
+
# Set working directory
|
|
17
|
+
WORKDIR /app
|
|
18
|
+
|
|
19
|
+
# Create output directory
|
|
20
|
+
RUN mkdir -p /output && chown sandboxuser:sandboxuser /output
|
|
21
|
+
|
|
22
|
+
# Copy execution script
|
|
23
|
+
COPY execute.js /app/execute.js
|
|
24
|
+
COPY package.json /app/package.json
|
|
25
|
+
|
|
26
|
+
# Install dependencies
|
|
27
|
+
RUN npm install --production && \
|
|
28
|
+
chown -R sandboxuser:sandboxuser /app
|
|
29
|
+
|
|
30
|
+
# Switch to non-root user
|
|
31
|
+
USER sandboxuser
|
|
32
|
+
|
|
33
|
+
# Set environment
|
|
34
|
+
ENV HOME=/home/sandboxuser
|
|
35
|
+
ENV NODE_ENV=production
|
|
36
|
+
|
|
37
|
+
# Default command (overridden by launcher)
|
|
38
|
+
CMD ["node", "/app/execute.js"]
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# Docker Claude Code Sandbox
|
|
2
|
+
|
|
3
|
+
Execute Claude Code in isolated Docker containers with AI-powered code generation using the Claude Agent SDK.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Install Docker
|
|
8
|
+
|
|
9
|
+
Ensure Docker is installed and running on your system:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Check Docker installation
|
|
13
|
+
docker --version
|
|
14
|
+
|
|
15
|
+
# Verify Docker daemon is running
|
|
16
|
+
docker ps
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If Docker is not installed, visit: https://docs.docker.com/get-docker/
|
|
20
|
+
|
|
21
|
+
### 2. Configure API Key
|
|
22
|
+
|
|
23
|
+
Set your Anthropic API key:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Set as environment variable
|
|
27
|
+
export ANTHROPIC_API_KEY=sk-ant-your-api-key-here
|
|
28
|
+
|
|
29
|
+
# Or pass directly when using the CLI
|
|
30
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
31
|
+
--agent development/frontend-developer \
|
|
32
|
+
--prompt "Create a React component" \
|
|
33
|
+
--anthropic-api-key sk-ant-your-key
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Run Your First Sandbox
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Basic execution
|
|
40
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
41
|
+
--prompt "Write a function to calculate factorial"
|
|
42
|
+
|
|
43
|
+
# With specific agent
|
|
44
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
45
|
+
--agent development/python-developer \
|
|
46
|
+
--prompt "Create a data validation script"
|
|
47
|
+
|
|
48
|
+
# With multiple components
|
|
49
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
50
|
+
--agent development/fullstack-developer \
|
|
51
|
+
--command development/setup-testing \
|
|
52
|
+
--prompt "Set up a complete testing environment"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
This sandbox combines two powerful technologies:
|
|
58
|
+
|
|
59
|
+
1. **Claude Agent SDK** - Provides programmatic access to Claude Code
|
|
60
|
+
2. **Docker** - Provides isolated container execution
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
User Prompt → Docker Launcher → Container Build → Execute Script → Claude Agent SDK → Output Files
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Components
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
docker/
|
|
70
|
+
├── docker-launcher.js # Node.js launcher that orchestrates Docker
|
|
71
|
+
├── Dockerfile # Container definition with Claude Agent SDK
|
|
72
|
+
├── execute.js # Script that runs inside container
|
|
73
|
+
├── package.json # Dependencies (Claude Agent SDK)
|
|
74
|
+
└── README.md # This file
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
### 1. Launcher Phase (docker-launcher.js)
|
|
80
|
+
- Checks Docker installation and daemon status
|
|
81
|
+
- Builds container image if it doesn't exist
|
|
82
|
+
- Prepares environment variables and volume mounts
|
|
83
|
+
- Launches container with user prompt
|
|
84
|
+
|
|
85
|
+
### 2. Container Phase (execute.js)
|
|
86
|
+
- Installs requested components (agents, commands, MCPs, etc.)
|
|
87
|
+
- Executes Claude Agent SDK with the user's prompt
|
|
88
|
+
- Auto-allows all tool uses (no permission prompts)
|
|
89
|
+
- Captures output and generated files
|
|
90
|
+
- Copies results to mounted output directory
|
|
91
|
+
|
|
92
|
+
### 3. Output Phase
|
|
93
|
+
- Generated files are saved to `output/` directory
|
|
94
|
+
- Files preserve directory structure
|
|
95
|
+
- Accessible on host machine for inspection
|
|
96
|
+
|
|
97
|
+
## Usage Examples
|
|
98
|
+
|
|
99
|
+
### Simple Code Generation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
103
|
+
--prompt "Create a REST API server with Express.js"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### With Specific Agent
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
110
|
+
--agent security/security-auditor \
|
|
111
|
+
--prompt "Audit this codebase for security vulnerabilities"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Multiple Components
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
118
|
+
--agent development/frontend-developer \
|
|
119
|
+
--command testing/setup-testing \
|
|
120
|
+
--setting performance/performance-optimization \
|
|
121
|
+
--prompt "Create a React app with testing setup"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Development Workflow
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# 1. Generate initial code
|
|
128
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
129
|
+
--agent development/fullstack-developer \
|
|
130
|
+
--prompt "Create a blog API with authentication"
|
|
131
|
+
|
|
132
|
+
# 2. Check output
|
|
133
|
+
ls -la output/
|
|
134
|
+
|
|
135
|
+
# 3. Iterate on generated code
|
|
136
|
+
npx claude-code-templates@latest --sandbox docker \
|
|
137
|
+
--prompt "Add pagination to the blog API"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
### Environment Variables
|
|
143
|
+
|
|
144
|
+
**Required:**
|
|
145
|
+
- `ANTHROPIC_API_KEY` - Your Anthropic API key
|
|
146
|
+
|
|
147
|
+
**Optional:**
|
|
148
|
+
- `DOCKER_BUILDKIT=1` - Enable BuildKit for faster builds
|
|
149
|
+
|
|
150
|
+
### Docker Image Details
|
|
151
|
+
|
|
152
|
+
The Docker image (`claude-sandbox`) includes:
|
|
153
|
+
|
|
154
|
+
- **Base**: Node.js 22 Alpine Linux (minimal, secure)
|
|
155
|
+
- **Runtime**: Git, Bash, Python3, Pip, Curl
|
|
156
|
+
- **Claude SDK**: `@anthropic-ai/claude-agent-sdk` installed globally
|
|
157
|
+
- **Security**: Runs as non-root user (UID 10001)
|
|
158
|
+
- **Working Directory**: `/app`
|
|
159
|
+
- **Output Directory**: `/output` (mounted as volume)
|
|
160
|
+
|
|
161
|
+
### Build Configuration
|
|
162
|
+
|
|
163
|
+
Edit `Dockerfile` to customize:
|
|
164
|
+
|
|
165
|
+
```dockerfile
|
|
166
|
+
# Add additional system dependencies
|
|
167
|
+
RUN apk --no-cache add postgresql-client redis
|
|
168
|
+
|
|
169
|
+
# Install additional global npm packages
|
|
170
|
+
RUN npm install -g typescript tsx
|
|
171
|
+
|
|
172
|
+
# Set custom environment variables
|
|
173
|
+
ENV CUSTOM_VAR=value
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Command Reference
|
|
177
|
+
|
|
178
|
+
### Build Image Manually
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
cd .claude/sandbox/docker
|
|
182
|
+
docker build -t claude-sandbox .
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Run Container Directly
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
docker run --rm \
|
|
189
|
+
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
|
|
190
|
+
-v $(pwd)/output:/output \
|
|
191
|
+
claude-sandbox \
|
|
192
|
+
node /app/execute.js "Your prompt here" ""
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Clean Up
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Remove built image
|
|
199
|
+
docker rmi claude-sandbox
|
|
200
|
+
|
|
201
|
+
# Remove all stopped containers
|
|
202
|
+
docker container prune
|
|
203
|
+
|
|
204
|
+
# Remove dangling images
|
|
205
|
+
docker image prune
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
### Docker Not Found
|
|
211
|
+
|
|
212
|
+
**Error:** `Docker is not installed`
|
|
213
|
+
|
|
214
|
+
**Solution:**
|
|
215
|
+
```bash
|
|
216
|
+
# Install Docker from official site
|
|
217
|
+
# macOS: https://docs.docker.com/desktop/install/mac-install/
|
|
218
|
+
# Linux: https://docs.docker.com/engine/install/
|
|
219
|
+
# Windows: https://docs.docker.com/desktop/install/windows-install/
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Docker Daemon Not Running
|
|
223
|
+
|
|
224
|
+
**Error:** `Docker daemon is not running`
|
|
225
|
+
|
|
226
|
+
**Solution:**
|
|
227
|
+
```bash
|
|
228
|
+
# macOS/Windows: Start Docker Desktop application
|
|
229
|
+
# Linux: sudo systemctl start docker
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### API Key Not Set
|
|
233
|
+
|
|
234
|
+
**Error:** `ANTHROPIC_API_KEY environment variable is required`
|
|
235
|
+
|
|
236
|
+
**Solution:**
|
|
237
|
+
```bash
|
|
238
|
+
export ANTHROPIC_API_KEY=sk-ant-your-key-here
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Build Failures
|
|
242
|
+
|
|
243
|
+
**Error:** Failed to build Docker image
|
|
244
|
+
|
|
245
|
+
**Solution:**
|
|
246
|
+
```bash
|
|
247
|
+
# Check Docker logs
|
|
248
|
+
docker logs <container-id>
|
|
249
|
+
|
|
250
|
+
# Rebuild from scratch
|
|
251
|
+
docker build --no-cache -t claude-sandbox .
|
|
252
|
+
|
|
253
|
+
# Check disk space
|
|
254
|
+
docker system df
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Permission Issues
|
|
258
|
+
|
|
259
|
+
**Error:** Permission denied when accessing output files
|
|
260
|
+
|
|
261
|
+
**Solution:**
|
|
262
|
+
```bash
|
|
263
|
+
# Check output directory permissions
|
|
264
|
+
ls -la output/
|
|
265
|
+
|
|
266
|
+
# Fix permissions (if needed)
|
|
267
|
+
sudo chown -R $USER:$USER output/
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Container Execution Failures
|
|
271
|
+
|
|
272
|
+
**Error:** Container failed with code 1
|
|
273
|
+
|
|
274
|
+
**Solution:**
|
|
275
|
+
```bash
|
|
276
|
+
# Run container interactively for debugging
|
|
277
|
+
docker run -it --rm \
|
|
278
|
+
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
|
|
279
|
+
claude-sandbox \
|
|
280
|
+
/bin/bash
|
|
281
|
+
|
|
282
|
+
# Check container logs
|
|
283
|
+
docker logs <container-id>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Performance Tips
|
|
287
|
+
|
|
288
|
+
1. **Image Caching**: First build takes longer, subsequent builds are fast
|
|
289
|
+
2. **Volume Mounts**: Use volumes instead of COPY for faster iteration
|
|
290
|
+
3. **Layer Optimization**: Group RUN commands to reduce image layers
|
|
291
|
+
4. **BuildKit**: Enable for parallel builds (`DOCKER_BUILDKIT=1`)
|
|
292
|
+
5. **Prune Regularly**: Clean up unused images and containers
|
|
293
|
+
|
|
294
|
+
## Security
|
|
295
|
+
|
|
296
|
+
- **Isolation**: Containers are isolated from host system
|
|
297
|
+
- **Non-root User**: Execution runs as `sandboxuser` (UID 10001)
|
|
298
|
+
- **No Network**: Container has no internet access (except during build)
|
|
299
|
+
- **Read-only**: Host filesystem is mounted read-only
|
|
300
|
+
- **Resource Limits**: Docker enforces CPU and memory limits
|
|
301
|
+
- **Secret Management**: API keys are passed as environment variables (not stored in image)
|
|
302
|
+
|
|
303
|
+
## Cost Estimation
|
|
304
|
+
|
|
305
|
+
**Docker:**
|
|
306
|
+
- Free and open-source
|
|
307
|
+
- No cloud costs (runs locally)
|
|
308
|
+
- Resource usage: ~500MB disk space, ~512MB RAM during execution
|
|
309
|
+
|
|
310
|
+
**Anthropic API:**
|
|
311
|
+
- Claude Sonnet 4.5: ~$3 per million input tokens
|
|
312
|
+
- Average request: ~200 tokens = $0.0006 per request
|
|
313
|
+
|
|
314
|
+
**Example costs for 100 executions:**
|
|
315
|
+
- Docker: $0 (local execution)
|
|
316
|
+
- Anthropic: ~$0.06 (avg 200 tokens/request)
|
|
317
|
+
- **Total: ~$0.06**
|
|
318
|
+
|
|
319
|
+
## Comparison with Other Providers
|
|
320
|
+
|
|
321
|
+
| Feature | Docker | E2B | Cloudflare |
|
|
322
|
+
|---------|--------|-----|------------|
|
|
323
|
+
| Execution Location | 🏠 Local | ☁️ Cloud | 🌍 Edge |
|
|
324
|
+
| Setup Complexity | Medium | Easy | Easy |
|
|
325
|
+
| Internet Required | Setup only | Yes | Yes |
|
|
326
|
+
| Cost | Free | Paid | Paid |
|
|
327
|
+
| Privacy | Full control | Third-party | Third-party |
|
|
328
|
+
| Offline Support | Yes | No | No |
|
|
329
|
+
| Best For | Local dev, privacy, offline | Full stack projects | Serverless, global APIs |
|
|
330
|
+
|
|
331
|
+
## Development
|
|
332
|
+
|
|
333
|
+
### Project Structure
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
docker/
|
|
337
|
+
├── docker-launcher.js # Orchestrates container lifecycle
|
|
338
|
+
│ ├── checkDockerInstalled()
|
|
339
|
+
│ ├── checkDockerRunning()
|
|
340
|
+
│ ├── buildDockerImage()
|
|
341
|
+
│ └── runDockerContainer()
|
|
342
|
+
├── Dockerfile # Container definition
|
|
343
|
+
│ ├── Base image (Node 22 Alpine)
|
|
344
|
+
│ ├── System dependencies
|
|
345
|
+
│ ├── Claude Agent SDK
|
|
346
|
+
│ └── Security (non-root user)
|
|
347
|
+
├── execute.js # Execution script (runs in container)
|
|
348
|
+
│ ├── installComponents()
|
|
349
|
+
│ ├── executeQuery()
|
|
350
|
+
│ └── copyGeneratedFiles()
|
|
351
|
+
├── package.json # NPM dependencies
|
|
352
|
+
└── README.md # Documentation
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Scripts
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Build image
|
|
359
|
+
npm run build
|
|
360
|
+
|
|
361
|
+
# Clean image
|
|
362
|
+
npm run clean
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Extending the Image
|
|
366
|
+
|
|
367
|
+
Add custom tools to the Dockerfile:
|
|
368
|
+
|
|
369
|
+
```dockerfile
|
|
370
|
+
# Install Python packages
|
|
371
|
+
RUN pip install --no-cache-dir pandas numpy matplotlib
|
|
372
|
+
|
|
373
|
+
# Install Node.js packages globally
|
|
374
|
+
RUN npm install -g typescript eslint prettier
|
|
375
|
+
|
|
376
|
+
# Add custom scripts
|
|
377
|
+
COPY scripts/ /app/scripts/
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Advanced Usage
|
|
381
|
+
|
|
382
|
+
### Custom Dockerfile
|
|
383
|
+
|
|
384
|
+
Create a custom Dockerfile for specialized environments:
|
|
385
|
+
|
|
386
|
+
```dockerfile
|
|
387
|
+
FROM node:22-alpine
|
|
388
|
+
|
|
389
|
+
# Install database clients
|
|
390
|
+
RUN apk add --no-cache postgresql-client mysql-client
|
|
391
|
+
|
|
392
|
+
# Install development tools
|
|
393
|
+
RUN apk add --no-cache vim nano tmux
|
|
394
|
+
|
|
395
|
+
# Install Claude Agent SDK
|
|
396
|
+
RUN npm install -g @anthropic-ai/claude-agent-sdk
|
|
397
|
+
|
|
398
|
+
# ... rest of configuration
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Multi-stage Builds
|
|
402
|
+
|
|
403
|
+
Optimize image size with multi-stage builds:
|
|
404
|
+
|
|
405
|
+
```dockerfile
|
|
406
|
+
# Build stage
|
|
407
|
+
FROM node:22-alpine AS builder
|
|
408
|
+
WORKDIR /build
|
|
409
|
+
COPY package*.json ./
|
|
410
|
+
RUN npm ci --only=production
|
|
411
|
+
|
|
412
|
+
# Runtime stage
|
|
413
|
+
FROM node:22-alpine
|
|
414
|
+
COPY --from=builder /build/node_modules ./node_modules
|
|
415
|
+
# ... rest of configuration
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Persistent Storage
|
|
419
|
+
|
|
420
|
+
Mount additional volumes for persistent data:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
docker run --rm \
|
|
424
|
+
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
|
|
425
|
+
-v $(pwd)/output:/output \
|
|
426
|
+
-v $(pwd)/cache:/cache \
|
|
427
|
+
claude-sandbox
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Resources
|
|
431
|
+
|
|
432
|
+
- [Docker Documentation](https://docs.docker.com/)
|
|
433
|
+
- [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk)
|
|
434
|
+
- [Anthropic API Documentation](https://docs.anthropic.com/)
|
|
435
|
+
- [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/)
|
|
436
|
+
- [Container Security](https://docs.docker.com/engine/security/)
|
|
437
|
+
|
|
438
|
+
## License
|
|
439
|
+
|
|
440
|
+
MIT License - See LICENSE file for details
|
|
441
|
+
|
|
442
|
+
## Support
|
|
443
|
+
|
|
444
|
+
For issues and questions:
|
|
445
|
+
1. Check Docker installation: `docker --version && docker ps`
|
|
446
|
+
2. Verify API key: `echo $ANTHROPIC_API_KEY`
|
|
447
|
+
3. Check container logs: `docker logs <container-id>`
|
|
448
|
+
4. Review output directory: `ls -la output/`
|
|
449
|
+
5. Open an issue on GitHub
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
Built with ❤️ using Docker, Node.js, and Claude Agent SDK
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Docker Sandbox Launcher
|
|
5
|
+
* Orchestrates Docker container execution for Claude Code
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn, execSync } from 'child_process';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
|
|
16
|
+
// Parse command line arguments
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const prompt = args[0] || 'Hello, Claude!';
|
|
19
|
+
const componentsToInstall = args[1] || '';
|
|
20
|
+
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
21
|
+
|
|
22
|
+
// Validate API key
|
|
23
|
+
if (!anthropicApiKey) {
|
|
24
|
+
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('🐳 Docker Sandbox Launcher');
|
|
29
|
+
console.log('═══════════════════════════════════════\n');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if Docker is installed
|
|
33
|
+
*/
|
|
34
|
+
function checkDockerInstalled() {
|
|
35
|
+
try {
|
|
36
|
+
execSync('docker --version', { stdio: 'pipe' });
|
|
37
|
+
return true;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('❌ Error: Docker is not installed');
|
|
40
|
+
console.error(' Please install Docker: https://docs.docker.com/get-docker/\n');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if Docker daemon is running
|
|
47
|
+
*/
|
|
48
|
+
function checkDockerRunning() {
|
|
49
|
+
try {
|
|
50
|
+
execSync('docker ps', { stdio: 'pipe' });
|
|
51
|
+
return true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('❌ Error: Docker daemon is not running');
|
|
54
|
+
console.error(' Please start Docker and try again\n');
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build Docker image if it doesn't exist
|
|
61
|
+
*/
|
|
62
|
+
async function buildDockerImage() {
|
|
63
|
+
console.log('🔨 Checking Docker image...');
|
|
64
|
+
|
|
65
|
+
// Check if image exists
|
|
66
|
+
try {
|
|
67
|
+
execSync('docker image inspect claude-sandbox', { stdio: 'pipe' });
|
|
68
|
+
console.log(' ✅ Image already exists\n');
|
|
69
|
+
return true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// Image doesn't exist, build it
|
|
72
|
+
console.log(' 📦 Building Docker image (this may take a few minutes)...\n');
|
|
73
|
+
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const build = spawn('docker', [
|
|
76
|
+
'build',
|
|
77
|
+
'-t', 'claude-sandbox',
|
|
78
|
+
'.'
|
|
79
|
+
], {
|
|
80
|
+
cwd: __dirname,
|
|
81
|
+
stdio: 'inherit'
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
build.on('close', (code) => {
|
|
85
|
+
if (code === 0) {
|
|
86
|
+
console.log('\n✅ Docker image built successfully\n');
|
|
87
|
+
resolve(true);
|
|
88
|
+
} else {
|
|
89
|
+
console.error('\n❌ Failed to build Docker image');
|
|
90
|
+
resolve(false);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
build.on('error', (error) => {
|
|
95
|
+
console.error('❌ Build error:', error.message);
|
|
96
|
+
resolve(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run Docker container
|
|
104
|
+
*/
|
|
105
|
+
async function runDockerContainer() {
|
|
106
|
+
console.log('🚀 Starting Docker container...\n');
|
|
107
|
+
|
|
108
|
+
// Create output directory
|
|
109
|
+
const outputDir = path.join(process.cwd(), 'output');
|
|
110
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
111
|
+
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
const dockerArgs = [
|
|
114
|
+
'run',
|
|
115
|
+
'--rm',
|
|
116
|
+
'-e', `ANTHROPIC_API_KEY=${anthropicApiKey}`,
|
|
117
|
+
'-v', `${outputDir}:/output`,
|
|
118
|
+
'claude-sandbox',
|
|
119
|
+
'node', '/app/execute.js',
|
|
120
|
+
prompt,
|
|
121
|
+
componentsToInstall
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const container = spawn('docker', dockerArgs, {
|
|
125
|
+
stdio: 'inherit'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
container.on('close', (code) => {
|
|
129
|
+
if (code === 0) {
|
|
130
|
+
console.log('\n✅ Docker container completed successfully');
|
|
131
|
+
|
|
132
|
+
// Show output directory
|
|
133
|
+
console.log(`\n📂 Output files saved to: ${outputDir}`);
|
|
134
|
+
resolve(true);
|
|
135
|
+
} else {
|
|
136
|
+
console.error('\n❌ Docker container failed with code:', code);
|
|
137
|
+
resolve(false);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
container.on('error', (error) => {
|
|
142
|
+
console.error('❌ Container error:', error.message);
|
|
143
|
+
resolve(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Main execution flow
|
|
150
|
+
*/
|
|
151
|
+
async function main() {
|
|
152
|
+
try {
|
|
153
|
+
// Step 1: Check Docker installation
|
|
154
|
+
if (!checkDockerInstalled()) {
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Step 2: Check Docker daemon
|
|
159
|
+
if (!checkDockerRunning()) {
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Step 3: Build Docker image
|
|
164
|
+
const buildSuccess = await buildDockerImage();
|
|
165
|
+
if (!buildSuccess) {
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Step 4: Run container
|
|
170
|
+
const runSuccess = await runDockerContainer();
|
|
171
|
+
if (!runSuccess) {
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('\n🎉 Docker sandbox execution completed!');
|
|
176
|
+
process.exit(0);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('❌ Fatal error:', error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Run main function
|
|
184
|
+
main();
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Docker Sandbox Executor
|
|
5
|
+
* Runs inside Docker container using Claude Agent SDK
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
|
+
|
|
13
|
+
// Parse command line arguments
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const prompt = args[0] || 'Hello, Claude!';
|
|
16
|
+
const componentsToInstall = args[1] || '';
|
|
17
|
+
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
18
|
+
|
|
19
|
+
// Validate API key
|
|
20
|
+
if (!anthropicApiKey) {
|
|
21
|
+
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('🐳 Docker Sandbox Executor');
|
|
26
|
+
console.log('═══════════════════════════════════════\n');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Install Claude Code components if specified
|
|
30
|
+
*/
|
|
31
|
+
async function installComponents() {
|
|
32
|
+
if (!componentsToInstall || componentsToInstall.trim() === '') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('📦 Installing components...');
|
|
37
|
+
console.log(` Components: ${componentsToInstall}\n`);
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const installCmd = `npx claude-code-templates@latest ${componentsToInstall} --yes`;
|
|
41
|
+
|
|
42
|
+
const child = spawn('sh', ['-c', installCmd], {
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
env: process.env
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on('close', (code) => {
|
|
48
|
+
if (code === 0) {
|
|
49
|
+
console.log('\n✅ Components installed successfully\n');
|
|
50
|
+
resolve(true);
|
|
51
|
+
} else {
|
|
52
|
+
console.log('\n⚠️ Component installation had warnings (continuing...)\n');
|
|
53
|
+
resolve(true); // Continue even if installation has warnings
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.on('error', (error) => {
|
|
58
|
+
console.error(`❌ Installation error: ${error.message}`);
|
|
59
|
+
resolve(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Execute Claude Code query using Agent SDK
|
|
66
|
+
*/
|
|
67
|
+
async function executeQuery() {
|
|
68
|
+
try {
|
|
69
|
+
console.log('🤖 Executing Claude Code...');
|
|
70
|
+
console.log(` Prompt: "${prompt.substring(0, 80)}${prompt.length > 80 ? '...' : ''}"\n`);
|
|
71
|
+
console.log('─'.repeat(60));
|
|
72
|
+
console.log('📝 CLAUDE OUTPUT:');
|
|
73
|
+
console.log('─'.repeat(60) + '\n');
|
|
74
|
+
|
|
75
|
+
// Enhance prompt with working directory context
|
|
76
|
+
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.`;
|
|
77
|
+
|
|
78
|
+
// query() returns an async generator - we need to iterate it
|
|
79
|
+
const generator = query({
|
|
80
|
+
prompt: enhancedPrompt,
|
|
81
|
+
options: {
|
|
82
|
+
apiKey: anthropicApiKey,
|
|
83
|
+
model: 'claude-sonnet-4-5',
|
|
84
|
+
permissionMode: 'bypassPermissions', // Auto-allow all tool uses
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let assistantResponses = [];
|
|
89
|
+
let messageCount = 0;
|
|
90
|
+
|
|
91
|
+
// Iterate through the async generator
|
|
92
|
+
for await (const message of generator) {
|
|
93
|
+
messageCount++;
|
|
94
|
+
|
|
95
|
+
if (message.type === 'assistant') {
|
|
96
|
+
// Extract text from assistant message content
|
|
97
|
+
if (message.message && message.message.content) {
|
|
98
|
+
const content = Array.isArray(message.message.content)
|
|
99
|
+
? message.message.content
|
|
100
|
+
: [message.message.content];
|
|
101
|
+
|
|
102
|
+
content.forEach(block => {
|
|
103
|
+
if (block.type === 'text') {
|
|
104
|
+
console.log(block.text);
|
|
105
|
+
assistantResponses.push(block.text);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} else if (message.type === 'result') {
|
|
110
|
+
// Show final result metadata
|
|
111
|
+
console.log('\n' + '─'.repeat(60));
|
|
112
|
+
console.log(`✅ Execution completed (${message.num_turns} turn${message.num_turns > 1 ? 's' : ''})`);
|
|
113
|
+
console.log(` Duration: ${message.duration_ms}ms`);
|
|
114
|
+
console.log(` Cost: $${message.total_cost_usd.toFixed(5)}`);
|
|
115
|
+
console.log('─'.repeat(60) + '\n');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Show response summary
|
|
120
|
+
const responseText = assistantResponses.join('\n');
|
|
121
|
+
if (responseText) {
|
|
122
|
+
console.log('📄 Response Summary:');
|
|
123
|
+
console.log(` ${messageCount} message(s) received`);
|
|
124
|
+
console.log(` ${assistantResponses.length} assistant response(s)`);
|
|
125
|
+
console.log(` ${responseText.length} characters generated`);
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('\n❌ Execution error:', error.message);
|
|
132
|
+
if (error.stack) {
|
|
133
|
+
console.error('\nStack trace:');
|
|
134
|
+
console.error(error.stack);
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Find and copy generated files to output directory
|
|
142
|
+
*/
|
|
143
|
+
async function copyGeneratedFiles() {
|
|
144
|
+
try {
|
|
145
|
+
console.log('📁 Searching for generated files...\n');
|
|
146
|
+
|
|
147
|
+
// Common file extensions to look for
|
|
148
|
+
const extensions = [
|
|
149
|
+
'js', 'jsx', 'ts', 'tsx',
|
|
150
|
+
'py', 'html', 'css', 'scss',
|
|
151
|
+
'json', 'md', 'yaml', 'yml',
|
|
152
|
+
'txt', 'sh', 'bash'
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Search for files in multiple directories
|
|
156
|
+
const { execSync } = await import('child_process');
|
|
157
|
+
|
|
158
|
+
const findPattern = extensions.map(ext => `-name "*.${ext}"`).join(' -o ');
|
|
159
|
+
|
|
160
|
+
// Search in /app and /tmp for generated files
|
|
161
|
+
const searchPaths = ['/app', '/tmp'];
|
|
162
|
+
let allFiles = [];
|
|
163
|
+
|
|
164
|
+
for (const searchPath of searchPaths) {
|
|
165
|
+
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`;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const output = execSync(findCmd, { encoding: 'utf8' });
|
|
169
|
+
const files = output.trim().split('\n').filter(f => f.trim());
|
|
170
|
+
allFiles = allFiles.concat(files);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// Continue to next search path
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (allFiles.length === 0) {
|
|
177
|
+
console.log('ℹ️ No generated files found\n');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(`📦 Found ${allFiles.length} file(s):\n`);
|
|
182
|
+
|
|
183
|
+
// Copy files to output directory preserving structure
|
|
184
|
+
let copiedCount = 0;
|
|
185
|
+
for (const file of allFiles) {
|
|
186
|
+
try {
|
|
187
|
+
// Determine relative path based on source directory
|
|
188
|
+
let relativePath;
|
|
189
|
+
if (file.startsWith('/app/')) {
|
|
190
|
+
relativePath = file.replace('/app/', '');
|
|
191
|
+
} else if (file.startsWith('/tmp/')) {
|
|
192
|
+
relativePath = file.replace('/tmp/', '');
|
|
193
|
+
} else {
|
|
194
|
+
relativePath = path.basename(file);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const outputPath = path.join('/output', relativePath);
|
|
198
|
+
|
|
199
|
+
// Create directory structure
|
|
200
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
201
|
+
|
|
202
|
+
// Copy file
|
|
203
|
+
await fs.copyFile(file, outputPath);
|
|
204
|
+
|
|
205
|
+
console.log(` ✅ ${relativePath}`);
|
|
206
|
+
copiedCount++;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.log(` ⚠️ Failed to copy: ${file}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (copiedCount > 0) {
|
|
213
|
+
console.log(`\n✅ Copied ${copiedCount} file(s) to output directory\n`);
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('❌ Error copying files:', error.message);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Main execution flow
|
|
222
|
+
*/
|
|
223
|
+
async function main() {
|
|
224
|
+
try {
|
|
225
|
+
// Step 1: Install components
|
|
226
|
+
const installSuccess = await installComponents();
|
|
227
|
+
if (!installSuccess) {
|
|
228
|
+
console.error('❌ Component installation failed');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Step 2: Execute Claude query
|
|
233
|
+
const executeSuccess = await executeQuery();
|
|
234
|
+
if (!executeSuccess) {
|
|
235
|
+
console.error('❌ Query execution failed');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Step 3: Copy generated files
|
|
240
|
+
await copyGeneratedFiles();
|
|
241
|
+
|
|
242
|
+
console.log('🎉 Docker sandbox execution completed successfully!');
|
|
243
|
+
process.exit(0);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('❌ Fatal error:', error.message);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Run main function
|
|
251
|
+
main();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-docker-sandbox",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Docker sandbox for Claude Code execution with Claude Agent SDK",
|
|
5
|
+
"main": "docker-launcher.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "docker build -t claude-sandbox .",
|
|
9
|
+
"clean": "docker rmi claude-sandbox || true"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@anthropic-ai/claude-agent-sdk": "^0.1.30"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"docker",
|
|
20
|
+
"sandbox",
|
|
21
|
+
"ai",
|
|
22
|
+
"agent"
|
|
23
|
+
],
|
|
24
|
+
"author": "Claude Code Templates",
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"src/",
|
|
95
95
|
"components/sandbox/e2b/",
|
|
96
96
|
"components/sandbox/cloudflare/",
|
|
97
|
+
"components/sandbox/docker/",
|
|
97
98
|
"README.md"
|
|
98
99
|
],
|
|
99
100
|
"devDependencies": {
|
package/src/index.js
CHANGED
|
@@ -2447,37 +2447,38 @@ async function launchClaudeCodeStudio(options, targetDir) {
|
|
|
2447
2447
|
}
|
|
2448
2448
|
|
|
2449
2449
|
async function executeSandbox(options, targetDir) {
|
|
2450
|
-
const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey } = options;
|
|
2450
|
+
const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey, yes } = options;
|
|
2451
2451
|
let { agent, prompt } = options;
|
|
2452
|
-
|
|
2452
|
+
|
|
2453
2453
|
// Validate sandbox provider
|
|
2454
|
-
if (sandbox !== 'e2b' && sandbox !== 'cloudflare') {
|
|
2454
|
+
if (sandbox !== 'e2b' && sandbox !== 'cloudflare' && sandbox !== 'docker') {
|
|
2455
2455
|
console.log(chalk.red('❌ Error: Invalid sandbox provider'));
|
|
2456
|
-
console.log(chalk.yellow('💡 Available providers: e2b, cloudflare'));
|
|
2456
|
+
console.log(chalk.yellow('💡 Available providers: e2b, cloudflare, docker'));
|
|
2457
2457
|
console.log(chalk.gray(' Example: --sandbox e2b --prompt "Create a web app"'));
|
|
2458
2458
|
console.log(chalk.gray(' Example: --sandbox cloudflare --prompt "Calculate factorial of 5"'));
|
|
2459
|
+
console.log(chalk.gray(' Example: --sandbox docker --prompt "Write a function"'));
|
|
2459
2460
|
return;
|
|
2460
2461
|
}
|
|
2461
|
-
|
|
2462
|
-
// Interactive agent selection if not provided
|
|
2463
|
-
if (!agent) {
|
|
2462
|
+
|
|
2463
|
+
// Interactive agent selection if not provided and --yes not used
|
|
2464
|
+
if (!agent && !yes) {
|
|
2464
2465
|
const inquirer = require('inquirer');
|
|
2465
|
-
|
|
2466
|
+
|
|
2466
2467
|
console.log(chalk.blue('\n🤖 Agent Selection'));
|
|
2467
2468
|
console.log(chalk.cyan('═══════════════════════════════════════'));
|
|
2468
2469
|
console.log(chalk.gray('Select one or more agents for your task (use SPACE to select, ENTER to confirm).\n'));
|
|
2469
|
-
|
|
2470
|
+
|
|
2470
2471
|
// Fetch available agents
|
|
2471
2472
|
console.log(chalk.gray('⏳ Fetching available agents...'));
|
|
2472
2473
|
const agents = await getAvailableAgentsFromGitHub();
|
|
2473
|
-
|
|
2474
|
+
|
|
2474
2475
|
// Format agents for selection with full path
|
|
2475
2476
|
const agentChoices = agents.map(a => ({
|
|
2476
2477
|
name: `${a.path} ${chalk.gray(`- ${a.category}`)}`,
|
|
2477
2478
|
value: a.path, // This already includes folder/agent-name format
|
|
2478
2479
|
short: a.path
|
|
2479
2480
|
}));
|
|
2480
|
-
|
|
2481
|
+
|
|
2481
2482
|
// First ask if they want to select agents
|
|
2482
2483
|
const { wantAgents } = await inquirer.prompt([{
|
|
2483
2484
|
type: 'confirm',
|
|
@@ -2485,7 +2486,7 @@ async function executeSandbox(options, targetDir) {
|
|
|
2485
2486
|
message: 'Do you want to select specific agents for this task?',
|
|
2486
2487
|
default: true
|
|
2487
2488
|
}]);
|
|
2488
|
-
|
|
2489
|
+
|
|
2489
2490
|
if (wantAgents) {
|
|
2490
2491
|
const { selectedAgents } = await inquirer.prompt([{
|
|
2491
2492
|
type: 'checkbox',
|
|
@@ -2495,7 +2496,7 @@ async function executeSandbox(options, targetDir) {
|
|
|
2495
2496
|
pageSize: 15
|
|
2496
2497
|
// Removed validation - allow empty selection
|
|
2497
2498
|
}]);
|
|
2498
|
-
|
|
2499
|
+
|
|
2499
2500
|
if (selectedAgents && selectedAgents.length > 0) {
|
|
2500
2501
|
// Join multiple agents with comma
|
|
2501
2502
|
agent = selectedAgents.join(',');
|
|
@@ -2507,6 +2508,9 @@ async function executeSandbox(options, targetDir) {
|
|
|
2507
2508
|
} else {
|
|
2508
2509
|
console.log(chalk.yellow('⚠️ Continuing without specific agents'));
|
|
2509
2510
|
}
|
|
2511
|
+
} else if (!agent && yes) {
|
|
2512
|
+
// --yes flag used without --agent, proceed without agents
|
|
2513
|
+
console.log(chalk.yellow('⚠️ No agent specified, continuing without specific agents'));
|
|
2510
2514
|
}
|
|
2511
2515
|
|
|
2512
2516
|
// Get prompt from user if not provided
|
|
@@ -2603,6 +2607,19 @@ async function executeSandbox(options, targetDir) {
|
|
|
2603
2607
|
|
|
2604
2608
|
// Execute Cloudflare sandbox
|
|
2605
2609
|
await executeCloudflareSandbox({ sandbox, agent, prompt, command, mcp, setting, hook, anthropicKey }, targetDir);
|
|
2610
|
+
|
|
2611
|
+
} else if (sandbox === 'docker') {
|
|
2612
|
+
if (!anthropicKey) {
|
|
2613
|
+
console.log(chalk.red('❌ Error: Anthropic API key is required for Docker sandbox'));
|
|
2614
|
+
console.log(chalk.yellow('💡 Options:'));
|
|
2615
|
+
console.log(chalk.gray(' 1. Set environment variable: ANTHROPIC_API_KEY=your_key'));
|
|
2616
|
+
console.log(chalk.gray(' 2. Use CLI parameter: --anthropic-api-key your_key'));
|
|
2617
|
+
console.log(chalk.blue(' Get your key at: https://console.anthropic.com'));
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// Execute Docker sandbox
|
|
2622
|
+
await executeDockerSandbox({ sandbox, agent, prompt, command, mcp, setting, hook, anthropicKey, yes: options.yes }, targetDir);
|
|
2606
2623
|
}
|
|
2607
2624
|
}
|
|
2608
2625
|
|
|
@@ -2817,6 +2834,199 @@ async function executeCloudflareSandbox(options, targetDir) {
|
|
|
2817
2834
|
}
|
|
2818
2835
|
}
|
|
2819
2836
|
|
|
2837
|
+
async function executeDockerSandbox(options, targetDir) {
|
|
2838
|
+
const { agent, command, mcp, setting, hook, prompt, anthropicKey, yes } = options;
|
|
2839
|
+
|
|
2840
|
+
console.log(chalk.blue('\n🐳 Docker Sandbox Execution'));
|
|
2841
|
+
console.log(chalk.cyan('═══════════════════════════════════════'));
|
|
2842
|
+
|
|
2843
|
+
if (agent) {
|
|
2844
|
+
const agentList = agent.split(',');
|
|
2845
|
+
if (agentList.length > 1) {
|
|
2846
|
+
console.log(chalk.white(`📋 Agents (${agentList.length}):`));
|
|
2847
|
+
agentList.forEach(a => console.log(chalk.yellow(` • ${a.trim()}`)));
|
|
2848
|
+
} else {
|
|
2849
|
+
console.log(chalk.white(`📋 Agent: ${chalk.yellow(agent)}`));
|
|
2850
|
+
}
|
|
2851
|
+
} else {
|
|
2852
|
+
console.log(chalk.white(`📋 Agent: ${chalk.yellow('default')}`));
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
const truncatedPrompt = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
|
|
2856
|
+
console.log(chalk.white(`💭 Prompt: ${chalk.cyan('"' + truncatedPrompt + '"')}`));
|
|
2857
|
+
console.log(chalk.white(`🐳 Provider: ${chalk.green('Docker Local')}`));
|
|
2858
|
+
console.log(chalk.gray('\n🔧 Execution details:'));
|
|
2859
|
+
console.log(chalk.gray(' • Uses Claude Agent SDK for execution'));
|
|
2860
|
+
console.log(chalk.gray(' • Executes in isolated Docker container'));
|
|
2861
|
+
console.log(chalk.gray(' • Local execution with full filesystem access\n'));
|
|
2862
|
+
|
|
2863
|
+
// Skip confirmation prompt if --yes flag is used
|
|
2864
|
+
if (!yes) {
|
|
2865
|
+
const inquirer = require('inquirer');
|
|
2866
|
+
|
|
2867
|
+
const { shouldExecute } = await inquirer.prompt([{
|
|
2868
|
+
type: 'confirm',
|
|
2869
|
+
name: 'shouldExecute',
|
|
2870
|
+
message: 'Execute this prompt in Docker sandbox?',
|
|
2871
|
+
default: true
|
|
2872
|
+
}]);
|
|
2873
|
+
|
|
2874
|
+
if (!shouldExecute) {
|
|
2875
|
+
console.log(chalk.yellow('⏹️ Docker sandbox execution cancelled by user.'));
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
try {
|
|
2881
|
+
console.log(chalk.blue('🔮 Setting up Docker sandbox environment...'));
|
|
2882
|
+
|
|
2883
|
+
const spinner = ora('Installing Docker sandbox component...').start();
|
|
2884
|
+
|
|
2885
|
+
// Create .claude/sandbox/docker directory
|
|
2886
|
+
const sandboxDir = path.join(targetDir, '.claude', 'sandbox', 'docker');
|
|
2887
|
+
await fs.ensureDir(sandboxDir);
|
|
2888
|
+
|
|
2889
|
+
// Copy Docker component files
|
|
2890
|
+
const componentsDir = path.join(__dirname, '..', 'components', 'sandbox', 'docker');
|
|
2891
|
+
|
|
2892
|
+
try {
|
|
2893
|
+
if (await fs.pathExists(componentsDir)) {
|
|
2894
|
+
console.log(chalk.gray('📦 Using local Docker component files...'));
|
|
2895
|
+
console.log(chalk.dim(` Source: ${componentsDir}`));
|
|
2896
|
+
console.log(chalk.dim(` Target: ${sandboxDir}`));
|
|
2897
|
+
|
|
2898
|
+
// Copy all files from docker directory
|
|
2899
|
+
await fs.copy(componentsDir, sandboxDir, {
|
|
2900
|
+
overwrite: true
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2903
|
+
// Verify files were copied
|
|
2904
|
+
const copiedFiles = await fs.readdir(sandboxDir);
|
|
2905
|
+
console.log(chalk.dim(` Copied ${copiedFiles.length} items`));
|
|
2906
|
+
if (copiedFiles.length === 0) {
|
|
2907
|
+
throw new Error('No files were copied from Docker component directory');
|
|
2908
|
+
}
|
|
2909
|
+
} else {
|
|
2910
|
+
throw new Error(`Docker component files not found at: ${componentsDir}`);
|
|
2911
|
+
}
|
|
2912
|
+
} catch (error) {
|
|
2913
|
+
spinner.fail(`Failed to install Docker component: ${error.message}`);
|
|
2914
|
+
throw error;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
spinner.succeed('Docker sandbox component installed successfully');
|
|
2918
|
+
|
|
2919
|
+
// Check for Docker
|
|
2920
|
+
const dockerSpinner = ora('Checking Docker environment...').start();
|
|
2921
|
+
|
|
2922
|
+
try {
|
|
2923
|
+
const { spawn } = require('child_process');
|
|
2924
|
+
|
|
2925
|
+
// Check Docker installation
|
|
2926
|
+
const checkDocker = () => {
|
|
2927
|
+
return new Promise((resolve) => {
|
|
2928
|
+
const check = spawn('docker', ['--version'], { stdio: 'pipe' });
|
|
2929
|
+
check.on('close', (code) => resolve(code === 0));
|
|
2930
|
+
check.on('error', () => resolve(false));
|
|
2931
|
+
});
|
|
2932
|
+
};
|
|
2933
|
+
|
|
2934
|
+
const dockerAvailable = await checkDocker();
|
|
2935
|
+
if (!dockerAvailable) {
|
|
2936
|
+
dockerSpinner.fail('Docker not found');
|
|
2937
|
+
console.log(chalk.red('❌ Docker is required for Docker sandbox'));
|
|
2938
|
+
console.log(chalk.yellow('💡 Please install Docker and try again'));
|
|
2939
|
+
console.log(chalk.blue(' Visit: https://docs.docker.com/get-docker/'));
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// Check Docker daemon
|
|
2944
|
+
const checkDockerRunning = () => {
|
|
2945
|
+
return new Promise((resolve) => {
|
|
2946
|
+
const check = spawn('docker', ['ps'], { stdio: 'pipe' });
|
|
2947
|
+
check.on('close', (code) => resolve(code === 0));
|
|
2948
|
+
check.on('error', () => resolve(false));
|
|
2949
|
+
});
|
|
2950
|
+
};
|
|
2951
|
+
|
|
2952
|
+
const dockerRunning = await checkDockerRunning();
|
|
2953
|
+
if (!dockerRunning) {
|
|
2954
|
+
dockerSpinner.fail('Docker daemon not running');
|
|
2955
|
+
console.log(chalk.red('❌ Docker daemon is not running'));
|
|
2956
|
+
console.log(chalk.yellow('💡 Please start Docker and try again'));
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
dockerSpinner.succeed('Docker environment ready');
|
|
2961
|
+
|
|
2962
|
+
// Build components string for installation inside sandbox
|
|
2963
|
+
let componentsToInstall = '';
|
|
2964
|
+
if (agent) {
|
|
2965
|
+
const agentList = agent.split(',').map(a => `--agent ${a.trim()}`);
|
|
2966
|
+
componentsToInstall += agentList.join(' ');
|
|
2967
|
+
}
|
|
2968
|
+
if (command) {
|
|
2969
|
+
const commandList = command.split(',').map(c => ` --command ${c.trim()}`);
|
|
2970
|
+
componentsToInstall += commandList.join(' ');
|
|
2971
|
+
}
|
|
2972
|
+
if (mcp) {
|
|
2973
|
+
const mcpList = mcp.split(',').map(m => ` --mcp ${m.trim()}`);
|
|
2974
|
+
componentsToInstall += mcpList.join(' ');
|
|
2975
|
+
}
|
|
2976
|
+
if (setting) {
|
|
2977
|
+
const settingList = setting.split(',').map(s => ` --setting ${s.trim()}`);
|
|
2978
|
+
componentsToInstall += settingList.join(' ');
|
|
2979
|
+
}
|
|
2980
|
+
if (hook) {
|
|
2981
|
+
const hookList = hook.split(',').map(h => ` --hook ${h.trim()}`);
|
|
2982
|
+
componentsToInstall += hookList.join(' ');
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
// Execute Docker launcher
|
|
2986
|
+
const execSpinner = ora('Executing Docker sandbox...').start();
|
|
2987
|
+
|
|
2988
|
+
const launcherPath = path.join(sandboxDir, 'docker-launcher.js');
|
|
2989
|
+
|
|
2990
|
+
const dockerExec = spawn('node', [launcherPath, prompt, componentsToInstall.trim()], {
|
|
2991
|
+
cwd: sandboxDir,
|
|
2992
|
+
stdio: 'inherit',
|
|
2993
|
+
env: {
|
|
2994
|
+
...process.env,
|
|
2995
|
+
ANTHROPIC_API_KEY: anthropicKey
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
|
|
2999
|
+
await new Promise((resolve, reject) => {
|
|
3000
|
+
dockerExec.on('close', (dockerCode) => {
|
|
3001
|
+
if (dockerCode === 0) {
|
|
3002
|
+
execSpinner.succeed('Docker sandbox execution completed successfully');
|
|
3003
|
+
console.log(chalk.green('\n✅ Docker sandbox execution finished!'));
|
|
3004
|
+
console.log(chalk.white('📁 Output files are in the output/ directory'));
|
|
3005
|
+
resolve();
|
|
3006
|
+
} else {
|
|
3007
|
+
execSpinner.fail(`Docker sandbox execution failed with code ${dockerCode}`);
|
|
3008
|
+
reject(new Error(`Docker execution failed with code ${dockerCode}`));
|
|
3009
|
+
}
|
|
3010
|
+
});
|
|
3011
|
+
|
|
3012
|
+
dockerExec.on('error', (error) => {
|
|
3013
|
+
execSpinner.fail('Failed to execute Docker sandbox');
|
|
3014
|
+
reject(error);
|
|
3015
|
+
});
|
|
3016
|
+
});
|
|
3017
|
+
|
|
3018
|
+
} catch (error) {
|
|
3019
|
+
dockerSpinner.fail('Failed to check Docker environment');
|
|
3020
|
+
console.log(chalk.red(`❌ Error: ${error.message}`));
|
|
3021
|
+
throw error;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
console.log(chalk.red(`❌ Error setting up Docker sandbox: ${error.message}`));
|
|
3026
|
+
console.log(chalk.yellow('💡 Please check your Docker installation and try again'));
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
|
|
2820
3030
|
async function executeE2BSandbox(options, targetDir) {
|
|
2821
3031
|
const { agent, prompt, command, mcp, setting, hook, e2bKey, anthropicKey } = options;
|
|
2822
3032
|
|