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,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
|
+
}
|