claude-code-templates 1.20.3 → 1.21.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/src/index.js CHANGED
@@ -115,6 +115,18 @@ async function createClaudeConfig(options = {}) {
115
115
  return;
116
116
  }
117
117
 
118
+ // Handle Claude Code Studio launch
119
+ if (options.studio) {
120
+ await launchClaudeCodeStudio(options, targetDir);
121
+ return;
122
+ }
123
+
124
+ // Handle sandbox execution FIRST (before individual components)
125
+ if (options.sandbox) {
126
+ await executeSandbox(options, targetDir);
127
+ return;
128
+ }
129
+
118
130
  // Handle multiple components installation (new approach)
119
131
  if (options.agent || options.command || options.mcp || options.setting || options.hook) {
120
132
  // If --workflow is used with components, treat it as YAML
@@ -155,6 +167,8 @@ async function createClaudeConfig(options = {}) {
155
167
  return;
156
168
  }
157
169
 
170
+ // (Sandbox execution handled earlier)
171
+
158
172
  // Handle command stats analysis (both singular and plural)
159
173
  if (options.commandStats || options.commandsStats) {
160
174
  await runCommandStats(options);
@@ -362,8 +376,8 @@ async function createClaudeConfig(options = {}) {
362
376
  await runPostInstallationValidation(targetDir, templateConfig);
363
377
  }
364
378
 
365
- // Handle prompt execution if provided
366
- if (options.prompt) {
379
+ // Handle prompt execution if provided (but not in sandbox mode)
380
+ if (options.prompt && !options.sandbox) {
367
381
  await handlePromptExecution(options.prompt, targetDir);
368
382
  }
369
383
  }
@@ -1444,8 +1458,8 @@ async function installMultipleComponents(options, targetDir) {
1444
1458
 
1445
1459
  // Note: Individual components are already tracked separately in their installation functions
1446
1460
 
1447
- // Handle prompt execution if provided
1448
- if (options.prompt) {
1461
+ // Handle prompt execution if provided (but not in sandbox mode)
1462
+ if (options.prompt && !options.sandbox) {
1449
1463
  await handlePromptExecution(options.prompt, targetDir);
1450
1464
  }
1451
1465
 
@@ -1613,8 +1627,8 @@ async function installWorkflow(workflowHash, targetDir, options) {
1613
1627
  target_directory: path.relative(process.cwd(), targetDir)
1614
1628
  });
1615
1629
 
1616
- // Handle prompt execution if provided
1617
- if (options.prompt) {
1630
+ // Handle prompt execution if provided (but not in sandbox mode)
1631
+ if (options.prompt && !options.sandbox) {
1618
1632
  await handlePromptExecution(options.prompt, targetDir);
1619
1633
  }
1620
1634
 
@@ -2077,4 +2091,427 @@ async function handlePromptExecution(prompt, targetDir) {
2077
2091
  }
2078
2092
  }
2079
2093
 
2094
+ async function launchClaudeCodeStudio(options, targetDir) {
2095
+ console.log(chalk.blue('\nšŸŽØ Claude Code Studio'));
2096
+ console.log(chalk.cyan('═══════════════════════════════════════'));
2097
+ console.log(chalk.white('šŸš€ Starting Claude Code Studio interface...'));
2098
+ console.log(chalk.gray('šŸ’” This interface supports both local and cloud execution'));
2099
+
2100
+ const { spawn } = require('child_process');
2101
+ const open = require('open');
2102
+ const path = require('path');
2103
+
2104
+ // Start the studio server
2105
+ const serverPath = path.join(__dirname, 'sandbox-server.js');
2106
+ const serverProcess = spawn('node', [serverPath], {
2107
+ stdio: 'inherit'
2108
+ });
2109
+
2110
+ // Wait a moment for server to start, then open browser
2111
+ setTimeout(async () => {
2112
+ try {
2113
+ await open('http://localhost:3444');
2114
+ console.log(chalk.green('āœ… Claude Code Studio launched at http://localhost:3444'));
2115
+ console.log(chalk.gray('šŸ’” Choose between Local Machine or E2B Cloud execution'));
2116
+ } catch (error) {
2117
+ console.log(chalk.yellow('šŸ’” Please manually open: http://localhost:3444'));
2118
+ }
2119
+ }, 2000);
2120
+
2121
+ // Handle process cleanup
2122
+ process.on('SIGINT', () => {
2123
+ console.log(chalk.yellow('\nšŸ›‘ Shutting down Claude Code Studio...'));
2124
+ serverProcess.kill();
2125
+ process.exit(0);
2126
+ });
2127
+
2128
+ return;
2129
+ }
2130
+
2131
+ async function executeSandbox(options, targetDir) {
2132
+ const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey } = options;
2133
+ let { agent, prompt } = options;
2134
+
2135
+ // Validate sandbox provider
2136
+ if (sandbox !== 'e2b') {
2137
+ console.log(chalk.red('āŒ Error: Only E2B sandbox is currently supported'));
2138
+ console.log(chalk.yellow('šŸ’” Available providers: e2b'));
2139
+ console.log(chalk.gray(' Example: --sandbox e2b --prompt "Create a web app"'));
2140
+ return;
2141
+ }
2142
+
2143
+ // Interactive agent selection if not provided
2144
+ if (!agent) {
2145
+ const inquirer = require('inquirer');
2146
+
2147
+ console.log(chalk.blue('\nšŸ¤– Agent Selection'));
2148
+ console.log(chalk.cyan('═══════════════════════════════════════'));
2149
+ console.log(chalk.gray('Select an agent for your task, or continue without one.\n'));
2150
+
2151
+ // Fetch available agents
2152
+ console.log(chalk.gray('ā³ Fetching available agents...'));
2153
+ const agents = await getAvailableAgentsFromGitHub();
2154
+
2155
+ // Format agents for selection
2156
+ const agentChoices = agents.map(a => ({
2157
+ name: `${a.path} ${chalk.gray(`- ${a.category}`)}`,
2158
+ value: a.path,
2159
+ short: a.path
2160
+ }));
2161
+
2162
+ // Add option to continue without agent
2163
+ agentChoices.unshift({
2164
+ name: chalk.yellow('⚔ Continue without agent (use default Claude)'),
2165
+ value: null,
2166
+ short: 'No agent'
2167
+ });
2168
+
2169
+ const { selectedAgent } = await inquirer.prompt([{
2170
+ type: 'list',
2171
+ name: 'selectedAgent',
2172
+ message: 'Select an agent for your task:',
2173
+ choices: agentChoices,
2174
+ pageSize: 15
2175
+ }]);
2176
+
2177
+ if (selectedAgent) {
2178
+ agent = selectedAgent;
2179
+ console.log(chalk.green(`āœ… Selected agent: ${chalk.cyan(agent)}`));
2180
+ } else {
2181
+ console.log(chalk.yellow('āš ļø Continuing without specific agent'));
2182
+ }
2183
+ }
2184
+
2185
+ // Get prompt from user if not provided
2186
+ if (!prompt) {
2187
+ console.log(chalk.blue('\nšŸ“ Project Requirements'));
2188
+ console.log(chalk.cyan('═══════════════════════════════════════'));
2189
+ console.log(chalk.gray('Describe what you want to create in detail. The more specific you are,'));
2190
+ console.log(chalk.gray('the better Claude Code will understand your requirements.\n'));
2191
+
2192
+ const inquirer = require('inquirer');
2193
+
2194
+ const { userPrompt } = await inquirer.prompt([{
2195
+ type: 'input',
2196
+ name: 'userPrompt',
2197
+ message: 'What would you like to create?',
2198
+ validate: (input) => {
2199
+ if (!input || input.trim().length < 10) {
2200
+ return 'Please provide a more detailed description (at least 10 characters)';
2201
+ }
2202
+ return true;
2203
+ }
2204
+ }]);
2205
+
2206
+ prompt = userPrompt.trim();
2207
+ console.log(chalk.green('āœ… Project requirements captured!'));
2208
+ }
2209
+
2210
+ // Load .env file if it exists (for API keys)
2211
+ try {
2212
+ const fs = require('fs');
2213
+ const path = require('path');
2214
+ const envPath = path.join(targetDir, '.env');
2215
+
2216
+ if (fs.existsSync(envPath)) {
2217
+ const envContent = fs.readFileSync(envPath, 'utf8');
2218
+ const envVars = envContent.split('\n')
2219
+ .filter(line => line.trim() && !line.startsWith('#'))
2220
+ .reduce((acc, line) => {
2221
+ const [key, ...valueParts] = line.split('=');
2222
+ if (key && valueParts.length > 0) {
2223
+ const value = valueParts.join('=').trim();
2224
+ acc[key.trim()] = value;
2225
+ }
2226
+ return acc;
2227
+ }, {});
2228
+
2229
+ // Set environment variables if not already set
2230
+ Object.keys(envVars).forEach(key => {
2231
+ if (!process.env[key]) {
2232
+ process.env[key] = envVars[key];
2233
+ }
2234
+ });
2235
+ }
2236
+ } catch (error) {
2237
+ // Ignore .env loading errors
2238
+ }
2239
+
2240
+ // Check for API keys (either from CLI parameters or environment variables)
2241
+ const e2bKey = e2bApiKey || process.env.E2B_API_KEY;
2242
+ const anthropicKey = anthropicApiKey || process.env.ANTHROPIC_API_KEY;
2243
+
2244
+ if (!e2bKey) {
2245
+ console.log(chalk.red('āŒ Error: E2B API key is required'));
2246
+ console.log(chalk.yellow('šŸ’” Options:'));
2247
+ console.log(chalk.gray(' 1. Set environment variable: E2B_API_KEY=your_key'));
2248
+ console.log(chalk.gray(' 2. Use CLI parameter: --e2b-api-key your_key'));
2249
+ console.log(chalk.blue(' Get your key at: https://e2b.dev/dashboard'));
2250
+ return;
2251
+ }
2252
+
2253
+ if (!anthropicKey) {
2254
+ console.log(chalk.red('āŒ Error: Anthropic API key is required'));
2255
+ console.log(chalk.yellow('šŸ’” Options:'));
2256
+ console.log(chalk.gray(' 1. Set environment variable: ANTHROPIC_API_KEY=your_key'));
2257
+ console.log(chalk.gray(' 2. Use CLI parameter: --anthropic-api-key your_key'));
2258
+ console.log(chalk.blue(' Get your key at: https://console.anthropic.com'));
2259
+ return;
2260
+ }
2261
+
2262
+ // Sandbox execution confirmation
2263
+ console.log(chalk.blue('\nā˜ļø E2B Sandbox Execution'));
2264
+ console.log(chalk.cyan('═══════════════════════════════════════'));
2265
+ console.log(chalk.white(`šŸ“‹ Agent: ${chalk.yellow(agent || 'default')}`));
2266
+ const truncatedPrompt = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
2267
+ console.log(chalk.white(`šŸ’­ Prompt: ${chalk.cyan('"' + truncatedPrompt + '"')}`));
2268
+ console.log(chalk.white(`🌐 Provider: ${chalk.green('E2B Cloud')}`));
2269
+ console.log(chalk.gray('\nšŸ”§ Execution details:'));
2270
+ console.log(chalk.gray(' • Execution logs will be displayed in real-time'));
2271
+ console.log(chalk.gray(` • Files will be downloaded to: ${chalk.cyan(targetDir)}`));
2272
+ console.log(chalk.gray(' • Extended timeout: 15 minutes for complex operations'));
2273
+ console.log(chalk.yellow(' • Press ESC anytime to cancel execution\n'));
2274
+
2275
+ const inquirer = require('inquirer');
2276
+
2277
+ const { shouldExecuteSandbox } = await inquirer.prompt([{
2278
+ type: 'confirm',
2279
+ name: 'shouldExecuteSandbox',
2280
+ message: `Execute this agent in E2B sandbox?`,
2281
+ default: true
2282
+ }]);
2283
+
2284
+ if (!shouldExecuteSandbox) {
2285
+ console.log(chalk.yellow('ā¹ļø E2B sandbox execution cancelled by user.'));
2286
+ return;
2287
+ }
2288
+
2289
+ try {
2290
+ console.log(chalk.blue('šŸ”® Setting up E2B sandbox environment...'));
2291
+
2292
+ // Install E2B sandbox component
2293
+ const spinner = ora('Installing E2B sandbox component...').start();
2294
+
2295
+ // Create .claude/sandbox directory
2296
+ const sandboxDir = path.join(targetDir, '.claude', 'sandbox');
2297
+ await fs.ensureDir(sandboxDir);
2298
+
2299
+ // Download E2B component files from new structure
2300
+ const baseUrl = 'https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/sandbox/e2b';
2301
+
2302
+ // Download launcher script
2303
+ const launcherResponse = await fetch(`${baseUrl}/e2b-launcher.py`);
2304
+ if (!launcherResponse.ok) {
2305
+ throw new Error(`Failed to download e2b-launcher.py: ${launcherResponse.status} ${launcherResponse.statusText}`);
2306
+ }
2307
+ const launcherContent = await launcherResponse.text();
2308
+ await fs.writeFile(path.join(sandboxDir, 'e2b-launcher.py'), launcherContent, { mode: 0o755 });
2309
+
2310
+ // Download requirements.txt
2311
+ const requirementsResponse = await fetch(`${baseUrl}/requirements.txt`);
2312
+ if (!requirementsResponse.ok) {
2313
+ throw new Error(`Failed to download requirements.txt: ${requirementsResponse.status} ${requirementsResponse.statusText}`);
2314
+ }
2315
+ const requirementsContent = await requirementsResponse.text();
2316
+ await fs.writeFile(path.join(sandboxDir, 'requirements.txt'), requirementsContent);
2317
+
2318
+ // Download .env.example
2319
+ const envExampleResponse = await fetch(`${baseUrl}/.env.example`);
2320
+ if (!envExampleResponse.ok) {
2321
+ throw new Error(`Failed to download .env.example: ${envExampleResponse.status} ${envExampleResponse.statusText}`);
2322
+ }
2323
+ const envExampleContent = await envExampleResponse.text();
2324
+ await fs.writeFile(path.join(sandboxDir, '.env.example'), envExampleContent);
2325
+
2326
+ spinner.succeed('E2B sandbox component installed successfully');
2327
+
2328
+ // Check for Python and install dependencies
2329
+ const pythonSpinner = ora('Checking Python environment...').start();
2330
+
2331
+ try {
2332
+ const { spawn } = require('child_process');
2333
+
2334
+ // Helper function to check Python version availability
2335
+ const checkPythonVersion = (pythonCmd) => {
2336
+ return new Promise((resolve) => {
2337
+ const check = spawn(pythonCmd, ['--version'], { stdio: 'pipe' });
2338
+ check.on('close', (code) => resolve(code === 0));
2339
+ check.on('error', () => resolve(false));
2340
+ });
2341
+ };
2342
+
2343
+ // Check for Python 3.11 first, fallback to python3
2344
+ let pythonCmd = 'python3';
2345
+ const python311Available = await checkPythonVersion('python3.11');
2346
+ if (python311Available) {
2347
+ pythonCmd = 'python3.11';
2348
+ console.log(chalk.blue('āœ“ Using Python 3.11 (recommended for E2B)'));
2349
+ } else {
2350
+ console.log(chalk.yellow('⚠ Python 3.11 not found, using python3 (may have package restrictions)'));
2351
+ }
2352
+
2353
+ // Verify chosen Python version works
2354
+ const pythonAvailable = await checkPythonVersion(pythonCmd);
2355
+ if (!pythonAvailable) {
2356
+ pythonSpinner.fail('Python 3 not found');
2357
+ console.log(chalk.red('āŒ Python 3.11+ is required for E2B sandbox'));
2358
+ console.log(chalk.yellow('šŸ’” Please install Python 3.11+ and try again'));
2359
+ console.log(chalk.blue(' Visit: https://python.org/downloads'));
2360
+ return;
2361
+ }
2362
+
2363
+ pythonSpinner.succeed(`Python environment ready (${pythonCmd})`);
2364
+
2365
+ // Install Python dependencies
2366
+ const depSpinner = ora('Installing E2B Python SDK...').start();
2367
+
2368
+ const pipInstall = spawn(pythonCmd, ['-m', 'pip', 'install', '-r', path.join(sandboxDir, 'requirements.txt')], {
2369
+ cwd: sandboxDir,
2370
+ stdio: 'pipe'
2371
+ });
2372
+
2373
+ let pipOutput = '';
2374
+ let pipError = '';
2375
+
2376
+ pipInstall.stdout.on('data', (data) => {
2377
+ pipOutput += data.toString();
2378
+ });
2379
+
2380
+ pipInstall.stderr.on('data', (data) => {
2381
+ pipError += data.toString();
2382
+ });
2383
+
2384
+ pipInstall.on('close', async (pipCode) => {
2385
+ if (pipCode === 0) {
2386
+ depSpinner.succeed('E2B Python SDK installed successfully');
2387
+
2388
+ // Build components string for installation inside sandbox
2389
+ let componentsToInstall = '';
2390
+ if (agent) componentsToInstall += ` --agent ${agent}`;
2391
+ if (command) componentsToInstall += ` --command ${command}`;
2392
+ if (mcp) componentsToInstall += ` --mcp ${mcp}`;
2393
+ if (setting) componentsToInstall += ` --setting ${setting}`;
2394
+ if (hook) componentsToInstall += ` --hook ${hook}`;
2395
+
2396
+ // Execute sandbox
2397
+ console.log(chalk.blue('šŸš€ Launching E2B sandbox with Claude Code...'));
2398
+ console.log(chalk.gray(`šŸ“ Prompt: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`));
2399
+ console.log(chalk.cyan('ā±ļø Extended timeout: 15 minutes for complex operations'));
2400
+
2401
+ if (componentsToInstall) {
2402
+ console.log(chalk.gray(`šŸ“¦ Components to install:${componentsToInstall}`));
2403
+ }
2404
+
2405
+ // Execute sandbox and wait for completion
2406
+ console.log(chalk.blue('šŸš€ Starting E2B sandbox execution...'));
2407
+ console.log(chalk.yellow('šŸ’” Press ESC anytime to cancel the execution'));
2408
+
2409
+ await new Promise((resolve, reject) => {
2410
+ const sandboxExecution = spawn(pythonCmd, [
2411
+ path.join(sandboxDir, 'e2b-launcher.py'),
2412
+ prompt,
2413
+ componentsToInstall.trim(),
2414
+ e2bKey,
2415
+ anthropicKey
2416
+ ], {
2417
+ cwd: targetDir, // Run from user's current directory to download files there
2418
+ stdio: 'inherit',
2419
+ timeout: 900000, // 15 minutes timeout for complex operations
2420
+ env: {
2421
+ ...process.env,
2422
+ E2B_API_KEY: e2bKey,
2423
+ ANTHROPIC_API_KEY: anthropicKey
2424
+ }
2425
+ });
2426
+
2427
+ // Setup ESC key listener for cancellation
2428
+ process.stdin.setRawMode(true);
2429
+ process.stdin.resume();
2430
+ process.stdin.setEncoding('utf8');
2431
+
2432
+ const keyListener = (key) => {
2433
+ // ESC key (ASCII 27)
2434
+ if (key === '\u001b') {
2435
+ console.log(chalk.yellow('\nā¹ļø Cancelling E2B sandbox execution...'));
2436
+ sandboxExecution.kill('SIGTERM');
2437
+
2438
+ // Cleanup
2439
+ process.stdin.setRawMode(false);
2440
+ process.stdin.pause();
2441
+ process.stdin.removeListener('data', keyListener);
2442
+
2443
+ resolve(); // Resolve to prevent hanging
2444
+ }
2445
+ };
2446
+
2447
+ process.stdin.on('data', keyListener);
2448
+
2449
+ sandboxExecution.on('close', (sandboxCode) => {
2450
+ // Cleanup stdin listener
2451
+ process.stdin.setRawMode(false);
2452
+ process.stdin.pause();
2453
+ process.stdin.removeListener('data', keyListener);
2454
+
2455
+ if (sandboxCode === 0) {
2456
+ console.log(chalk.green('šŸŽ‰ Sandbox execution completed successfully!'));
2457
+ console.log(chalk.blue('šŸ’” Files were created inside the E2B sandbox environment'));
2458
+ resolve();
2459
+ } else if (sandboxCode === null) {
2460
+ console.log(chalk.yellow('ā¹ļø Sandbox execution was cancelled'));
2461
+ resolve();
2462
+ } else {
2463
+ console.log(chalk.yellow(`āš ļø Sandbox execution finished with exit code ${sandboxCode}`));
2464
+ console.log(chalk.gray('šŸ’” Check the output above for any error details'));
2465
+ resolve(); // Still resolve even with non-zero exit code
2466
+ }
2467
+ });
2468
+
2469
+ sandboxExecution.on('error', (error) => {
2470
+ // Cleanup stdin listener
2471
+ process.stdin.setRawMode(false);
2472
+ process.stdin.pause();
2473
+ process.stdin.removeListener('data', keyListener);
2474
+
2475
+ if (error.code === 'TIMEOUT') {
2476
+ console.log(chalk.yellow('ā±ļø Sandbox execution timed out after 15 minutes'));
2477
+ console.log(chalk.gray('šŸ’” This may happen with very complex prompts or large projects'));
2478
+ console.log(chalk.blue('šŸ’” Try breaking down your prompt into smaller, more specific requests'));
2479
+ } else {
2480
+ console.log(chalk.red(`āŒ Error executing sandbox: ${error.message}`));
2481
+ console.log(chalk.yellow('šŸ’” Make sure you have set E2B_API_KEY and ANTHROPIC_API_KEY environment variables'));
2482
+ console.log(chalk.gray(' Create a .env file in the .claude/sandbox directory with your API keys'));
2483
+ }
2484
+ reject(error);
2485
+ });
2486
+ });
2487
+
2488
+ } else {
2489
+ depSpinner.fail('Failed to install E2B Python SDK');
2490
+ console.log(chalk.red(`āŒ pip install failed with exit code ${pipCode}`));
2491
+ if (pipError) {
2492
+ console.log(chalk.red('Error output:'));
2493
+ console.log(chalk.gray(pipError.trim()));
2494
+ }
2495
+ if (pipOutput) {
2496
+ console.log(chalk.blue('Full output:'));
2497
+ console.log(chalk.gray(pipOutput.trim()));
2498
+ }
2499
+ console.log(chalk.yellow('šŸ’” Please install dependencies manually:'));
2500
+ console.log(chalk.gray(` cd ${sandboxDir}`));
2501
+ console.log(chalk.gray(` ${pythonCmd} -m pip install -r requirements.txt`));
2502
+ console.log(chalk.gray(` ${pythonCmd} -m pip install e2b`));
2503
+ }
2504
+ });
2505
+
2506
+ } catch (error) {
2507
+ pythonSpinner.fail('Failed to check Python environment');
2508
+ console.log(chalk.red(`āŒ Error: ${error.message}`));
2509
+ }
2510
+
2511
+ } catch (error) {
2512
+ console.log(chalk.red(`āŒ Error setting up sandbox: ${error.message}`));
2513
+ console.log(chalk.yellow('šŸ’” Please check your internet connection and try again'));
2514
+ }
2515
+ }
2516
+
2080
2517
  module.exports = { createClaudeConfig, showMainMenu };