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.
- 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 +241 -18
- package/src/tracking-service.js +72 -0
|
@@ -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
|
@@ -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
|
|