import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { writeFile, mkdir } from 'fs/promises'; import { join } from 'path'; import { homedir, tmpdir } from 'os'; import { spawn } from 'child_process'; import { mkdirSync, rmSync } from 'fs'; import type { MorphFileFormat } from '$lib/types/morph'; import { createMorphFile } from '$lib/types/morph'; const PANELS_DIR = join(homedir(), 'morphbox', 'panels'); interface PanelMetadata { id: string; name: string; description: string; promptHistory: Array<{ prompt: string; timestamp: string; type: 'create' | 'morph'; }>; version: string; createdAt: string; updatedAt: string; } function generatePanelId(name: string): string { return name .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, '') + '-' + Date.now().toString(36); } const DEFAULT_SYSTEM_PROMPT = `Create a vanilla JavaScript panel for MorphBox with the following requirements: Panel Name: {name} Description: {description} Generate a complete HTML/CSS/JavaScript panel that: 1. Uses vanilla JavaScript (no frameworks) 2. Has access to these variables: panelId, data, websocketUrl 3. Can connect to WebSocket for real-time data: const ws = new WebSocket(websocketUrl) 4. Uses MorphBox CSS variables for theming (--bg-primary, --text-primary, --border-color, etc.) 5. Implements the functionality described above 6. Uses proper error handling and loading states where applicable 7. IMPORTANT: The JavaScript code will be automatically wrapped in an onMount() function, so you can safely access DOM elements directly without waiting for DOMContentLoaded 8. Is responsive and works well on mobile WebSocket Access: - Connect using: new WebSocket(websocketUrl) - Listen for 'OUTPUT' events (terminal output) - Listen for 'context_update' events (Claude Code context tracking) - See full API: https://github.com/instant-unicorn/morphbox/blob/main/docs/CUSTOM_PANELS.md IMPORTANT: Return ONLY the HTML code starting with
tags. Do not include any markdown formatting, code blocks, or explanations. Just the raw HTML/CSS/JavaScript code. The panel should follow this structure:

{name}

Make it fully functional and production-ready. Use modern JavaScript features.`; async function generatePanelWithClaude(name: string, description: string, customPrompt?: string): Promise { return new Promise((resolve, reject) => { console.log('=== Starting Claude Panel Generation ==='); console.log('Panel Name:', name); console.log('Description:', description); console.log('Using custom prompt:', !!customPrompt); // Use custom prompt or default, replacing placeholders const promptTemplate = customPrompt || DEFAULT_SYSTEM_PROMPT; const prompt = promptTemplate .replace(/\{name\}/g, name) .replace(/\{description\}/g, description); console.log('Prompt length:', prompt.length); // Use Claude CLI in a dedicated temporary directory const startTime = Date.now(); console.log('Executing Claude CLI in dedicated temp directory...'); // Create a temporary directory for this Claude session const tempDir = join(tmpdir(), `claude-session-${Date.now()}`); mkdirSync(tempDir, { recursive: true }); console.log('Created temp directory:', tempDir); const claude = spawn('claude', ['-p', prompt], { stdio: ['pipe', 'pipe', 'pipe'], cwd: tempDir, // Run Claude in the temp directory env: { ...process.env } }); console.log('Claude process spawned, PID:', claude.pid); console.log('Command:', `claude -p [prompt] (in ${tempDir})`); let output = ''; let error = ''; let isResolved = false; // Set a timeout (2 minutes for complex panel generation) const timeout = setTimeout(() => { if (!isResolved) { console.error('=== Claude Process Timeout ==='); console.error('Process took longer than 120 seconds'); console.error('Output received so far:', output.length, 'characters'); console.error('Error output:', error); claude.kill('SIGTERM'); reject(new Error('Claude process timed out after 120 seconds')); } }, 120000); // Handle stdout claude.stdout.on('data', (data) => { const chunk = data.toString(); output += chunk; console.log(`Received stdout chunk: ${chunk.length} characters`); console.log('Chunk preview:', chunk.substring(0, 200)); }); // Handle stderr claude.stderr.on('data', (data) => { const chunk = data.toString(); error += chunk; console.error('Received stderr:', chunk); }); // Handle errors claude.on('error', (err) => { clearTimeout(timeout); isResolved = true; console.error('Claude spawn error:', err); reject(err); }); // Handle exit claude.on('exit', (code) => { clearTimeout(timeout); isResolved = true; // Clean up temp directory try { rmSync(tempDir, { recursive: true, force: true }); console.log('Cleaned up temp directory'); } catch (e) { console.warn('Failed to clean up temp directory:', e); } const duration = Date.now() - startTime; console.log(`=== Claude Process Completed ===`); console.log(`Exit code: ${code}`); console.log(`Duration: ${duration}ms`); console.log(`Total output: ${output.length} characters`); console.log(`Total error: ${error.length} characters`); if (code !== 0) { console.error('Claude process failed with code:', code); console.error('Error output:', error); console.error('Standard output preview:', output.substring(0, 500)); reject(new Error(`Claude process exited with code ${code}: ${error}`)); } else { console.log('Claude completed successfully'); console.log('Full output preview (first 1000 chars):', output.substring(0, 1000)); // Try multiple patterns to extract the component // 1. Look for content between code blocks (most common) const codeBlockPatterns = [ /```(?:html|svelte|xml|javascript)?\s*\n([\s\S]*?)```/, /```\s*\n([\s\S]*?)```/ ]; for (const pattern of codeBlockPatterns) { const match = output.match(pattern); if (match && match[1]) { console.log('Found code block match with pattern:', pattern); resolve(match[1].trim()); return; } } // 2. Look for complete HTML structure with metadata const fullMatch = output.match(/