claude-code-templates 1.20.2 → 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.
@@ -0,0 +1,555 @@
1
+ #!/usr/bin/env node
2
+
3
+ const express = require('express');
4
+ const path = require('path');
5
+ const { spawn } = require('child_process');
6
+ const chalk = require('chalk');
7
+ const fs = require('fs');
8
+
9
+ const app = express();
10
+ const PORT = process.env.PORT || 3444;
11
+
12
+ // Load .env file from current working directory (where user runs the command)
13
+ function loadEnvFile() {
14
+ const envPath = path.join(process.cwd(), '.env');
15
+ if (fs.existsSync(envPath)) {
16
+ console.log(chalk.blue('šŸ“„ Loading .env file from:'), chalk.gray(envPath));
17
+
18
+ const envContent = fs.readFileSync(envPath, 'utf8');
19
+ const envVars = envContent.split('\n')
20
+ .filter(line => line.trim() && !line.startsWith('#'))
21
+ .reduce((acc, line) => {
22
+ const [key, ...valueParts] = line.split('=');
23
+ if (key && valueParts.length > 0) {
24
+ const value = valueParts.join('=').trim().replace(/^["']|["']$/g, ''); // Remove quotes
25
+ acc[key.trim()] = value;
26
+ }
27
+ return acc;
28
+ }, {});
29
+
30
+ // Set environment variables
31
+ Object.assign(process.env, envVars);
32
+
33
+ const hasE2B = !!process.env.E2B_API_KEY;
34
+ const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
35
+
36
+ console.log(chalk.green('āœ… Environment variables loaded:'));
37
+ console.log(chalk.gray(` • E2B_API_KEY: ${hasE2B ? 'Found' : 'Missing'}`));
38
+ console.log(chalk.gray(` • ANTHROPIC_API_KEY: ${hasAnthropic ? 'Found' : 'Missing'}`));
39
+
40
+ return hasE2B && hasAnthropic;
41
+ } else {
42
+ console.log(chalk.yellow('āš ļø No .env file found in:'), chalk.gray(envPath));
43
+ return false;
44
+ }
45
+ }
46
+
47
+ // Load environment variables on startup
48
+ const hasApiKeys = loadEnvFile();
49
+
50
+ // Simple CORS middleware
51
+ app.use((req, res, next) => {
52
+ res.header('Access-Control-Allow-Origin', '*');
53
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
54
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
55
+ if (req.method === 'OPTIONS') {
56
+ res.sendStatus(200);
57
+ } else {
58
+ next();
59
+ }
60
+ });
61
+
62
+ // JSON parsing middleware
63
+ app.use(express.json());
64
+
65
+ // Store active tasks
66
+ const activeTasks = new Map();
67
+
68
+ // Serve the sandbox interface at root
69
+ app.get('/', (req, res) => {
70
+ res.sendFile(path.join(__dirname, '../../docs/sandbox-interface.html'));
71
+ });
72
+
73
+ // Serve static files for CSS, JS, etc. (but not index.html at root)
74
+ app.use('/css', express.static(path.join(__dirname, '../../docs/css')));
75
+ app.use('/js', express.static(path.join(__dirname, '../../docs/js')));
76
+ app.use('/assets', express.static(path.join(__dirname, '../../docs/assets')));
77
+
78
+ // Serve components.json for agent autocomplete
79
+ app.get('/components.json', (req, res) => {
80
+ const componentsPath = path.join(__dirname, '../../docs/components.json');
81
+ if (fs.existsSync(componentsPath)) {
82
+ res.sendFile(componentsPath);
83
+ } else {
84
+ res.status(404).json({ error: 'Components file not found' });
85
+ }
86
+ });
87
+
88
+ // API endpoint to execute task (local or cloud)
89
+ app.post('/api/execute', async (req, res) => {
90
+ const { prompt, mode = 'local', agent = 'development-team/frontend-developer' } = req.body;
91
+
92
+ if (!prompt || prompt.trim().length < 10) {
93
+ return res.status(400).json({
94
+ success: false,
95
+ error: 'Please provide a detailed prompt (at least 10 characters)'
96
+ });
97
+ }
98
+
99
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
100
+
101
+ // Create task object
102
+ const task = {
103
+ id: taskId,
104
+ title: prompt.substring(0, 60) + (prompt.length > 60 ? '...' : ''),
105
+ prompt: prompt.trim(),
106
+ agent: agent,
107
+ mode: mode,
108
+ status: 'running',
109
+ startTime: new Date(),
110
+ progress: 0,
111
+ output: [],
112
+ sandboxId: null
113
+ };
114
+
115
+ activeTasks.set(taskId, task);
116
+
117
+ // Execute based on mode
118
+ if (mode === 'cloud') {
119
+ executeE2BTask(task);
120
+ } else {
121
+ executeLocalTask(task);
122
+ }
123
+
124
+ res.json({
125
+ success: true,
126
+ taskId: taskId,
127
+ message: 'Task started successfully'
128
+ });
129
+ });
130
+
131
+ // API endpoint to get task status
132
+ app.get('/api/task/:taskId', (req, res) => {
133
+ const task = activeTasks.get(req.params.taskId);
134
+ if (!task) {
135
+ return res.status(404).json({
136
+ success: false,
137
+ error: 'Task not found'
138
+ });
139
+ }
140
+
141
+ res.json({
142
+ success: true,
143
+ task: {
144
+ id: task.id,
145
+ title: task.title,
146
+ status: task.status,
147
+ progress: task.progress,
148
+ output: task.output.join('\\n'),
149
+ startTime: task.startTime,
150
+ endTime: task.endTime,
151
+ sandboxId: task.sandboxId
152
+ }
153
+ });
154
+ });
155
+
156
+ // API endpoint to get all tasks
157
+ app.get('/api/tasks', (req, res) => {
158
+ const tasks = Array.from(activeTasks.values()).map(task => ({
159
+ id: task.id,
160
+ title: task.title,
161
+ status: task.status,
162
+ progress: task.progress,
163
+ startTime: task.startTime,
164
+ endTime: task.endTime,
165
+ sandboxId: task.sandboxId,
166
+ output: task.output.slice(-3).join('\\n') // Last 3 lines for preview
167
+ }));
168
+
169
+ res.json({
170
+ success: true,
171
+ tasks: tasks.sort((a, b) => new Date(b.startTime) - new Date(a.startTime))
172
+ });
173
+ });
174
+
175
+ // API endpoint to install agent
176
+ app.post('/api/install-agent', async (req, res) => {
177
+ const { agentName } = req.body;
178
+
179
+ if (!agentName) {
180
+ return res.status(400).json({
181
+ success: false,
182
+ error: 'Agent name is required'
183
+ });
184
+ }
185
+
186
+ try {
187
+ console.log(chalk.blue('šŸ”§ Installing agent:'), chalk.cyan(agentName));
188
+
189
+ // Use the CLI tool to install the agent
190
+ const child = spawn('npx', ['claude-code-templates@latest', '--agent', agentName, '--yes'], {
191
+ cwd: process.cwd(),
192
+ stdio: ['pipe', 'pipe', 'pipe'],
193
+ shell: true
194
+ });
195
+
196
+ let output = [];
197
+ let error = [];
198
+
199
+ child.stdout.on('data', (data) => {
200
+ const lines = data.toString().split('\n').filter(line => line.trim());
201
+ output.push(...lines);
202
+ });
203
+
204
+ child.stderr.on('data', (data) => {
205
+ const lines = data.toString().split('\n').filter(line => line.trim());
206
+ error.push(...lines);
207
+ });
208
+
209
+ child.on('close', (code) => {
210
+ if (code === 0) {
211
+ console.log(chalk.green('āœ… Agent installed successfully:'), chalk.cyan(agentName));
212
+ res.json({
213
+ success: true,
214
+ message: `Agent ${agentName} installed successfully`,
215
+ output: output.join('\n')
216
+ });
217
+ } else {
218
+ console.error(chalk.red('āŒ Agent installation failed:'), chalk.cyan(agentName));
219
+ res.status(500).json({
220
+ success: false,
221
+ error: `Failed to install agent ${agentName}`,
222
+ output: error.join('\n')
223
+ });
224
+ }
225
+ });
226
+
227
+ child.on('error', (error) => {
228
+ console.error(chalk.red('āŒ Agent installation error:'), error.message);
229
+ res.status(500).json({
230
+ success: false,
231
+ error: `Installation error: ${error.message}`
232
+ });
233
+ });
234
+
235
+ } catch (error) {
236
+ console.error(chalk.red('āŒ Failed to start agent installation:'), error.message);
237
+ res.status(500).json({
238
+ success: false,
239
+ error: `Failed to start installation: ${error.message}`
240
+ });
241
+ }
242
+ });
243
+
244
+ async function checkAndInstallAgent(agentName, task) {
245
+ // Check if agent exists in .claude directory
246
+ const claudeDir = path.join(process.cwd(), '.claude');
247
+ const agentPath = path.join(claudeDir, 'agents', `${agentName}.md`);
248
+
249
+ if (fs.existsSync(agentPath)) {
250
+ return true; // Agent already exists
251
+ }
252
+
253
+ task.output.push(`šŸ”§ Agent ${agentName} not found locally. Installing...`);
254
+
255
+ return new Promise((resolve, reject) => {
256
+ const child = spawn('npx', ['claude-code-templates@latest', '--agent', agentName, '--yes'], {
257
+ cwd: process.cwd(),
258
+ stdio: ['pipe', 'pipe', 'pipe'],
259
+ shell: true
260
+ });
261
+
262
+ child.stdout.on('data', (data) => {
263
+ const lines = data.toString().split('\n').filter(line => line.trim());
264
+ lines.forEach(line => {
265
+ task.output.push(`šŸ“¦ ${line}`);
266
+ });
267
+ });
268
+
269
+ child.stderr.on('data', (data) => {
270
+ const lines = data.toString().split('\n').filter(line => line.trim());
271
+ lines.forEach(line => {
272
+ task.output.push(`āš ļø ${line}`);
273
+ });
274
+ });
275
+
276
+ child.on('close', (code) => {
277
+ if (code === 0) {
278
+ task.output.push(`āœ… Agent ${agentName} installed successfully`);
279
+ resolve(true);
280
+ } else {
281
+ task.output.push(`āŒ Failed to install agent ${agentName}`);
282
+ resolve(false);
283
+ }
284
+ });
285
+
286
+ child.on('error', (error) => {
287
+ task.output.push(`āŒ Installation error: ${error.message}`);
288
+ resolve(false);
289
+ });
290
+ });
291
+ }
292
+
293
+ async function executeE2BTask(task) {
294
+ try {
295
+ task.output.push('šŸš€ Initializing E2B sandbox execution...');
296
+ task.progress = 10;
297
+
298
+ // Check and install agent if needed
299
+ if (task.agent !== 'development-team/frontend-developer') {
300
+ task.output.push(`šŸ” Checking agent: ${task.agent}`);
301
+ const agentInstalled = await checkAndInstallAgent(task.agent, task);
302
+ if (!agentInstalled) {
303
+ task.status = 'failed';
304
+ task.endTime = new Date();
305
+ task.output.push(`āŒ Could not install required agent: ${task.agent}`);
306
+ return;
307
+ }
308
+ task.progress = 15;
309
+ }
310
+
311
+ const e2bLauncherPath = path.join(__dirname, '../components/sandbox/e2b/e2b-launcher.py');
312
+ const agentParam = `--agent=${task.agent} --yes`;
313
+
314
+ // Build command arguments
315
+ const args = [
316
+ e2bLauncherPath,
317
+ task.prompt,
318
+ agentParam
319
+ ];
320
+
321
+ // Add API keys from environment if available
322
+ if (process.env.E2B_API_KEY) {
323
+ args.push(process.env.E2B_API_KEY);
324
+ }
325
+ if (process.env.ANTHROPIC_API_KEY) {
326
+ args.push(process.env.ANTHROPIC_API_KEY);
327
+ }
328
+
329
+ task.output.push('šŸ”§ Starting Python E2B launcher...');
330
+ task.progress = 20;
331
+
332
+ // Execute the E2B launcher from the user's working directory
333
+ const child = spawn('python3', args, {
334
+ cwd: process.cwd(), // This ensures it runs from where the user executed the command
335
+ stdio: ['pipe', 'pipe', 'pipe'],
336
+ env: {
337
+ ...process.env, // Pass all environment variables including loaded ones
338
+ PATH: process.env.PATH
339
+ }
340
+ });
341
+
342
+ // Handle stdout
343
+ child.stdout.on('data', (data) => {
344
+ const lines = data.toString().split('\\n').filter(line => line.trim());
345
+ lines.forEach(line => {
346
+ task.output.push(line);
347
+
348
+ // Update progress based on output
349
+ if (line.includes('Sandbox created:')) {
350
+ task.sandboxId = line.split('Sandbox created: ')[1] || 'unknown';
351
+ task.progress = 40;
352
+ } else if (line.includes('Installing')) {
353
+ task.progress = 60;
354
+ } else if (line.includes('Executing Claude Code')) {
355
+ task.progress = 80;
356
+ } else if (line.includes('Downloaded:')) {
357
+ task.progress = 95;
358
+ } else if (line.includes('Execution completed successfully')) {
359
+ task.progress = 100;
360
+ task.status = 'completed';
361
+ task.endTime = new Date();
362
+ }
363
+ });
364
+ });
365
+
366
+ // Handle stderr
367
+ child.stderr.on('data', (data) => {
368
+ const lines = data.toString().split('\\n').filter(line => line.trim());
369
+ lines.forEach(line => {
370
+ task.output.push(`āš ļø ${line}`);
371
+ });
372
+ });
373
+
374
+ // Handle process exit
375
+ child.on('close', (code) => {
376
+ if (code === 0) {
377
+ if (task.status !== 'completed') {
378
+ task.status = 'completed';
379
+ task.endTime = new Date();
380
+ task.progress = 100;
381
+ }
382
+ task.output.push('āœ… Task completed successfully!');
383
+ } else {
384
+ task.status = 'failed';
385
+ task.endTime = new Date();
386
+
387
+ // Check if it's an API key error
388
+ const outputText = task.output.join(' ');
389
+ if (outputText.includes('E2B API key is required') || outputText.includes('Anthropic API key is required')) {
390
+ task.output.push('āŒ Missing API keys! Please add E2B_API_KEY and ANTHROPIC_API_KEY to your .env file');
391
+ task.output.push('šŸ”‘ Get E2B key: https://e2b.dev/dashboard');
392
+ task.output.push('šŸ”‘ Get Anthropic key: https://console.anthropic.com');
393
+ } else {
394
+ task.output.push(`āŒ Process exited with code: ${code}`);
395
+ }
396
+ }
397
+ });
398
+
399
+ // Handle process error
400
+ child.on('error', (error) => {
401
+ task.status = 'failed';
402
+ task.endTime = new Date();
403
+ task.output.push(`āŒ Execution error: ${error.message}`);
404
+ });
405
+
406
+ } catch (error) {
407
+ task.status = 'failed';
408
+ task.endTime = new Date();
409
+ task.output.push(`āŒ Failed to start execution: ${error.message}`);
410
+ }
411
+ }
412
+
413
+ async function executeLocalTask(task) {
414
+ try {
415
+ task.output.push('šŸ–„ļø Executing Claude Code locally...');
416
+ task.progress = 10;
417
+
418
+ // Check and install agent if needed
419
+ if (task.agent !== 'development-team/frontend-developer') {
420
+ task.output.push(`šŸ” Checking agent: ${task.agent}`);
421
+ const agentInstalled = await checkAndInstallAgent(task.agent, task);
422
+ if (!agentInstalled) {
423
+ task.status = 'failed';
424
+ task.endTime = new Date();
425
+ task.output.push(`āŒ Could not install required agent: ${task.agent}`);
426
+ return;
427
+ }
428
+ task.progress = 15;
429
+ }
430
+
431
+ task.output.push('šŸ” Checking if Claude Code CLI is available...');
432
+
433
+ // For local execution, we'll include the agent in the prompt if specified
434
+ let finalPrompt = task.prompt;
435
+ if (task.agent && task.agent !== 'development-team/frontend-developer') {
436
+ finalPrompt = `As a ${task.agent.replace('-', ' ')}, ${task.prompt}`;
437
+ }
438
+
439
+ // Execute Claude Code locally with just the prompt
440
+ const child = spawn('claude', [finalPrompt], {
441
+ cwd: process.cwd(),
442
+ stdio: ['ignore', 'pipe', 'pipe'], // Ignore stdin to prevent hanging
443
+ env: {
444
+ ...process.env,
445
+ PATH: process.env.PATH
446
+ },
447
+ shell: true, // Use shell to find claude command
448
+ timeout: 300000 // 5 minute timeout
449
+ });
450
+
451
+ task.output.push('šŸš€ Claude Code execution started');
452
+ task.progress = 30;
453
+
454
+ // Set up timeout to prevent hanging
455
+ const executionTimeout = setTimeout(() => {
456
+ task.output.push('ā° Execution timeout reached (5 minutes)');
457
+ task.output.push('šŸ’” This might indicate Claude Code is waiting for input or has hung');
458
+ task.status = 'failed';
459
+ task.endTime = new Date();
460
+ child.kill('SIGTERM');
461
+ }, 300000); // 5 minutes
462
+
463
+ // Handle stdout
464
+ child.stdout.on('data', (data) => {
465
+ const lines = data.toString().split('\\n').filter(line => line.trim());
466
+ lines.forEach(line => {
467
+ task.output.push(line);
468
+
469
+ // Update progress based on output patterns
470
+ if (line.includes('Reading') || line.includes('Analyzing')) {
471
+ task.progress = Math.min(task.progress + 5, 60);
472
+ } else if (line.includes('Writing') || line.includes('Creating')) {
473
+ task.progress = Math.min(task.progress + 10, 90);
474
+ } else if (line.includes('Done') || line.includes('Complete')) {
475
+ task.progress = 95;
476
+ }
477
+ });
478
+ });
479
+
480
+ // Handle stderr
481
+ child.stderr.on('data', (data) => {
482
+ const lines = data.toString().split('\\n').filter(line => line.trim());
483
+ lines.forEach(line => {
484
+ task.output.push(`āš ļø ${line}`);
485
+ });
486
+ });
487
+
488
+ // Handle process exit
489
+ child.on('close', (code) => {
490
+ clearTimeout(executionTimeout); // Clear timeout when process exits
491
+
492
+ if (code === 0) {
493
+ task.status = 'completed';
494
+ task.endTime = new Date();
495
+ task.progress = 100;
496
+ task.output.push('āœ… Claude Code execution completed successfully!');
497
+ task.output.push('šŸ“‚ Files were created/modified in your current directory');
498
+ } else {
499
+ task.status = 'failed';
500
+ task.endTime = new Date();
501
+
502
+ const outputText = task.output.join(' ');
503
+ if (outputText.includes('claude: command not found') || outputText.includes('not recognized')) {
504
+ task.output.push('āŒ Claude Code CLI not found!');
505
+ task.output.push('šŸ’” Please install Claude Code CLI first:');
506
+ task.output.push('šŸ”— Visit: https://claude.ai/code');
507
+ } else {
508
+ task.output.push(`āŒ Process exited with code: ${code}`);
509
+ }
510
+ }
511
+ });
512
+
513
+ // Handle process error
514
+ child.on('error', (error) => {
515
+ clearTimeout(executionTimeout); // Clear timeout on error
516
+
517
+ task.status = 'failed';
518
+ task.endTime = new Date();
519
+
520
+ if (error.code === 'ENOENT') {
521
+ task.output.push('āŒ Claude Code CLI not found in PATH!');
522
+ task.output.push('šŸ’” Please install Claude Code CLI first:');
523
+ task.output.push('šŸ”— Visit: https://claude.ai/code');
524
+ task.output.push('šŸ”— Documentation: https://docs.anthropic.com/claude-code');
525
+ } else {
526
+ task.output.push(`āŒ Execution error: ${error.message}`);
527
+ }
528
+ });
529
+
530
+ } catch (error) {
531
+ task.status = 'failed';
532
+ task.endTime = new Date();
533
+ task.output.push(`āŒ Failed to start local execution: ${error.message}`);
534
+ }
535
+ }
536
+
537
+ // Start server
538
+ app.listen(PORT, () => {
539
+ console.log(chalk.blue('\\nšŸŽØ Claude Code Studio Server'));
540
+ console.log(chalk.cyan('═══════════════════════════════════════'));
541
+ console.log(chalk.green(`šŸš€ Server running on http://localhost:${PORT}`));
542
+ console.log(chalk.gray('šŸ’” Local and cloud execution interface ready'));
543
+
544
+ if (hasApiKeys) {
545
+ console.log(chalk.green('\\nāœ… All API keys are configured and ready!'));
546
+ } else {
547
+ console.log(chalk.yellow('\\nāš ļø API Keys Status:'));
548
+ console.log(chalk.gray(` • E2B_API_KEY: ${process.env.E2B_API_KEY ? 'Found' : 'Missing'}`));
549
+ console.log(chalk.gray(` • ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? 'Found' : 'Missing'}`));
550
+ console.log(chalk.yellow(' • Please add these keys to your .env file'));
551
+ }
552
+ console.log('');
553
+ });
554
+
555
+ module.exports = app;