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.
@@ -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.26.4",
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