UNPKG

morphbox

Version:

Docker-based AI sandbox for development with Claude integration

266 lines (254 loc) 9.96 kB
import { j as json } from './index-3BbzJtgI.js'; import { mkdir, writeFile } from 'fs/promises'; import { join } from 'path'; import { homedir, tmpdir } from 'os'; import { spawn } from 'child_process'; import { mkdirSync, rmSync } from 'fs'; import { c as createMorphFile } from './morph-f-EPjDPR.js'; const PANELS_DIR = join(homedir(), "morphbox", "panels"); function generatePanelId(name) { 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 <div> tags. Do not include any markdown formatting, code blocks, or explanations. Just the raw HTML/CSS/JavaScript code. The panel should follow this structure: <!-- @morphbox-panel id: (will be generated) name: {name} description: {description} version: 1.0.0 --> <div class="custom-panel"> <div class="panel-header"> <h2>{name}</h2> </div> <div class="panel-content"> <!-- Panel content here --> </div> </div> <style> /* Panel styles using CSS variables */ </style> <script> // Panel logic here // Available: panelId, data, websocketUrl // Use vanilla JS, no frameworks <\/script> Make it fully functional and production-ready. Use modern JavaScript features.`; async function generatePanelWithClaude(name, description, customPrompt) { 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); const promptTemplate = customPrompt || DEFAULT_SYSTEM_PROMPT; const prompt = promptTemplate.replace(/\{name\}/g, name).replace(/\{description\}/g, description); console.log("Prompt length:", prompt.length); const startTime = Date.now(); console.log("Executing Claude CLI in dedicated temp directory..."); 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; 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")); } }, 12e4); 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)); }); claude.stderr.on("data", (data) => { const chunk = data.toString(); error += chunk; console.error("Received stderr:", chunk); }); claude.on("error", (err) => { clearTimeout(timeout); isResolved = true; console.error("Claude spawn error:", err); reject(err); }); claude.on("exit", (code) => { clearTimeout(timeout); isResolved = true; 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, 1e3)); 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; } } const fullMatch = output.match(/<!--\s*@morphbox-panel[\s\S]*?<\/script>/i); if (fullMatch) { console.log("Found full panel match"); resolve(fullMatch[0]); return; } const htmlMatch = output.match(/(<div[\s\S]*?<\/script>)/i); if (htmlMatch) { console.log("Found HTML structure match"); resolve(htmlMatch[1]); return; } const doctypeMatch = output.match(/<!DOCTYPE html>[\s\S]*/i); if (doctypeMatch) { console.log("Found DOCTYPE match"); resolve(doctypeMatch[0]); return; } if (output.trim().startsWith("<") && (output.includes("</div>") || output.includes("</html>"))) { console.log("Output appears to be raw HTML"); resolve(output.trim()); return; } console.error("=== Could not parse Claude response ==="); console.error("Output length:", output.length); console.error("First 500 chars:", output.substring(0, 500)); console.error("Last 500 chars:", output.substring(output.length - 500)); if (output.trim().length > 0) { console.log("Returning raw output as fallback"); resolve(output.trim()); return; } reject(new Error("Could not extract panel component from Claude response")); } }); claude.stdin.end(); }); } const POST = async ({ request }) => { try { const { name, description, customPrompt } = await request.json(); if (!name || !description) { return json({ error: "Name and description are required" }, { status: 400 }); } await mkdir(PANELS_DIR, { recursive: true }); const id = generatePanelId(name); const filename = `${id}.morph`; const filepath = join(PANELS_DIR, filename); let content; try { content = await generatePanelWithClaude(name, description, customPrompt); content = content.replace( /id:\s*\(will be generated\)/, `id: ${id}` ); } catch (error) { console.error("Failed to generate panel with Claude:", error); let errorMessage = "Failed to generate panel with Claude"; let errorDetails = ""; if (error instanceof Error) { if (error.message.includes("Claude process timed out")) { errorMessage = "Claude took too long to respond"; errorDetails = "Please ensure Claude CLI is running and try again."; } else if (error.message.includes("exited with code")) { errorMessage = "Claude CLI failed to generate panel"; errorDetails = "Please check that Claude CLI is properly installed and configured."; } else { errorMessage = error.message; } } return json({ error: errorMessage, details: errorDetails }, { status: 500 }); } const morphFile = createMorphFile( { id, name, description, version: "1.0.0", features: [], tags: [] }, content, description ); await writeFile(filepath, JSON.stringify(morphFile, null, 2), "utf-8"); return json({ id, filename, path: filepath, metadata: morphFile.metadata, format: "morph" }); } catch (error) { console.error("Failed to create custom panel:", error); let errorMessage = "Failed to create panel"; let errorDetails = ""; if (error instanceof Error) { errorMessage = error.message; if (error.message.includes("Claude process")) { errorDetails = "The Claude CLI might be unavailable or took too long to respond. The panel was created with a basic template instead."; } } return json({ error: errorMessage, details: errorDetails }, { status: 500 }); } }; export { POST }; //# sourceMappingURL=_server.ts-CNNf8r07.js.map