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,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"types": ["@cloudflare/workers-types", "node"],
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["src/*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"src/**/*.ts",
|
|
23
|
+
"launcher.ts",
|
|
24
|
+
"monitor.ts"
|
|
25
|
+
],
|
|
26
|
+
"exclude": [
|
|
27
|
+
"node_modules",
|
|
28
|
+
"dist",
|
|
29
|
+
".wrangler"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#:schema node_modules/wrangler/config-schema.json
|
|
2
|
+
name = "cloudflare-claude-sandbox"
|
|
3
|
+
main = "src/index.ts"
|
|
4
|
+
compatibility_date = "2024-01-01"
|
|
5
|
+
compatibility_flags = ["nodejs_compat"]
|
|
6
|
+
|
|
7
|
+
# Durable Objects configuration for sandbox persistence
|
|
8
|
+
[[durable_objects.bindings]]
|
|
9
|
+
name = "Sandbox"
|
|
10
|
+
class_name = "Sandbox"
|
|
11
|
+
|
|
12
|
+
# Durable Object migration (required for first deployment)
|
|
13
|
+
[[migrations]]
|
|
14
|
+
tag = "v1"
|
|
15
|
+
new_classes = ["Sandbox"]
|
|
16
|
+
|
|
17
|
+
# Environment variables (non-secret)
|
|
18
|
+
[vars]
|
|
19
|
+
ENVIRONMENT = "production"
|
|
20
|
+
|
|
21
|
+
# Development environment configuration
|
|
22
|
+
[env.development]
|
|
23
|
+
vars = { ENVIRONMENT = "development" }
|
|
24
|
+
|
|
25
|
+
# Staging environment configuration
|
|
26
|
+
[env.staging]
|
|
27
|
+
vars = { ENVIRONMENT = "staging" }
|
|
28
|
+
|
|
29
|
+
# Resource limits
|
|
30
|
+
[limits]
|
|
31
|
+
# CPU time limit per request (milliseconds)
|
|
32
|
+
cpu_ms = 50
|
|
33
|
+
|
|
34
|
+
# Build configuration
|
|
35
|
+
[build]
|
|
36
|
+
command = ""
|
|
37
|
+
|
|
38
|
+
# Observability
|
|
39
|
+
[observability]
|
|
40
|
+
enabled = true
|
|
41
|
+
|
|
42
|
+
# Usage model - required for Durable Objects
|
|
43
|
+
[usage_model]
|
|
44
|
+
# Use 'bundled' for Workers Paid plan ($5/month)
|
|
45
|
+
# Includes 10M requests/month and unlimited Durable Objects
|
|
46
|
+
# For free tier, comment this out (limited functionality)
|
|
47
|
+
# usage_model = "bundled"
|
|
48
|
+
|
|
49
|
+
# Note: Uncomment the line above after setting up Workers Paid plan
|
|
50
|
+
# Or remove this section entirely for the free tier (sandbox will be limited)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.2",
|
|
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": {
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"bin/",
|
|
93
93
|
"src/",
|
|
94
94
|
"components/sandbox/e2b/",
|
|
95
|
+
"components/sandbox/cloudflare/",
|
|
95
96
|
"README.md"
|
|
96
97
|
],
|
|
97
98
|
"devDependencies": {
|
package/src/index.js
CHANGED
|
@@ -1399,124 +1399,89 @@ async function installIndividualSkill(skillName, targetDir, options) {
|
|
|
1399
1399
|
// Extract the actual skill name (last part of the path)
|
|
1400
1400
|
const skillBaseName = skillName.includes('/') ? skillName.split('/').pop() : skillName;
|
|
1401
1401
|
|
|
1402
|
-
//
|
|
1403
|
-
|
|
1404
|
-
const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/skills/${skillName}/SKILL.md`;
|
|
1405
|
-
|
|
1406
|
-
console.log(chalk.gray(`š„ Downloading from GitHub (main branch)...`));
|
|
1407
|
-
|
|
1408
|
-
const response = await fetch(githubUrl);
|
|
1409
|
-
if (!response.ok) {
|
|
1410
|
-
if (response.status === 404) {
|
|
1411
|
-
console.log(chalk.red(`ā Skill "${skillName}" not found`));
|
|
1412
|
-
console.log(chalk.yellow('š” Tip: Use format "category/skill-name" (e.g., creative-design/algorithmic-art)'));
|
|
1413
|
-
console.log(chalk.yellow('Available categories: creative-design, development, document-processing, enterprise-communication'));
|
|
1414
|
-
return false;
|
|
1415
|
-
}
|
|
1416
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
const skillContent = await response.text();
|
|
1420
|
-
|
|
1421
|
-
// Check if there are additional files to download (e.g., referenced .md files, scripts, reference)
|
|
1422
|
-
const additionalFiles = {};
|
|
1402
|
+
// Use GitHub API to download ALL files and directories for the skill
|
|
1403
|
+
const githubApiUrl = `https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/skills/${skillName}`;
|
|
1423
1404
|
|
|
1424
|
-
|
|
1425
|
-
const referencePattern = /\[([A-Z_]+\.md)\]\(\1\)|\[.*?\]\(\.\/([a-z_]+)\/\)/g;
|
|
1426
|
-
let match;
|
|
1405
|
+
console.log(chalk.gray(`š„ Downloading skill from GitHub (main branch)...`));
|
|
1427
1406
|
|
|
1428
|
-
|
|
1429
|
-
const referencedFile = match[1] || match[2];
|
|
1430
|
-
const referencedUrl = githubUrl.replace('SKILL.md', referencedFile);
|
|
1407
|
+
const downloadedFiles = {};
|
|
1431
1408
|
|
|
1409
|
+
// Recursive function to download all files and directories
|
|
1410
|
+
async function downloadDirectory(apiUrl, relativePath = '') {
|
|
1432
1411
|
try {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
executable: false
|
|
1440
|
-
};
|
|
1441
|
-
console.log(chalk.green(`ā Found referenced file: ${referencedFile}`));
|
|
1442
|
-
}
|
|
1443
|
-
} catch (error) {
|
|
1444
|
-
// Referenced file is optional, continue if not found
|
|
1445
|
-
console.log(chalk.gray(` (Referenced file ${referencedFile} not found, continuing...)`));
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// 2. Try to download common directories (scripts/, reference/) from GitHub API
|
|
1450
|
-
const githubApiUrl = `https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/skills/${skillName}`;
|
|
1412
|
+
const response = await fetch(apiUrl, {
|
|
1413
|
+
headers: {
|
|
1414
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
1415
|
+
'User-Agent': 'claude-code-templates'
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1451
1418
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1419
|
+
if (!response.ok) {
|
|
1420
|
+
if (response.status === 404) {
|
|
1421
|
+
console.log(chalk.red(`ā Skill "${skillName}" not found`));
|
|
1422
|
+
console.log(chalk.yellow('š” Tip: Use format "category/skill-name" (e.g., creative-design/algorithmic-art)'));
|
|
1423
|
+
console.log(chalk.yellow('Available categories: creative-design, development, document-processing, enterprise-communication'));
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1457
1427
|
}
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
if (dirResponse.ok) {
|
|
1461
|
-
const dirContents = await dirResponse.json();
|
|
1462
|
-
|
|
1463
|
-
// Find directories (scripts, reference, templates)
|
|
1464
|
-
for (const item of dirContents) {
|
|
1465
|
-
if (item.type === 'dir' && ['scripts', 'reference', 'templates'].includes(item.name)) {
|
|
1466
|
-
console.log(chalk.gray(`š Found directory: ${item.name}/`));
|
|
1467
1428
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
additionalFiles[`.claude/skills/${skillBaseName}/${item.name}/${file.name}`] = {
|
|
1488
|
-
content: fileContent,
|
|
1489
|
-
executable: isExecutable
|
|
1490
|
-
};
|
|
1491
|
-
console.log(chalk.green(`ā Downloaded: ${item.name}/${file.name}`));
|
|
1492
|
-
}
|
|
1493
|
-
} catch (err) {
|
|
1494
|
-
console.log(chalk.gray(` (Could not download ${item.name}/${file.name})`));
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1429
|
+
const contents = await response.json();
|
|
1430
|
+
|
|
1431
|
+
for (const item of contents) {
|
|
1432
|
+
const itemPath = relativePath ? `${relativePath}/${item.name}` : item.name;
|
|
1433
|
+
|
|
1434
|
+
if (item.type === 'file') {
|
|
1435
|
+
// Download file
|
|
1436
|
+
try {
|
|
1437
|
+
const fileResponse = await fetch(item.download_url);
|
|
1438
|
+
if (fileResponse.ok) {
|
|
1439
|
+
const fileContent = await fileResponse.text();
|
|
1440
|
+
const isExecutable = item.name.endsWith('.py') || item.name.endsWith('.sh');
|
|
1441
|
+
|
|
1442
|
+
const targetPath = `.claude/skills/${skillBaseName}/${itemPath}`;
|
|
1443
|
+
downloadedFiles[targetPath] = {
|
|
1444
|
+
content: fileContent,
|
|
1445
|
+
executable: isExecutable
|
|
1446
|
+
};
|
|
1447
|
+
console.log(chalk.green(`ā Downloaded: ${itemPath}`));
|
|
1497
1448
|
}
|
|
1449
|
+
} catch (err) {
|
|
1450
|
+
console.log(chalk.gray(` (Could not download ${itemPath})`));
|
|
1498
1451
|
}
|
|
1452
|
+
} else if (item.type === 'dir') {
|
|
1453
|
+
// Recursively download directory contents
|
|
1454
|
+
console.log(chalk.gray(`š Downloading directory: ${itemPath}/`));
|
|
1455
|
+
await downloadDirectory(item.url, itemPath);
|
|
1499
1456
|
}
|
|
1500
1457
|
}
|
|
1458
|
+
|
|
1459
|
+
return true;
|
|
1460
|
+
} catch (error) {
|
|
1461
|
+
console.log(chalk.gray(` (Could not access GitHub API: ${error.message})`));
|
|
1462
|
+
return false;
|
|
1501
1463
|
}
|
|
1502
|
-
} catch (error) {
|
|
1503
|
-
// GitHub API access optional, continue if not available
|
|
1504
|
-
console.log(chalk.gray(` (Could not access GitHub API for directory listing)`));
|
|
1505
1464
|
}
|
|
1506
1465
|
|
|
1507
|
-
//
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
1466
|
+
// Download all files from the skill directory
|
|
1467
|
+
const success = await downloadDirectory(githubApiUrl);
|
|
1468
|
+
if (!success) {
|
|
1469
|
+
return false;
|
|
1470
|
+
}
|
|
1511
1471
|
|
|
1512
|
-
//
|
|
1513
|
-
const
|
|
1472
|
+
// Check if SKILL.md was downloaded (required)
|
|
1473
|
+
const skillMdPath = `.claude/skills/${skillBaseName}/SKILL.md`;
|
|
1474
|
+
if (!downloadedFiles[skillMdPath]) {
|
|
1475
|
+
console.log(chalk.red(`ā SKILL.md not found in skill directory`));
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1514
1478
|
|
|
1515
|
-
//
|
|
1516
|
-
|
|
1479
|
+
// Create .claude/skills/skill-name directory (Anthropic standard structure)
|
|
1480
|
+
const skillsDir = path.join(targetDir, '.claude', 'skills');
|
|
1481
|
+
await fs.ensureDir(skillsDir);
|
|
1517
1482
|
|
|
1518
|
-
// Write
|
|
1519
|
-
for (const [filePath, fileData] of Object.entries(
|
|
1483
|
+
// Write all downloaded files
|
|
1484
|
+
for (const [filePath, fileData] of Object.entries(downloadedFiles)) {
|
|
1520
1485
|
const fullPath = path.join(targetDir, filePath);
|
|
1521
1486
|
await fs.ensureDir(path.dirname(fullPath));
|
|
1522
1487
|
await fs.writeFile(fullPath, fileData.content, 'utf8');
|
|
@@ -1526,13 +1491,13 @@ async function installIndividualSkill(skillName, targetDir, options) {
|
|
|
1526
1491
|
}
|
|
1527
1492
|
}
|
|
1528
1493
|
|
|
1494
|
+
const targetFile = path.join(skillsDir, skillBaseName, 'SKILL.md');
|
|
1495
|
+
|
|
1529
1496
|
if (!options.silent) {
|
|
1530
1497
|
console.log(chalk.green(`ā
Skill "${skillName}" installed successfully!`));
|
|
1531
1498
|
console.log(chalk.cyan(`š Installed to: ${path.relative(targetDir, targetFile)}`));
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1535
|
-
console.log(chalk.cyan(`š¦ Downloaded from: ${githubUrl}`));
|
|
1499
|
+
console.log(chalk.cyan(`š Total files downloaded: ${Object.keys(downloadedFiles).length}`));
|
|
1500
|
+
console.log(chalk.cyan(`š¦ Downloaded from: ${githubApiUrl}`));
|
|
1536
1501
|
}
|
|
1537
1502
|
|
|
1538
1503
|
// Track successful skill installation
|
|
@@ -1540,7 +1505,7 @@ async function installIndividualSkill(skillName, targetDir, options) {
|
|
|
1540
1505
|
installation_type: 'individual_skill',
|
|
1541
1506
|
target_directory: path.relative(process.cwd(), targetDir),
|
|
1542
1507
|
source: 'github_main',
|
|
1543
|
-
|
|
1508
|
+
total_files: Object.keys(downloadedFiles).length
|
|
1544
1509
|
});
|
|
1545
1510
|
|
|
1546
1511
|
return true;
|
|
@@ -2430,10 +2395,11 @@ async function executeSandbox(options, targetDir) {
|
|
|
2430
2395
|
let { agent, prompt } = options;
|
|
2431
2396
|
|
|
2432
2397
|
// Validate sandbox provider
|
|
2433
|
-
if (sandbox !== 'e2b') {
|
|
2434
|
-
console.log(chalk.red('ā Error:
|
|
2435
|
-
console.log(chalk.yellow('š” Available providers: e2b'));
|
|
2398
|
+
if (sandbox !== 'e2b' && sandbox !== 'cloudflare') {
|
|
2399
|
+
console.log(chalk.red('ā Error: Invalid sandbox provider'));
|
|
2400
|
+
console.log(chalk.yellow('š” Available providers: e2b, cloudflare'));
|
|
2436
2401
|
console.log(chalk.gray(' Example: --sandbox e2b --prompt "Create a web app"'));
|
|
2402
|
+
console.log(chalk.gray(' Example: --sandbox cloudflare --prompt "Calculate factorial of 5"'));
|
|
2437
2403
|
return;
|
|
2438
2404
|
}
|
|
2439
2405
|
|
|
@@ -2542,28 +2508,247 @@ async function executeSandbox(options, targetDir) {
|
|
|
2542
2508
|
// Ignore .env loading errors
|
|
2543
2509
|
}
|
|
2544
2510
|
|
|
2545
|
-
// Check for API keys
|
|
2546
|
-
const e2bKey = e2bApiKey || process.env.E2B_API_KEY;
|
|
2511
|
+
// Check for API keys based on sandbox provider
|
|
2547
2512
|
const anthropicKey = anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
2548
|
-
|
|
2549
|
-
if (
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2513
|
+
|
|
2514
|
+
if (sandbox === 'e2b') {
|
|
2515
|
+
const e2bKey = e2bApiKey || process.env.E2B_API_KEY;
|
|
2516
|
+
|
|
2517
|
+
if (!e2bKey) {
|
|
2518
|
+
console.log(chalk.red('ā Error: E2B API key is required'));
|
|
2519
|
+
console.log(chalk.yellow('š” Options:'));
|
|
2520
|
+
console.log(chalk.gray(' 1. Set environment variable: E2B_API_KEY=your_key'));
|
|
2521
|
+
console.log(chalk.gray(' 2. Use CLI parameter: --e2b-api-key your_key'));
|
|
2522
|
+
console.log(chalk.blue(' Get your key at: https://e2b.dev/dashboard'));
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
if (!anthropicKey) {
|
|
2527
|
+
console.log(chalk.red('ā Error: Anthropic API key is required'));
|
|
2528
|
+
console.log(chalk.yellow('š” Options:'));
|
|
2529
|
+
console.log(chalk.gray(' 1. Set environment variable: ANTHROPIC_API_KEY=your_key'));
|
|
2530
|
+
console.log(chalk.gray(' 2. Use CLI parameter: --anthropic-api-key your_key'));
|
|
2531
|
+
console.log(chalk.blue(' Get your key at: https://console.anthropic.com'));
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// Execute E2B sandbox
|
|
2536
|
+
await executeE2BSandbox({ sandbox, agent, prompt, command, mcp, setting, hook, e2bKey, anthropicKey }, targetDir);
|
|
2537
|
+
|
|
2538
|
+
} else if (sandbox === 'cloudflare') {
|
|
2539
|
+
if (!anthropicKey) {
|
|
2540
|
+
console.log(chalk.red('ā Error: Anthropic API key is required for Cloudflare sandbox'));
|
|
2541
|
+
console.log(chalk.yellow('š” Options:'));
|
|
2542
|
+
console.log(chalk.gray(' 1. Set environment variable: ANTHROPIC_API_KEY=your_key'));
|
|
2543
|
+
console.log(chalk.gray(' 2. Use CLI parameter: --anthropic-api-key your_key'));
|
|
2544
|
+
console.log(chalk.blue(' Get your key at: https://console.anthropic.com'));
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// Execute Cloudflare sandbox
|
|
2549
|
+
await executeCloudflareSandbox({ sandbox, agent, prompt, command, mcp, setting, hook, anthropicKey }, targetDir);
|
|
2556
2550
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
async function executeCloudflareSandbox(options, targetDir) {
|
|
2554
|
+
const { agent, prompt, anthropicKey } = options;
|
|
2555
|
+
|
|
2556
|
+
console.log(chalk.blue('\nāļø Cloudflare Sandbox Execution'));
|
|
2557
|
+
console.log(chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
2558
|
+
|
|
2559
|
+
if (agent) {
|
|
2560
|
+
const agentList = agent.split(',');
|
|
2561
|
+
if (agentList.length > 1) {
|
|
2562
|
+
console.log(chalk.white(`š Agents (${agentList.length}):`));
|
|
2563
|
+
agentList.forEach(a => console.log(chalk.yellow(` ⢠${a.trim()}`)));
|
|
2564
|
+
} else {
|
|
2565
|
+
console.log(chalk.white(`š Agent: ${chalk.yellow(agent)}`));
|
|
2566
|
+
}
|
|
2567
|
+
} else {
|
|
2568
|
+
console.log(chalk.white(`š Agent: ${chalk.yellow('default')}`));
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
const truncatedPrompt = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
|
|
2572
|
+
console.log(chalk.white(`š Prompt: ${chalk.cyan('"' + truncatedPrompt + '"')}`));
|
|
2573
|
+
console.log(chalk.white(`š Provider: ${chalk.green('Cloudflare Workers')}`));
|
|
2574
|
+
console.log(chalk.gray('\nš§ Execution details:'));
|
|
2575
|
+
console.log(chalk.gray(' ⢠Uses Claude AI for code generation'));
|
|
2576
|
+
console.log(chalk.gray(' ⢠Executes in isolated Cloudflare sandbox'));
|
|
2577
|
+
console.log(chalk.gray(' ⢠Global edge deployment for low latency\n'));
|
|
2578
|
+
|
|
2579
|
+
const inquirer = require('inquirer');
|
|
2580
|
+
|
|
2581
|
+
const { shouldExecute } = await inquirer.prompt([{
|
|
2582
|
+
type: 'confirm',
|
|
2583
|
+
name: 'shouldExecute',
|
|
2584
|
+
message: 'Execute this prompt in Cloudflare sandbox?',
|
|
2585
|
+
default: true
|
|
2586
|
+
}]);
|
|
2587
|
+
|
|
2588
|
+
if (!shouldExecute) {
|
|
2589
|
+
console.log(chalk.yellow('ā¹ļø Cloudflare sandbox execution cancelled by user.'));
|
|
2564
2590
|
return;
|
|
2565
2591
|
}
|
|
2566
|
-
|
|
2592
|
+
|
|
2593
|
+
try {
|
|
2594
|
+
console.log(chalk.blue('š® Setting up Cloudflare sandbox environment...'));
|
|
2595
|
+
|
|
2596
|
+
const spinner = ora('Installing Cloudflare sandbox component...').start();
|
|
2597
|
+
|
|
2598
|
+
// Create .claude/sandbox/cloudflare directory
|
|
2599
|
+
const sandboxDir = path.join(targetDir, '.claude', 'sandbox', 'cloudflare');
|
|
2600
|
+
await fs.ensureDir(sandboxDir);
|
|
2601
|
+
|
|
2602
|
+
// Copy Cloudflare component files
|
|
2603
|
+
const componentsDir = path.join(__dirname, '..', 'components', 'sandbox', 'cloudflare');
|
|
2604
|
+
|
|
2605
|
+
try {
|
|
2606
|
+
if (await fs.pathExists(componentsDir)) {
|
|
2607
|
+
console.log(chalk.gray('š¦ Using local Cloudflare component files...'));
|
|
2608
|
+
|
|
2609
|
+
// Copy all files from cloudflare directory
|
|
2610
|
+
await fs.copy(componentsDir, sandboxDir, {
|
|
2611
|
+
overwrite: true,
|
|
2612
|
+
filter: (src) => {
|
|
2613
|
+
// Exclude node_modules and build artifacts
|
|
2614
|
+
return !src.includes('node_modules') &&
|
|
2615
|
+
!src.includes('.wrangler') &&
|
|
2616
|
+
!src.includes('dist');
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
} else {
|
|
2620
|
+
throw new Error('Cloudflare component files not found in package');
|
|
2621
|
+
}
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
spinner.fail(`Failed to install Cloudflare component: ${error.message}`);
|
|
2624
|
+
throw error;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
spinner.succeed('Cloudflare sandbox component installed successfully');
|
|
2628
|
+
|
|
2629
|
+
// Check for Node.js
|
|
2630
|
+
const nodeSpinner = ora('Checking Node.js environment...').start();
|
|
2631
|
+
|
|
2632
|
+
try {
|
|
2633
|
+
const { spawn } = require('child_process');
|
|
2634
|
+
|
|
2635
|
+
// Check Node.js version
|
|
2636
|
+
const checkNode = () => {
|
|
2637
|
+
return new Promise((resolve) => {
|
|
2638
|
+
const check = spawn('node', ['--version'], { stdio: 'pipe' });
|
|
2639
|
+
check.on('close', (code) => resolve(code === 0));
|
|
2640
|
+
check.on('error', () => resolve(false));
|
|
2641
|
+
});
|
|
2642
|
+
};
|
|
2643
|
+
|
|
2644
|
+
const nodeAvailable = await checkNode();
|
|
2645
|
+
if (!nodeAvailable) {
|
|
2646
|
+
nodeSpinner.fail('Node.js not found');
|
|
2647
|
+
console.log(chalk.red('ā Node.js 16.17.0+ is required for Cloudflare sandbox'));
|
|
2648
|
+
console.log(chalk.yellow('š” Please install Node.js and try again'));
|
|
2649
|
+
console.log(chalk.blue(' Visit: https://nodejs.org'));
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
nodeSpinner.succeed('Node.js environment ready');
|
|
2654
|
+
|
|
2655
|
+
// Install NPM dependencies
|
|
2656
|
+
const depSpinner = ora('Installing Cloudflare dependencies...').start();
|
|
2657
|
+
|
|
2658
|
+
const npmInstall = spawn('npm', ['install'], {
|
|
2659
|
+
cwd: sandboxDir,
|
|
2660
|
+
stdio: 'pipe'
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
let npmOutput = '';
|
|
2664
|
+
let npmError = '';
|
|
2665
|
+
|
|
2666
|
+
npmInstall.stdout.on('data', (data) => {
|
|
2667
|
+
npmOutput += data.toString();
|
|
2668
|
+
});
|
|
2669
|
+
|
|
2670
|
+
npmInstall.stderr.on('data', (data) => {
|
|
2671
|
+
npmError += data.toString();
|
|
2672
|
+
});
|
|
2673
|
+
|
|
2674
|
+
await new Promise((resolve, reject) => {
|
|
2675
|
+
npmInstall.on('close', async (npmCode) => {
|
|
2676
|
+
if (npmCode === 0) {
|
|
2677
|
+
depSpinner.succeed('Cloudflare dependencies installed successfully');
|
|
2678
|
+
|
|
2679
|
+
// Execute using launcher
|
|
2680
|
+
console.log(chalk.blue('š Launching Cloudflare sandbox...'));
|
|
2681
|
+
console.log(chalk.gray(`š Prompt: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`));
|
|
2682
|
+
|
|
2683
|
+
// Use ts-node or tsx to execute TypeScript launcher
|
|
2684
|
+
const launcherPath = path.join(sandboxDir, 'launcher.ts');
|
|
2685
|
+
|
|
2686
|
+
console.log(chalk.blue('š Starting Cloudflare sandbox execution...'));
|
|
2687
|
+
|
|
2688
|
+
const sandboxExecution = spawn('npx', [
|
|
2689
|
+
'tsx',
|
|
2690
|
+
launcherPath,
|
|
2691
|
+
prompt,
|
|
2692
|
+
agent || '',
|
|
2693
|
+
anthropicKey,
|
|
2694
|
+
'http://localhost:8787', // Local dev server URL
|
|
2695
|
+
targetDir // Project root directory for file output
|
|
2696
|
+
], {
|
|
2697
|
+
cwd: sandboxDir,
|
|
2698
|
+
stdio: 'inherit',
|
|
2699
|
+
timeout: 300000, // 5 minutes
|
|
2700
|
+
env: {
|
|
2701
|
+
...process.env,
|
|
2702
|
+
ANTHROPIC_API_KEY: anthropicKey
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2705
|
+
|
|
2706
|
+
sandboxExecution.on('close', (code) => {
|
|
2707
|
+
if (code === 0) {
|
|
2708
|
+
console.log(chalk.green('š Cloudflare sandbox execution completed successfully!'));
|
|
2709
|
+
resolve();
|
|
2710
|
+
} else if (code === null) {
|
|
2711
|
+
console.log(chalk.yellow('ā¹ļø Sandbox execution was cancelled'));
|
|
2712
|
+
resolve();
|
|
2713
|
+
} else {
|
|
2714
|
+
console.log(chalk.yellow(`ā ļø Sandbox execution finished with exit code ${code}`));
|
|
2715
|
+
console.log(chalk.gray('š” Check the output above for error details'));
|
|
2716
|
+
resolve();
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
sandboxExecution.on('error', (error) => {
|
|
2721
|
+
console.log(chalk.red(`ā Error executing sandbox: ${error.message}`));
|
|
2722
|
+
console.log(chalk.yellow('š” Make sure you have set ANTHROPIC_API_KEY'));
|
|
2723
|
+
reject(error);
|
|
2724
|
+
});
|
|
2725
|
+
} else {
|
|
2726
|
+
depSpinner.fail('Failed to install Cloudflare dependencies');
|
|
2727
|
+
console.log(chalk.red(`ā npm install failed with exit code ${npmCode}`));
|
|
2728
|
+
if (npmError) {
|
|
2729
|
+
console.log(chalk.red('Error output:'));
|
|
2730
|
+
console.log(chalk.gray(npmError.trim()));
|
|
2731
|
+
}
|
|
2732
|
+
reject(new Error('Failed to install dependencies'));
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
});
|
|
2736
|
+
|
|
2737
|
+
} catch (error) {
|
|
2738
|
+
nodeSpinner.fail('Failed to check Node.js environment');
|
|
2739
|
+
console.log(chalk.red(`ā Error: ${error.message}`));
|
|
2740
|
+
throw error;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
console.log(chalk.red(`ā Error setting up Cloudflare sandbox: ${error.message}`));
|
|
2745
|
+
console.log(chalk.yellow('š” Please check your internet connection and try again'));
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
async function executeE2BSandbox(options, targetDir) {
|
|
2750
|
+
const { agent, prompt, command, mcp, setting, hook, e2bKey, anthropicKey } = options;
|
|
2751
|
+
|
|
2567
2752
|
// Sandbox execution confirmation
|
|
2568
2753
|
console.log(chalk.blue('\nāļø E2B Sandbox Execution'));
|
|
2569
2754
|
console.log(chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|