claude-code-templates 1.26.3 → 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,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.3",
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
@@ -126,6 +126,10 @@ async function createClaudeConfig(options = {}) {
126
126
 
127
127
  // Handle sandbox execution FIRST (before individual components)
128
128
  if (options.sandbox) {
129
+ trackingService.trackCommandExecution('sandbox', {
130
+ provider: options.sandbox,
131
+ hasPrompt: !!options.prompt
132
+ });
129
133
  await executeSandbox(options, targetDir);
130
134
  return;
131
135
  }
@@ -174,24 +178,28 @@ async function createClaudeConfig(options = {}) {
174
178
 
175
179
  // Handle command stats analysis (both singular and plural)
176
180
  if (options.commandStats || options.commandsStats) {
181
+ trackingService.trackCommandExecution('command-stats');
177
182
  await runCommandStats(options);
178
183
  return;
179
184
  }
180
-
185
+
181
186
  // Handle hook stats analysis (both singular and plural)
182
187
  if (options.hookStats || options.hooksStats) {
188
+ trackingService.trackCommandExecution('hook-stats');
183
189
  await runHookStats(options);
184
190
  return;
185
191
  }
186
-
192
+
187
193
  // Handle MCP stats analysis (both singular and plural)
188
194
  if (options.mcpStats || options.mcpsStats) {
195
+ trackingService.trackCommandExecution('mcp-stats');
189
196
  await runMCPStats(options);
190
197
  return;
191
198
  }
192
199
 
193
200
  // Handle analytics dashboard
194
201
  if (options.analytics) {
202
+ trackingService.trackCommandExecution('analytics', { tunnel: options.tunnel || false });
195
203
  trackingService.trackAnalyticsDashboard({ page: 'dashboard', source: 'command_line' });
196
204
  await runAnalytics(options);
197
205
  return;
@@ -199,6 +207,7 @@ async function createClaudeConfig(options = {}) {
199
207
 
200
208
  // Handle plugin dashboard
201
209
  if (options.plugins) {
210
+ trackingService.trackCommandExecution('plugins');
202
211
  trackingService.trackAnalyticsDashboard({ page: 'plugins', source: 'command_line' });
203
212
  await runPluginDashboard(options);
204
213
  return;
@@ -206,20 +215,23 @@ async function createClaudeConfig(options = {}) {
206
215
 
207
216
  // Handle chats dashboard (now points to mobile chats interface)
208
217
  if (options.chats) {
218
+ trackingService.trackCommandExecution('chats', { tunnel: options.tunnel || false });
209
219
  trackingService.trackAnalyticsDashboard({ page: 'chats-mobile', source: 'command_line' });
210
220
  await startChatsMobile(options);
211
221
  return;
212
222
  }
213
-
223
+
214
224
  // Handle agents dashboard (separate from chats)
215
225
  if (options.agents) {
226
+ trackingService.trackCommandExecution('agents', { tunnel: options.tunnel || false });
216
227
  trackingService.trackAnalyticsDashboard({ page: 'agents', source: 'command_line' });
217
228
  await runAnalytics({ ...options, openTo: 'agents' });
218
229
  return;
219
230
  }
220
-
231
+
221
232
  // Handle mobile chats interface
222
233
  if (options.chatsMobile) {
234
+ trackingService.trackCommandExecution('chats-mobile', { tunnel: options.tunnel || false });
223
235
  trackingService.trackAnalyticsDashboard({ page: 'chats-mobile', source: 'command_line' });
224
236
  await startChatsMobile(options);
225
237
  return;
@@ -269,8 +281,9 @@ async function createClaudeConfig(options = {}) {
269
281
  // Handle health check
270
282
  let shouldRunSetup = false;
271
283
  if (options.healthCheck || options.health || options.check || options.verify) {
284
+ trackingService.trackCommandExecution('health-check');
272
285
  const healthResult = await runHealthCheck();
273
-
286
+
274
287
  // Track health check usage
275
288
  trackingService.trackHealthCheck({
276
289
  setup_recommended: healthResult.runSetup,
@@ -2434,37 +2447,38 @@ async function launchClaudeCodeStudio(options, targetDir) {
2434
2447
  }
2435
2448
 
2436
2449
  async function executeSandbox(options, targetDir) {
2437
- const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey } = options;
2450
+ const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey, yes } = options;
2438
2451
  let { agent, prompt } = options;
2439
-
2452
+
2440
2453
  // Validate sandbox provider
2441
- if (sandbox !== 'e2b' && sandbox !== 'cloudflare') {
2454
+ if (sandbox !== 'e2b' && sandbox !== 'cloudflare' && sandbox !== 'docker') {
2442
2455
  console.log(chalk.red('āŒ Error: Invalid sandbox provider'));
2443
- console.log(chalk.yellow('šŸ’” Available providers: e2b, cloudflare'));
2456
+ console.log(chalk.yellow('šŸ’” Available providers: e2b, cloudflare, docker'));
2444
2457
  console.log(chalk.gray(' Example: --sandbox e2b --prompt "Create a web app"'));
2445
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"'));
2446
2460
  return;
2447
2461
  }
2448
-
2449
- // Interactive agent selection if not provided
2450
- if (!agent) {
2462
+
2463
+ // Interactive agent selection if not provided and --yes not used
2464
+ if (!agent && !yes) {
2451
2465
  const inquirer = require('inquirer');
2452
-
2466
+
2453
2467
  console.log(chalk.blue('\nšŸ¤– Agent Selection'));
2454
2468
  console.log(chalk.cyan('═══════════════════════════════════════'));
2455
2469
  console.log(chalk.gray('Select one or more agents for your task (use SPACE to select, ENTER to confirm).\n'));
2456
-
2470
+
2457
2471
  // Fetch available agents
2458
2472
  console.log(chalk.gray('ā³ Fetching available agents...'));
2459
2473
  const agents = await getAvailableAgentsFromGitHub();
2460
-
2474
+
2461
2475
  // Format agents for selection with full path
2462
2476
  const agentChoices = agents.map(a => ({
2463
2477
  name: `${a.path} ${chalk.gray(`- ${a.category}`)}`,
2464
2478
  value: a.path, // This already includes folder/agent-name format
2465
2479
  short: a.path
2466
2480
  }));
2467
-
2481
+
2468
2482
  // First ask if they want to select agents
2469
2483
  const { wantAgents } = await inquirer.prompt([{
2470
2484
  type: 'confirm',
@@ -2472,7 +2486,7 @@ async function executeSandbox(options, targetDir) {
2472
2486
  message: 'Do you want to select specific agents for this task?',
2473
2487
  default: true
2474
2488
  }]);
2475
-
2489
+
2476
2490
  if (wantAgents) {
2477
2491
  const { selectedAgents } = await inquirer.prompt([{
2478
2492
  type: 'checkbox',
@@ -2482,7 +2496,7 @@ async function executeSandbox(options, targetDir) {
2482
2496
  pageSize: 15
2483
2497
  // Removed validation - allow empty selection
2484
2498
  }]);
2485
-
2499
+
2486
2500
  if (selectedAgents && selectedAgents.length > 0) {
2487
2501
  // Join multiple agents with comma
2488
2502
  agent = selectedAgents.join(',');
@@ -2494,6 +2508,9 @@ async function executeSandbox(options, targetDir) {
2494
2508
  } else {
2495
2509
  console.log(chalk.yellow('āš ļø Continuing without specific agents'));
2496
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'));
2497
2514
  }
2498
2515
 
2499
2516
  // Get prompt from user if not provided
@@ -2590,6 +2607,19 @@ async function executeSandbox(options, targetDir) {
2590
2607
 
2591
2608
  // Execute Cloudflare sandbox
2592
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);
2593
2623
  }
2594
2624
  }
2595
2625
 
@@ -2804,6 +2834,199 @@ async function executeCloudflareSandbox(options, targetDir) {
2804
2834
  }
2805
2835
  }
2806
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
+
2807
3030
  async function executeE2BSandbox(options, targetDir) {
2808
3031
  const { agent, prompt, command, mcp, setting, hook, e2bKey, anthropicKey } = options;
2809
3032