claude-code-templates 1.24.1 → 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.
@@ -0,0 +1,472 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cloudflare Sandbox Launcher
4
+ * Executes Claude Code prompts using Cloudflare Workers and Sandbox SDK
5
+ */
6
+
7
+ import Anthropic from '@anthropic-ai/sdk';
8
+ import fetch from 'node-fetch';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+
12
+ interface ExecutionResult {
13
+ success: boolean;
14
+ question: string;
15
+ code: string;
16
+ output: string;
17
+ error: string;
18
+ sandboxId?: string;
19
+ files?: { path: string; content: string }[];
20
+ }
21
+
22
+ interface LauncherConfig {
23
+ prompt: string;
24
+ componentsToInstall: string;
25
+ anthropicApiKey: string;
26
+ workerUrl?: string;
27
+ useLocalWorker?: boolean;
28
+ targetDir?: string;
29
+ }
30
+
31
+ // ANSI color codes for terminal output
32
+ const colors = {
33
+ reset: '\x1b[0m',
34
+ bright: '\x1b[1m',
35
+ dim: '\x1b[2m',
36
+ red: '\x1b[31m',
37
+ green: '\x1b[32m',
38
+ yellow: '\x1b[33m',
39
+ blue: '\x1b[34m',
40
+ magenta: '\x1b[35m',
41
+ cyan: '\x1b[36m',
42
+ };
43
+
44
+ function log(message: string, level: 'info' | 'success' | 'error' | 'warning' = 'info') {
45
+ const timestamp = new Date().toLocaleTimeString();
46
+ const prefix = {
47
+ info: `${colors.blue}ℹ${colors.reset}`,
48
+ success: `${colors.green}✓${colors.reset}`,
49
+ error: `${colors.red}✗${colors.reset}`,
50
+ warning: `${colors.yellow}⚠${colors.reset}`,
51
+ }[level];
52
+
53
+ console.log(`[${timestamp}] ${prefix} ${message}`);
54
+ }
55
+
56
+ function printSeparator(char: string = '=', length: number = 60) {
57
+ console.log(char.repeat(length));
58
+ }
59
+
60
+ /**
61
+ * Extract files from generated code (handles multiple code blocks)
62
+ */
63
+ function extractFilesFromCode(code: string): { path: string; content: string }[] {
64
+ const files: { path: string; content: string }[] = [];
65
+
66
+ // Pattern to match code blocks with file names
67
+ // Matches: ```html:filename.html or ```javascript:script.js or ```css:styles.css
68
+ const fileBlockPattern = /```(\w+):([^\n]+)\n([\s\S]*?)```/g;
69
+ let match;
70
+
71
+ while ((match = fileBlockPattern.exec(code)) !== null) {
72
+ const [, _language, filename, content] = match;
73
+ files.push({
74
+ path: filename.trim(),
75
+ content: content.trim()
76
+ });
77
+ }
78
+
79
+ // If no explicit filenames, try to detect from code blocks
80
+ if (files.length === 0) {
81
+ const codeBlockPattern = /```(\w+)\n([\s\S]*?)```/g;
82
+ const languageExtensions: Record<string, string> = {
83
+ html: 'index.html',
84
+ css: 'styles.css',
85
+ javascript: 'script.js',
86
+ js: 'script.js',
87
+ typescript: 'index.ts',
88
+ ts: 'index.ts',
89
+ python: 'main.py',
90
+ py: 'main.py'
91
+ };
92
+
93
+ const detectedFiles = new Map<string, string>();
94
+
95
+ while ((match = codeBlockPattern.exec(code)) !== null) {
96
+ const [, language, content] = match;
97
+ const ext = language.toLowerCase();
98
+ const filename = languageExtensions[ext] || `code.${ext}`;
99
+
100
+ // If we already have this type of file, append a number
101
+ let finalFilename = filename;
102
+ let counter = 1;
103
+ while (detectedFiles.has(finalFilename)) {
104
+ const parts = filename.split('.');
105
+ const extension = parts.pop();
106
+ const base = parts.join('.');
107
+ finalFilename = `${base}${counter}.${extension}`;
108
+ counter++;
109
+ }
110
+
111
+ detectedFiles.set(finalFilename, content.trim());
112
+ }
113
+
114
+ detectedFiles.forEach((content, filename) => {
115
+ files.push({ path: filename, content });
116
+ });
117
+ }
118
+
119
+ return files;
120
+ }
121
+
122
+ /**
123
+ * Save files to local directory
124
+ */
125
+ function saveFilesToDirectory(files: { path: string; content: string }[], baseDir: string): void {
126
+ if (files.length === 0) {
127
+ return;
128
+ }
129
+
130
+ // Create output directory
131
+ if (!fs.existsSync(baseDir)) {
132
+ fs.mkdirSync(baseDir, { recursive: true });
133
+ }
134
+
135
+ console.log('');
136
+ printSeparator();
137
+ console.log(`${colors.bright}💾 DOWNLOADING FILES${colors.reset}`);
138
+ printSeparator();
139
+ console.log('');
140
+ console.log(`${colors.cyan}Output directory:${colors.reset} ${baseDir}`);
141
+ console.log('');
142
+
143
+ files.forEach(file => {
144
+ const fullPath = path.join(baseDir, file.path);
145
+ const dir = path.dirname(fullPath);
146
+
147
+ // Create directory if needed
148
+ if (!fs.existsSync(dir)) {
149
+ fs.mkdirSync(dir, { recursive: true });
150
+ }
151
+
152
+ // Write file
153
+ fs.writeFileSync(fullPath, file.content, 'utf-8');
154
+ log(`Downloaded: ${file.path} → ${fullPath}`, 'success');
155
+ });
156
+
157
+ console.log('');
158
+ console.log(`${colors.green}✓${colors.reset} All files saved to: ${colors.cyan}${path.resolve(baseDir)}${colors.reset}`);
159
+ printSeparator();
160
+ }
161
+
162
+ async function executeViaWorker(
163
+ config: LauncherConfig
164
+ ): Promise<ExecutionResult> {
165
+ const workerUrl = config.workerUrl || 'http://localhost:8787';
166
+ const endpoint = `${workerUrl}/execute`;
167
+
168
+ log(`Sending request to Cloudflare Worker: ${endpoint}`);
169
+
170
+ try {
171
+ const response = await fetch(endpoint, {
172
+ method: 'POST',
173
+ headers: {
174
+ 'Content-Type': 'application/json',
175
+ },
176
+ body: JSON.stringify({
177
+ question: config.prompt,
178
+ }),
179
+ });
180
+
181
+ if (!response.ok) {
182
+ const errorText = await response.text();
183
+ throw new Error(`Worker returned ${response.status}: ${errorText}`);
184
+ }
185
+
186
+ const result = (await response.json()) as ExecutionResult;
187
+ return result;
188
+ } catch (error) {
189
+ const errorMessage = error instanceof Error ? error.message : String(error);
190
+ throw new Error(`Failed to execute via worker: ${errorMessage}`);
191
+ }
192
+ }
193
+
194
+ async function executeDirectly(config: LauncherConfig): Promise<ExecutionResult> {
195
+ log('Executing directly using Anthropic SDK...');
196
+
197
+ const anthropic = new Anthropic({
198
+ apiKey: config.anthropicApiKey,
199
+ });
200
+
201
+ // Build enhanced prompt with component context
202
+ let enhancedPrompt = config.prompt;
203
+
204
+ if (config.componentsToInstall) {
205
+ const agents = extractAgents(config.componentsToInstall);
206
+ if (agents.length > 0) {
207
+ enhancedPrompt = `You are Claude Code, an AI assistant specialized in software development.
208
+
209
+ IMPORTANT INSTRUCTIONS:
210
+ 1. Execute the user's request immediately and create the requested code/files
211
+ 2. You have access to the following specialized agents: ${agents.join(', ')}
212
+ 3. Use these agents appropriately for completing the task
213
+ 4. Generate all necessary files and code to fulfill the request
214
+ 5. Be proactive and create a complete, working implementation
215
+
216
+ USER REQUEST: ${config.prompt}
217
+
218
+ Now, please execute this request and provide the code.`;
219
+ }
220
+ }
221
+
222
+ try {
223
+ log('Generating code with Claude Sonnet 4.5...');
224
+
225
+ // Detect if this is a web development request
226
+ const isWebRequest = /html|css|javascript|webpage|website|form|ui|interface|frontend/i.test(enhancedPrompt);
227
+
228
+ const promptContent = isWebRequest
229
+ ? `Create a complete web application for: "${enhancedPrompt}"
230
+
231
+ IMPORTANT FORMAT REQUIREMENTS:
232
+ - Provide complete, working code for ALL files needed
233
+ - Use this EXACT format for each file:
234
+
235
+ \`\`\`html:index.html
236
+ [your HTML code here]
237
+ \`\`\`
238
+
239
+ \`\`\`css:styles.css
240
+ [your CSS code here]
241
+ \`\`\`
242
+
243
+ \`\`\`javascript:script.js
244
+ [your JavaScript code here]
245
+ \`\`\`
246
+
247
+ Requirements:
248
+ - Create a complete, functional web application
249
+ - Include all necessary HTML, CSS, and JavaScript
250
+ - Use modern, responsive design
251
+ - Add proper comments
252
+ - Ensure code is ready to run
253
+ - Do NOT include any explanations, ONLY code blocks with filenames`
254
+ : `Generate Python code to answer: "${enhancedPrompt}"
255
+
256
+ Requirements:
257
+ - Use only Python standard library
258
+ - Print the result using print()
259
+ - Keep code simple and safe
260
+ - Include proper error handling
261
+
262
+ Return ONLY the code, no explanations.`;
263
+
264
+ const codeGeneration = await anthropic.messages.create({
265
+ model: 'claude-sonnet-4-5',
266
+ max_tokens: 4096,
267
+ messages: [
268
+ {
269
+ role: 'user',
270
+ content: promptContent,
271
+ },
272
+ ],
273
+ });
274
+
275
+ const generatedCode =
276
+ codeGeneration.content[0]?.type === 'text'
277
+ ? codeGeneration.content[0].text
278
+ : '';
279
+
280
+ if (!generatedCode) {
281
+ throw new Error('Failed to generate code from Claude');
282
+ }
283
+
284
+ log('Code generated successfully', 'success');
285
+
286
+ // Note: Direct execution would require local Python runtime
287
+ // For now, we return the code for manual execution or deployment
288
+ return {
289
+ success: true,
290
+ question: config.prompt,
291
+ code: generatedCode,
292
+ output: 'Code generated. Deploy to Cloudflare Worker to execute.',
293
+ error: '',
294
+ };
295
+ } catch (error) {
296
+ const errorMessage = error instanceof Error ? error.message : String(error);
297
+ throw new Error(`Direct execution failed: ${errorMessage}`);
298
+ }
299
+ }
300
+
301
+ function extractAgents(componentsString: string): string[] {
302
+ const agents: string[] = [];
303
+ const parts = componentsString.split('--');
304
+
305
+ for (const part of parts) {
306
+ const trimmed = part.trim();
307
+ if (trimmed.startsWith('agent ')) {
308
+ const agentNames = trimmed.substring(6).trim();
309
+ if (agentNames) {
310
+ agents.push(...agentNames.split(',').map((a) => a.trim()));
311
+ }
312
+ }
313
+ }
314
+
315
+ return agents;
316
+ }
317
+
318
+ function displayResults(result: ExecutionResult, targetDir?: string) {
319
+ console.log('');
320
+ printSeparator();
321
+ console.log(`${colors.bright}📊 EXECUTION RESULTS${colors.reset}`);
322
+ printSeparator();
323
+ console.log('');
324
+
325
+ console.log(`${colors.cyan}Question:${colors.reset} ${result.question}`);
326
+ console.log('');
327
+
328
+ console.log(`${colors.cyan}Generated Code:${colors.reset}`);
329
+ printSeparator('-');
330
+ console.log(result.code);
331
+ printSeparator('-');
332
+ console.log('');
333
+
334
+ if (result.output) {
335
+ console.log(`${colors.cyan}Output:${colors.reset}`);
336
+ console.log(result.output);
337
+ console.log('');
338
+ }
339
+
340
+ if (result.error) {
341
+ console.log(`${colors.red}Error:${colors.reset}`);
342
+ console.log(result.error);
343
+ console.log('');
344
+ }
345
+
346
+ if (result.sandboxId) {
347
+ console.log(`${colors.dim}Sandbox ID: ${result.sandboxId}${colors.reset}`);
348
+ }
349
+
350
+ console.log(`${colors.green}Status:${colors.reset} ${result.success ? 'Success ✓' : 'Failed ✗'}`);
351
+ printSeparator();
352
+
353
+ // Extract and save files
354
+ const files = extractFilesFromCode(result.code);
355
+ if (files.length > 0) {
356
+ // Generate unique directory name with timestamp (similar to E2B's sandbox-xxxxxxxx)
357
+ const timestamp = Date.now().toString(36);
358
+ const baseDir = targetDir || process.cwd();
359
+ const outputDir = path.join(baseDir, `cloudflare-${timestamp}`);
360
+ saveFilesToDirectory(files, outputDir);
361
+ }
362
+ }
363
+
364
+ async function checkWorkerAvailability(url: string): Promise<boolean> {
365
+ try {
366
+ const response = await fetch(url, { method: 'GET' });
367
+ return response.ok || response.status === 405; // 405 is fine, means worker is up
368
+ } catch {
369
+ return false;
370
+ }
371
+ }
372
+
373
+ async function main() {
374
+ // Parse command line arguments
375
+ const args = process.argv.slice(2);
376
+
377
+ if (args.length < 1) {
378
+ console.log('Cloudflare Sandbox Launcher');
379
+ console.log('');
380
+ console.log('Usage:');
381
+ console.log(' node launcher.ts <prompt> [components] [anthropic_api_key] [worker_url]');
382
+ console.log('');
383
+ console.log('Examples:');
384
+ console.log(' node launcher.ts "Calculate factorial of 5"');
385
+ console.log(' node launcher.ts "Create a React app" "--agent frontend-developer" YOUR_KEY');
386
+ console.log(' node launcher.ts "Fibonacci" "" YOUR_KEY https://your-worker.workers.dev');
387
+ console.log('');
388
+ console.log('Environment Variables:');
389
+ console.log(' ANTHROPIC_API_KEY - Anthropic API key');
390
+ console.log(' CLOUDFLARE_WORKER_URL - Cloudflare Worker endpoint');
391
+ process.exit(1);
392
+ }
393
+
394
+ const config: LauncherConfig = {
395
+ prompt: args[0],
396
+ componentsToInstall: args[1] || '',
397
+ anthropicApiKey: args[2] || process.env.ANTHROPIC_API_KEY || '',
398
+ workerUrl: args[3] || process.env.CLOUDFLARE_WORKER_URL || 'http://localhost:8787',
399
+ targetDir: args[4] || process.cwd(),
400
+ useLocalWorker: true,
401
+ };
402
+
403
+ if (!config.anthropicApiKey) {
404
+ log('Error: Anthropic API key is required', 'error');
405
+ console.log('Provide via command line argument or ANTHROPIC_API_KEY environment variable');
406
+ process.exit(1);
407
+ }
408
+
409
+ console.log('');
410
+ printSeparator();
411
+ console.log(`${colors.bright}☁️ CLOUDFLARE SANDBOX LAUNCHER${colors.reset}`);
412
+ printSeparator();
413
+ console.log('');
414
+
415
+ log(`Prompt: "${config.prompt.substring(0, 100)}${config.prompt.length > 100 ? '...' : ''}"`);
416
+
417
+ if (config.componentsToInstall) {
418
+ const agents = extractAgents(config.componentsToInstall);
419
+ if (agents.length > 0) {
420
+ log(`Agents: ${agents.join(', ')}`);
421
+ }
422
+ }
423
+
424
+ console.log('');
425
+
426
+ try {
427
+ // Check if worker is available
428
+ log('Checking Cloudflare Worker availability...');
429
+ const workerAvailable = await checkWorkerAvailability(config.workerUrl || 'http://localhost:8787');
430
+
431
+ let result: ExecutionResult;
432
+
433
+ if (workerAvailable) {
434
+ log('Cloudflare Worker is available', 'success');
435
+ log('Executing via Cloudflare Sandbox...');
436
+ result = await executeViaWorker(config);
437
+ } else {
438
+ log('Cloudflare Worker not available, using direct execution', 'warning');
439
+ log('For full sandbox execution, deploy worker with: npx wrangler deploy', 'warning');
440
+ result = await executeDirectly(config);
441
+ }
442
+
443
+ displayResults(result, config.targetDir);
444
+
445
+ if (!result.success) {
446
+ process.exit(1);
447
+ }
448
+ } catch (error) {
449
+ console.log('');
450
+ log(`Execution failed: ${error instanceof Error ? error.message : String(error)}`, 'error');
451
+ console.log('');
452
+
453
+ log('Troubleshooting:', 'info');
454
+ console.log('1. Ensure Cloudflare Worker is deployed: npx wrangler deploy');
455
+ console.log('2. Check API key is set: npx wrangler secret put ANTHROPIC_API_KEY');
456
+ console.log('3. Wait 2-3 minutes after first deployment for container provisioning');
457
+ console.log('4. Check container status: npx wrangler containers list');
458
+ console.log('5. For local testing: npm run dev');
459
+
460
+ process.exit(1);
461
+ }
462
+ }
463
+
464
+ export { executeViaWorker, executeDirectly, type LauncherConfig, type ExecutionResult };
465
+
466
+ // Run if executed directly (ES modules compatible)
467
+ if (import.meta.url === `file://${process.argv[1]}`) {
468
+ main().catch((error) => {
469
+ console.error('Fatal error:', error);
470
+ process.exit(1);
471
+ });
472
+ }