claude-code-templates 1.24.0 → 1.24.2
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/README.md +92 -10
- package/components/sandbox/cloudflare/.dev.vars.example +13 -0
- package/components/sandbox/cloudflare/IMPLEMENTATION_SUMMARY.md +375 -0
- package/components/sandbox/cloudflare/QUICKSTART.md +267 -0
- package/components/sandbox/cloudflare/README.md +301 -0
- package/components/sandbox/cloudflare/SANDBOX_DEBUGGING.md +442 -0
- package/components/sandbox/cloudflare/claude-code-sandbox.md +314 -0
- package/components/sandbox/cloudflare/launcher.ts +472 -0
- package/components/sandbox/cloudflare/monitor.ts +388 -0
- package/components/sandbox/cloudflare/package.json +54 -0
- package/components/sandbox/cloudflare/src/index.ts +240 -0
- package/components/sandbox/cloudflare/tsconfig.json +31 -0
- package/components/sandbox/cloudflare/wrangler.toml +50 -0
- package/package.json +2 -1
- package/src/index.js +312 -127
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cloudflare Sandbox Real-time Monitor
|
|
4
|
+
* Provides real-time monitoring and debugging of Cloudflare sandbox operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fetch from 'node-fetch';
|
|
8
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
9
|
+
|
|
10
|
+
interface MonitoringMetrics {
|
|
11
|
+
executionTime: number;
|
|
12
|
+
codeGenerationTime: number;
|
|
13
|
+
sandboxExecutionTime: number;
|
|
14
|
+
memoryUsage?: number;
|
|
15
|
+
success: boolean;
|
|
16
|
+
errorDetails?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SandboxState {
|
|
20
|
+
status: 'idle' | 'generating' | 'executing' | 'completed' | 'failed';
|
|
21
|
+
currentStep: string;
|
|
22
|
+
progress: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ANSI color codes
|
|
26
|
+
const colors = {
|
|
27
|
+
reset: '\x1b[0m',
|
|
28
|
+
bright: '\x1b[1m',
|
|
29
|
+
dim: '\x1b[2m',
|
|
30
|
+
red: '\x1b[31m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
magenta: '\x1b[35m',
|
|
35
|
+
cyan: '\x1b[36m',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function logWithTimestamp(message: string, level: 'INFO' | 'SUCCESS' | 'ERROR' | 'WARNING' = 'INFO') {
|
|
39
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
40
|
+
const icon = {
|
|
41
|
+
INFO: `${colors.blue}ℹ${colors.reset}`,
|
|
42
|
+
SUCCESS: `${colors.green}✓${colors.reset}`,
|
|
43
|
+
ERROR: `${colors.red}✗${colors.reset}`,
|
|
44
|
+
WARNING: `${colors.yellow}⚠${colors.reset}`,
|
|
45
|
+
}[level];
|
|
46
|
+
|
|
47
|
+
const colorCode = {
|
|
48
|
+
INFO: colors.blue,
|
|
49
|
+
SUCCESS: colors.green,
|
|
50
|
+
ERROR: colors.red,
|
|
51
|
+
WARNING: colors.yellow,
|
|
52
|
+
}[level];
|
|
53
|
+
|
|
54
|
+
console.log(`${colors.dim}[${timestamp}]${colors.reset} ${icon} ${colorCode}${message}${colors.reset}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printSeparator(char: string = '=', length: number = 60) {
|
|
58
|
+
console.log(colors.dim + char.repeat(length) + colors.reset);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function monitorWorkerHealth(workerUrl: string): Promise<boolean> {
|
|
62
|
+
logWithTimestamp('🔍 Checking Cloudflare Worker health...');
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(workerUrl, {
|
|
66
|
+
method: 'GET',
|
|
67
|
+
signal: AbortSignal.timeout(5000), // 5 second timeout
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (response.ok || response.status === 405) {
|
|
71
|
+
logWithTimestamp('✅ Worker is responding', 'SUCCESS');
|
|
72
|
+
logWithTimestamp(` Status: ${response.status} ${response.statusText}`);
|
|
73
|
+
return true;
|
|
74
|
+
} else {
|
|
75
|
+
logWithTimestamp(`⚠️ Worker returned: ${response.status} ${response.statusText}`, 'WARNING');
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
80
|
+
logWithTimestamp(`❌ Worker health check failed: ${errorMsg}`, 'ERROR');
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function monitorCodeGeneration(
|
|
86
|
+
anthropic: Anthropic,
|
|
87
|
+
prompt: string
|
|
88
|
+
): Promise<{ code: string; duration: number }> {
|
|
89
|
+
logWithTimestamp('🤖 Starting code generation with Claude...');
|
|
90
|
+
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const response = await anthropic.messages.create({
|
|
95
|
+
model: 'claude-sonnet-4-5',
|
|
96
|
+
max_tokens: 4096,
|
|
97
|
+
messages: [
|
|
98
|
+
{
|
|
99
|
+
role: 'user',
|
|
100
|
+
content: `Generate Python code to answer: "${prompt}"
|
|
101
|
+
|
|
102
|
+
Requirements:
|
|
103
|
+
- Use only Python standard library
|
|
104
|
+
- Print the result using print()
|
|
105
|
+
- Keep code simple and safe
|
|
106
|
+
- Include proper error handling
|
|
107
|
+
|
|
108
|
+
Return ONLY the code, no explanations.`,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const duration = Date.now() - startTime;
|
|
114
|
+
|
|
115
|
+
const code = response.content[0]?.type === 'text' ? response.content[0].text : '';
|
|
116
|
+
|
|
117
|
+
if (!code) {
|
|
118
|
+
throw new Error('No code generated');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
logWithTimestamp(`✅ Code generated in ${duration}ms`, 'SUCCESS');
|
|
122
|
+
logWithTimestamp(` Model: ${response.model}`);
|
|
123
|
+
logWithTimestamp(` Tokens used: ${response.usage.input_tokens} in, ${response.usage.output_tokens} out`);
|
|
124
|
+
logWithTimestamp(` Code length: ${code.length} characters`);
|
|
125
|
+
|
|
126
|
+
if (code.length < 500) {
|
|
127
|
+
console.log('');
|
|
128
|
+
logWithTimestamp('📝 Generated Code Preview:');
|
|
129
|
+
printSeparator('-', 60);
|
|
130
|
+
console.log(colors.cyan + code + colors.reset);
|
|
131
|
+
printSeparator('-', 60);
|
|
132
|
+
console.log('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { code, duration };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
138
|
+
logWithTimestamp(`❌ Code generation failed: ${errorMsg}`, 'ERROR');
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function monitorSandboxExecution(
|
|
144
|
+
workerUrl: string,
|
|
145
|
+
question: string
|
|
146
|
+
): Promise<{ result: any; duration: number }> {
|
|
147
|
+
logWithTimestamp('⚙️ Executing in Cloudflare Sandbox...');
|
|
148
|
+
|
|
149
|
+
const startTime = Date.now();
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(`${workerUrl}/execute`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify({ question }),
|
|
158
|
+
signal: AbortSignal.timeout(60000), // 60 second timeout
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const duration = Date.now() - startTime;
|
|
162
|
+
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
const errorText = await response.text();
|
|
165
|
+
throw new Error(`Sandbox returned ${response.status}: ${errorText}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = await response.json();
|
|
169
|
+
|
|
170
|
+
logWithTimestamp(`✅ Sandbox execution completed in ${duration}ms`, 'SUCCESS');
|
|
171
|
+
logWithTimestamp(` Exit code: ${result.success ? '0 (success)' : '1 (failed)'}`);
|
|
172
|
+
logWithTimestamp(` Output length: ${result.output?.length || 0} characters`);
|
|
173
|
+
|
|
174
|
+
if (result.error) {
|
|
175
|
+
logWithTimestamp(` Error output: ${result.error.length} characters`, 'WARNING');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { result, duration };
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const duration = Date.now() - startTime;
|
|
181
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
182
|
+
logWithTimestamp(`❌ Sandbox execution failed after ${duration}ms: ${errorMsg}`, 'ERROR');
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function displayMetrics(metrics: MonitoringMetrics) {
|
|
188
|
+
console.log('');
|
|
189
|
+
printSeparator();
|
|
190
|
+
console.log(`${colors.bright}📊 PERFORMANCE METRICS${colors.reset}`);
|
|
191
|
+
printSeparator();
|
|
192
|
+
console.log('');
|
|
193
|
+
|
|
194
|
+
console.log(`${colors.cyan}Total Execution Time:${colors.reset} ${metrics.executionTime}ms`);
|
|
195
|
+
console.log(`${colors.cyan} ├─ Code Generation:${colors.reset} ${metrics.codeGenerationTime}ms`);
|
|
196
|
+
console.log(`${colors.cyan} └─ Sandbox Execution:${colors.reset} ${metrics.sandboxExecutionTime}ms`);
|
|
197
|
+
|
|
198
|
+
if (metrics.memoryUsage) {
|
|
199
|
+
console.log(`${colors.cyan}Memory Usage:${colors.reset} ${metrics.memoryUsage}MB`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(
|
|
204
|
+
`${colors.cyan}Status:${colors.reset} ${metrics.success ? `${colors.green}Success ✓${colors.reset}` : `${colors.red}Failed ✗${colors.reset}`}`
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (metrics.errorDetails) {
|
|
208
|
+
console.log(`${colors.red}Error Details:${colors.reset} ${metrics.errorDetails}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
printSeparator();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function displaySystemInfo() {
|
|
215
|
+
console.log('');
|
|
216
|
+
printSeparator();
|
|
217
|
+
console.log(`${colors.bright}🖥️ SYSTEM INFORMATION${colors.reset}`);
|
|
218
|
+
printSeparator();
|
|
219
|
+
console.log('');
|
|
220
|
+
|
|
221
|
+
console.log(`${colors.cyan}Node.js Version:${colors.reset} ${process.version}`);
|
|
222
|
+
console.log(`${colors.cyan}Platform:${colors.reset} ${process.platform}`);
|
|
223
|
+
console.log(`${colors.cyan}Architecture:${colors.reset} ${process.arch}`);
|
|
224
|
+
console.log(
|
|
225
|
+
`${colors.cyan}Memory Usage:${colors.reset} ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB / ${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)}MB`
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
printSeparator();
|
|
229
|
+
console.log('');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function enhancedSandboxMonitoring(
|
|
233
|
+
prompt: string,
|
|
234
|
+
anthropicApiKey: string,
|
|
235
|
+
workerUrl: string = 'http://localhost:8787'
|
|
236
|
+
): Promise<boolean> {
|
|
237
|
+
logWithTimestamp('🚀 Starting enhanced Cloudflare sandbox monitoring');
|
|
238
|
+
printSeparator();
|
|
239
|
+
|
|
240
|
+
displaySystemInfo();
|
|
241
|
+
|
|
242
|
+
const metrics: MonitoringMetrics = {
|
|
243
|
+
executionTime: 0,
|
|
244
|
+
codeGenerationTime: 0,
|
|
245
|
+
sandboxExecutionTime: 0,
|
|
246
|
+
success: false,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const startTime = Date.now();
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
// Step 1: Check worker health
|
|
253
|
+
const workerHealthy = await monitorWorkerHealth(workerUrl);
|
|
254
|
+
if (!workerHealthy) {
|
|
255
|
+
logWithTimestamp('⚠️ Worker is not healthy, attempting to continue...', 'WARNING');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log('');
|
|
259
|
+
|
|
260
|
+
// Step 2: Generate code with monitoring
|
|
261
|
+
const anthropic = new Anthropic({ apiKey: anthropicApiKey });
|
|
262
|
+
const { code, duration: codeGenDuration } = await monitorCodeGeneration(anthropic, prompt);
|
|
263
|
+
metrics.codeGenerationTime = codeGenDuration;
|
|
264
|
+
|
|
265
|
+
console.log('');
|
|
266
|
+
|
|
267
|
+
// Step 3: Execute in sandbox with monitoring
|
|
268
|
+
const { result, duration: execDuration } = await monitorSandboxExecution(workerUrl, prompt);
|
|
269
|
+
metrics.sandboxExecutionTime = execDuration;
|
|
270
|
+
|
|
271
|
+
console.log('');
|
|
272
|
+
|
|
273
|
+
// Display results
|
|
274
|
+
printSeparator();
|
|
275
|
+
console.log(`${colors.bright}🎯 EXECUTION RESULTS${colors.reset}`);
|
|
276
|
+
printSeparator();
|
|
277
|
+
console.log('');
|
|
278
|
+
|
|
279
|
+
console.log(`${colors.cyan}Question:${colors.reset} ${result.question}`);
|
|
280
|
+
console.log('');
|
|
281
|
+
|
|
282
|
+
console.log(`${colors.cyan}Generated Code:${colors.reset}`);
|
|
283
|
+
printSeparator('-', 60);
|
|
284
|
+
console.log(colors.green + result.code + colors.reset);
|
|
285
|
+
printSeparator('-', 60);
|
|
286
|
+
console.log('');
|
|
287
|
+
|
|
288
|
+
if (result.output) {
|
|
289
|
+
console.log(`${colors.cyan}Output:${colors.reset}`);
|
|
290
|
+
console.log(colors.bright + result.output + colors.reset);
|
|
291
|
+
console.log('');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (result.error) {
|
|
295
|
+
console.log(`${colors.red}Error Output:${colors.reset}`);
|
|
296
|
+
console.log(result.error);
|
|
297
|
+
console.log('');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
metrics.executionTime = Date.now() - startTime;
|
|
301
|
+
metrics.success = result.success;
|
|
302
|
+
metrics.memoryUsage = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
303
|
+
|
|
304
|
+
displayMetrics(metrics);
|
|
305
|
+
|
|
306
|
+
logWithTimestamp('✅ Monitoring session completed successfully', 'SUCCESS');
|
|
307
|
+
return true;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
metrics.executionTime = Date.now() - startTime;
|
|
310
|
+
metrics.success = false;
|
|
311
|
+
metrics.errorDetails = error instanceof Error ? error.message : String(error);
|
|
312
|
+
metrics.memoryUsage = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
313
|
+
|
|
314
|
+
console.log('');
|
|
315
|
+
displayMetrics(metrics);
|
|
316
|
+
|
|
317
|
+
logWithTimestamp(`❌ Monitoring session failed: ${metrics.errorDetails}`, 'ERROR');
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function main() {
|
|
323
|
+
const args = process.argv.slice(2);
|
|
324
|
+
|
|
325
|
+
if (args.length < 1) {
|
|
326
|
+
console.log('Cloudflare Sandbox Monitor');
|
|
327
|
+
console.log('');
|
|
328
|
+
console.log('Usage:');
|
|
329
|
+
console.log(' node monitor.ts <prompt> [anthropic_api_key] [worker_url]');
|
|
330
|
+
console.log('');
|
|
331
|
+
console.log('Examples:');
|
|
332
|
+
console.log(' node monitor.ts "Calculate factorial of 5"');
|
|
333
|
+
console.log(' node monitor.ts "Fibonacci 10" YOUR_API_KEY');
|
|
334
|
+
console.log(' node monitor.ts "Sum array" YOUR_KEY https://your-worker.workers.dev');
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log('Environment Variables:');
|
|
337
|
+
console.log(' ANTHROPIC_API_KEY - Anthropic API key');
|
|
338
|
+
console.log(' CLOUDFLARE_WORKER_URL - Worker endpoint (default: http://localhost:8787)');
|
|
339
|
+
console.log('');
|
|
340
|
+
console.log('This tool provides enhanced monitoring and debugging for Cloudflare sandbox operations.');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const prompt = args[0];
|
|
345
|
+
const anthropicApiKey = args[1] || process.env.ANTHROPIC_API_KEY || '';
|
|
346
|
+
const workerUrl = args[2] || process.env.CLOUDFLARE_WORKER_URL || 'http://localhost:8787';
|
|
347
|
+
|
|
348
|
+
if (!anthropicApiKey) {
|
|
349
|
+
logWithTimestamp('❌ Anthropic API key is required', 'ERROR');
|
|
350
|
+
console.log('Provide via command line argument or ANTHROPIC_API_KEY environment variable');
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log('');
|
|
355
|
+
printSeparator('=', 70);
|
|
356
|
+
console.log(`${colors.bright}${colors.cyan} 🎬 CLOUDFLARE SANDBOX MONITOR${colors.reset}`);
|
|
357
|
+
printSeparator('=', 70);
|
|
358
|
+
console.log('');
|
|
359
|
+
|
|
360
|
+
const success = await enhancedSandboxMonitoring(prompt, anthropicApiKey, workerUrl);
|
|
361
|
+
|
|
362
|
+
console.log('');
|
|
363
|
+
|
|
364
|
+
if (success) {
|
|
365
|
+
logWithTimestamp('🎉 Monitoring completed successfully', 'SUCCESS');
|
|
366
|
+
process.exit(0);
|
|
367
|
+
} else {
|
|
368
|
+
logWithTimestamp('💔 Monitoring failed', 'ERROR');
|
|
369
|
+
console.log('');
|
|
370
|
+
console.log('Troubleshooting:');
|
|
371
|
+
console.log('1. Ensure worker is deployed: npx wrangler deploy');
|
|
372
|
+
console.log('2. Check API key is set: npx wrangler secret put ANTHROPIC_API_KEY');
|
|
373
|
+
console.log('3. Verify worker URL is correct');
|
|
374
|
+
console.log('4. Check worker logs: npx wrangler tail');
|
|
375
|
+
console.log('5. Test locally: npm run dev');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Run if executed directly
|
|
381
|
+
if (require.main === module) {
|
|
382
|
+
main().catch((error) => {
|
|
383
|
+
console.error('Fatal error:', error);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export { monitorWorkerHealth, monitorCodeGeneration, monitorSandboxExecution };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cloudflare-claude-sandbox",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cloudflare Workers sandbox for executing Claude Code with AI-powered code generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "wrangler dev",
|
|
9
|
+
"deploy": "wrangler deploy",
|
|
10
|
+
"tail": "wrangler tail",
|
|
11
|
+
"launch": "tsx launcher.ts",
|
|
12
|
+
"monitor": "tsx monitor.ts",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"test:watch": "vitest --watch",
|
|
15
|
+
"type-check": "tsc --noEmit",
|
|
16
|
+
"format": "prettier --write .",
|
|
17
|
+
"format:check": "prettier --check ."
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@anthropic-ai/sdk": "^0.20.0",
|
|
21
|
+
"@cloudflare/sandbox": "^0.1.0",
|
|
22
|
+
"node-fetch": "^3.3.2"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
|
26
|
+
"@cloudflare/workers-types": "^4.20240129.0",
|
|
27
|
+
"@types/node": "^20.11.0",
|
|
28
|
+
"prettier": "^3.2.4",
|
|
29
|
+
"tsx": "^4.7.0",
|
|
30
|
+
"typescript": "^5.3.3",
|
|
31
|
+
"vitest": "^1.2.0",
|
|
32
|
+
"wrangler": "^3.78.12"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=16.17.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"cloudflare",
|
|
39
|
+
"workers",
|
|
40
|
+
"sandbox",
|
|
41
|
+
"claude",
|
|
42
|
+
"anthropic",
|
|
43
|
+
"ai",
|
|
44
|
+
"code-execution",
|
|
45
|
+
"durable-objects",
|
|
46
|
+
"edge-computing"
|
|
47
|
+
],
|
|
48
|
+
"author": "Claude Code Templates",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/anthropics/claude-code-templates"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { getSandbox, type Sandbox } from '@cloudflare/sandbox';
|
|
2
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
+
|
|
4
|
+
export { Sandbox } from '@cloudflare/sandbox';
|
|
5
|
+
|
|
6
|
+
interface Env {
|
|
7
|
+
Sandbox: DurableObjectNamespace<Sandbox>;
|
|
8
|
+
ANTHROPIC_API_KEY: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ExecuteRequest {
|
|
12
|
+
question: string;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
language?: 'python' | 'javascript';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ExecuteResponse {
|
|
19
|
+
success: boolean;
|
|
20
|
+
question: string;
|
|
21
|
+
code: string;
|
|
22
|
+
output: string;
|
|
23
|
+
error: string;
|
|
24
|
+
sandboxId?: string;
|
|
25
|
+
executionTime?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Main Worker handler
|
|
30
|
+
* Receives code execution requests and orchestrates Claude AI + Cloudflare Sandbox
|
|
31
|
+
*/
|
|
32
|
+
export default {
|
|
33
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
34
|
+
const url = new URL(request.url);
|
|
35
|
+
|
|
36
|
+
// CORS headers for browser access
|
|
37
|
+
const corsHeaders = {
|
|
38
|
+
'Access-Control-Allow-Origin': '*',
|
|
39
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
40
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Handle OPTIONS request for CORS
|
|
44
|
+
if (request.method === 'OPTIONS') {
|
|
45
|
+
return new Response(null, { headers: corsHeaders });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Root endpoint - return usage instructions
|
|
49
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
50
|
+
return new Response(
|
|
51
|
+
JSON.stringify({
|
|
52
|
+
name: 'Cloudflare Claude Code Sandbox',
|
|
53
|
+
version: '1.0.0',
|
|
54
|
+
endpoints: {
|
|
55
|
+
execute: 'POST /execute - Execute code via Claude AI',
|
|
56
|
+
health: 'GET /health - Check worker health',
|
|
57
|
+
},
|
|
58
|
+
usage: {
|
|
59
|
+
example: {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
url: '/execute',
|
|
62
|
+
body: {
|
|
63
|
+
question: 'What is the 10th Fibonacci number?',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
{
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
...corsHeaders,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Health check endpoint
|
|
78
|
+
if (request.method === 'GET' && url.pathname === '/health') {
|
|
79
|
+
return new Response(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
status: 'healthy',
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
worker: 'cloudflare-claude-sandbox',
|
|
84
|
+
}),
|
|
85
|
+
{
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
...corsHeaders,
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Execute endpoint
|
|
95
|
+
if (request.method === 'POST' && url.pathname === '/execute') {
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Parse request body
|
|
100
|
+
const body = (await request.json()) as ExecuteRequest;
|
|
101
|
+
|
|
102
|
+
if (!body.question) {
|
|
103
|
+
return Response.json(
|
|
104
|
+
{ error: 'Question is required' },
|
|
105
|
+
{ status: 400, headers: corsHeaders }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Validate API key
|
|
110
|
+
if (!env.ANTHROPIC_API_KEY) {
|
|
111
|
+
return Response.json(
|
|
112
|
+
{
|
|
113
|
+
error: 'ANTHROPIC_API_KEY not configured',
|
|
114
|
+
message: 'Set the API key using: npx wrangler secret put ANTHROPIC_API_KEY',
|
|
115
|
+
},
|
|
116
|
+
{ status: 500, headers: corsHeaders }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Initialize Anthropic client
|
|
121
|
+
const anthropic = new Anthropic({
|
|
122
|
+
apiKey: env.ANTHROPIC_API_KEY,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Generate code using Claude
|
|
126
|
+
console.log('Generating code with Claude for:', body.question.substring(0, 100));
|
|
127
|
+
|
|
128
|
+
const language = body.language || 'python';
|
|
129
|
+
const codePrompt =
|
|
130
|
+
language === 'python'
|
|
131
|
+
? `Generate Python code to answer: "${body.question}"
|
|
132
|
+
|
|
133
|
+
Requirements:
|
|
134
|
+
- Use only Python standard library
|
|
135
|
+
- Print the result using print()
|
|
136
|
+
- Keep code simple and safe
|
|
137
|
+
- Include proper error handling
|
|
138
|
+
- Use descriptive variable names
|
|
139
|
+
|
|
140
|
+
Return ONLY the code, no explanations or markdown formatting.`
|
|
141
|
+
: `Generate JavaScript code to answer: "${body.question}"
|
|
142
|
+
|
|
143
|
+
Requirements:
|
|
144
|
+
- Use only Node.js standard library
|
|
145
|
+
- Print the result using console.log()
|
|
146
|
+
- Keep code simple and safe
|
|
147
|
+
- Include proper error handling
|
|
148
|
+
- Use descriptive variable names
|
|
149
|
+
|
|
150
|
+
Return ONLY the code, no explanations or markdown formatting.`;
|
|
151
|
+
|
|
152
|
+
const codeGeneration = await anthropic.messages.create({
|
|
153
|
+
model: 'claude-sonnet-4-5',
|
|
154
|
+
max_tokens: body.maxTokens || 2048,
|
|
155
|
+
messages: [
|
|
156
|
+
{
|
|
157
|
+
role: 'user',
|
|
158
|
+
content: codePrompt,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const generatedCode =
|
|
164
|
+
codeGeneration.content[0]?.type === 'text' ? codeGeneration.content[0].text : '';
|
|
165
|
+
|
|
166
|
+
if (!generatedCode) {
|
|
167
|
+
return Response.json(
|
|
168
|
+
{ error: 'Failed to generate code from Claude' },
|
|
169
|
+
{ status: 500, headers: corsHeaders }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Clean up code (remove markdown formatting if present)
|
|
174
|
+
const cleanCode = generatedCode
|
|
175
|
+
.replace(/```(?:python|javascript|js)?\n?/g, '')
|
|
176
|
+
.replace(/```\n?$/g, '')
|
|
177
|
+
.trim();
|
|
178
|
+
|
|
179
|
+
console.log('Code generated, executing in sandbox...');
|
|
180
|
+
|
|
181
|
+
// Execute the code in a sandbox
|
|
182
|
+
// Use a unique ID per request to avoid conflicts
|
|
183
|
+
const sandboxId = `user-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
184
|
+
const sandbox = getSandbox(env.Sandbox, sandboxId);
|
|
185
|
+
|
|
186
|
+
// Determine execution command based on language
|
|
187
|
+
const fileName = language === 'python' ? '/tmp/code.py' : '/tmp/code.js';
|
|
188
|
+
const execCommand =
|
|
189
|
+
language === 'python' ? 'python /tmp/code.py' : 'node /tmp/code.js';
|
|
190
|
+
|
|
191
|
+
// Write code to sandbox and execute
|
|
192
|
+
await sandbox.writeFile(fileName, cleanCode);
|
|
193
|
+
|
|
194
|
+
const result = await sandbox.exec(execCommand, {
|
|
195
|
+
timeout: body.timeout || 30000, // 30 seconds default
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const executionTime = Date.now() - startTime;
|
|
199
|
+
|
|
200
|
+
const response: ExecuteResponse = {
|
|
201
|
+
success: result.success,
|
|
202
|
+
question: body.question,
|
|
203
|
+
code: cleanCode,
|
|
204
|
+
output: result.stdout || '',
|
|
205
|
+
error: result.stderr || '',
|
|
206
|
+
sandboxId: sandboxId,
|
|
207
|
+
executionTime: executionTime,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
console.log(
|
|
211
|
+
`Execution completed in ${executionTime}ms. Success: ${result.success}`
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
return Response.json(response, {
|
|
215
|
+
headers: corsHeaders,
|
|
216
|
+
});
|
|
217
|
+
} catch (error: unknown) {
|
|
218
|
+
console.error('Execution error:', error);
|
|
219
|
+
|
|
220
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
221
|
+
const executionTime = Date.now() - startTime;
|
|
222
|
+
|
|
223
|
+
return Response.json(
|
|
224
|
+
{
|
|
225
|
+
error: 'Internal server error',
|
|
226
|
+
message: errorMessage,
|
|
227
|
+
executionTime: executionTime,
|
|
228
|
+
},
|
|
229
|
+
{ status: 500, headers: corsHeaders }
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Unknown endpoint
|
|
235
|
+
return new Response(
|
|
236
|
+
'POST /execute with { "question": "your question" }\nGET /health for health check\nGET / for API information',
|
|
237
|
+
{ status: 404, headers: corsHeaders }
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
};
|