clawfire 0.4.2 → 0.4.4

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/dist/dev.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dev.ts","../src/dev/dev-server.ts","../src/core/schema.ts","../src/core/errors.ts","../src/core/logger.ts","../src/firebase/auth.ts","../src/routing/router.ts","../src/routing/discover.ts","../src/playground/html.ts","../src/dev/watcher.ts","../src/dev/page-compiler.ts","../src/dev/env-manager.ts","../src/dev/firebase-status.ts","../src/dev/dashboard-html.ts","../src/dev/firebase-setup.ts"],"sourcesContent":["/**\n * clawfire/dev — Development Server with Hot Reload\n *\n * @example\n * ```ts\n * import { startDevServer } from \"clawfire/dev\";\n *\n * startDevServer({ port: 3456 });\n * ```\n */\nexport { DevServer, startDevServer, type DevServerOptions } from \"./dev/index.js\";\nexport { FileWatcher, type WatchEvent, type WatchEventType } from \"./dev/index.js\";\n","/**\n * Clawfire Development Server\n *\n * Two-port architecture:\n * - Frontend Server (port 3000): pages, public/ serving, HMR, API proxy, SPA fallback\n * - API Server (port 3456): API routes, Playground, SSE\n */\nimport http from \"node:http\";\nimport { resolve, join, relative, extname } from \"node:path\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport type { APIContract } from \"../core/schema.js\";\nimport { ClawfireRouter, createRouter, type RouterOptions } from \"../routing/router.js\";\nimport { discoverRoutes } from \"../routing/discover.js\";\nimport { generatePlaygroundHtml } from \"../playground/html.js\";\nimport { FileWatcher, type WatchEvent } from \"./watcher.js\";\nimport { PageCompiler } from \"./page-compiler.js\";\nimport { logger } from \"../core/logger.js\";\nimport { EnvManager } from \"./env-manager.js\";\nimport { checkFirebaseStatus, clearFirebaseStatusCache, fetchFirebaseSdkConfig } from \"./firebase-status.js\";\nimport { generateDashboardHtml } from \"./dashboard-html.js\";\nimport { FirebaseSetup } from \"./firebase-setup.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface DevServerOptions {\n /** Project root path */\n projectDir?: string;\n /** Frontend server port (default: 3000) */\n port?: number;\n /** API server port (default: 3456) */\n apiPort?: number;\n /** Router options */\n routerOptions?: Partial<RouterOptions>;\n /** Hot reload enabled (default: true) */\n hotReload?: boolean;\n /** Change detection debounce ms (default: 150) */\n debounceMs?: number;\n /** Callback to manually register routes (instead of file-based) */\n onSetupRoutes?: (router: ClawfireRouter) => void | Promise<void>;\n}\n\ninterface SSEClient {\n id: number;\n res: http.ServerResponse;\n}\n\n// ─── MIME Types ──────────────────────────────────────────────────────\n\nconst MIME_TYPES: Record<string, string> = {\n html: \"text/html; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n js: \"application/javascript; charset=utf-8\",\n mjs: \"application/javascript; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n svg: \"image/svg+xml\",\n ico: \"image/x-icon\",\n webp: \"image/webp\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n ttf: \"font/ttf\",\n eot: \"application/vnd.ms-fontobject\",\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n pdf: \"application/pdf\",\n txt: \"text/plain; charset=utf-8\",\n xml: \"application/xml; charset=utf-8\",\n};\n\n/** File extensions treated as non-HTML static assets */\nconst STATIC_EXTENSIONS = new Set([\n \".css\", \".js\", \".mjs\", \".json\", \".png\", \".jpg\", \".jpeg\", \".gif\", \".svg\",\n \".ico\", \".webp\", \".woff\", \".woff2\", \".ttf\", \".eot\", \".mp4\", \".webm\",\n \".mp3\", \".wav\", \".pdf\", \".txt\", \".xml\", \".map\",\n]);\n\n// ─── HMR Script ─────────────────────────────────────────────────────\n\nfunction generateHmrScript(port: number): string {\n return `\n<script data-clawfire-hmr>\n(function() {\n var dot, status;\n function createBanner() {\n var banner = document.createElement('div');\n banner.id = 'clawfire-dev-banner';\n banner.style.cssText = 'position:fixed;bottom:0;left:0;right:0;padding:6px 16px;background:#1a1a2e;color:#f97316;font-size:12px;font-family:monospace;z-index:99999;display:flex;align-items:center;gap:8px;border-top:1px solid #2a2a2a;';\n banner.innerHTML = '<span style=\"width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;\" id=\"clawfire-dot\"></span><span>Clawfire Dev</span><span style=\"color:#666;margin-left:auto;\" id=\"clawfire-status\">Connected</span>';\n document.body.appendChild(banner);\n dot = document.getElementById('clawfire-dot');\n status = document.getElementById('clawfire-status');\n }\n\n function refreshCss() {\n var links = document.querySelectorAll('link[rel=\"stylesheet\"]');\n for (var i = 0; i < links.length; i++) {\n var href = links[i].getAttribute('href');\n if (href) {\n var url = new URL(href, location.href);\n url.searchParams.set('_hmr', Date.now().toString());\n links[i].setAttribute('href', url.toString());\n }\n }\n var styles = document.querySelectorAll('style[data-href]');\n for (var j = 0; j < styles.length; j++) {\n var dataHref = styles[j].getAttribute('data-href');\n if (dataHref) {\n fetch(dataHref + '?_hmr=' + Date.now())\n .then(function(r) { return r.text(); })\n .then(function(css) { styles[j].textContent = css; })\n .catch(function() {});\n }\n }\n if (status) status.textContent = 'CSS updated';\n setTimeout(function() { if (status) status.textContent = 'Connected'; }, 1500);\n }\n\n var reconnectTimer;\n function connect() {\n var es = new EventSource('http://localhost:${port}/__dev/events');\n es.onopen = function() {\n if (!dot) createBanner();\n dot.style.background = '#22c55e';\n status.textContent = 'Connected';\n if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }\n };\n es.onmessage = function(e) {\n try {\n var data = JSON.parse(e.data);\n if (data.type === 'connected') return;\n if (data.type === 'css-change') {\n refreshCss();\n return;\n }\n if (data.type === 'error') {\n if (dot) dot.style.background = '#ef4444';\n if (status) status.textContent = 'Error: ' + (data.message || 'reload failed');\n return;\n }\n // frontend-change, route-change, page-change, component-change → full reload\n if (dot) dot.style.background = '#eab308';\n if (status) status.textContent = 'Reloading...';\n setTimeout(function() { location.reload(); }, 300);\n } catch(err) {}\n };\n es.onerror = function() {\n es.close();\n if (dot) dot.style.background = '#ef4444';\n if (status) status.textContent = 'Disconnected';\n reconnectTimer = setTimeout(connect, 2000);\n };\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() { connect(); });\n } else {\n connect();\n }\n})();\n</script>`;\n}\n\n// ─── Client-Side Router Script ──────────────────────────────────────\n\nfunction generateRouterScript(): string {\n return `\n<script data-clawfire-router>\n(function() {\n function updateActiveNav() {\n var path = location.pathname;\n var links = document.querySelectorAll('#nav-links a[href]');\n for (var i = 0; i < links.length; i++) {\n var href = links[i].getAttribute('href');\n if (!href || href.startsWith('http')) continue;\n var isActive = (path === '/' && href === '/') || (href !== '/' && path.startsWith(href));\n links[i].setAttribute('data-active', isActive ? 'true' : 'false');\n }\n }\n\n function navigate(url) {\n var target = new URL(url, location.href);\n // Only handle same-origin, non-hash navigation\n if (target.origin !== location.origin) return false;\n if (target.pathname === location.pathname && target.hash) return false;\n\n fetch(target.pathname, {\n headers: { 'X-Clawfire-Partial': 'true' }\n })\n .then(function(res) {\n if (!res.ok) throw new Error('Page not found');\n return res.json();\n })\n .then(function(data) {\n // Update page content\n var container = document.getElementById('clawfire-page');\n if (container) {\n container.innerHTML = data.html;\n // Execute scripts in new content\n var scripts = container.querySelectorAll('script');\n for (var i = 0; i < scripts.length; i++) {\n var newScript = document.createElement('script');\n if (scripts[i].src) {\n newScript.src = scripts[i].src;\n } else {\n newScript.textContent = scripts[i].textContent;\n }\n scripts[i].parentNode.replaceChild(newScript, scripts[i]);\n }\n }\n // Update title\n if (data.meta && data.meta.title) {\n document.title = data.meta.title;\n }\n // Update URL\n history.pushState(null, '', target.pathname);\n // Update nav active state\n updateActiveNav();\n // Dispatch event for page scripts\n document.dispatchEvent(new CustomEvent('clawfire:navigate', { detail: { path: data.path } }));\n // Scroll to top\n window.scrollTo(0, 0);\n })\n .catch(function(err) {\n // Fallback: full page navigation\n location.href = url;\n });\n\n return true;\n }\n\n // Intercept link clicks\n document.addEventListener('click', function(e) {\n var anchor = e.target.closest ? e.target.closest('a[href]') : null;\n if (!anchor) return;\n var href = anchor.getAttribute('href');\n if (!href) return;\n // Skip external links, new-tab links, modified clicks\n if (href.startsWith('http') || href.startsWith('//')) return;\n if (anchor.target === '_blank') return;\n if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;\n if (anchor.hasAttribute('download')) return;\n\n e.preventDefault();\n navigate(href);\n });\n\n // Handle back/forward\n window.addEventListener('popstate', function() {\n navigate(location.pathname);\n });\n\n // Set initial active nav state\n updateActiveNav();\n})();\n</script>`;\n}\n\n// ─── Dev Server ──────────────────────────────────────────────────────\n\nexport class DevServer {\n private frontendServer: http.Server | null = null;\n private apiServer: http.Server | null = null;\n private router: ClawfireRouter;\n private watcher: FileWatcher | null = null;\n private frontendSseClients: SSEClient[] = [];\n private apiSseClients: SSEClient[] = [];\n private sseIdCounter = 0;\n private options: Required<DevServerOptions>;\n private routesDir: string;\n private schemasDir: string;\n private publicDir: string;\n private pagesDir: string;\n private componentsDir: string;\n private playgroundHtml = \"\";\n private importCounter = 0;\n private isReloading = false;\n private pageCompiler: PageCompiler;\n private envManager: EnvManager;\n private firebaseSetup: FirebaseSetup;\n\n constructor(options: DevServerOptions = {}) {\n this.options = {\n projectDir: options.projectDir || process.cwd(),\n port: options.port || 3000,\n apiPort: options.apiPort || 3456,\n routerOptions: options.routerOptions || {},\n hotReload: options.hotReload !== false,\n debounceMs: options.debounceMs || 150,\n onSetupRoutes: options.onSetupRoutes || (() => {}),\n };\n\n this.routesDir = resolve(this.options.projectDir, \"app/routes\");\n this.schemasDir = resolve(this.options.projectDir, \"app/schemas\");\n this.publicDir = resolve(this.options.projectDir, \"public\");\n this.pagesDir = resolve(this.options.projectDir, \"app/pages\");\n this.componentsDir = resolve(this.options.projectDir, \"app/components\");\n this.pageCompiler = new PageCompiler(this.options.projectDir);\n this.envManager = new EnvManager(this.options.projectDir);\n this.firebaseSetup = new FirebaseSetup(this.options.projectDir);\n\n this.router = createRouter({\n cors: [\"*\"],\n rateLimit: 0,\n ...this.options.routerOptions,\n });\n }\n\n // ─── Lifecycle ──────────────────────────────────────────────────────\n\n async start(): Promise<void> {\n await this.loadRoutes();\n this.regeneratePlayground();\n\n // Create API server (port 3456)\n this.apiServer = http.createServer((req, res) => this.handleApiRequest(req, res));\n\n // Create Frontend server (port 3000)\n this.frontendServer = http.createServer((req, res) => this.handleFrontendRequest(req, res));\n\n if (this.options.hotReload) {\n this.startWatcher();\n }\n\n // Start API server first\n await new Promise<void>((resolve, reject) => {\n this.apiServer!.listen(this.options.apiPort, () => resolve());\n this.apiServer!.on(\"error\", reject);\n });\n\n // Then start frontend server\n await new Promise<void>((resolve, reject) => {\n this.frontendServer!.listen(this.options.port, () => resolve());\n this.frontendServer!.on(\"error\", reject);\n });\n\n this.printStartupBanner();\n }\n\n async stop(): Promise<void> {\n this.watcher?.close();\n this.firebaseSetup.destroy();\n\n for (const client of [...this.frontendSseClients, ...this.apiSseClients]) {\n client.res.end();\n }\n this.frontendSseClients = [];\n this.apiSseClients = [];\n this.router.destroy();\n\n if (this.apiServer) {\n await new Promise<void>((resolve) => {\n this.apiServer!.close(() => resolve());\n });\n }\n if (this.frontendServer) {\n await new Promise<void>((resolve) => {\n this.frontendServer!.close(() => resolve());\n });\n }\n }\n\n // ─── Route Loading ─────────────────────────────────────────────────\n\n private async loadRoutes(): Promise<void> {\n this.router.destroy();\n this.router = createRouter({\n cors: [\"*\"],\n rateLimit: 0,\n ...this.options.routerOptions,\n });\n\n if (this.options.onSetupRoutes) {\n await this.options.onSetupRoutes(this.router);\n if (this.router.getRoutes().length > 0) return;\n }\n\n if (!existsSync(this.routesDir)) return;\n\n const discovered = discoverRoutes(this.routesDir);\n for (const route of discovered) {\n try {\n const fullPath = resolve(this.routesDir, route.filePath);\n const fileUrl = pathToFileURL(fullPath).href;\n const mod = await import(`${fileUrl}?v=${++this.importCounter}`);\n const contract = mod.default as APIContract;\n if (contract && typeof contract.handler === \"function\" && contract.input && contract.output && contract.meta) {\n this.router.register(route.apiPath, contract);\n }\n } catch (err) {\n logger.warn(`Failed to load route: ${route.filePath}`, err);\n }\n }\n }\n\n private async reloadRoutes(event: WatchEvent): Promise<void> {\n if (this.isReloading) return;\n this.isReloading = true;\n\n const relPath = relative(this.options.projectDir, event.filePath);\n const timestamp = new Date().toLocaleTimeString();\n\n console.log(`\\n \\x1b[33m[${timestamp}]\\x1b[0m \\x1b[36m${relPath}\\x1b[0m changed`);\n console.log(\" Reloading routes...\");\n\n try {\n await this.loadRoutes();\n this.regeneratePlayground();\n clearFirebaseStatusCache();\n\n const routeCount = this.router.getRoutes().length;\n console.log(` \\x1b[32m✓\\x1b[0m ${routeCount} routes loaded`);\n\n // Notify API SSE clients (playground)\n this.broadcastSSE(this.apiSseClients, {\n type: event.type,\n file: relPath,\n timestamp: event.timestamp,\n routes: routeCount,\n });\n\n // Also notify frontend SSE clients for route changes\n this.broadcastSSE(this.frontendSseClients, {\n type: event.type,\n file: relPath,\n timestamp: event.timestamp,\n routes: routeCount,\n });\n } catch (err) {\n console.log(` \\x1b[31m✗\\x1b[0m Reload failed:`, err);\n const errorData = {\n type: \"error\",\n file: relPath,\n message: err instanceof Error ? err.message : \"Unknown error\",\n };\n this.broadcastSSE(this.apiSseClients, errorData);\n this.broadcastSSE(this.frontendSseClients, errorData);\n } finally {\n this.isReloading = false;\n }\n }\n\n // ─── Frontend Change Handler ───────────────────────────────────────\n\n private handleFrontendChange(event: WatchEvent): void {\n const relPath = relative(this.options.projectDir, event.filePath);\n const timestamp = new Date().toLocaleTimeString();\n\n if (event.type === \"css-change\") {\n console.log(`\\n \\x1b[33m[${timestamp}]\\x1b[0m \\x1b[35m${relPath}\\x1b[0m CSS updated`);\n this.broadcastSSE(this.frontendSseClients, {\n type: \"css-change\",\n file: relPath,\n timestamp: event.timestamp,\n });\n } else {\n console.log(`\\n \\x1b[33m[${timestamp}]\\x1b[0m \\x1b[35m${relPath}\\x1b[0m changed → reload`);\n this.broadcastSSE(this.frontendSseClients, {\n type: event.type,\n file: relPath,\n timestamp: event.timestamp,\n });\n }\n }\n\n // ─── File Watcher ──────────────────────────────────────────────────\n\n private startWatcher(): void {\n this.watcher = new FileWatcher(this.options.debounceMs);\n\n // routes\n if (existsSync(this.routesDir)) {\n this.watcher.watchDir(this.routesDir, \"route-change\");\n }\n\n // schemas\n if (existsSync(this.schemasDir)) {\n this.watcher.watchDir(this.schemasDir, \"schema-change\");\n }\n\n // config file\n const configFile = resolve(this.options.projectDir, \"clawfire.config.ts\");\n if (existsSync(configFile)) {\n this.watcher.watchFile(configFile, \"config-change\");\n }\n\n // public/ directory (frontend hot reload)\n if (existsSync(this.publicDir)) {\n this.watcher.watchDirFrontend(this.publicDir);\n }\n\n // pages/ directory\n if (existsSync(this.pagesDir)) {\n this.watcher.watchDir(this.pagesDir, \"page-change\");\n }\n\n // components/ directory\n if (existsSync(this.componentsDir)) {\n this.watcher.watchDir(this.componentsDir, \"component-change\");\n }\n\n // API/schema/config changes → route reload\n this.watcher.on(\"route-change\", (event: WatchEvent) => this.reloadRoutes(event));\n this.watcher.on(\"schema-change\", (event: WatchEvent) => this.reloadRoutes(event));\n this.watcher.on(\"config-change\", (event: WatchEvent) => this.reloadRoutes(event));\n\n // Frontend changes → browser notification (CSS hot / full reload)\n this.watcher.on(\"frontend-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n this.watcher.on(\"css-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n\n // Page/component changes → full reload broadcast\n this.watcher.on(\"page-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n this.watcher.on(\"component-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n }\n\n // ─── SSE (Server-Sent Events) ──────────────────────────────────────\n\n private handleSSE(req: http.IncomingMessage, res: http.ServerResponse, clients: SSEClient[]): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n\n const clientId = ++this.sseIdCounter;\n const client: SSEClient = { id: clientId, res };\n clients.push(client);\n\n res.write(`data: ${JSON.stringify({ type: \"connected\", id: clientId })}\\n\\n`);\n\n req.on(\"close\", () => {\n const idx = clients.indexOf(client);\n if (idx !== -1) clients.splice(idx, 1);\n });\n }\n\n private broadcastSSE(clients: SSEClient[], data: Record<string, unknown>): void {\n const message = `data: ${JSON.stringify(data)}\\n\\n`;\n for (const client of clients) {\n try {\n client.res.write(message);\n } catch {\n // disconnected client\n }\n }\n }\n\n // ─── Playground ────────────────────────────────────────────────────\n\n private regeneratePlayground(): void {\n const baseHtml = generatePlaygroundHtml({\n title: \"Clawfire Dev Playground\",\n apiBaseUrl: `http://localhost:${this.options.apiPort}`,\n });\n\n const dashboardHtml = generateDashboardHtml({\n apiPort: this.options.apiPort,\n });\n\n // Tab bar + tab switching script\n const tabBar = `\n<div id=\"clawfire-tab-bar\" style=\"position:sticky;top:0;z-index:9999;background:#0a0a0a;border-bottom:1px solid #2a2a2a;padding:0 16px;display:flex;gap:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;\">\n <button onclick=\"switchTab('apis')\" id=\"tab-btn-apis\" style=\"padding:10px 20px;background:transparent;border:none;border-bottom:2px solid #f97316;color:#f97316;font-size:14px;font-weight:600;cursor:pointer;\">APIs</button>\n <button onclick=\"switchTab('dashboard')\" id=\"tab-btn-dashboard\" style=\"padding:10px 20px;background:transparent;border:none;border-bottom:2px solid transparent;color:#a3a3a3;font-size:14px;font-weight:600;cursor:pointer;\">Dashboard</button>\n</div>`;\n\n const tabScript = `\n<script>\nfunction switchTab(tab) {\n var apis = document.getElementById('tab-apis');\n var dashboard = document.getElementById('tab-dashboard');\n var btnApis = document.getElementById('tab-btn-apis');\n var btnDash = document.getElementById('tab-btn-dashboard');\n\n if (tab === 'apis') {\n apis.style.display = 'block';\n dashboard.style.display = 'none';\n btnApis.style.borderBottomColor = '#f97316';\n btnApis.style.color = '#f97316';\n btnDash.style.borderBottomColor = 'transparent';\n btnDash.style.color = '#a3a3a3';\n } else {\n apis.style.display = 'none';\n dashboard.style.display = 'block';\n btnApis.style.borderBottomColor = 'transparent';\n btnApis.style.color = '#a3a3a3';\n btnDash.style.borderBottomColor = '#f97316';\n btnDash.style.color = '#f97316';\n // Lazy-load dashboard data on first click\n if (window._loadDashboard) window._loadDashboard();\n }\n}\n</script>`;\n\n const liveReloadScript = `\n<script>\n(function() {\n var banner = document.createElement('div');\n banner.id = 'dev-banner';\n banner.style.cssText = 'position:fixed;bottom:0;left:0;right:0;padding:6px 16px;background:#1a1a2e;color:#f97316;font-size:12px;font-family:monospace;z-index:9999;display:flex;align-items:center;gap:8px;border-top:1px solid #2a2a2a;';\n banner.innerHTML = '<span style=\"width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;\" id=\"dev-dot\"></span><span>Clawfire Dev Server</span><span style=\"color:#666;margin-left:auto;\" id=\"dev-status\">Connected</span>';\n document.body.appendChild(banner);\n\n var reconnectTimer;\n function connect() {\n var es = new EventSource('http://localhost:${this.options.apiPort}/__dev/events');\n es.onopen = function() {\n document.getElementById('dev-dot').style.background = '#22c55e';\n document.getElementById('dev-status').textContent = 'Connected';\n if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }\n };\n es.onmessage = function(e) {\n try {\n var data = JSON.parse(e.data);\n if (data.type === 'connected') return;\n if (data.type === 'error') {\n document.getElementById('dev-dot').style.background = '#ef4444';\n document.getElementById('dev-status').textContent = 'Error: ' + (data.message || 'reload failed');\n return;\n }\n document.getElementById('dev-dot').style.background = '#eab308';\n document.getElementById('dev-status').textContent = 'Reloading...';\n setTimeout(function() { window.location.reload(); }, 300);\n } catch(err) {}\n };\n es.onerror = function() {\n es.close();\n document.getElementById('dev-dot').style.background = '#ef4444';\n document.getElementById('dev-status').textContent = 'Disconnected — reconnecting...';\n reconnectTimer = setTimeout(connect, 2000);\n };\n }\n connect();\n})();\n</script>`;\n\n // Inject: tab bar after <body>, wrap playground in tab-apis, add dashboard tab\n let html = baseHtml;\n // Insert tab bar right after <body...>\n html = html.replace(/<body[^>]*>/, (match) => `${match}\\n${tabBar}`);\n // Wrap existing content: find the first element after body and wrap everything until </body>\n // Strategy: wrap all body content in tab-apis div, then append dashboard\n const bodyOpenMatch = html.match(/<body[^>]*>/);\n if (bodyOpenMatch) {\n const bodyOpenEnd = html.indexOf(bodyOpenMatch[0]) + bodyOpenMatch[0].length;\n const tabBarEnd = html.indexOf(\"</div>\", bodyOpenEnd) + \"</div>\".length; // end of tab bar\n const bodyClose = html.lastIndexOf(\"</body>\");\n\n const beforeTabBar = html.slice(0, tabBarEnd);\n const playgroundContent = html.slice(tabBarEnd, bodyClose);\n const afterBody = html.slice(bodyClose);\n\n html = beforeTabBar +\n `\\n<div id=\"tab-apis\">${playgroundContent}</div>` +\n `\\n<div id=\"tab-dashboard\" style=\"display:none;\">${dashboardHtml}</div>` +\n `\\n${tabScript}` +\n `\\n${liveReloadScript}\\n` +\n afterBody;\n } else {\n // Fallback: just append scripts\n html = html.replace(\"</body>\", `${liveReloadScript}\\n</body>`);\n }\n\n this.playgroundHtml = html;\n }\n\n // ─── Script Injection ──────────────────────────────────────────────\n\n /**\n * Inject HMR and (optionally) client-side router scripts before </body>.\n */\n private injectScripts(html: string, includeRouter: boolean): string {\n const hmr = generateHmrScript(this.options.port);\n const router = includeRouter ? generateRouterScript() : \"\";\n const scripts = router + hmr;\n\n if (html.includes(\"</body>\")) {\n return html.replace(\"</body>\", scripts + \"\\n</body>\");\n }\n return html + scripts;\n }\n\n // ─── Static File Serving ──────────────────────────────────────────\n\n private serveStaticFile(filePath: string, res: http.ServerResponse): boolean {\n if (!existsSync(filePath)) return false;\n\n try {\n const content = readFileSync(filePath);\n const ext = extname(filePath).slice(1).toLowerCase();\n const mime = MIME_TYPES[ext] || \"application/octet-stream\";\n\n // HTML files get HMR script injected\n if (ext === \"html\") {\n const html = content.toString(\"utf-8\");\n const injected = this.injectScripts(html, false);\n res.writeHead(200, { \"Content-Type\": mime });\n res.end(injected);\n return true;\n }\n\n res.writeHead(200, { \"Content-Type\": mime });\n res.end(content);\n return true;\n } catch {\n return false;\n }\n }\n\n // ─── API Proxy ────────────────────────────────────────────────────\n\n private proxyToApiServer(req: http.IncomingMessage, res: http.ServerResponse): void {\n const proxyReq = http.request(\n {\n hostname: \"127.0.0.1\",\n port: this.options.apiPort,\n path: req.url,\n method: req.method,\n headers: {\n ...req.headers,\n host: `localhost:${this.options.apiPort}`,\n },\n },\n (proxyRes) => {\n res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);\n proxyRes.pipe(res, { end: true });\n },\n );\n\n proxyReq.on(\"error\", (err) => {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: { code: \"PROXY_ERROR\", message: \"API server unavailable\" } }));\n });\n\n req.pipe(proxyReq, { end: true });\n }\n\n // ─── Frontend Request Handler ─────────────────────────────────────\n\n private handleFrontendRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url || \"/\", `http://localhost:${this.options.port}`);\n\n // 1. SSE endpoint for frontend HMR\n if (url.pathname === \"/__dev/events\") {\n this.handleSSE(req, res, this.frontendSseClients);\n return;\n }\n\n // 2. Proxy /api/* requests to API server\n if (url.pathname.startsWith(\"/api\")) {\n this.proxyToApiServer(req, res);\n return;\n }\n\n // 3. Proxy /__dev/* (except /events handled above) and /__playground to API server\n if (url.pathname.startsWith(\"/__\")) {\n this.proxyToApiServer(req, res);\n return;\n }\n\n // 4. Non-HTML file extensions → serve from public/\n const ext = extname(url.pathname);\n if (ext && STATIC_EXTENSIONS.has(ext)) {\n const filePath = resolve(this.publicDir, url.pathname.slice(1));\n if (filePath.startsWith(this.publicDir) && this.serveStaticFile(filePath, res)) {\n return;\n }\n // Static file not found\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n // 5. Page system\n if (this.pageCompiler.isActive()) {\n const isPartial = req.headers[\"x-clawfire-partial\"] === \"true\";\n const pagePath = this.pageCompiler.resolve(url.pathname);\n\n if (pagePath) {\n try {\n if (isPartial) {\n // SPA navigation — return JSON with page content only\n const partial = this.pageCompiler.compilePartial(pagePath, url.pathname);\n res.writeHead(200, { \"Content-Type\": \"application/json; charset=utf-8\" });\n res.end(JSON.stringify(partial));\n } else {\n // Full page render with layouts + components + script injection\n const compiled = this.pageCompiler.compile(pagePath);\n const html = this.injectScripts(compiled.html, true);\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n }\n } catch (err) {\n logger.warn(`Page compilation error: ${url.pathname}`, err);\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(`<h1>500 — Page Compilation Error</h1><pre>${err instanceof Error ? err.message : \"Unknown error\"}</pre>`);\n }\n return;\n }\n\n // 404 page\n const page404 = this.pageCompiler.resolve404();\n if (page404) {\n try {\n if (isPartial) {\n const partial = this.pageCompiler.compilePartial(page404, url.pathname);\n res.writeHead(404, { \"Content-Type\": \"application/json; charset=utf-8\" });\n res.end(JSON.stringify(partial));\n } else {\n const compiled = this.pageCompiler.compile(page404);\n const html = this.injectScripts(compiled.html, true);\n res.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n }\n } catch {\n res.writeHead(404);\n res.end(\"Not found\");\n }\n return;\n }\n }\n\n // 6. Serve static files from public/ (for paths without extension, e.g. directory index)\n const requestedPath = url.pathname === \"/\" ? \"index.html\" : url.pathname.slice(1);\n const filePath = resolve(this.publicDir, requestedPath);\n\n // Security: prevent directory traversal\n if (!filePath.startsWith(this.publicDir)) {\n res.writeHead(403);\n res.end(\"Forbidden\");\n return;\n }\n\n if (this.serveStaticFile(filePath, res)) {\n return;\n }\n\n // 7. SPA fallback: serve index.html for unmatched routes\n const indexPath = resolve(this.publicDir, \"index.html\");\n if (existsSync(indexPath)) {\n this.serveStaticFile(indexPath, res);\n return;\n }\n\n res.writeHead(404);\n res.end(\"Not found\");\n }\n\n // ─── API Request Handler ──────────────────────────────────────────\n\n private handleApiRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url || \"/\", `http://localhost:${this.options.apiPort}`);\n\n // SSE endpoint for playground\n if (url.pathname === \"/__dev/events\") {\n this.handleSSE(req, res, this.apiSseClients);\n return;\n }\n\n // Playground\n if (url.pathname === \"/\" || url.pathname === \"/__playground\") {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(this.playgroundHtml);\n return;\n }\n\n // API requests\n if (url.pathname.startsWith(\"/api\")) {\n // CORS\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"POST, GET, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", async () => {\n let parsed = {};\n try { parsed = body ? JSON.parse(body) : {}; } catch {}\n\n await this.router.handleRequest(\n {\n method: req.method,\n path: url.pathname,\n body: parsed,\n headers: req.headers as Record<string, string>,\n ip: req.socket.remoteAddress || \"127.0.0.1\",\n },\n {\n set(h: Record<string, string>) {\n for (const [k, v] of Object.entries(h)) {\n try { res.setHeader(k, v); } catch {}\n }\n },\n status(code: number) {\n return {\n json(data: unknown) {\n res.writeHead(code, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n },\n send(data: string) { res.writeHead(code); res.end(data); },\n end() { res.writeHead(code); res.end(); },\n };\n },\n },\n );\n });\n return;\n }\n\n // Dev dashboard endpoints\n if (url.pathname.startsWith(\"/__dev/\")) {\n this.handleDevEndpoint(req, res, url);\n return;\n }\n\n // 404\n res.writeHead(404);\n res.end(\"Not found\");\n }\n\n // ─── Startup Banner ────────────────────────────────────────────────\n\n private printStartupBanner(): void {\n const routes = this.router.getRoutes();\n const watching = this.options.hotReload;\n const pagesActive = this.pageCompiler.isActive();\n\n console.log(\"\");\n console.log(\" \\x1b[1m\\x1b[33m⚡ Clawfire Dev Server\\x1b[0m\");\n console.log(\" \\x1b[2m─────────────────────────────────────\\x1b[0m\");\n console.log(` \\x1b[36mApp\\x1b[0m : http://localhost:${this.options.port}`);\n console.log(` \\x1b[36mAPI\\x1b[0m : http://localhost:${this.options.apiPort}/api/...`);\n console.log(` \\x1b[36mPlayground\\x1b[0m : http://localhost:${this.options.apiPort}`);\n console.log(` \\x1b[36mPages\\x1b[0m : ${pagesActive ? \"\\x1b[32mON\\x1b[0m (app/pages/)\" : \"\\x1b[2mOFF\\x1b[0m\"}`);\n console.log(\"\");\n console.log(` \\x1b[32mRoutes (${routes.length})\\x1b[0m:`);\n for (const route of routes) {\n const auth = route.contract.meta.auth || \"public\";\n const authColor = auth === \"public\" ? \"32\" : auth === \"authenticated\" ? \"34\" : auth === \"role\" ? \"33\" : \"31\";\n console.log(` POST /api\\x1b[1m${route.path}\\x1b[0m \\x1b[${authColor}m[${auth}]\\x1b[0m \\x1b[2m${route.contract.meta.description}\\x1b[0m`);\n }\n console.log(\"\");\n if (watching) {\n const watchDirs = [\"app/routes/\", \"app/schemas/\", \"public/\"];\n if (pagesActive) watchDirs.push(\"app/pages/\", \"app/components/\");\n console.log(` \\x1b[35mHot Reload\\x1b[0m : \\x1b[32mON\\x1b[0m`);\n console.log(` \\x1b[2mWatching: ${watchDirs.join(\", \")}\\x1b[0m`);\n } else {\n console.log(` \\x1b[35mHot Reload\\x1b[0m : OFF`);\n }\n console.log(`\\n \\x1b[2mPress Ctrl+C to stop\\x1b[0m\\n`);\n }\n\n // ─── Dev Dashboard Endpoints ──────────────────────────────────────\n\n private handleDevEndpoint(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n url: URL,\n ): void {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const sendJson = (data: unknown, status = 200) => {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n };\n\n // GET /__dev/firebase-status\n if (url.pathname === \"/__dev/firebase-status\" && req.method === \"GET\") {\n checkFirebaseStatus(this.options.projectDir)\n .then((status) => sendJson(status))\n .catch((err) => sendJson({ error: err.message }, 500));\n return;\n }\n\n // GET /__dev/config\n if (url.pathname === \"/__dev/config\" && req.method === \"GET\") {\n sendJson(this.readProjectConfig());\n return;\n }\n\n // POST /__dev/config — update config field values\n if (url.pathname === \"/__dev/config\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const data = JSON.parse(body);\n if (data.action === \"set\" && data.key && data.value !== undefined) {\n this.updateProjectConfig(data.key, String(data.value));\n clearFirebaseStatusCache();\n sendJson({ ok: true });\n } else if (data.action === \"set-multiple\" && data.fields) {\n for (const [key, value] of Object.entries(data.fields)) {\n this.updateProjectConfig(key, String(value));\n }\n clearFirebaseStatusCache();\n sendJson({ ok: true });\n } else {\n sendJson({ error: \"Invalid action\" }, 400);\n }\n } catch (err) {\n sendJson({ error: err instanceof Error ? err.message : \"Failed\" }, 400);\n }\n });\n return;\n }\n\n // GET /__dev/firebase-sdk-config — auto-fill from Firebase CLI\n if (url.pathname === \"/__dev/firebase-sdk-config\" && req.method === \"GET\") {\n fetchFirebaseSdkConfig(this.options.projectDir)\n .then((config) => sendJson(config))\n .catch((err) => sendJson({ error: err instanceof Error ? err.message : \"Failed to fetch SDK config\" }, 500));\n return;\n }\n\n // GET /__dev/env\n if (url.pathname === \"/__dev/env\" && req.method === \"GET\") {\n try {\n sendJson(this.envManager.read());\n } catch (err) {\n sendJson({ error: err instanceof Error ? err.message : \"Read failed\" }, 500);\n }\n return;\n }\n\n // POST /__dev/env\n if (url.pathname === \"/__dev/env\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const data = JSON.parse(body);\n if (data.action === \"set\") {\n this.envManager.set(data.key, data.value, data.description);\n sendJson({ ok: true });\n } else if (data.action === \"delete\") {\n this.envManager.delete(data.key);\n sendJson({ ok: true });\n } else {\n sendJson({ error: \"Invalid action\" }, 400);\n }\n } catch (err) {\n sendJson({ error: err instanceof Error ? err.message : \"Failed\" }, 400);\n }\n });\n return;\n }\n\n // ─── Setup Wizard Endpoints ─────────────────────────────────────\n\n // GET /__dev/setup/status\n if (url.pathname === \"/__dev/setup/status\" && req.method === \"GET\") {\n this.firebaseSetup.getStatus()\n .then((status) => sendJson(status))\n .catch((err) => sendJson({ error: err instanceof Error ? err.message : \"Failed\" }, 500));\n return;\n }\n\n // POST /__dev/setup/install-cli\n if (url.pathname === \"/__dev/setup/install-cli\" && req.method === \"POST\") {\n this.firebaseSetup.installCli()\n .then((result) => sendJson(result))\n .catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : \"Failed\" }, 500));\n return;\n }\n\n // POST /__dev/setup/login — opens a real terminal window\n if (url.pathname === \"/__dev/setup/login\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n let reauth = false;\n try {\n const data = JSON.parse(body);\n reauth = !!data.reauth;\n } catch {\n // default: not reauth\n }\n const result = this.firebaseSetup.openLoginTerminal(reauth);\n sendJson(result);\n });\n return;\n }\n\n // GET /__dev/setup/projects\n if (url.pathname === \"/__dev/setup/projects\" && req.method === \"GET\") {\n this.firebaseSetup.listProjects()\n .then((result) => sendJson(result))\n .catch((err) => sendJson({ projects: [], error: err instanceof Error ? err.message : \"Failed\" }, 500));\n return;\n }\n\n // POST /__dev/setup/select-project\n if (url.pathname === \"/__dev/setup/select-project\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const data = JSON.parse(body);\n if (!data.projectId) {\n sendJson({ success: false, message: \"projectId is required\" }, 400);\n return;\n }\n this.firebaseSetup.selectProject(data.projectId)\n .then((result) => {\n clearFirebaseStatusCache();\n sendJson(result);\n })\n .catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : \"Failed\" }, 500));\n } catch {\n sendJson({ success: false, message: \"Invalid JSON body\" }, 400);\n }\n });\n return;\n }\n\n // Unknown /__dev/ endpoint\n res.writeHead(404);\n res.end(\"Not found\");\n }\n\n // ─── Config Reader ────────────────────────────────────────────────\n\n private readProjectConfig(): { fields: Array<{ key: string; value: string; isPlaceholder: boolean }> } {\n const configPath = resolve(this.options.projectDir, \"clawfire.config.ts\");\n const fields: Array<{ key: string; value: string; isPlaceholder: boolean }> = [];\n\n if (!existsSync(configPath)) {\n return { fields };\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\");\n\n // Parse key-value pairs from the config file\n // Match patterns like: apiKey: \"value\", authDomain: \"value\", etc.\n const kvPattern = /(\\w+)\\s*:\\s*[\"'`]([^\"'`]*)[\"'`]/g;\n let match: RegExpExecArray | null;\n\n while ((match = kvPattern.exec(content)) !== null) {\n const key = match[1];\n const value = match[2];\n const isPlaceholder = /^YOUR_/i.test(value) || /^CHANGE_ME$/i.test(value) || /^TODO$/i.test(value);\n fields.push({ key, value, isPlaceholder });\n }\n } catch {\n // ignore read errors\n }\n\n return { fields };\n }\n\n /** Update a single key's value in clawfire.config.ts */\n private updateProjectConfig(key: string, value: string): void {\n const configPath = resolve(this.options.projectDir, \"clawfire.config.ts\");\n if (!existsSync(configPath)) {\n throw new Error(\"clawfire.config.ts not found\");\n }\n\n let content = readFileSync(configPath, \"utf-8\");\n\n // Match key: \"value\" or key: 'value' patterns\n const pattern = new RegExp(\n `(${this.escapeRegex(key)}\\\\s*:\\\\s*)[\"'\\`][^\"'\\`]*[\"'\\`]`,\n );\n\n if (!pattern.test(content)) {\n throw new Error(`Key \"${key}\" not found in config`);\n }\n\n content = content.replace(pattern, `$1\"${value.replace(/\"/g, '\\\\\"')}\"`);\n writeFileSync(configPath, content, \"utf-8\");\n }\n\n private escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n}\n\n/**\n * Create and start dev server (one-line helper)\n */\nexport async function startDevServer(options?: DevServerOptions): Promise<DevServer> {\n const server = new DevServer(options);\n await server.start();\n\n const shutdown = async () => {\n console.log(\"\\n Shutting down...\");\n await server.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n return server;\n}\n","/**\n * Clawfire Schema & Contract System\n *\n * 모든 API는 input/output schema + meta + handler로 구성된 \"계약(Contract)\"으로 정의됩니다.\n * Zod를 사용하여 타입 안전성과 런타임 검증을 동시에 보장합니다.\n */\nimport { z, type ZodType, type ZodObject, type ZodRawShape } from \"zod\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\n/** 인증 컨텍스트 */\nexport interface AuthContext {\n uid: string;\n email?: string;\n emailVerified?: boolean;\n role?: string;\n customClaims?: Record<string, unknown>;\n token?: string;\n}\n\n/** API 핸들러에 전달되는 컨텍스트 */\nexport interface HandlerContext {\n auth: AuthContext | null;\n /** 재인증 여부 (민감 작업용) */\n reauthenticated?: boolean;\n /** 원본 요청 헤더 */\n headers?: Record<string, string>;\n /** 요청 IP */\n ip?: string;\n}\n\n/** 권한 수준 */\nexport type AuthLevel = \"public\" | \"authenticated\" | \"role\" | \"reauth\";\n\n/** API 메타데이터 */\nexport interface APIMeta {\n /** API 설명 (AI/Playground용) */\n description: string;\n /** 태그 (그룹화용) */\n tags?: string[];\n /** 인증 요구 수준 */\n auth?: AuthLevel;\n /** 필요 역할 (auth가 'role'일 때) */\n roles?: string[];\n /** 재인증 필요 여부 */\n reauth?: boolean;\n /** Rate limit (초당 요청 수) */\n rateLimit?: number;\n /** 비활성화 여부 */\n deprecated?: boolean;\n /** 예시 입력값 */\n exampleInput?: unknown;\n /** 예시 출력값 */\n exampleOutput?: unknown;\n}\n\n/** API 계약 정의 */\nexport interface APIContract<\n TInput extends ZodType = ZodType,\n TOutput extends ZodType = ZodType,\n> {\n /** 입력 스키마 */\n input: TInput;\n /** 출력 스키마 */\n output: TOutput;\n /** 메타데이터 */\n meta: APIMeta;\n /** 핸들러 함수 */\n handler: (\n input: z.infer<TInput>,\n ctx: HandlerContext,\n ) => Promise<z.infer<TOutput>>;\n}\n\n/** 모델 필드 정의 */\nexport interface ModelField {\n type: \"string\" | \"number\" | \"boolean\" | \"timestamp\" | \"array\" | \"map\" | \"reference\" | \"geopoint\";\n required?: boolean;\n description?: string;\n default?: unknown;\n /** 배열 아이템 타입 */\n items?: ModelField;\n /** 맵 값 타입 */\n values?: ModelField;\n /** 참조 대상 컬렉션 */\n ref?: string;\n /** enum 값 리스트 */\n enum?: string[];\n}\n\n/** 모델 정의 */\nexport interface ModelDefinition {\n /** 컬렉션 이름 */\n collection: string;\n /** 필드 정의 */\n fields: Record<string, ModelField>;\n /** 서브컬렉션 */\n subcollections?: Record<string, ModelDefinition>;\n /** 인덱스 */\n indexes?: ModelIndex[];\n /** 보안 규칙 */\n rules?: ModelRules;\n /** 타임스탬프 자동 생성 */\n timestamps?: boolean;\n /** 소프트 삭제 */\n softDelete?: boolean;\n}\n\n/** Firestore 인덱스 */\nexport interface ModelIndex {\n fields: Array<{ field: string; order?: \"asc\" | \"desc\" }>;\n}\n\n/** 모델 보안 규칙 */\nexport interface ModelRules {\n read?: AuthLevel;\n create?: AuthLevel;\n update?: AuthLevel;\n delete?: AuthLevel;\n readRoles?: string[];\n createRoles?: string[];\n updateRoles?: string[];\n deleteRoles?: string[];\n /** 소유자만 읽기/쓰기 가능 필드 */\n ownerField?: string;\n}\n\n// ─── Builders ────────────────────────────────────────────────────────\n\n/**\n * API 계약 정의\n *\n * @example\n * ```ts\n * export default defineAPI({\n * input: z.object({ name: z.string() }),\n * output: z.object({ id: z.string(), name: z.string() }),\n * meta: { description: \"상품 생성\", auth: \"authenticated\" },\n * handler: async (input, ctx) => {\n * const id = await db.create(\"products\", input);\n * return { id, ...input };\n * }\n * });\n * ```\n */\nexport function defineAPI<\n TInput extends ZodType,\n TOutput extends ZodType,\n>(contract: APIContract<TInput, TOutput>): APIContract<TInput, TOutput> {\n return contract;\n}\n\n/**\n * 모델(Firestore 컬렉션) 정의\n *\n * @example\n * ```ts\n * export const Product = defineModel({\n * collection: \"products\",\n * fields: {\n * name: { type: \"string\", required: true },\n * price: { type: \"number\", required: true },\n * tags: { type: \"array\", items: { type: \"string\" } },\n * },\n * timestamps: true,\n * rules: { read: \"public\", create: \"authenticated\" }\n * });\n * ```\n */\nexport function defineModel(definition: ModelDefinition): ModelDefinition {\n return {\n timestamps: true,\n ...definition,\n };\n}\n\n// ─── Schema Utilities ────────────────────────────────────────────────\n\n/** Zod 스키마에서 JSON Schema 생성 (Playground/문서용) */\nexport function zodToJsonSchema(schema: ZodType): Record<string, unknown> {\n return extractZodShape(schema);\n}\n\nfunction extractZodShape(schema: ZodType): Record<string, unknown> {\n const def = (schema as any)._def;\n\n if (!def) return { type: \"unknown\" };\n\n switch (def.typeName) {\n case \"ZodObject\": {\n const shape = (schema as ZodObject<ZodRawShape>).shape;\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, value] of Object.entries(shape)) {\n properties[key] = extractZodShape(value as ZodType);\n if (!(value as any).isOptional?.()) {\n const innerDef = (value as any)._def;\n if (innerDef?.typeName !== \"ZodOptional\" && innerDef?.typeName !== \"ZodDefault\") {\n required.push(key);\n }\n }\n }\n\n return { type: \"object\", properties, ...(required.length > 0 ? { required } : {}) };\n }\n case \"ZodString\":\n return { type: \"string\", ...(def.checks?.length ? extractStringChecks(def.checks) : {}) };\n case \"ZodNumber\":\n return { type: \"number\" };\n case \"ZodBoolean\":\n return { type: \"boolean\" };\n case \"ZodArray\":\n return { type: \"array\", items: extractZodShape(def.type) };\n case \"ZodEnum\":\n return { type: \"string\", enum: def.values };\n case \"ZodOptional\":\n return { ...extractZodShape(def.innerType), optional: true };\n case \"ZodDefault\":\n return { ...extractZodShape(def.innerType), default: def.defaultValue() };\n case \"ZodNullable\":\n return { ...extractZodShape(def.innerType), nullable: true };\n case \"ZodLiteral\":\n return { type: typeof def.value, const: def.value };\n case \"ZodUnion\":\n return { oneOf: def.options.map((o: ZodType) => extractZodShape(o)) };\n case \"ZodRecord\":\n return { type: \"object\", additionalProperties: extractZodShape(def.valueType) };\n case \"ZodDate\":\n return { type: \"string\", format: \"date-time\" };\n default:\n return { type: \"unknown\" };\n }\n}\n\nfunction extractStringChecks(checks: Array<{ kind: string; value?: unknown }>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const check of checks) {\n switch (check.kind) {\n case \"min\": result.minLength = check.value; break;\n case \"max\": result.maxLength = check.value; break;\n case \"email\": result.format = \"email\"; break;\n case \"url\": result.format = \"uri\"; break;\n case \"uuid\": result.format = \"uuid\"; break;\n }\n }\n return result;\n}\n\n/** 모델 정의에서 Zod 스키마 자동 생성 */\nexport function modelToZodSchema(model: ModelDefinition): ZodObject<ZodRawShape> {\n const shape: ZodRawShape = {};\n\n for (const [key, field] of Object.entries(model.fields)) {\n let fieldSchema: ZodType = fieldToZod(field);\n if (!field.required) {\n fieldSchema = fieldSchema.optional();\n }\n shape[key] = fieldSchema;\n }\n\n if (model.timestamps) {\n shape.createdAt = z.string().datetime().optional();\n shape.updatedAt = z.string().datetime().optional();\n }\n\n if (model.softDelete) {\n shape.deletedAt = z.string().datetime().nullable().optional();\n }\n\n return z.object(shape);\n}\n\nfunction fieldToZod(field: ModelField): ZodType {\n switch (field.type) {\n case \"string\":\n if (field.enum) return z.enum(field.enum as [string, ...string[]]);\n return z.string();\n case \"number\":\n return z.number();\n case \"boolean\":\n return z.boolean();\n case \"timestamp\":\n return z.string().datetime();\n case \"array\":\n if (field.items) return z.array(fieldToZod(field.items));\n return z.array(z.unknown());\n case \"map\":\n if (field.values) return z.record(z.string(), fieldToZod(field.values));\n return z.record(z.string(), z.unknown());\n case \"reference\":\n return z.string(); // 참조는 문서 경로 문자열\n case \"geopoint\":\n return z.object({ latitude: z.number(), longitude: z.number() });\n default:\n return z.unknown();\n }\n}\n\n// ─── Manifest ────────────────────────────────────────────────────────\n\n/** API 매니페스트 항목 */\nexport interface ManifestEntry {\n path: string;\n method: \"POST\"; // Clawfire는 모두 POST\n meta: APIMeta;\n inputSchema: Record<string, unknown>;\n outputSchema: Record<string, unknown>;\n}\n\n/** 전체 매니페스트 */\nexport interface Manifest {\n version: string;\n generatedAt: string;\n apis: ManifestEntry[];\n models: Record<string, ModelDefinition>;\n}\n\n/** 계약에서 매니페스트 항목 생성 */\nexport function contractToManifest(\n path: string,\n contract: APIContract,\n): ManifestEntry {\n return {\n path,\n method: \"POST\",\n meta: contract.meta,\n inputSchema: zodToJsonSchema(contract.input),\n outputSchema: zodToJsonSchema(contract.output),\n };\n}\n","/**\n * Clawfire Error System\n *\n * 표준화된 에러 코드와 구조로 일관된 에러 응답을 제공합니다.\n */\n\nexport type ClawfireErrorCode =\n | \"VALIDATION_ERROR\"\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"REAUTH_REQUIRED\"\n | \"INTERNAL_ERROR\"\n | \"SERVICE_UNAVAILABLE\";\n\nconst HTTP_STATUS_MAP: Record<ClawfireErrorCode, number> = {\n VALIDATION_ERROR: 400,\n UNAUTHORIZED: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n RATE_LIMITED: 429,\n REAUTH_REQUIRED: 401,\n INTERNAL_ERROR: 500,\n SERVICE_UNAVAILABLE: 503,\n};\n\nexport class ClawfireError extends Error {\n readonly code: ClawfireErrorCode;\n readonly statusCode: number;\n readonly details?: unknown;\n\n constructor(code: ClawfireErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = \"ClawfireError\";\n this.code = code;\n this.statusCode = HTTP_STATUS_MAP[code];\n this.details = details;\n }\n\n toJSON() {\n return {\n error: {\n code: this.code,\n message: this.message,\n ...(this.details ? { details: this.details } : {}),\n },\n };\n }\n}\n\n/** 편의 팩토리 함수 */\nexport const Errors = {\n validation: (message: string, details?: unknown) =>\n new ClawfireError(\"VALIDATION_ERROR\", message, details),\n\n unauthorized: (message = \"Authentication required\") =>\n new ClawfireError(\"UNAUTHORIZED\", message),\n\n forbidden: (message = \"Insufficient permissions\") =>\n new ClawfireError(\"FORBIDDEN\", message),\n\n notFound: (message = \"Resource not found\") =>\n new ClawfireError(\"NOT_FOUND\", message),\n\n conflict: (message: string) =>\n new ClawfireError(\"CONFLICT\", message),\n\n rateLimited: (message = \"Too many requests\") =>\n new ClawfireError(\"RATE_LIMITED\", message),\n\n reauthRequired: (message = \"Re-authentication required for this action\") =>\n new ClawfireError(\"REAUTH_REQUIRED\", message),\n\n internal: (message = \"Internal server error\") =>\n new ClawfireError(\"INTERNAL_ERROR\", message),\n\n unavailable: (message = \"Service temporarily unavailable\") =>\n new ClawfireError(\"SERVICE_UNAVAILABLE\", message),\n};\n","/**\n * Clawfire Logger\n *\n * 보안 로깅 (민감 정보 마스킹 포함)\n */\n\n/** 마스킹할 필드명 패턴 */\nconst SENSITIVE_FIELDS = [\n \"password\",\n \"token\",\n \"secret\",\n \"apiKey\",\n \"api_key\",\n \"authorization\",\n \"credit_card\",\n \"creditCard\",\n \"ssn\",\n \"cardNumber\",\n \"card_number\",\n \"cvv\",\n \"pin\",\n];\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nlet currentLevel: LogLevel = \"info\";\n\nexport function setLogLevel(level: LogLevel) {\n currentLevel = level;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];\n}\n\n/** 민감 필드 마스킹 */\nexport function maskSensitive(obj: unknown): unknown {\n if (obj === null || obj === undefined) return obj;\n if (typeof obj === \"string\") return obj;\n if (typeof obj !== \"object\") return obj;\n\n if (Array.isArray(obj)) {\n return obj.map(maskSensitive);\n }\n\n const masked: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_FIELDS.some((f) => key.toLowerCase().includes(f.toLowerCase()))) {\n masked[key] = \"***MASKED***\";\n } else if (typeof value === \"object\" && value !== null) {\n masked[key] = maskSensitive(value);\n } else {\n masked[key] = value;\n }\n }\n return masked;\n}\n\nfunction formatLog(level: LogLevel, message: string, data?: unknown): string {\n const timestamp = new Date().toISOString();\n const prefix = `[${timestamp}] [CLAWFIRE] [${level.toUpperCase()}]`;\n if (data !== undefined) {\n return `${prefix} ${message} ${JSON.stringify(maskSensitive(data))}`;\n }\n return `${prefix} ${message}`;\n}\n\nexport const logger = {\n debug(message: string, data?: unknown) {\n if (shouldLog(\"debug\")) console.debug(formatLog(\"debug\", message, data));\n },\n info(message: string, data?: unknown) {\n if (shouldLog(\"info\")) console.info(formatLog(\"info\", message, data));\n },\n warn(message: string, data?: unknown) {\n if (shouldLog(\"warn\")) console.warn(formatLog(\"warn\", message, data));\n },\n error(message: string, data?: unknown) {\n if (shouldLog(\"error\")) console.error(formatLog(\"error\", message, data));\n },\n};\n","/**\n * Clawfire Auth Helpers\n *\n * Firebase Auth 통합: 토큰 검증, 역할 관리, 재인증\n */\nimport type { AuthContext, AuthLevel } from \"../core/schema.js\";\nimport { Errors } from \"../core/errors.js\";\n\n// ─── Server-side Auth (Admin SDK) ────────────────────────────────────\n\n/**\n * Firebase ID 토큰에서 AuthContext 추출 (서버사이드)\n */\nexport async function verifyToken(\n auth: any, // firebase-admin Auth instance\n idToken: string,\n): Promise<AuthContext> {\n const decoded = await auth.verifyIdToken(idToken);\n return {\n uid: decoded.uid,\n email: decoded.email,\n emailVerified: decoded.email_verified,\n role: decoded.role || decoded.customClaims?.role,\n customClaims: decoded,\n token: idToken,\n };\n}\n\n/**\n * 재인증 토큰 검증 (최근 5분 이내 인증)\n */\nexport async function verifyReauth(\n auth: any,\n idToken: string,\n maxAgeSeconds = 300,\n): Promise<AuthContext> {\n const decoded = await auth.verifyIdToken(idToken, true);\n const authTime = decoded.auth_time * 1000;\n const now = Date.now();\n\n if (now - authTime > maxAgeSeconds * 1000) {\n throw Errors.reauthRequired(\n `Re-authentication required. Last auth was ${Math.floor((now - authTime) / 1000)}s ago.`,\n );\n }\n\n return {\n uid: decoded.uid,\n email: decoded.email,\n emailVerified: decoded.email_verified,\n role: decoded.role || decoded.customClaims?.role,\n customClaims: decoded,\n token: idToken,\n };\n}\n\n/**\n * 요청에서 Authorization 헤더의 Bearer 토큰 추출\n */\nexport function extractBearerToken(authHeader?: string): string | null {\n if (!authHeader) return null;\n const parts = authHeader.split(\" \");\n if (parts.length !== 2 || parts[0] !== \"Bearer\") return null;\n return parts[1];\n}\n\n/**\n * 인증 수준 체크\n */\nexport function checkAuthLevel(\n authCtx: AuthContext | null,\n level: AuthLevel,\n roles?: string[],\n reauthenticated?: boolean,\n): void {\n switch (level) {\n case \"public\":\n return; // 누구나 접근 가능\n\n case \"authenticated\":\n if (!authCtx) throw Errors.unauthorized();\n return;\n\n case \"role\":\n if (!authCtx) throw Errors.unauthorized();\n if (!roles || roles.length === 0) return;\n if (!authCtx.role || !roles.includes(authCtx.role)) {\n throw Errors.forbidden(`Required role: ${roles.join(\" or \")}`);\n }\n return;\n\n case \"reauth\":\n if (!authCtx) throw Errors.unauthorized();\n if (!reauthenticated) {\n throw Errors.reauthRequired();\n }\n return;\n }\n}\n\n// ─── Custom Claims / Role Management ─────────────────────────────────\n\n/**\n * 사용자에게 역할(Role) 설정\n */\nexport async function setUserRole(\n auth: any,\n uid: string,\n role: string,\n): Promise<void> {\n const user = await auth.getUser(uid);\n const currentClaims = user.customClaims || {};\n await auth.setCustomUserClaims(uid, { ...currentClaims, role });\n}\n\n/**\n * 사용자의 역할 조회\n */\nexport async function getUserRole(auth: any, uid: string): Promise<string | undefined> {\n const user = await auth.getUser(uid);\n return user.customClaims?.role;\n}\n\n/**\n * 사용자에게 커스텀 클레임 설정\n */\nexport async function setCustomClaims(\n auth: any,\n uid: string,\n claims: Record<string, unknown>,\n): Promise<void> {\n const user = await auth.getUser(uid);\n const currentClaims = user.customClaims || {};\n await auth.setCustomUserClaims(uid, { ...currentClaims, ...claims });\n}\n\n// ─── Client-side Auth Helpers ────────────────────────────────────────\n\n/**\n * 클라이언트 사이드 Auth 헬퍼 (firebase/auth 래핑)\n */\nexport function createClientAuth(firebaseAuth: any) {\n return {\n /** 현재 사용자 조회 */\n getCurrentUser(): any | null {\n return firebaseAuth.currentUser;\n },\n\n /** ID 토큰 취득 */\n async getIdToken(forceRefresh = false): Promise<string | null> {\n const user = firebaseAuth.currentUser;\n if (!user) return null;\n return user.getIdToken(forceRefresh);\n },\n\n /** 인증 상태 변경 리스너 */\n onAuthStateChanged(callback: (user: any | null) => void): () => void {\n return firebaseAuth.onAuthStateChanged(callback);\n },\n\n /** 로그아웃 */\n async signOut(): Promise<void> {\n await firebaseAuth.signOut();\n },\n\n /** 원시 auth 접근 */\n raw: firebaseAuth,\n };\n}\n\nexport type ClientAuth = ReturnType<typeof createClientAuth>;\n","/**\n * Clawfire File-based Router\n *\n * app/routes/ 디렉터리 구조를 자동으로 API 경로로 매핑합니다.\n *\n * 파일 구조 → API 경로:\n * app/routes/products/list.ts → /api/products/list\n * app/routes/products/create.ts → /api/products/create\n * app/routes/auth/login.ts → /api/auth/login\n * app/routes/users/[id]/get.ts → /api/users/:id/get\n */\nimport { z } from \"zod\";\nimport type { APIContract, HandlerContext, AuthContext, Manifest, ManifestEntry } from \"../core/schema.js\";\nimport { contractToManifest, zodToJsonSchema } from \"../core/schema.js\";\nimport { ClawfireError, Errors } from \"../core/errors.js\";\nimport { logger } from \"../core/logger.js\";\nimport { checkAuthLevel, extractBearerToken, verifyToken, verifyReauth } from \"../firebase/auth.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface Route {\n path: string;\n contract: APIContract;\n}\n\nexport interface RouterOptions {\n /** Firebase Admin Auth 인스턴스 */\n auth?: any;\n /** Firebase Admin Firestore 인스턴스 */\n firestore?: any;\n /** CORS 허용 도메인 */\n cors?: string[];\n /** 전역 rate limit */\n rateLimit?: number;\n /** 전역 미들웨어 */\n middleware?: Middleware[];\n /** 플레이그라운드 활성화 */\n playground?: boolean;\n}\n\nexport type Middleware = (\n input: unknown,\n ctx: HandlerContext,\n next: () => Promise<unknown>,\n) => Promise<unknown>;\n\n// ─── Rate Limiter (in-memory) ────────────────────────────────────────\n\nclass RateLimiter {\n private store = new Map<string, { count: number; resetAt: number }>();\n private defaultLimit: number;\n\n constructor(defaultLimit: number) {\n this.defaultLimit = defaultLimit;\n }\n\n check(key: string, limit?: number): boolean {\n const max = limit || this.defaultLimit;\n const now = Date.now();\n const entry = this.store.get(key);\n\n if (!entry || now > entry.resetAt) {\n this.store.set(key, { count: 1, resetAt: now + 60000 }); // 1분 윈도우\n return true;\n }\n\n if (entry.count >= max) {\n return false;\n }\n\n entry.count++;\n return true;\n }\n\n // 오래된 엔트리 정리\n cleanup() {\n const now = Date.now();\n for (const [key, entry] of this.store) {\n if (now > entry.resetAt) this.store.delete(key);\n }\n }\n}\n\n// ─── Router ──────────────────────────────────────────────────────────\n\nexport class ClawfireRouter {\n private routes = new Map<string, Route>();\n private options: RouterOptions;\n private rateLimiter: RateLimiter;\n private cleanupInterval?: ReturnType<typeof setInterval>;\n\n constructor(options: RouterOptions = {}) {\n this.options = options;\n this.rateLimiter = new RateLimiter(options.rateLimit || 100);\n this.cleanupInterval = setInterval(() => this.rateLimiter.cleanup(), 60000);\n }\n\n /** 라우트 등록 */\n register(path: string, contract: APIContract): this {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n this.routes.set(normalizedPath, { path: normalizedPath, contract });\n logger.debug(`Route registered: ${normalizedPath}`);\n return this;\n }\n\n /** 여러 라우트 한 번에 등록 */\n registerAll(routes: Record<string, APIContract>): this {\n for (const [path, contract] of Object.entries(routes)) {\n this.register(path, contract);\n }\n return this;\n }\n\n /** 매니페스트 생성 */\n getManifest(): Manifest {\n const apis: ManifestEntry[] = [];\n for (const [path, route] of this.routes) {\n apis.push(contractToManifest(path, route.contract));\n }\n\n return {\n version: \"1.0.0\",\n generatedAt: new Date().toISOString(),\n apis,\n models: {},\n };\n }\n\n /** HTTP 요청 핸들러 (Firebase Functions에서 사용) */\n async handleRequest(\n req: { path?: string; url?: string; body?: unknown; headers?: Record<string, string>; ip?: string; method?: string },\n res: { status: (code: number) => { json: (data: unknown) => void; send: (data: string) => void; end: () => void }; set: (headers: Record<string, string>) => void },\n ): Promise<void> {\n // CORS 처리\n const corsOrigins = this.options.cors || [];\n const origin = req.headers?.origin || req.headers?.Origin || \"\";\n\n if (corsOrigins.length > 0 && corsOrigins.includes(origin)) {\n res.set({\n \"Access-Control-Allow-Origin\": origin,\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Max-Age\": \"86400\",\n });\n } else if (corsOrigins.length === 0) {\n // CORS 기본 deny — 설정 없으면 허용하지 않음\n res.set({\n \"Access-Control-Allow-Origin\": \"\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n });\n }\n\n // OPTIONS (preflight)\n if (req.method === \"OPTIONS\") {\n res.status(204).end();\n return;\n }\n\n // 안전 헤더\n res.set({\n \"X-Content-Type-Options\": \"nosniff\",\n \"X-Frame-Options\": \"DENY\",\n \"X-XSS-Protection\": \"1; mode=block\",\n \"Content-Type\": \"application/json\",\n });\n\n // 경로 추출 — /api/xxx/yyy → /xxx/yyy\n let routePath = req.path || req.url || \"\";\n if (routePath.startsWith(\"/api\")) {\n routePath = routePath.slice(4);\n }\n\n // 시스템 엔드포인트 (GET 허용)\n if (routePath === \"/__manifest\") {\n res.status(200).json(this.getManifest());\n return;\n }\n\n // POST만 허용 (시스템 엔드포인트 제외)\n if (req.method && req.method !== \"POST\") {\n res.status(405).json({ error: { code: \"METHOD_NOT_ALLOWED\", message: \"Only POST is allowed\" } });\n return;\n }\n\n // 라우트 매칭\n const route = this.matchRoute(routePath);\n if (!route) {\n res.status(404).json({ error: { code: \"NOT_FOUND\", message: `API not found: ${routePath}` } });\n return;\n }\n\n try {\n // Rate limit\n const clientKey = req.ip || \"unknown\";\n const rateLimit = route.contract.meta.rateLimit || this.options.rateLimit;\n if (rateLimit && !this.rateLimiter.check(clientKey, rateLimit)) {\n throw Errors.rateLimited();\n }\n\n // Auth 처리\n let authCtx: AuthContext | null = null;\n let reauthenticated = false;\n const authHeader = req.headers?.authorization || req.headers?.Authorization;\n\n if (authHeader && this.options.auth) {\n const token = extractBearerToken(authHeader as string);\n if (token) {\n if (route.contract.meta.reauth || route.contract.meta.auth === \"reauth\") {\n authCtx = await verifyReauth(this.options.auth, token);\n reauthenticated = true;\n } else {\n authCtx = await verifyToken(this.options.auth, token);\n }\n }\n }\n\n // 권한 체크\n if (route.contract.meta.auth) {\n checkAuthLevel(authCtx, route.contract.meta.auth, route.contract.meta.roles, reauthenticated);\n }\n\n // 입력 검증\n const rawInput = req.body || {};\n const parsed = route.contract.input.safeParse(rawInput);\n if (!parsed.success) {\n throw Errors.validation(\"Invalid input\", parsed.error.flatten());\n }\n\n // 핸들러 컨텍스트\n const ctx: HandlerContext = {\n auth: authCtx,\n reauthenticated,\n headers: req.headers as Record<string, string>,\n ip: req.ip,\n };\n\n // 미들웨어 + 핸들러 실행\n let result: unknown;\n if (this.options.middleware && this.options.middleware.length > 0) {\n result = await this.runMiddleware(\n this.options.middleware,\n parsed.data,\n ctx,\n () => route.contract.handler(parsed.data, ctx),\n );\n } else {\n result = await route.contract.handler(parsed.data, ctx);\n }\n\n // 성공 응답\n res.status(200).json({ data: result });\n } catch (err) {\n if (err instanceof ClawfireError) {\n logger.warn(`API error [${err.code}]: ${err.message}`);\n res.status(err.statusCode).json(err.toJSON());\n } else {\n logger.error(\"Unhandled error\", err);\n res.status(500).json({\n error: {\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred\",\n },\n });\n }\n }\n }\n\n /** 라우트 매칭 (동적 파라미터 지원) */\n private matchRoute(path: string): Route | undefined {\n // 정확한 매칭\n const exact = this.routes.get(path);\n if (exact) return exact;\n\n // 동적 파라미터 매칭 (/users/:id/get → /users/abc123/get)\n for (const [routePath, route] of this.routes) {\n if (this.matchDynamicRoute(routePath, path)) {\n return route;\n }\n }\n\n return undefined;\n }\n\n private matchDynamicRoute(pattern: string, actual: string): boolean {\n const patternParts = pattern.split(\"/\").filter(Boolean);\n const actualParts = actual.split(\"/\").filter(Boolean);\n if (patternParts.length !== actualParts.length) return false;\n\n return patternParts.every(\n (part, i) => part.startsWith(\":\") || part.startsWith(\"[\") || part === actualParts[i],\n );\n }\n\n /** 미들웨어 체인 실행 */\n private async runMiddleware(\n middlewares: Middleware[],\n input: unknown,\n ctx: HandlerContext,\n handler: () => Promise<unknown>,\n ): Promise<unknown> {\n let index = 0;\n const next = async (): Promise<unknown> => {\n if (index >= middlewares.length) {\n return handler();\n }\n const mw = middlewares[index++];\n return mw(input, ctx, next);\n };\n return next();\n }\n\n /** 등록된 라우트 목록 */\n getRoutes(): Route[] {\n return Array.from(this.routes.values());\n }\n\n /** 리소스 정리 */\n destroy() {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n }\n}\n\n/**\n * 라우터 생성 헬퍼\n */\nexport function createRouter(options?: RouterOptions): ClawfireRouter {\n return new ClawfireRouter(options);\n}\n","/**\n * Clawfire Route Discovery\n *\n * 파일 시스템에서 라우트 자동 발견 (빌드 타임 & 런타임)\n */\nimport { resolve, relative, join } from \"path\";\nimport { existsSync, readdirSync, statSync } from \"fs\";\n\nexport interface DiscoveredRoute {\n /** 파일 경로 (상대) */\n filePath: string;\n /** API 경로 (/products/list) */\n apiPath: string;\n /** 동적 파라미터 이름 */\n params: string[];\n}\n\n/**\n * routes 디렉터리에서 라우트 파일 자동 발견\n *\n * @param routesDir - routes 디렉터리 절대 경로\n * @returns 발견된 라우트 목록\n *\n * @example\n * ```\n * app/routes/products/list.ts → { apiPath: \"/products/list\", params: [] }\n * app/routes/products/[id]/get.ts → { apiPath: \"/products/:id/get\", params: [\"id\"] }\n * app/routes/health.ts → { apiPath: \"/health\", params: [] }\n * ```\n */\nexport function discoverRoutes(routesDir: string): DiscoveredRoute[] {\n if (!existsSync(routesDir)) {\n return [];\n }\n\n const routes: DiscoveredRoute[] = [];\n scanDirectory(routesDir, routesDir, routes);\n return routes.sort((a, b) => a.apiPath.localeCompare(b.apiPath));\n}\n\nfunction scanDirectory(baseDir: string, currentDir: string, routes: DiscoveredRoute[]): void {\n const entries = readdirSync(currentDir);\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n // 숨김 디렉터리, node_modules 무시\n if (entry.startsWith(\".\") || entry === \"node_modules\") continue;\n scanDirectory(baseDir, fullPath, routes);\n } else if (stat.isFile()) {\n // .ts, .js 파일만\n if (!entry.endsWith(\".ts\") && !entry.endsWith(\".js\")) continue;\n // index, _로 시작하는 파일 무시\n if (entry.startsWith(\"_\")) continue;\n // .d.ts 무시\n if (entry.endsWith(\".d.ts\")) continue;\n\n const relativePath = relative(baseDir, fullPath);\n const route = filePathToRoute(relativePath);\n routes.push(route);\n }\n }\n}\n\nfunction filePathToRoute(filePath: string): DiscoveredRoute {\n const params: string[] = [];\n\n // 확장자 제거\n let routePath = filePath.replace(/\\.(ts|js)$/, \"\");\n\n // Windows 경로 → POSIX\n routePath = routePath.replace(/\\\\/g, \"/\");\n\n // index 파일은 디렉터리 자체\n if (routePath.endsWith(\"/index\") || routePath === \"index\") {\n routePath = routePath.replace(/\\/?index$/, \"\");\n }\n\n // [param] → :param 변환\n routePath = routePath.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n params.push(param);\n return `:${param}`;\n });\n\n // 앞에 / 추가\n const apiPath = `/${routePath}`;\n\n return {\n filePath,\n apiPath,\n params,\n };\n}\n\n/**\n * 라우트 파일에서 import하여 라우터에 등록하는 코드 생성 (빌드 타임)\n */\nexport function generateRouteImports(routes: DiscoveredRoute[], routesDir: string): string {\n const lines: string[] = [\n '// AUTO-GENERATED by Clawfire — DO NOT EDIT',\n '// This file is regenerated whenever routes change.',\n '',\n 'import { createRouter } from \"clawfire/functions\";',\n '',\n ];\n\n routes.forEach((route, i) => {\n const importPath = `./${route.filePath.replace(/\\.(ts|js)$/, \".js\")}`;\n lines.push(`import route_${i} from \"${importPath}\";`);\n });\n\n lines.push('');\n lines.push('export function registerAllRoutes(router: ReturnType<typeof createRouter>) {');\n\n routes.forEach((route, i) => {\n lines.push(` router.register(\"${route.apiPath}\", route_${i});`);\n });\n\n lines.push(' return router;');\n lines.push('}');\n\n return lines.join('\\n');\n}\n","/**\n * Clawfire Playground\n *\n * 웹 기반 API 탐색기: API 목록, 인증 테스트, 요청/응답 뷰어\n * 단일 HTML 파일로 생성되어 Firebase Hosting에 배포됩니다.\n */\n\nexport function generatePlaygroundHtml(options?: {\n title?: string;\n apiBaseUrl?: string;\n}): string {\n const title = options?.title || \"Clawfire Playground\";\n const apiBaseUrl = options?.apiBaseUrl || \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${title}</title>\n <style>\n :root {\n --bg: #0a0a0a;\n --surface: #141414;\n --surface2: #1e1e1e;\n --border: #2a2a2a;\n --text: #e5e5e5;\n --text2: #a3a3a3;\n --accent: #f97316;\n --accent2: #fb923c;\n --green: #22c55e;\n --red: #ef4444;\n --blue: #3b82f6;\n --yellow: #eab308;\n --radius: 8px;\n --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --mono: 'JetBrains Mono', 'Fira Code', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; }\n\n .layout { display: grid; grid-template-columns: 320px 1fr; min-height: 100vh; }\n .sidebar { background: var(--surface); border-right: 1px solid var(--border); overflow-y: auto; }\n .main { padding: 24px; overflow-y: auto; }\n\n .logo { padding: 20px; border-bottom: 1px solid var(--border); }\n .logo h1 { font-size: 20px; font-weight: 700; color: var(--accent); }\n .logo p { font-size: 12px; color: var(--text2); margin-top: 4px; }\n\n .auth-section { padding: 16px; border-bottom: 1px solid var(--border); }\n .auth-section label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 6px; }\n .auth-input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: var(--radius); color: var(--text); font-family: var(--mono); font-size: 12px; }\n .auth-status { font-size: 11px; margin-top: 6px; }\n .auth-status.ok { color: var(--green); }\n .auth-status.no { color: var(--text2); }\n\n .search { padding: 12px 16px; border-bottom: 1px solid var(--border); }\n .search input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: var(--radius); color: var(--text); font-size: 13px; }\n\n .api-list { padding: 8px 0; }\n .api-group { padding: 4px 0; }\n .api-group-title { padding: 8px 16px; font-size: 11px; color: var(--text2); text-transform: uppercase;\n letter-spacing: 0.5px; font-weight: 600; }\n .api-item { padding: 8px 16px; cursor: pointer; transition: background 0.15s; display: flex; align-items: center;\n gap: 8px; font-size: 13px; }\n .api-item:hover { background: var(--surface2); }\n .api-item.active { background: var(--surface2); border-left: 2px solid var(--accent); }\n .api-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; }\n .badge-public { background: #22c55e20; color: var(--green); }\n .badge-auth { background: #3b82f620; color: var(--blue); }\n .badge-role { background: #eab30820; color: var(--yellow); }\n .badge-reauth { background: #ef444420; color: var(--red); }\n\n .panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 16px; }\n .panel-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center;\n justify-content: space-between; }\n .panel-header h2 { font-size: 16px; font-weight: 600; }\n .panel-body { padding: 16px; }\n\n .field { margin-bottom: 12px; }\n .field label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 4px; }\n .field-type { font-size: 11px; color: var(--text2); font-family: var(--mono); }\n .field-required { color: var(--red); font-size: 11px; }\n\n textarea, input[type=\"text\"] { width: 100%; padding: 10px 14px; background: var(--surface2);\n border: 1px solid var(--border); border-radius: var(--radius); color: var(--text);\n font-family: var(--mono); font-size: 13px; resize: vertical; }\n textarea { min-height: 200px; }\n\n .btn { padding: 10px 20px; border: none; border-radius: var(--radius); font-size: 14px;\n font-weight: 600; cursor: pointer; transition: all 0.15s; }\n .btn-primary { background: var(--accent); color: white; }\n .btn-primary:hover { background: var(--accent2); }\n .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }\n\n .response-section { margin-top: 16px; }\n .status-badge { font-size: 12px; padding: 4px 8px; border-radius: 4px; font-weight: 600; }\n .status-ok { background: #22c55e20; color: var(--green); }\n .status-err { background: #ef444420; color: var(--red); }\n pre { background: var(--surface2); padding: 16px; border-radius: var(--radius); overflow-x: auto;\n font-family: var(--mono); font-size: 13px; line-height: 1.5; white-space: pre-wrap; }\n\n .schema-info { font-size: 12px; color: var(--text2); line-height: 1.6; }\n .schema-info code { background: var(--surface2); padding: 2px 6px; border-radius: 4px; font-family: var(--mono);\n font-size: 11px; }\n\n .empty-state { text-align: center; padding: 80px 40px; color: var(--text2); }\n .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }\n\n .timer { font-size: 12px; color: var(--text2); font-family: var(--mono); }\n\n @media (max-width: 768px) {\n .layout { grid-template-columns: 1fr; }\n .sidebar { max-height: 40vh; }\n }\n </style>\n</head>\n<body>\n <div class=\"layout\">\n <div class=\"sidebar\">\n <div class=\"logo\">\n <h1>Clawfire</h1>\n <p>API Playground</p>\n </div>\n <div class=\"auth-section\">\n <label>Bearer Token</label>\n <input type=\"text\" class=\"auth-input\" id=\"token-input\" placeholder=\"Paste your ID token...\">\n <div class=\"auth-status no\" id=\"auth-status\">Not authenticated</div>\n </div>\n <div class=\"search\">\n <input type=\"text\" id=\"search-input\" placeholder=\"Search APIs...\">\n </div>\n <div class=\"api-list\" id=\"api-list\"></div>\n </div>\n <div class=\"main\" id=\"main-content\">\n <div class=\"empty-state\">\n <h2>Select an API</h2>\n <p>Choose an API from the sidebar to test it.</p>\n </div>\n </div>\n </div>\n\n <script>\n const BASE_URL = ${JSON.stringify(apiBaseUrl)} || window.location.origin;\n let manifest = null;\n let selectedApi = null;\n\n async function loadManifest() {\n try {\n const res = await fetch(BASE_URL + '/api/__manifest', { method: 'POST' });\n manifest = await res.json();\n renderApiList(manifest.apis);\n } catch (e) {\n document.getElementById('api-list').innerHTML =\n '<div style=\"padding:16px;color:var(--red);font-size:13px;\">Failed to load API manifest. Make sure your server is running.</div>';\n }\n }\n\n function renderApiList(apis) {\n const groups = {};\n apis.forEach(api => {\n const parts = api.path.split('/').filter(Boolean);\n const group = parts.length > 1 ? parts[0] : 'root';\n if (!groups[group]) groups[group] = [];\n groups[group].push(api);\n });\n\n const el = document.getElementById('api-list');\n el.innerHTML = Object.entries(groups).map(([group, items]) =>\n '<div class=\"api-group\">' +\n '<div class=\"api-group-title\">' + group + '</div>' +\n items.map(api => {\n const auth = api.meta.auth || 'public';\n const badgeClass = 'badge-' + auth;\n return '<div class=\"api-item\" onclick=\"selectApi(\\\\'' + api.path + '\\\\')\">' +\n '<span class=\"api-badge ' + badgeClass + '\">' + auth.toUpperCase() + '</span>' +\n '<span>' + api.path + '</span>' +\n '</div>';\n }).join('') +\n '</div>'\n ).join('');\n }\n\n function selectApi(path) {\n selectedApi = manifest.apis.find(a => a.path === path);\n if (!selectedApi) return;\n\n document.querySelectorAll('.api-item').forEach(el => el.classList.remove('active'));\n event.currentTarget?.classList.add('active');\n\n const main = document.getElementById('main-content');\n const exampleInput = selectedApi.meta.exampleInput\n ? JSON.stringify(selectedApi.meta.exampleInput, null, 2)\n : generateExampleFromSchema(selectedApi.inputSchema);\n\n main.innerHTML =\n '<div class=\"panel\">' +\n '<div class=\"panel-header\">' +\n '<h2>POST ' + selectedApi.path + '</h2>' +\n '<span class=\"api-badge badge-' + (selectedApi.meta.auth || 'public') + '\">' +\n (selectedApi.meta.auth || 'public').toUpperCase() + '</span>' +\n '</div>' +\n '<div class=\"panel-body\">' +\n '<p style=\"color:var(--text2);margin-bottom:16px;\">' + (selectedApi.meta.description || '') + '</p>' +\n (selectedApi.meta.tags ? '<div style=\"margin-bottom:12px;\">' + selectedApi.meta.tags.map(t =>\n '<span style=\"background:var(--surface2);padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px;\">' + t + '</span>'\n ).join('') + '</div>' : '') +\n '<div class=\"schema-info\" style=\"margin-bottom:16px;\">' +\n '<strong>Input Schema:</strong><br>' + renderSchemaInfo(selectedApi.inputSchema) +\n '</div>' +\n '<div class=\"schema-info\" style=\"margin-bottom:16px;\">' +\n '<strong>Output Schema:</strong><br>' + renderSchemaInfo(selectedApi.outputSchema) +\n '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"panel\">' +\n '<div class=\"panel-header\"><h2>Request</h2></div>' +\n '<div class=\"panel-body\">' +\n '<textarea id=\"req-body\" placeholder=\"Request JSON body\">' + exampleInput + '</textarea>' +\n '<div style=\"margin-top:12px;display:flex;align-items:center;gap:12px;\">' +\n '<button class=\"btn btn-primary\" onclick=\"sendRequest()\">Send Request</button>' +\n '<span class=\"timer\" id=\"timer\"></span>' +\n '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"response-section\" id=\"response-section\"></div>';\n }\n\n function renderSchemaInfo(schema) {\n if (!schema || !schema.properties) return '<code>void</code>';\n return Object.entries(schema.properties).map(([key, prop]) => {\n const required = schema.required?.includes(key);\n const type = prop.type || 'unknown';\n const enumVals = prop.enum ? ' (' + prop.enum.join(', ') + ')' : '';\n return '<code>' + key + '</code>: <span class=\"field-type\">' + type + enumVals + '</span>' +\n (required ? ' <span class=\"field-required\">required</span>' : ' <span style=\"color:var(--text2);font-size:11px;\">optional</span>');\n }).join('<br>');\n }\n\n function generateExampleFromSchema(schema) {\n if (!schema || !schema.properties) return '{}';\n const obj = {};\n for (const [key, prop] of Object.entries(schema.properties)) {\n if (prop.enum) { obj[key] = prop.enum[0]; continue; }\n switch (prop.type) {\n case 'string': obj[key] = prop.format === 'email' ? 'user@example.com' : 'string'; break;\n case 'number': obj[key] = 0; break;\n case 'boolean': obj[key] = false; break;\n case 'array': obj[key] = []; break;\n case 'object': obj[key] = {}; break;\n default: obj[key] = null;\n }\n }\n return JSON.stringify(obj, null, 2);\n }\n\n async function sendRequest() {\n if (!selectedApi) return;\n const body = document.getElementById('req-body').value;\n const token = document.getElementById('token-input').value;\n const timer = document.getElementById('timer');\n const section = document.getElementById('response-section');\n\n let parsed;\n try { parsed = JSON.parse(body); } catch {\n section.innerHTML = '<div class=\"panel\"><div class=\"panel-body\"><pre style=\"color:var(--red)\">Invalid JSON</pre></div></div>';\n return;\n }\n\n const start = performance.now();\n timer.textContent = 'Sending...';\n\n try {\n const headers = { 'Content-Type': 'application/json' };\n if (token) headers['Authorization'] = 'Bearer ' + token;\n\n const res = await fetch(BASE_URL + '/api' + selectedApi.path, {\n method: 'POST', headers, body: JSON.stringify(parsed)\n });\n const elapsed = Math.round(performance.now() - start);\n timer.textContent = elapsed + 'ms';\n\n const json = await res.json();\n const isOk = res.ok;\n\n section.innerHTML =\n '<div class=\"panel\">' +\n '<div class=\"panel-header\">' +\n '<h2>Response</h2>' +\n '<span class=\"status-badge ' + (isOk ? 'status-ok' : 'status-err') + '\">' +\n res.status + ' ' + res.statusText + '</span>' +\n '</div>' +\n '<div class=\"panel-body\"><pre>' + syntaxHighlight(JSON.stringify(json, null, 2)) + '</pre></div>' +\n '</div>';\n } catch (e) {\n const elapsed = Math.round(performance.now() - start);\n timer.textContent = elapsed + 'ms';\n section.innerHTML =\n '<div class=\"panel\"><div class=\"panel-body\"><pre style=\"color:var(--red)\">Network error: ' + e.message + '</pre></div></div>';\n }\n }\n\n function syntaxHighlight(json) {\n return json.replace(/(\"(\\\\\\\\u[a-fA-F0-9]{4}|\\\\\\\\[^u]|[^\\\\\\\\\"])*\"(\\\\s*:)?)|\\\\b(true|false|null)\\\\b|-?\\\\d+(\\\\.\\\\d+)?([eE][+-]?\\\\d+)?/g,\n function(match) {\n let cls = 'color:#eab308';\n if (/^\"/.test(match)) {\n if (/:$/.test(match)) cls = 'color:#3b82f6';\n else cls = 'color:#22c55e';\n } else if (/true|false/.test(match)) cls = 'color:#f97316';\n else if (/null/.test(match)) cls = 'color:#ef4444';\n return '<span style=\"' + cls + '\">' + match + '</span>';\n }\n );\n }\n\n // Search\n document.getElementById('search-input')?.addEventListener('input', (e) => {\n if (!manifest) return;\n const q = e.target.value.toLowerCase();\n const filtered = manifest.apis.filter(a => a.path.toLowerCase().includes(q) || a.meta.description?.toLowerCase().includes(q));\n renderApiList(filtered);\n });\n\n // Token status\n document.getElementById('token-input')?.addEventListener('input', (e) => {\n const el = document.getElementById('auth-status');\n if (e.target.value) {\n el.textContent = 'Token set';\n el.className = 'auth-status ok';\n } else {\n el.textContent = 'Not authenticated';\n el.className = 'auth-status no';\n }\n });\n\n loadManifest();\n </script>\n</body>\n</html>`;\n}\n","/**\n * Clawfire File Watcher\n *\n * Node.js fs.watch 기반 파일 변경 감지 (외부 의존성 없음)\n * macOS/Windows에서 recursive 지원, Linux에서는 디렉터리별 개별 감시\n */\nimport { watch, existsSync, statSync, readdirSync } from \"fs\";\nimport { join, extname } from \"path\";\nimport { EventEmitter } from \"events\";\n\nexport type WatchEventType = \"route-change\" | \"schema-change\" | \"config-change\" | \"frontend-change\" | \"css-change\" | \"page-change\" | \"component-change\" | \"any-change\";\n\nexport interface WatchEvent {\n type: WatchEventType;\n filePath: string;\n timestamp: number;\n}\n\nexport class FileWatcher extends EventEmitter {\n private watchers: ReturnType<typeof watch>[] = [];\n private debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n private debounceMs: number;\n\n constructor(debounceMs = 150) {\n super();\n this.debounceMs = debounceMs;\n }\n\n /**\n * 디렉터리 감시 시작\n */\n watchDir(dir: string, eventType: WatchEventType): this {\n if (!existsSync(dir)) return this;\n\n try {\n // macOS/Windows: recursive 옵션 지원\n const watcher = watch(dir, { recursive: true }, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n this.emitDebounced(filePath, eventType);\n });\n\n watcher.on(\"error\", (err) => {\n // Linux에서 recursive 미지원 시 fallback\n if ((err as any).code === \"ERR_FEATURE_UNAVAILABLE_ON_PLATFORM\") {\n this.watchDirRecursiveManual(dir, eventType);\n }\n });\n\n this.watchers.push(watcher);\n } catch {\n // fallback: 수동 재귀 감시\n this.watchDirRecursiveManual(dir, eventType);\n }\n\n return this;\n }\n\n /**\n * 단일 파일 감시\n */\n watchFile(filePath: string, eventType: WatchEventType): this {\n if (!existsSync(filePath)) return this;\n\n const watcher = watch(filePath, (event) => {\n this.emitDebounced(filePath, eventType);\n });\n\n this.watchers.push(watcher);\n return this;\n }\n\n /**\n * 프론트엔드 디렉터리 감시 (확장자별 이벤트 타입 자동 결정)\n */\n watchDirFrontend(dir: string): this {\n if (!existsSync(dir)) return this;\n\n try {\n const watcher = watch(dir, { recursive: true }, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n const ext = extname(filePath);\n const eventType: WatchEventType = ext === \".css\" ? \"css-change\" : \"frontend-change\";\n this.emitDebounced(filePath, eventType);\n });\n\n watcher.on(\"error\", (err) => {\n if ((err as any).code === \"ERR_FEATURE_UNAVAILABLE_ON_PLATFORM\") {\n this.watchDirFrontendRecursiveManual(dir);\n }\n });\n\n this.watchers.push(watcher);\n } catch {\n this.watchDirFrontendRecursiveManual(dir);\n }\n\n return this;\n }\n\n /**\n * Linux fallback: 프론트엔드 디렉터리 수동 재귀 감시\n */\n private watchDirFrontendRecursiveManual(dir: string): void {\n if (!existsSync(dir)) return;\n\n const watcher = watch(dir, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n const ext = extname(filePath);\n const eventType: WatchEventType = ext === \".css\" ? \"css-change\" : \"frontend-change\";\n this.emitDebounced(filePath, eventType);\n });\n this.watchers.push(watcher);\n\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith(\".\") && entry.name !== \"node_modules\") {\n this.watchDirFrontendRecursiveManual(join(dir, entry.name));\n }\n }\n } catch {}\n }\n\n /**\n * Linux fallback: 디렉터리 수동 재귀 감시\n */\n private watchDirRecursiveManual(dir: string, eventType: WatchEventType): void {\n if (!existsSync(dir)) return;\n\n const watcher = watch(dir, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n this.emitDebounced(filePath, eventType);\n });\n this.watchers.push(watcher);\n\n // 서브디렉터리도 감시\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith(\".\") && entry.name !== \"node_modules\") {\n this.watchDirRecursiveManual(join(dir, entry.name), eventType);\n }\n }\n } catch {}\n }\n\n /**\n * 감시 대상 파일인지 확인\n */\n private isWatchedFile(filePath: string): boolean {\n const ext = extname(filePath);\n return [\".ts\", \".tsx\", \".js\", \".jsx\", \".json\", \".html\", \".css\", \".svg\", \".png\", \".jpg\", \".jpeg\", \".gif\", \".ico\", \".webp\", \".woff\", \".woff2\"].includes(ext);\n }\n\n /**\n * 디바운스 이벤트 발생\n */\n private emitDebounced(filePath: string, type: WatchEventType): void {\n const existing = this.debounceTimers.get(filePath);\n if (existing) clearTimeout(existing);\n\n this.debounceTimers.set(\n filePath,\n setTimeout(() => {\n this.debounceTimers.delete(filePath);\n const event: WatchEvent = { type, filePath, timestamp: Date.now() };\n this.emit(\"change\", event);\n this.emit(type, event);\n }, this.debounceMs),\n );\n }\n\n /**\n * 모든 감시 중지\n */\n close(): void {\n for (const watcher of this.watchers) {\n watcher.close();\n }\n this.watchers = [];\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer);\n }\n this.debounceTimers.clear();\n this.removeAllListeners();\n }\n}\n","/**\n * Clawfire Page Compiler\n *\n * File-based page routing with layouts and components.\n * Resolves URLs to page files, processes components (<c-name />),\n * applies nested layouts (<slot />), and extracts metadata.\n */\nimport { resolve, join, relative, dirname, basename, extname } from \"node:path\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface PageMeta {\n title?: string;\n description?: string;\n [key: string]: string | undefined;\n}\n\nexport interface CompiledPage {\n html: string;\n meta: PageMeta;\n filePath: string;\n layouts: string[];\n}\n\nexport interface PartialPage {\n html: string;\n meta: PageMeta;\n path: string;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────\n\nconst MAX_COMPONENT_DEPTH = 10;\nconst META_REGEX = /<!--\\s*@(\\w+):\\s*(.+?)\\s*-->/g;\nconst COMPONENT_REGEX = /<c-([a-z][a-z0-9-]*)\\s*\\/>/g;\nconst SLOT_MARKER = \"<slot />\";\n\n// ─── Page Compiler ───────────────────────────────────────────────────\n\nexport class PageCompiler {\n private pagesDir: string;\n private componentsDir: string;\n\n constructor(private projectDir: string) {\n this.pagesDir = resolve(projectDir, \"app/pages\");\n this.componentsDir = resolve(projectDir, \"app/components\");\n }\n\n /**\n * Check if the page system is active (app/pages/ exists)\n */\n isActive(): boolean {\n return existsSync(this.pagesDir);\n }\n\n /**\n * Resolve a URL pathname to a page file path.\n * Returns null if no matching page exists.\n *\n * / → app/pages/index.html\n * /about → app/pages/about.html OR app/pages/about/index.html\n * /todos → app/pages/todos/index.html OR app/pages/todos.html\n */\n resolve(pathname: string): string | null {\n if (!this.isActive()) return null;\n\n // Normalize: strip trailing slash, default to /\n const clean = pathname === \"/\" ? \"\" : pathname.replace(/\\/+$/, \"\");\n const segments = clean ? clean.split(\"/\").filter(Boolean) : [];\n\n // Build candidate paths\n const candidates: string[] = [];\n\n if (segments.length === 0) {\n candidates.push(join(this.pagesDir, \"index.html\"));\n } else {\n const pathPart = segments.join(\"/\");\n // Try direct file first, then directory/index\n candidates.push(join(this.pagesDir, `${pathPart}.html`));\n candidates.push(join(this.pagesDir, pathPart, \"index.html\"));\n }\n\n for (const candidate of candidates) {\n // Security: must be within pagesDir\n if (!candidate.startsWith(this.pagesDir)) continue;\n // Skip layout and special files\n const name = basename(candidate);\n if (name.startsWith(\"_\")) continue;\n if (existsSync(candidate)) return candidate;\n }\n\n return null;\n }\n\n /**\n * Resolve the 404 page file path.\n */\n resolve404(): string | null {\n const path404 = join(this.pagesDir, \"_404.html\");\n return existsSync(path404) ? path404 : null;\n }\n\n /**\n * Compile a full page with layouts and components.\n */\n compile(pagePath: string): CompiledPage {\n // 1. Read page file\n let pageHtml = readFileSync(pagePath, \"utf-8\");\n\n // 2. Extract metadata\n const meta = this.extractMeta(pageHtml);\n pageHtml = this.stripMeta(pageHtml);\n\n // 3. Process components in page\n pageHtml = this.processComponents(pageHtml);\n\n // 4. Wrap in page container div\n pageHtml = this.wrapPageContent(pageHtml);\n\n // 5. Collect layout chain\n const layouts = this.collectLayouts(pagePath);\n\n // 6. Apply layouts (innermost first)\n let html = pageHtml;\n for (const layoutPath of layouts) {\n let layoutHtml = readFileSync(layoutPath, \"utf-8\");\n layoutHtml = this.processComponents(layoutHtml);\n html = layoutHtml.replace(SLOT_MARKER, html);\n }\n\n // 7. Set <title> from metadata\n if (meta.title) {\n html = this.setTitle(html, meta.title);\n }\n\n return {\n html,\n meta,\n filePath: pagePath,\n layouts,\n };\n }\n\n /**\n * Compile a partial page for SPA navigation.\n * Returns only the page content (no layout) plus metadata.\n */\n compilePartial(pagePath: string, pathname: string): PartialPage {\n let pageHtml = readFileSync(pagePath, \"utf-8\");\n const meta = this.extractMeta(pageHtml);\n pageHtml = this.stripMeta(pageHtml);\n pageHtml = this.processComponents(pageHtml);\n\n return {\n html: pageHtml,\n meta,\n path: pathname,\n };\n }\n\n // ─── Internal Methods ────────────────────────────────────────────\n\n /**\n * Extract <!-- @key: value --> metadata from HTML.\n */\n private extractMeta(html: string): PageMeta {\n const meta: PageMeta = {};\n let match: RegExpExecArray | null;\n const regex = new RegExp(META_REGEX.source, META_REGEX.flags);\n while ((match = regex.exec(html)) !== null) {\n meta[match[1]] = match[2];\n }\n return meta;\n }\n\n /**\n * Strip metadata comments from HTML.\n */\n private stripMeta(html: string): string {\n return html.replace(META_REGEX, \"\").trim();\n }\n\n /**\n * Process <c-name /> component tags by replacing them with component file contents.\n * Supports nested components up to MAX_COMPONENT_DEPTH.\n */\n private processComponents(html: string, depth = 0): string {\n if (depth >= MAX_COMPONENT_DEPTH) return html;\n if (!COMPONENT_REGEX.test(html)) return html;\n\n // Reset regex state\n const regex = new RegExp(COMPONENT_REGEX.source, COMPONENT_REGEX.flags);\n\n const result = html.replace(regex, (_match, name: string) => {\n const componentPath = join(this.componentsDir, `${name}.html`);\n if (!existsSync(componentPath)) {\n return `<!-- component \"${name}\" not found -->`;\n }\n const content = readFileSync(componentPath, \"utf-8\").trim();\n // Recursively process nested components\n return this.processComponents(content, depth + 1);\n });\n\n return result;\n }\n\n /**\n * Wrap page content in the router target div.\n */\n private wrapPageContent(html: string): string {\n return `<div id=\"clawfire-page\">\\n${html}\\n</div>`;\n }\n\n /**\n * Collect layout files from page directory up to pages root.\n * Returns innermost layout first (closest to page).\n */\n private collectLayouts(pagePath: string): string[] {\n const layouts: string[] = [];\n let dir = dirname(pagePath);\n\n while (dir.startsWith(this.pagesDir)) {\n const layoutPath = join(dir, \"_layout.html\");\n if (existsSync(layoutPath)) {\n layouts.push(layoutPath);\n }\n // Stop at pages root\n if (dir === this.pagesDir) break;\n dir = dirname(dir);\n }\n\n // Reverse: outermost layout applied last (wraps everything)\n // We want to reduce from innermost out, so reverse:\n // page → innermost layout's <slot /> → outer layout's <slot />\n // The array is already [innermost, ..., outermost] from the walk.\n // We apply innermost first, which means page goes into innermost <slot />,\n // then that result goes into the next outer layout's <slot />, etc.\n return layouts;\n }\n\n /**\n * Set or update <title> tag in HTML.\n */\n private setTitle(html: string, title: string): string {\n if (html.includes(\"<title>\")) {\n return html.replace(/<title>[^<]*<\\/title>/, `<title>${title}</title>`);\n }\n if (html.includes(\"</head>\")) {\n return html.replace(\"</head>\", ` <title>${title}</title>\\n</head>`);\n }\n return html;\n }\n}\n","/**\n * Clawfire Environment Variable Manager\n *\n * Manages .env file and .env.descriptions.json for the dev dashboard.\n * Preserves comments and ordering in .env files.\n */\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface EnvVariable {\n key: string;\n value: string;\n description: string;\n isPlaceholder: boolean;\n line: number;\n}\n\nexport interface EnvData {\n variables: EnvVariable[];\n descriptions: Record<string, string>;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────\n\nconst KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/i;\nconst PLACEHOLDER_PATTERNS = [\n /^YOUR_/i,\n /^CHANGE_ME$/i,\n /^TODO$/i,\n /^REPLACE_/i,\n /^XXX/i,\n /^$/,\n];\n\n// ─── EnvManager ──────────────────────────────────────────────────────\n\nexport class EnvManager {\n private envPath: string;\n private descriptionsPath: string;\n\n constructor(projectDir: string) {\n this.envPath = resolve(projectDir, \".env\");\n this.descriptionsPath = resolve(projectDir, \".env.descriptions.json\");\n }\n\n /** Read all env variables with descriptions and placeholder detection */\n read(): EnvData {\n const descriptions = this.readDescriptions();\n const variables: EnvVariable[] = [];\n\n if (!existsSync(this.envPath)) {\n return { variables, descriptions };\n }\n\n const content = readFileSync(this.envPath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n // Skip empty lines and comments\n if (!line || line.startsWith(\"#\")) continue;\n\n const eqIdx = line.indexOf(\"=\");\n if (eqIdx === -1) continue;\n\n const key = line.slice(0, eqIdx).trim();\n const value = line.slice(eqIdx + 1).trim();\n\n if (!KEY_PATTERN.test(key)) continue;\n\n variables.push({\n key,\n value,\n description: descriptions[key] || \"\",\n isPlaceholder: isPlaceholder(value),\n line: i + 1,\n });\n }\n\n return { variables, descriptions };\n }\n\n /** Set or update an env variable */\n set(key: string, value: string, description?: string): void {\n if (!KEY_PATTERN.test(key)) {\n throw new Error(`Invalid key: \"${key}\" — must match ${KEY_PATTERN}`);\n }\n\n // Update .env file\n let lines: string[] = [];\n if (existsSync(this.envPath)) {\n lines = readFileSync(this.envPath, \"utf-8\").split(\"\\n\");\n }\n\n let found = false;\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (trimmed.startsWith(\"#\") || !trimmed) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx === -1) continue;\n const existingKey = trimmed.slice(0, eqIdx).trim();\n if (existingKey === key) {\n lines[i] = `${key}=${value}`;\n found = true;\n break;\n }\n }\n\n if (!found) {\n // Add to end, ensure trailing newline separation\n if (lines.length > 0 && lines[lines.length - 1].trim() !== \"\") {\n lines.push(`${key}=${value}`);\n } else {\n // Replace trailing empty line\n lines.push(`${key}=${value}`);\n }\n }\n\n writeFileSync(this.envPath, lines.join(\"\\n\"), \"utf-8\");\n\n // Update description if provided\n if (description !== undefined) {\n const descriptions = this.readDescriptions();\n descriptions[key] = description;\n this.writeDescriptions(descriptions);\n }\n }\n\n /** Delete an env variable */\n delete(key: string): void {\n if (!existsSync(this.envPath)) return;\n\n const lines = readFileSync(this.envPath, \"utf-8\").split(\"\\n\");\n const filtered = lines.filter((line) => {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\") || !trimmed) return true;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx === -1) return true;\n return trimmed.slice(0, eqIdx).trim() !== key;\n });\n\n writeFileSync(this.envPath, filtered.join(\"\\n\"), \"utf-8\");\n\n // Remove description\n const descriptions = this.readDescriptions();\n if (key in descriptions) {\n delete descriptions[key];\n this.writeDescriptions(descriptions);\n }\n }\n\n // ─── Private ─────────────────────────────────────────────────────\n\n private readDescriptions(): Record<string, string> {\n if (!existsSync(this.descriptionsPath)) return {};\n try {\n return JSON.parse(readFileSync(this.descriptionsPath, \"utf-8\"));\n } catch {\n return {};\n }\n }\n\n private writeDescriptions(descriptions: Record<string, string>): void {\n writeFileSync(\n this.descriptionsPath,\n JSON.stringify(descriptions, null, 2) + \"\\n\",\n \"utf-8\",\n );\n }\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction isPlaceholder(value: string): boolean {\n return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));\n}\n","/**\n * Clawfire Firebase Status Checker\n *\n * Checks Firebase project health via file inspection and CLI commands.\n * Results are cached for 30 seconds to avoid repeated CLI calls.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execFile } from \"node:child_process\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport type ServiceStatus = \"configured\" | \"placeholder\" | \"missing\";\n\nexport interface ServiceInfo {\n name: string;\n status: ServiceStatus;\n detail?: string;\n}\n\nexport interface FirebaseEnvironmentStatus {\n /** CLI info */\n cli: {\n installed: boolean;\n version: string;\n authenticated: boolean;\n user: string;\n };\n /** Active Firebase project */\n project: {\n id: string;\n hasFirebaseJson: boolean;\n };\n /** Per-service status */\n services: ServiceInfo[];\n /** Config placeholder warnings */\n configWarnings: string[];\n /** Timestamp of this check */\n timestamp: number;\n}\n\n// ─── Cache ───────────────────────────────────────────────────────────\n\nlet cachedStatus: FirebaseEnvironmentStatus | null = null;\nlet cacheTime = 0;\nconst CACHE_TTL = 30_000; // 30 seconds\n\n// ─── Main ────────────────────────────────────────────────────────────\n\nexport async function checkFirebaseStatus(\n projectDir: string,\n): Promise<FirebaseEnvironmentStatus> {\n const now = Date.now();\n if (cachedStatus && now - cacheTime < CACHE_TTL) {\n return cachedStatus;\n }\n\n const [fileStatus, cliStatus] = await Promise.all([\n checkFiles(projectDir),\n checkCli(projectDir),\n ]);\n\n const status: FirebaseEnvironmentStatus = {\n cli: cliStatus,\n project: fileStatus.project,\n services: fileStatus.services,\n configWarnings: fileStatus.configWarnings,\n timestamp: now,\n };\n\n cachedStatus = status;\n cacheTime = now;\n return status;\n}\n\n/** Clear the status cache (useful after config changes) */\nexport function clearFirebaseStatusCache(): void {\n cachedStatus = null;\n cacheTime = 0;\n}\n\n// ─── File Checks (sync, fast) ────────────────────────────────────────\n\ninterface FileCheckResult {\n project: { id: string; hasFirebaseJson: boolean };\n services: ServiceInfo[];\n configWarnings: string[];\n}\n\nfunction checkFiles(projectDir: string): FileCheckResult {\n const services: ServiceInfo[] = [];\n const configWarnings: string[] = [];\n\n // firebase.json\n const firebaseJsonPath = resolve(projectDir, \"firebase.json\");\n let hasFirebaseJson = false;\n let firebaseConfig: Record<string, unknown> = {};\n\n if (existsSync(firebaseJsonPath)) {\n hasFirebaseJson = true;\n try {\n firebaseConfig = JSON.parse(readFileSync(firebaseJsonPath, \"utf-8\"));\n } catch {\n // invalid JSON\n }\n }\n\n // Hosting\n if (firebaseConfig.hosting) {\n services.push({ name: \"Hosting\", status: \"configured\", detail: \"firebase.json\" });\n } else {\n services.push({ name: \"Hosting\", status: \"missing\" });\n }\n\n // Firestore\n const firestoreRulesPath = resolve(projectDir, \"firestore.rules\");\n if (firebaseConfig.firestore) {\n if (existsSync(firestoreRulesPath)) {\n services.push({ name: \"Firestore\", status: \"configured\", detail: \"Rules file found\" });\n } else {\n services.push({ name: \"Firestore\", status: \"placeholder\", detail: \"Configured but no rules file\" });\n }\n } else {\n services.push({ name: \"Firestore\", status: \"missing\" });\n }\n\n // Functions\n const functionsIndexPath = resolve(projectDir, \"functions/index.ts\");\n if (firebaseConfig.functions) {\n if (existsSync(functionsIndexPath)) {\n services.push({ name: \"Functions\", status: \"configured\", detail: \"functions/index.ts found\" });\n } else {\n services.push({ name: \"Functions\", status: \"placeholder\", detail: \"Configured but no entry file\" });\n }\n } else {\n services.push({ name: \"Functions\", status: \"missing\" });\n }\n\n // Storage\n const storageRulesPath = resolve(projectDir, \"storage.rules\");\n if (firebaseConfig.storage) {\n if (existsSync(storageRulesPath)) {\n services.push({ name: \"Storage\", status: \"configured\", detail: \"Rules file found\" });\n } else {\n services.push({ name: \"Storage\", status: \"placeholder\", detail: \"Configured but no rules file\" });\n }\n } else {\n services.push({ name: \"Storage\", status: \"missing\" });\n }\n\n // Firestore indexes\n const indexesPath = resolve(projectDir, \"firestore.indexes.json\");\n if (existsSync(indexesPath)) {\n services.push({ name: \"Indexes\", status: \"configured\", detail: \"firestore.indexes.json\" });\n } else if (firebaseConfig.firestore) {\n services.push({ name: \"Indexes\", status: \"placeholder\", detail: \"No indexes file\" });\n }\n\n // clawfire.config.ts — check for placeholder values\n const configPath = resolve(projectDir, \"clawfire.config.ts\");\n if (existsSync(configPath)) {\n try {\n const configContent = readFileSync(configPath, \"utf-8\");\n const placeholderMatches = configContent.match(/YOUR_[A-Z_]+/g);\n if (placeholderMatches) {\n for (const match of new Set(placeholderMatches)) {\n configWarnings.push(`Placeholder found: ${match}`);\n }\n }\n } catch {\n // ignore read errors\n }\n }\n\n // Project ID from .firebaserc\n let projectId = \"\";\n const firebasercPath = resolve(projectDir, \".firebaserc\");\n if (existsSync(firebasercPath)) {\n try {\n const rc = JSON.parse(readFileSync(firebasercPath, \"utf-8\"));\n projectId = rc?.projects?.default || \"\";\n } catch {\n // invalid JSON\n }\n }\n\n return {\n project: { id: projectId, hasFirebaseJson },\n services,\n configWarnings,\n };\n}\n\n// ─── CLI Checks (async, with timeouts) ───────────────────────────────\n\ninterface CliCheckResult {\n installed: boolean;\n version: string;\n authenticated: boolean;\n user: string;\n}\n\nasync function checkCli(projectDir: string): Promise<CliCheckResult> {\n const result: CliCheckResult = {\n installed: false,\n version: \"\",\n authenticated: false,\n user: \"\",\n };\n\n // Check firebase --version\n try {\n const version = await execWithTimeout(\"firebase\", [\"--version\"], projectDir, 5000);\n result.installed = true;\n result.version = version.trim();\n } catch {\n return result; // If CLI not installed, skip auth check\n }\n\n // Check firebase login status\n try {\n const loginOutput = await execWithTimeout(\n \"firebase\",\n [\"login:list\", \"--json\"],\n projectDir,\n 5000,\n );\n const loginData = JSON.parse(loginOutput);\n if (loginData?.result && Array.isArray(loginData.result) && loginData.result.length > 0) {\n result.authenticated = true;\n result.user = loginData.result[0]?.user?.email || loginData.result[0]?.email || \"\";\n }\n } catch {\n // Not authenticated or command failed\n }\n\n return result;\n}\n\n// ─── Firebase SDK Config (auto-fill) ─────────────────────────────────\n\nexport interface FirebaseSdkConfig {\n apiKey?: string;\n authDomain?: string;\n projectId?: string;\n storageBucket?: string;\n messagingSenderId?: string;\n appId?: string;\n}\n\n/**\n * Fetch Firebase web SDK config via CLI.\n * Requires: firebase CLI installed, logged in, active project set.\n */\nexport async function fetchFirebaseSdkConfig(\n projectDir: string,\n): Promise<FirebaseSdkConfig> {\n // Try firebase apps:sdkconfig web --json\n const output = await execWithTimeout(\n \"firebase\",\n [\"apps:sdkconfig\", \"web\", \"--json\"],\n projectDir,\n 15000,\n );\n\n const data = JSON.parse(output);\n\n // Firebase CLI returns { status: \"success\", result: { sdkConfig: { ... } } }\n // or sometimes { result: { fileName: \"...\", fileContents: \"...\" } }\n if (data?.result?.sdkConfig) {\n return data.result.sdkConfig;\n }\n\n // Some CLI versions return fileContents with the config object as JS\n if (data?.result?.fileContents) {\n const contents = data.result.fileContents as string;\n const config: FirebaseSdkConfig = {};\n const extract = (key: string) => {\n const match = contents.match(new RegExp(`\"${key}\"\\\\s*:\\\\s*\"([^\"]+)\"`));\n return match ? match[1] : undefined;\n };\n config.apiKey = extract(\"apiKey\");\n config.authDomain = extract(\"authDomain\");\n config.projectId = extract(\"projectId\");\n config.storageBucket = extract(\"storageBucket\");\n config.messagingSenderId = extract(\"messagingSenderId\");\n config.appId = extract(\"appId\");\n return config;\n }\n\n throw new Error(\"Could not parse Firebase SDK config from CLI output\");\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction execWithTimeout(\n command: string,\n args: string[],\n cwd: string,\n timeoutMs: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {\n if (err) {\n reject(err);\n } else {\n resolve(stdout);\n }\n });\n // Extra safety: kill if timeout is reached\n const timer = setTimeout(() => {\n proc.kill(\"SIGTERM\");\n reject(new Error(\"Timeout\"));\n }, timeoutMs + 500);\n proc.on(\"exit\", () => clearTimeout(timer));\n });\n}\n","/**\n * Clawfire Dev Dashboard HTML Generator\n *\n * Generates the Dashboard tab content for the dev server.\n * Sections: Firebase Setup Wizard, Firebase Status, Config Overview (editable), Environment Variables.\n * All CSS/JS is inline — no external dependencies.\n * Data is lazy-loaded from /__dev/* endpoints.\n */\n\nexport interface DashboardHtmlOptions {\n apiPort: number;\n}\n\nexport function generateDashboardHtml(options: DashboardHtmlOptions): string {\n const { apiPort } = options;\n\n return `\n<div id=\"dashboard-content\" style=\"padding:24px;max-width:1200px;margin:0 auto;font-family:var(--font, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);\">\n\n <!-- Loading State -->\n <div id=\"dash-loading\" style=\"text-align:center;padding:60px 0;color:#a3a3a3;\">\n <div style=\"font-size:24px;margin-bottom:8px;\">Loading dashboard...</div>\n </div>\n\n <!-- Dashboard Sections (hidden until loaded) -->\n <div id=\"dash-loaded\" style=\"display:none;\">\n\n <!-- Section 0: Firebase Setup Wizard -->\n <div id=\"setup-wizard\" style=\"margin-bottom:32px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;\">Firebase Setup</h2>\n <div id=\"setup-card\" style=\"padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;\">\n\n <!-- Step indicators -->\n <div id=\"setup-steps\" style=\"display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;\">\n <div id=\"step-ind-1\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#1a1a2e;color:#f97316;border-right:1px solid #2a2a2a;\">\n 1. CLI Install\n </div>\n <div id=\"step-ind-2\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;\">\n 2. Login\n </div>\n <div id=\"step-ind-3\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;\">\n 3. Select Project\n </div>\n <div id=\"step-ind-4\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;\">\n 4. Auto-fill\n </div>\n </div>\n\n <!-- Step 1: CLI Install -->\n <div id=\"step-1\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step1-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Firebase CLI</div>\n <div id=\"step1-detail\" style=\"font-size:13px;color:#a3a3a3;\">Checking...</div>\n </div>\n </div>\n <div id=\"step1-action\" style=\"display:none;\">\n <button id=\"install-cli-btn\" onclick=\"installFirebaseCli()\" style=\"padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Install Firebase CLI\n </button>\n <div id=\"install-status\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n </div>\n\n <!-- Step 2: Login -->\n <div id=\"step-2\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step2-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Firebase Authentication</div>\n <div id=\"step2-detail\" style=\"font-size:13px;color:#a3a3a3;\">Checking...</div>\n </div>\n </div>\n <div id=\"step2-action\" style=\"display:none;\">\n <button id=\"login-btn\" onclick=\"startFirebaseLogin(false)\" style=\"padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Login to Firebase\n </button>\n <button id=\"reauth-btn\" onclick=\"startFirebaseLogin(true)\" style=\"display:none;padding:8px 20px;background:#eab308;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Re-authenticate\n </button>\n </div>\n <!-- Login waiting message -->\n <div id=\"login-waiting\" style=\"display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;\">\n <div style=\"display:flex;align-items:center;gap:8px;margin-bottom:8px;\">\n <span style=\"display:inline-block;width:14px;height:14px;border:2px solid #3b82f6;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;\"></span>\n <span style=\"color:#e5e5e5;font-size:13px;font-weight:600;\">Waiting for login...</span>\n </div>\n <div style=\"font-size:13px;color:#a3a3a3;\">A terminal window has been opened. Please complete the Firebase login there.</div>\n <div style=\"font-size:12px;color:#666;margin-top:6px;\">This page will update automatically when login is detected.</div>\n </div>\n <div id=\"login-result\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n\n <!-- Step 3: Select Project -->\n <div id=\"step-3\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step3-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Firebase Project</div>\n <div id=\"step3-detail\" style=\"font-size:13px;color:#a3a3a3;\">Checking...</div>\n </div>\n </div>\n <div id=\"step3-action\" style=\"display:none;\">\n <div style=\"display:flex;gap:8px;align-items:center;flex-wrap:wrap;\">\n <select id=\"project-select\" style=\"padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:13px;font-family:monospace;min-width:280px;outline:none;\">\n <option value=\"\">Loading projects...</option>\n </select>\n <button id=\"select-project-btn\" onclick=\"selectFirebaseProject()\" style=\"padding:8px 20px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Use This Project\n </button>\n <button onclick=\"refreshProjectList()\" style=\"padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;\" title=\"Refresh\">\n Refresh\n </button>\n </div>\n <div id=\"project-status\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n </div>\n\n <!-- Step 4: Done / Auto-fill -->\n <div id=\"step-4\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step4-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Auto-fill Config</div>\n <div id=\"step4-detail\" style=\"font-size:13px;color:#a3a3a3;\">Ready to auto-fill your clawfire.config.ts</div>\n </div>\n </div>\n <div id=\"step4-action\">\n <button id=\"autofill-wizard-btn\" onclick=\"autoFillConfig()\" style=\"padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Auto-fill Config Now\n </button>\n <div id=\"autofill-wizard-status\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n </div>\n\n <!-- All done banner -->\n <div id=\"setup-done\" style=\"display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;\">\n <div style=\"font-size:16px;color:#22c55e;font-weight:700;margin-bottom:4px;\">Setup Complete</div>\n <div id=\"setup-done-detail\" style=\"font-size:13px;color:#a3a3a3;\"></div>\n <button onclick=\"startFirebaseLogin(true)\" style=\"margin-top:8px;padding:6px 14px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:12px;cursor:pointer;\">Re-authenticate</button>\n </div>\n </div>\n </div>\n\n <!-- Section 1: Firebase Status -->\n <div style=\"margin-bottom:32px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;\">Firebase Status</h2>\n\n <!-- CLI Banner -->\n <div id=\"cli-banner\" style=\"padding:12px 16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;margin-bottom:16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;\">\n <span id=\"cli-dot\" style=\"width:10px;height:10px;border-radius:50%;background:#666;display:inline-block;flex-shrink:0;\"></span>\n <span id=\"cli-text\" style=\"color:#e5e5e5;font-size:14px;\">Checking CLI...</span>\n <span id=\"cli-project\" style=\"color:#a3a3a3;font-size:12px;margin-left:auto;font-family:monospace;\"></span>\n </div>\n\n <!-- Service Cards Grid -->\n <div id=\"service-grid\" style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;\"></div>\n </div>\n\n <!-- Section 2: Config Overview -->\n <div style=\"margin-bottom:32px;\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;\">Config Overview</h2>\n <button id=\"autofill-btn\" onclick=\"autoFillConfig()\" style=\"padding:6px 14px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Auto-fill from Firebase</button>\n </div>\n <div id=\"autofill-status\" style=\"display:none;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;\"></div>\n <div id=\"config-section\" style=\"border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;\">\n <table id=\"config-table\" style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr style=\"border-bottom:1px solid #2a2a2a;\">\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;width:180px;\">Key</th>\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Value</th>\n <th style=\"padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:80px;\">Action</th>\n </tr>\n </thead>\n <tbody id=\"config-tbody\"></tbody>\n </table>\n <div id=\"config-empty\" style=\"display:none;padding:32px;text-align:center;color:#666;\">\n No clawfire.config.ts found.\n </div>\n </div>\n </div>\n\n <!-- Section 3: Environment Variables -->\n <div style=\"margin-bottom:32px;\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;\">Environment Variables</h2>\n <button id=\"env-add-btn\" onclick=\"showEnvModal()\" style=\"padding:6px 14px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">+ Add Variable</button>\n </div>\n <div id=\"env-table-wrap\" style=\"border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;\">\n <table id=\"env-table\" style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr style=\"border-bottom:1px solid #2a2a2a;\">\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Key</th>\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Value</th>\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Description</th>\n <th style=\"padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:120px;\">Actions</th>\n </tr>\n </thead>\n <tbody id=\"env-tbody\"></tbody>\n </table>\n <div id=\"env-empty\" style=\"display:none;padding:32px;text-align:center;color:#666;\">\n No environment variables found. Click \"+ Add Variable\" to create one.\n </div>\n </div>\n </div>\n </div>\n\n <!-- Env Modal -->\n <div id=\"env-modal\" style=\"display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;align-items:center;justify-content:center;\">\n <div style=\"background:#1e1e1e;border:1px solid #2a2a2a;border-radius:12px;padding:24px;width:440px;max-width:90vw;\">\n <h3 id=\"modal-title\" style=\"font-size:16px;font-weight:700;color:#e5e5e5;margin-bottom:16px;\">Add Variable</h3>\n <div style=\"margin-bottom:12px;\">\n <label style=\"display:block;font-size:12px;color:#a3a3a3;margin-bottom:4px;\">Key</label>\n <input id=\"modal-key\" type=\"text\" placeholder=\"API_KEY\" style=\"width:100%;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;\" />\n </div>\n <div style=\"margin-bottom:12px;\">\n <label style=\"display:block;font-size:12px;color:#a3a3a3;margin-bottom:4px;\">Value</label>\n <input id=\"modal-value\" type=\"text\" placeholder=\"your-value-here\" style=\"width:100%;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;\" />\n </div>\n <div style=\"margin-bottom:20px;\">\n <label style=\"display:block;font-size:12px;color:#a3a3a3;margin-bottom:4px;\">Description (optional)</label>\n <input id=\"modal-desc\" type=\"text\" placeholder=\"What this variable is for\" style=\"width:100%;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:14px;outline:none;\" />\n </div>\n <div id=\"modal-error\" style=\"display:none;padding:8px 12px;background:#1c0808;border:1px solid #ef4444;border-radius:6px;color:#ef4444;font-size:12px;margin-bottom:12px;\"></div>\n <div style=\"display:flex;gap:8px;justify-content:flex-end;\">\n <button onclick=\"hideEnvModal()\" style=\"padding:8px 16px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;cursor:pointer;font-size:13px;\">Cancel</button>\n <button id=\"modal-save-btn\" onclick=\"saveEnvVar()\" style=\"padding:8px 16px;background:#f97316;border:none;border-radius:6px;color:#000;font-weight:600;cursor:pointer;font-size:13px;\">Save</button>\n </div>\n </div>\n </div>\n</div>\n\n<style>\n@keyframes spin { to { transform: rotate(360deg); } }\n#login-spinner { animation: spin 1s linear infinite; }\n</style>\n\n<script>\n(function() {\n var API = 'http://localhost:${apiPort}';\n var envData = [];\n var configData = [];\n var editingKey = null;\n var loginPollTimer = null;\n\n // ─── Load Dashboard Data ─────────────────────────────────────────\n window._loadDashboard = function() {\n if (window._dashboardLoaded) return;\n window._dashboardLoaded = true;\n Promise.all([\n fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }),\n fetch(API + '/__dev/config').then(function(r) { return r.json(); }),\n fetch(API + '/__dev/env').then(function(r) { return r.json(); }),\n fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),\n ]).then(function(results) {\n renderFirebaseStatus(results[0]);\n renderConfig(results[1]);\n renderEnvVars(results[2]);\n renderSetupWizard(results[3]);\n document.getElementById('dash-loading').style.display = 'none';\n document.getElementById('dash-loaded').style.display = 'block';\n }).catch(function(err) {\n document.getElementById('dash-loading').innerHTML =\n '<div style=\"color:#ef4444;\">Failed to load dashboard data</div>' +\n '<div style=\"color:#666;font-size:13px;margin-top:8px;\">' + err.message + '</div>';\n });\n };\n\n // ─── Setup Wizard ─────────────────────────────────────────────────\n\n function setStepIndicator(activeStep) {\n for (var i = 1; i <= 4; i++) {\n var el = document.getElementById('step-ind-' + i);\n if (i < activeStep) {\n el.style.background = '#0a1a0a';\n el.style.color = '#22c55e';\n } else if (i === activeStep) {\n el.style.background = '#1a1a2e';\n el.style.color = '#f97316';\n } else {\n el.style.background = '#0a0a0a';\n el.style.color = '#666';\n }\n }\n }\n\n function renderSetupWizard(status) {\n var GREEN = '\\\\u2705';\n var YELLOW = '\\\\u26A0\\\\uFE0F';\n var RED = '\\\\u274C';\n var CIRCLE = '\\\\u26AA';\n\n // Hide all steps first\n for (var i = 1; i <= 4; i++) {\n document.getElementById('step-' + i).style.display = 'none';\n }\n document.getElementById('setup-done').style.display = 'none';\n\n if (status.nextStep === 'done') {\n // All done!\n setStepIndicator(5);\n document.getElementById('setup-done').style.display = 'block';\n document.getElementById('setup-done-detail').textContent =\n 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;\n return;\n }\n\n // Step 1: CLI\n var step1 = document.getElementById('step-1');\n step1.style.display = 'block';\n if (status.cli.installed) {\n document.getElementById('step1-icon').textContent = GREEN;\n document.getElementById('step1-detail').textContent = 'Firebase CLI v' + status.cli.version + ' installed';\n document.getElementById('step1-action').style.display = 'none';\n } else {\n document.getElementById('step1-icon').textContent = RED;\n document.getElementById('step1-detail').textContent = 'Firebase CLI is not installed';\n document.getElementById('step1-action').style.display = 'block';\n }\n\n if (status.nextStep === 'install-cli') {\n setStepIndicator(1);\n return;\n }\n\n // Step 2: Auth\n var step2 = document.getElementById('step-2');\n step2.style.display = 'block';\n if (status.auth.authenticated) {\n document.getElementById('step2-icon').textContent = GREEN;\n document.getElementById('step2-detail').textContent = 'Logged in as ' + status.auth.user;\n document.getElementById('step2-action').style.display = 'block';\n document.getElementById('login-btn').style.display = 'none';\n document.getElementById('reauth-btn').style.display = 'inline-block';\n document.getElementById('login-waiting').style.display = 'none';\n } else {\n document.getElementById('step2-icon').textContent = RED;\n document.getElementById('step2-detail').textContent = 'Not logged in to Firebase';\n document.getElementById('step2-action').style.display = 'block';\n document.getElementById('login-btn').style.display = 'inline-block';\n document.getElementById('reauth-btn').style.display = 'none';\n document.getElementById('login-waiting').style.display = 'none';\n }\n\n if (status.nextStep === 'login') {\n setStepIndicator(2);\n return;\n }\n\n // Step 3: Project Selection\n var step3 = document.getElementById('step-3');\n step3.style.display = 'block';\n if (status.project.id) {\n document.getElementById('step3-icon').textContent = GREEN;\n document.getElementById('step3-detail').textContent = 'Active project: ' + status.project.id;\n document.getElementById('step3-action').style.display = 'block';\n } else {\n document.getElementById('step3-icon').textContent = YELLOW;\n document.getElementById('step3-detail').textContent = 'No project selected';\n document.getElementById('step3-action').style.display = 'block';\n }\n\n // Load project list\n loadProjectList(status.project.id);\n\n if (status.nextStep === 'select-project') {\n setStepIndicator(3);\n return;\n }\n\n // Step 4: Auto-fill\n var step4 = document.getElementById('step-4');\n step4.style.display = 'block';\n document.getElementById('step4-icon').textContent = CIRCLE;\n setStepIndicator(4);\n }\n\n // ─── Step 1: Install CLI ──────────────────────────────────────────\n window.installFirebaseCli = function() {\n var btn = document.getElementById('install-cli-btn');\n var status = document.getElementById('install-status');\n btn.disabled = true;\n btn.textContent = 'Installing...';\n status.style.display = 'block';\n status.textContent = 'Running npm install -g firebase-tools... This may take a minute.';\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';\n\n fetch(API + '/__dev/setup/install-cli', { method: 'POST' })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.success) {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n // Refresh wizard\n setTimeout(refreshSetupStatus, 1000);\n } else {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Install Firebase CLI';\n }\n })\n .catch(function(err) {\n status.textContent = 'Error: ' + err.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Install Firebase CLI';\n });\n };\n\n // ─── Step 2: Login (opens terminal window) ──────────────────────\n window.startFirebaseLogin = function(reauth) {\n var loginBtn = document.getElementById('login-btn');\n var reauthBtn = document.getElementById('reauth-btn');\n var waiting = document.getElementById('login-waiting');\n var result = document.getElementById('login-result');\n\n loginBtn.style.display = 'none';\n reauthBtn.style.display = 'none';\n waiting.style.display = 'block';\n result.style.display = 'none';\n\n fetch(API + '/__dev/setup/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ reauth: !!reauth })\n })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (!data.success) {\n waiting.style.display = 'none';\n result.textContent = data.message;\n result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n loginBtn.style.display = 'inline-block';\n return;\n }\n // Poll setup status until login is detected\n startLoginPoll();\n })\n .catch(function(err) {\n waiting.style.display = 'none';\n result.textContent = 'Failed: ' + err.message;\n result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n loginBtn.style.display = 'inline-block';\n });\n };\n\n function startLoginPoll() {\n if (loginPollTimer) clearInterval(loginPollTimer);\n loginPollTimer = setInterval(function() {\n fetch(API + '/__dev/setup/status')\n .then(function(r) { return r.json(); })\n .then(function(status) {\n if (status.auth.authenticated) {\n clearInterval(loginPollTimer);\n loginPollTimer = null;\n document.getElementById('login-waiting').style.display = 'none';\n var result = document.getElementById('login-result');\n result.textContent = 'Login successful! Logged in as ' + status.auth.user;\n result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n setTimeout(refreshSetupStatus, 1500);\n }\n })\n .catch(function() {});\n }, 3000);\n }\n\n // ─── Step 3: Project Selection ────────────────────────────────────\n function loadProjectList(currentProjectId) {\n var select = document.getElementById('project-select');\n select.innerHTML = '<option value=\"\">Loading projects...</option>';\n select.disabled = true;\n\n fetch(API + '/__dev/setup/projects')\n .then(function(r) { return r.json(); })\n .then(function(data) {\n select.innerHTML = '';\n if (data.error) {\n select.innerHTML = '<option value=\"\">Error: ' + escHtml(data.error) + '</option>';\n return;\n }\n if (!data.projects || data.projects.length === 0) {\n select.innerHTML = '<option value=\"\">No projects found</option>';\n return;\n }\n\n select.innerHTML = '<option value=\"\">-- Select a project --</option>';\n data.projects.forEach(function(p) {\n var opt = document.createElement('option');\n opt.value = p.projectId;\n opt.textContent = p.displayName + ' (' + p.projectId + ')';\n if (p.projectId === currentProjectId) opt.selected = true;\n select.appendChild(opt);\n });\n select.disabled = false;\n })\n .catch(function(err) {\n select.innerHTML = '<option value=\"\">Failed to load</option>';\n });\n }\n\n window.refreshProjectList = function() {\n loadProjectList('');\n };\n\n window.selectFirebaseProject = function() {\n var select = document.getElementById('project-select');\n var btn = document.getElementById('select-project-btn');\n var status = document.getElementById('project-status');\n var projectId = select.value;\n\n if (!projectId) {\n status.textContent = 'Please select a project first.';\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';\n return;\n }\n\n btn.disabled = true;\n btn.textContent = 'Setting...';\n status.style.display = 'none';\n\n fetch(API + '/__dev/setup/select-project', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ projectId: projectId })\n })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.success) {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n setTimeout(refreshSetupStatus, 1000);\n } else {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Use This Project';\n }\n })\n .catch(function(err) {\n status.textContent = 'Error: ' + err.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Use This Project';\n });\n };\n\n // ─── Refresh Setup Status ─────────────────────────────────────────\n function refreshSetupStatus() {\n fetch(API + '/__dev/setup/status')\n .then(function(r) { return r.json(); })\n .then(function(status) { renderSetupWizard(status); })\n .catch(function() {});\n\n // Also refresh firebase status section\n fetch(API + '/__dev/firebase-status')\n .then(function(r) { return r.json(); })\n .then(function(data) { renderFirebaseStatus(data); })\n .catch(function() {});\n }\n\n // ─── Firebase Status ─────────────────────────────────────────────\n function renderFirebaseStatus(data) {\n var dot = document.getElementById('cli-dot');\n var text = document.getElementById('cli-text');\n var proj = document.getElementById('cli-project');\n\n if (!data.cli.installed) {\n dot.style.background = '#ef4444';\n text.textContent = 'Firebase CLI not installed';\n } else if (!data.cli.authenticated) {\n dot.style.background = '#eab308';\n text.textContent = 'Firebase CLI v' + data.cli.version + ' \\\\u2014 Not logged in';\n } else {\n dot.style.background = '#22c55e';\n text.textContent = 'Firebase CLI v' + data.cli.version + ' \\\\u2014 ' + data.cli.user;\n }\n\n if (data.project.id) {\n proj.textContent = 'Project: ' + data.project.id;\n } else {\n proj.textContent = 'No active project';\n proj.style.color = '#eab308';\n }\n\n // Service Cards\n var grid = document.getElementById('service-grid');\n grid.innerHTML = '';\n var statusColors = { configured: '#22c55e', placeholder: '#eab308', missing: '#666' };\n var statusLabels = { configured: 'Ready', placeholder: 'Needs Setup', missing: 'Not Configured' };\n\n data.services.forEach(function(svc) {\n var card = document.createElement('div');\n card.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;';\n card.innerHTML =\n '<div style=\"display:flex;align-items:center;gap:8px;margin-bottom:6px;\">' +\n '<span style=\"width:8px;height:8px;border-radius:50%;background:' + statusColors[svc.status] + ';display:inline-block;\"></span>' +\n '<span style=\"font-weight:600;color:#e5e5e5;\">' + svc.name + '</span>' +\n '</div>' +\n '<div style=\"font-size:12px;color:' + statusColors[svc.status] + ';\">' + statusLabels[svc.status] + '</div>' +\n (svc.detail ? '<div style=\"font-size:11px;color:#666;margin-top:4px;\">' + svc.detail + '</div>' : '');\n grid.appendChild(card);\n });\n\n if (data.configWarnings && data.configWarnings.length > 0) {\n var warningCard = document.createElement('div');\n warningCard.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #eab308;background:#1a1a0a;grid-column:1/-1;';\n warningCard.innerHTML =\n '<div style=\"font-weight:600;color:#eab308;margin-bottom:6px;\">Config Warnings</div>' +\n data.configWarnings.map(function(w) {\n return '<div style=\"font-size:12px;color:#eab308;font-family:monospace;\">' + escHtml(w) + '</div>';\n }).join('');\n grid.appendChild(warningCard);\n }\n }\n\n // ─── Config Overview (editable) ──────────────────────────────────\n function renderConfig(data) {\n var tbody = document.getElementById('config-tbody');\n var empty = document.getElementById('config-empty');\n var table = document.getElementById('config-table');\n\n configData = (data && data.fields) ? data.fields : [];\n\n if (configData.length === 0) {\n table.style.display = 'none';\n empty.style.display = 'block';\n return;\n }\n\n table.style.display = 'table';\n empty.style.display = 'none';\n tbody.innerHTML = '';\n\n configData.forEach(function(field) {\n var tr = document.createElement('tr');\n tr.style.borderBottom = '1px solid #2a2a2a';\n tr.id = 'cfg-row-' + field.key;\n var color = field.isPlaceholder ? '#eab308' : '#a3a3a3';\n var badge = field.isPlaceholder ? ' <span style=\"background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;\">PLACEHOLDER</span>' : '';\n tr.innerHTML =\n '<td style=\"padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;\">' + escHtml(field.key) + '</td>' +\n '<td style=\"padding:10px 16px;font-family:monospace;\">' +\n '<span id=\"cfg-val-' + field.key + '\" style=\"color:' + color + ';\">' + escHtml(field.value) + '</span>' +\n badge +\n '<input id=\"cfg-input-' + field.key + '\" type=\"text\" value=\"' + escHtml(field.value) + '\" style=\"display:none;width:100%;padding:4px 8px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:4px;color:#e5e5e5;font-family:monospace;font-size:13px;outline:none;\" />' +\n '</td>' +\n '<td style=\"padding:10px 16px;text-align:right;white-space:nowrap;\">' +\n '<button id=\"cfg-edit-' + field.key + '\" onclick=\"editConfigField(\\\\'' + escHtml(field.key) + '\\\\')\" style=\"background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;\">Edit</button>' +\n '<button id=\"cfg-save-' + field.key + '\" onclick=\"saveConfigField(\\\\'' + escHtml(field.key) + '\\\\')\" style=\"display:none;background:none;border:none;color:#22c55e;cursor:pointer;font-size:12px;padding:4px 8px;\">Save</button>' +\n '<button id=\"cfg-cancel-' + field.key + '\" onclick=\"cancelConfigEdit(\\\\'' + escHtml(field.key) + '\\\\')\" style=\"display:none;background:none;border:none;color:#a3a3a3;cursor:pointer;font-size:12px;padding:4px 8px;\">Cancel</button>' +\n '</td>';\n tbody.appendChild(tr);\n });\n }\n\n window.editConfigField = function(key) {\n var valSpan = document.getElementById('cfg-val-' + key);\n var input = document.getElementById('cfg-input-' + key);\n var editBtn = document.getElementById('cfg-edit-' + key);\n var saveBtn = document.getElementById('cfg-save-' + key);\n var cancelBtn = document.getElementById('cfg-cancel-' + key);\n if (!valSpan || !input) return;\n var badges = valSpan.parentNode.querySelectorAll('span[style*=\"PLACEHOLDER\"]');\n for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';\n valSpan.style.display = 'none';\n input.style.display = 'block';\n input.focus();\n input.select();\n editBtn.style.display = 'none';\n saveBtn.style.display = 'inline';\n cancelBtn.style.display = 'inline';\n };\n\n window.cancelConfigEdit = function(key) {\n var valSpan = document.getElementById('cfg-val-' + key);\n var input = document.getElementById('cfg-input-' + key);\n var editBtn = document.getElementById('cfg-edit-' + key);\n var saveBtn = document.getElementById('cfg-save-' + key);\n var cancelBtn = document.getElementById('cfg-cancel-' + key);\n if (!valSpan || !input) return;\n var badges = valSpan.parentNode.querySelectorAll('span[style*=\"border-radius\"]');\n for (var i = 0; i < badges.length; i++) badges[i].style.display = '';\n valSpan.style.display = '';\n input.style.display = 'none';\n input.value = valSpan.textContent;\n editBtn.style.display = 'inline';\n saveBtn.style.display = 'none';\n cancelBtn.style.display = 'none';\n };\n\n window.saveConfigField = function(key) {\n var input = document.getElementById('cfg-input-' + key);\n var saveBtn = document.getElementById('cfg-save-' + key);\n if (!input) return;\n var value = input.value;\n saveBtn.textContent = '...';\n fetch(API + '/__dev/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'set', key: key, value: value })\n }).then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }\n return fetch(API + '/__dev/config').then(function(r) { return r.json(); });\n })\n .then(function(data) { if (data) renderConfig(data); })\n .catch(function(err) { alert('Failed: ' + err.message); saveBtn.textContent = 'Save'; });\n };\n\n // ─── Auto-fill from Firebase ─────────────────────────────────────\n window.autoFillConfig = function() {\n var btn = document.getElementById('autofill-btn');\n var wizBtn = document.getElementById('autofill-wizard-btn');\n var status = document.getElementById('autofill-status');\n var wizStatus = document.getElementById('autofill-wizard-status');\n if (btn) { btn.disabled = true; btn.textContent = 'Fetching...'; }\n if (wizBtn) { wizBtn.disabled = true; wizBtn.textContent = 'Fetching...'; }\n if (status) status.style.display = 'none';\n if (wizStatus) wizStatus.style.display = 'none';\n\n fetch(API + '/__dev/firebase-sdk-config')\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.error) {\n showAutoFillResult(false, data.error);\n return;\n }\n\n var fields = {};\n if (data.apiKey) fields.apiKey = data.apiKey;\n if (data.authDomain) fields.authDomain = data.authDomain;\n if (data.projectId) fields.projectId = data.projectId;\n if (data.storageBucket) fields.storageBucket = data.storageBucket;\n if (data.appId) fields.appId = data.appId;\n\n var count = Object.keys(fields).length;\n if (count === 0) {\n showAutoFillResult(false, 'No web app config found. Create a Web app in Firebase Console first.');\n return;\n }\n\n return fetch(API + '/__dev/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'set-multiple', fields: fields })\n }).then(function(r) { return r.json(); })\n .then(function(result) {\n if (result.error) {\n showAutoFillResult(false, 'Error saving: ' + result.error);\n } else {\n showAutoFillResult(true, count + ' fields updated from Firebase project!');\n return fetch(API + '/__dev/config').then(function(r) { return r.json(); })\n .then(function(cfg) { renderConfig(cfg); });\n }\n });\n })\n .catch(function(err) {\n showAutoFillResult(false, 'Failed: ' + err.message);\n })\n .finally(function() {\n if (btn) { btn.disabled = false; btn.textContent = 'Auto-fill from Firebase'; }\n if (wizBtn) { wizBtn.disabled = false; wizBtn.textContent = 'Auto-fill Config Now'; }\n });\n };\n\n function showAutoFillResult(success, message) {\n var targets = ['autofill-status', 'autofill-wizard-status'];\n targets.forEach(function(id) {\n var el = document.getElementById(id);\n if (!el) return;\n el.textContent = message;\n if (success) {\n el.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n } else {\n el.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n }\n });\n }\n\n // ─── Environment Variables ───────────────────────────────────────\n function renderEnvVars(data) {\n envData = data.variables || [];\n var tbody = document.getElementById('env-tbody');\n var empty = document.getElementById('env-empty');\n var table = document.getElementById('env-table');\n\n if (envData.length === 0) {\n table.style.display = 'none';\n empty.style.display = 'block';\n return;\n }\n\n table.style.display = 'table';\n empty.style.display = 'none';\n tbody.innerHTML = '';\n\n envData.forEach(function(v) {\n var tr = document.createElement('tr');\n tr.style.borderBottom = '1px solid #2a2a2a';\n var maskedVal = v.value ? v.value.slice(0, 3) + '...' + v.value.slice(-2) : '';\n if (v.value.length <= 5) maskedVal = '***';\n var placeholderBadge = v.isPlaceholder ? ' <span style=\"background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;\">PLACEHOLDER</span>' : '';\n tr.innerHTML =\n '<td style=\"padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;\">' + escHtml(v.key) + placeholderBadge + '</td>' +\n '<td style=\"padding:10px 16px;color:#a3a3a3;font-family:monospace;\">' +\n '<span class=\"env-val\" data-key=\"' + escAttr(v.key) + '\" data-masked=\"' + escAttr(maskedVal) + '\" data-full=\"' + escAttr(v.value) + '\">' + escHtml(maskedVal) + '</span>' +\n ' <button onclick=\"toggleReveal(this)\" style=\"background:none;border:none;color:#666;cursor:pointer;font-size:11px;padding:2px 4px;\">reveal</button>' +\n '</td>' +\n '<td style=\"padding:10px 16px;color:#666;font-size:12px;\">' + escHtml(v.description || '') + '</td>' +\n '<td style=\"padding:10px 16px;text-align:right;white-space:nowrap;\">' +\n '<button onclick=\"editEnvVar(\\\\'' + escAttr(v.key) + '\\\\')\" style=\"background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;\">Edit</button>' +\n '<button onclick=\"deleteEnvVar(\\\\'' + escAttr(v.key) + '\\\\')\" style=\"background:none;border:none;color:#ef4444;cursor:pointer;font-size:12px;padding:4px 8px;\">Delete</button>' +\n '</td>';\n tbody.appendChild(tr);\n });\n }\n\n // ─── Env Modal ───────────────────────────────────────────────────\n window.showEnvModal = function(key) {\n editingKey = key || null;\n var modal = document.getElementById('env-modal');\n var title = document.getElementById('modal-title');\n var keyInput = document.getElementById('modal-key');\n var valInput = document.getElementById('modal-value');\n var descInput = document.getElementById('modal-desc');\n var errorEl = document.getElementById('modal-error');\n errorEl.style.display = 'none';\n\n if (editingKey) {\n title.textContent = 'Edit Variable';\n keyInput.value = editingKey;\n keyInput.readOnly = true;\n keyInput.style.opacity = '0.5';\n var existing = envData.find(function(v) { return v.key === editingKey; });\n valInput.value = existing ? existing.value : '';\n descInput.value = existing ? existing.description : '';\n } else {\n title.textContent = 'Add Variable';\n keyInput.value = '';\n keyInput.readOnly = false;\n keyInput.style.opacity = '1';\n valInput.value = '';\n descInput.value = '';\n }\n\n modal.style.display = 'flex';\n (editingKey ? valInput : keyInput).focus();\n };\n\n window.hideEnvModal = function() {\n document.getElementById('env-modal').style.display = 'none';\n editingKey = null;\n };\n\n window.editEnvVar = function(key) {\n showEnvModal(key);\n };\n\n window.saveEnvVar = function() {\n var key = document.getElementById('modal-key').value.trim();\n var value = document.getElementById('modal-value').value;\n var desc = document.getElementById('modal-desc').value.trim();\n var errorEl = document.getElementById('modal-error');\n\n if (!key) {\n errorEl.textContent = 'Key is required';\n errorEl.style.display = 'block';\n return;\n }\n if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {\n errorEl.textContent = 'Invalid key format. Use UPPER_SNAKE_CASE.';\n errorEl.style.display = 'block';\n return;\n }\n\n fetch(API + '/__dev/env', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'set', key: key, value: value, description: desc || undefined })\n }).then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.error) {\n errorEl.textContent = data.error;\n errorEl.style.display = 'block';\n return;\n }\n hideEnvModal();\n return fetch(API + '/__dev/env').then(function(r) { return r.json(); });\n })\n .then(function(data) { if (data) renderEnvVars(data); })\n .catch(function(err) {\n errorEl.textContent = err.message;\n errorEl.style.display = 'block';\n });\n };\n\n window.deleteEnvVar = function(key) {\n if (!confirm('Delete ' + key + '?')) return;\n fetch(API + '/__dev/env', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'delete', key: key })\n }).then(function() {\n return fetch(API + '/__dev/env').then(function(r) { return r.json(); });\n }).then(function(data) { renderEnvVars(data); })\n .catch(function() {});\n };\n\n window.toggleReveal = function(btn) {\n var span = btn.previousElementSibling;\n var masked = span.getAttribute('data-masked');\n var full = span.getAttribute('data-full');\n if (span.textContent === masked) {\n span.textContent = full;\n btn.textContent = 'hide';\n } else {\n span.textContent = masked;\n btn.textContent = 'reveal';\n }\n };\n\n // ─── Helpers ─────────────────────────────────────────────────────\n function escHtml(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;'); }\n function escAttr(s) { return String(s).replace(/&/g,'&amp;').replace(/'/g,\"\\\\\\\\'\").replace(/\"/g,'&quot;').replace(/</g,'&lt;'); }\n})();\n</script>`;\n}\n","/**\n * Clawfire Firebase Setup Orchestration\n *\n * Manages the full Firebase setup flow:\n * 1. Check/install Firebase CLI\n * 2. Open a real terminal for `firebase login` (TTY required)\n * 3. List available projects\n * 4. Select project → auto-fill config\n *\n * All operations are dev-only (localhost).\n */\nimport { execFile, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport { tmpdir, platform } from \"node:os\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface SetupStatus {\n /** Firebase CLI */\n cli: {\n installed: boolean;\n version: string;\n };\n /** Auth state */\n auth: {\n authenticated: boolean;\n user: string;\n };\n /** Active project */\n project: {\n id: string;\n hasFirebaserc: boolean;\n };\n /** Overall readiness */\n ready: boolean;\n /** Current step the user should do next */\n nextStep: \"install-cli\" | \"login\" | \"select-project\" | \"done\";\n}\n\nexport interface FirebaseProject {\n projectId: string;\n displayName: string;\n projectNumber: string;\n state: string;\n}\n\n// ─── Firebase Setup Manager ──────────────────────────────────────────\n\nexport class FirebaseSetup {\n private projectDir: string;\n\n constructor(projectDir: string) {\n this.projectDir = projectDir;\n }\n\n // ─── Status Check ──────────────────────────────────────────────────\n\n async getStatus(): Promise<SetupStatus> {\n const status: SetupStatus = {\n cli: { installed: false, version: \"\" },\n auth: { authenticated: false, user: \"\" },\n project: { id: \"\", hasFirebaserc: false },\n ready: false,\n nextStep: \"install-cli\",\n };\n\n // 1. Check CLI\n try {\n const version = await this.execTimeout(\"firebase\", [\"--version\"], 5000);\n status.cli.installed = true;\n status.cli.version = version.trim();\n } catch {\n status.nextStep = \"install-cli\";\n return status;\n }\n\n // 2. Check auth — use login:list and also verify token works\n try {\n const loginOutput = await this.execTimeout(\n \"firebase\",\n [\"login:list\", \"--json\"],\n 5000,\n );\n const loginData = JSON.parse(loginOutput);\n if (loginData?.result && Array.isArray(loginData.result) && loginData.result.length > 0) {\n status.auth.authenticated = true;\n status.auth.user = loginData.result[0]?.user?.email || loginData.result[0]?.email || \"\";\n }\n } catch {\n // Not authenticated\n }\n\n if (!status.auth.authenticated) {\n status.nextStep = \"login\";\n return status;\n }\n\n // 3. Check active project\n const firebasercPath = resolve(this.projectDir, \".firebaserc\");\n if (existsSync(firebasercPath)) {\n status.project.hasFirebaserc = true;\n try {\n const rc = JSON.parse(readFileSync(firebasercPath, \"utf-8\"));\n status.project.id = rc?.projects?.default || \"\";\n } catch {\n // invalid JSON\n }\n }\n\n if (!status.project.id) {\n status.nextStep = \"select-project\";\n return status;\n }\n\n status.ready = true;\n status.nextStep = \"done\";\n return status;\n }\n\n // ─── CLI Install ───────────────────────────────────────────────────\n\n async installCli(): Promise<{ success: boolean; message: string }> {\n try {\n // Check if already installed\n try {\n await this.execTimeout(\"firebase\", [\"--version\"], 5000);\n return { success: true, message: \"Firebase CLI is already installed.\" };\n } catch {\n // Not installed, proceed\n }\n\n // Install via npm\n await this.execTimeout(\n \"npm\",\n [\"install\", \"-g\", \"firebase-tools\"],\n 120000, // 2 min timeout for install\n );\n\n // Verify installation\n try {\n const version = await this.execTimeout(\"firebase\", [\"--version\"], 5000);\n return { success: true, message: `Firebase CLI v${version.trim()} installed successfully.` };\n } catch {\n return { success: false, message: \"Installation completed but CLI not found in PATH. Try restarting your terminal.\" };\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n return { success: false, message: `Failed to install Firebase CLI: ${msg}` };\n }\n }\n\n // ─── Login via Terminal ───────────────────────────────────────────\n\n /**\n * Opens a new terminal window and runs `firebase login`.\n *\n * Firebase CLI requires a real TTY for interactive login.\n * Instead of trying to fake a TTY, we open an actual terminal.\n * The dashboard polls getStatus() to detect when login completes.\n *\n * macOS: Creates a .command file and opens it (Terminal.app)\n * Linux: Tries common terminal emulators\n * Windows: Opens a new cmd window\n */\n openLoginTerminal(reauth: boolean = false): { success: boolean; message: string } {\n const cmd = reauth ? \"firebase login --reauth\" : \"firebase login\";\n const os = platform();\n\n try {\n if (os === \"darwin\") {\n // macOS: .command files auto-open in Terminal.app — no special permissions needed\n const scriptPath = join(tmpdir(), \"clawfire-firebase-login.command\");\n writeFileSync(scriptPath, [\n \"#!/bin/bash\",\n `cd \"${this.projectDir}\"`,\n cmd,\n 'echo \"\"',\n 'echo \"Login complete! You can close this window.\"',\n 'read -p \"Press Enter to close...\"',\n ].join(\"\\n\"), { mode: 0o755 });\n\n const child = spawn(\"open\", [scriptPath], { detached: true, stdio: \"ignore\" });\n child.unref();\n } else if (os === \"win32\") {\n // Windows: open a new cmd window\n const child = spawn(\"cmd\", [\"/c\", \"start\", \"cmd\", \"/k\", cmd], {\n cwd: this.projectDir,\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n } else {\n // Linux: create script, try common terminal emulators\n const scriptPath = join(tmpdir(), \"clawfire-firebase-login.sh\");\n writeFileSync(scriptPath, [\n \"#!/bin/bash\",\n `cd \"${this.projectDir}\"`,\n cmd,\n 'echo \"\"',\n 'echo \"Login complete! You can close this window.\"',\n 'read -p \"Press Enter to close...\"',\n ].join(\"\\n\"), { mode: 0o755 });\n\n const terminals = [\n { cmd: \"x-terminal-emulator\", args: [\"-e\", scriptPath] },\n { cmd: \"gnome-terminal\", args: [\"--\", \"bash\", scriptPath] },\n { cmd: \"konsole\", args: [\"-e\", \"bash\", scriptPath] },\n { cmd: \"xfce4-terminal\", args: [\"-e\", scriptPath] },\n { cmd: \"xterm\", args: [\"-e\", scriptPath] },\n ];\n\n let opened = false;\n for (const t of terminals) {\n try {\n const child = spawn(t.cmd, t.args, { detached: true, stdio: \"ignore\" });\n child.unref();\n // If spawn didn't throw, it at least started\n child.on(\"error\", () => {});\n opened = true;\n break;\n } catch {\n continue;\n }\n }\n\n if (!opened) {\n return {\n success: false,\n message: `Could not find a terminal emulator. Please run \"${cmd}\" manually in your terminal.`,\n };\n }\n }\n\n return {\n success: true,\n message: \"Terminal window opened. Please complete the login in the new terminal.\",\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n return {\n success: false,\n message: `Failed to open terminal: ${msg}. Please run \"${cmd}\" manually in your terminal.`,\n };\n }\n }\n\n // ─── Project Listing ───────────────────────────────────────────────\n\n async listProjects(): Promise<{ projects: FirebaseProject[]; error?: string }> {\n try {\n const output = await this.execTimeout(\n \"firebase\",\n [\"projects:list\", \"--json\"],\n 30000, // 30s — can be slow on first call\n );\n\n const data = JSON.parse(output);\n\n if (data?.result && Array.isArray(data.result)) {\n const projects: FirebaseProject[] = data.result.map((p: Record<string, unknown>) => ({\n projectId: p.projectId || \"\",\n displayName: p.displayName || p.projectId || \"\",\n projectNumber: p.projectNumber || \"\",\n state: p.lifecycleState || p.state || \"ACTIVE\",\n }));\n return { projects };\n }\n\n return { projects: [], error: \"Unexpected response format\" };\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n // Common cases\n if (msg.includes(\"authenticate\") || msg.includes(\"login\") || msg.includes(\"credential\")) {\n return { projects: [], error: \"Session expired. Please re-authenticate first.\" };\n }\n if (msg.includes(\"Timeout\")) {\n return { projects: [], error: \"Request timed out. Please try again.\" };\n }\n return { projects: [], error: msg };\n }\n }\n\n // ─── Project Selection ─────────────────────────────────────────────\n\n async selectProject(projectId: string): Promise<{ success: boolean; message: string }> {\n if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {\n return { success: false, message: \"Invalid project ID format.\" };\n }\n\n try {\n // Set project via firebase use\n await this.execTimeout(\n \"firebase\",\n [\"use\", projectId],\n 10000,\n );\n\n // Also update .firebaserc directly for reliability\n const firebasercPath = resolve(this.projectDir, \".firebaserc\");\n let rc: Record<string, unknown> = {};\n if (existsSync(firebasercPath)) {\n try {\n rc = JSON.parse(readFileSync(firebasercPath, \"utf-8\"));\n } catch {\n rc = {};\n }\n }\n if (!rc.projects || typeof rc.projects !== \"object\") {\n rc.projects = {};\n }\n (rc.projects as Record<string, string>).default = projectId;\n writeFileSync(firebasercPath, JSON.stringify(rc, null, 2) + \"\\n\", \"utf-8\");\n\n return { success: true, message: `Active project set to \"${projectId}\".` };\n } catch {\n // If firebase use fails, still try writing .firebaserc directly\n try {\n const firebasercPath = resolve(this.projectDir, \".firebaserc\");\n const rc = {\n projects: { default: projectId },\n };\n writeFileSync(firebasercPath, JSON.stringify(rc, null, 2) + \"\\n\", \"utf-8\");\n return { success: true, message: `Active project set to \"${projectId}\" (via .firebaserc).` };\n } catch (writeErr) {\n const msg = writeErr instanceof Error ? writeErr.message : \"Unknown error\";\n return { success: false, message: `Failed to set project: ${msg}` };\n }\n }\n }\n\n // ─── Helpers ───────────────────────────────────────────────────────\n\n private execTimeout(command: string, args: string[], timeoutMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = execFile(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {\n if (err) {\n // Include stderr in error message for better debugging\n const detail = stderr?.trim() || stdout?.trim() || \"\";\n const enriched = new Error(`${err.message}${detail ? \"\\n\" + detail : \"\"}`);\n reject(enriched);\n } else {\n resolve(stdout);\n }\n });\n const timer = setTimeout(() => {\n proc.kill(\"SIGTERM\");\n reject(new Error(\"Timeout\"));\n }, timeoutMs + 500);\n proc.on(\"exit\", () => clearTimeout(timer));\n });\n }\n\n /** Cleanup resources */\n destroy(): void {\n // No persistent processes to clean up\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBA8HO,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAiOuB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["import_node_path","import_node_fs","import_fs","import_path","import_node_fs","import_node_path","import_node_fs","import_node_path","resolve","import_node_child_process","import_node_fs","import_node_path","resolve","http","resolve","filePath","isPlaceholder"]}
1
+ {"version":3,"sources":["../src/dev.ts","../src/dev/dev-server.ts","../src/core/schema.ts","../src/core/errors.ts","../src/core/logger.ts","../src/firebase/auth.ts","../src/routing/router.ts","../src/routing/discover.ts","../src/playground/html.ts","../src/dev/watcher.ts","../src/dev/page-compiler.ts","../src/dev/env-manager.ts","../src/dev/firebase-status.ts","../src/dev/dashboard-html.ts","../src/dev/firebase-setup.ts"],"sourcesContent":["/**\n * clawfire/dev — Development Server with Hot Reload\n *\n * @example\n * ```ts\n * import { startDevServer } from \"clawfire/dev\";\n *\n * startDevServer({ port: 3456 });\n * ```\n */\nexport { DevServer, startDevServer, type DevServerOptions } from \"./dev/index.js\";\nexport { FileWatcher, type WatchEvent, type WatchEventType } from \"./dev/index.js\";\n","/**\n * Clawfire Development Server\n *\n * Two-port architecture:\n * - Frontend Server (port 3000): pages, public/ serving, HMR, API proxy, SPA fallback\n * - API Server (port 3456): API routes, Playground, SSE\n */\nimport http from \"node:http\";\nimport { resolve, join, relative, extname } from \"node:path\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport type { APIContract } from \"../core/schema.js\";\nimport { ClawfireRouter, createRouter, type RouterOptions } from \"../routing/router.js\";\nimport { discoverRoutes } from \"../routing/discover.js\";\nimport { generatePlaygroundHtml } from \"../playground/html.js\";\nimport { FileWatcher, type WatchEvent } from \"./watcher.js\";\nimport { PageCompiler } from \"./page-compiler.js\";\nimport { logger } from \"../core/logger.js\";\nimport { EnvManager } from \"./env-manager.js\";\nimport { checkFirebaseStatus, clearFirebaseStatusCache, fetchFirebaseSdkConfig } from \"./firebase-status.js\";\nimport { generateDashboardHtml } from \"./dashboard-html.js\";\nimport { FirebaseSetup } from \"./firebase-setup.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface DevServerOptions {\n /** Project root path */\n projectDir?: string;\n /** Frontend server port (default: 3000) */\n port?: number;\n /** API server port (default: 3456) */\n apiPort?: number;\n /** Router options */\n routerOptions?: Partial<RouterOptions>;\n /** Hot reload enabled (default: true) */\n hotReload?: boolean;\n /** Change detection debounce ms (default: 150) */\n debounceMs?: number;\n /** Callback to manually register routes (instead of file-based) */\n onSetupRoutes?: (router: ClawfireRouter) => void | Promise<void>;\n}\n\ninterface SSEClient {\n id: number;\n res: http.ServerResponse;\n}\n\n// ─── MIME Types ──────────────────────────────────────────────────────\n\nconst MIME_TYPES: Record<string, string> = {\n html: \"text/html; charset=utf-8\",\n css: \"text/css; charset=utf-8\",\n js: \"application/javascript; charset=utf-8\",\n mjs: \"application/javascript; charset=utf-8\",\n json: \"application/json; charset=utf-8\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n svg: \"image/svg+xml\",\n ico: \"image/x-icon\",\n webp: \"image/webp\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n ttf: \"font/ttf\",\n eot: \"application/vnd.ms-fontobject\",\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mp3: \"audio/mpeg\",\n wav: \"audio/wav\",\n pdf: \"application/pdf\",\n txt: \"text/plain; charset=utf-8\",\n xml: \"application/xml; charset=utf-8\",\n};\n\n/** File extensions treated as non-HTML static assets */\nconst STATIC_EXTENSIONS = new Set([\n \".css\", \".js\", \".mjs\", \".json\", \".png\", \".jpg\", \".jpeg\", \".gif\", \".svg\",\n \".ico\", \".webp\", \".woff\", \".woff2\", \".ttf\", \".eot\", \".mp4\", \".webm\",\n \".mp3\", \".wav\", \".pdf\", \".txt\", \".xml\", \".map\",\n]);\n\n// ─── HMR Script ─────────────────────────────────────────────────────\n\nfunction generateHmrScript(port: number): string {\n return `\n<script data-clawfire-hmr>\n(function() {\n var dot, status;\n function createBanner() {\n var banner = document.createElement('div');\n banner.id = 'clawfire-dev-banner';\n banner.style.cssText = 'position:fixed;bottom:0;left:0;right:0;padding:6px 16px;background:#1a1a2e;color:#f97316;font-size:12px;font-family:monospace;z-index:99999;display:flex;align-items:center;gap:8px;border-top:1px solid #2a2a2a;';\n banner.innerHTML = '<span style=\"width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;\" id=\"clawfire-dot\"></span><span>Clawfire Dev</span><span style=\"color:#666;margin-left:auto;\" id=\"clawfire-status\">Connected</span>';\n document.body.appendChild(banner);\n dot = document.getElementById('clawfire-dot');\n status = document.getElementById('clawfire-status');\n }\n\n function refreshCss() {\n var links = document.querySelectorAll('link[rel=\"stylesheet\"]');\n for (var i = 0; i < links.length; i++) {\n var href = links[i].getAttribute('href');\n if (href) {\n var url = new URL(href, location.href);\n url.searchParams.set('_hmr', Date.now().toString());\n links[i].setAttribute('href', url.toString());\n }\n }\n var styles = document.querySelectorAll('style[data-href]');\n for (var j = 0; j < styles.length; j++) {\n var dataHref = styles[j].getAttribute('data-href');\n if (dataHref) {\n fetch(dataHref + '?_hmr=' + Date.now())\n .then(function(r) { return r.text(); })\n .then(function(css) { styles[j].textContent = css; })\n .catch(function() {});\n }\n }\n if (status) status.textContent = 'CSS updated';\n setTimeout(function() { if (status) status.textContent = 'Connected'; }, 1500);\n }\n\n var reconnectTimer;\n function connect() {\n var es = new EventSource('http://localhost:${port}/__dev/events');\n es.onopen = function() {\n if (!dot) createBanner();\n dot.style.background = '#22c55e';\n status.textContent = 'Connected';\n if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }\n };\n es.onmessage = function(e) {\n try {\n var data = JSON.parse(e.data);\n if (data.type === 'connected') return;\n if (data.type === 'css-change') {\n refreshCss();\n return;\n }\n if (data.type === 'error') {\n if (dot) dot.style.background = '#ef4444';\n if (status) status.textContent = 'Error: ' + (data.message || 'reload failed');\n return;\n }\n // frontend-change, route-change, page-change, component-change → full reload\n if (dot) dot.style.background = '#eab308';\n if (status) status.textContent = 'Reloading...';\n setTimeout(function() { location.reload(); }, 300);\n } catch(err) {}\n };\n es.onerror = function() {\n es.close();\n if (dot) dot.style.background = '#ef4444';\n if (status) status.textContent = 'Disconnected';\n reconnectTimer = setTimeout(connect, 2000);\n };\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function() { connect(); });\n } else {\n connect();\n }\n})();\n</script>`;\n}\n\n// ─── Client-Side Router Script ──────────────────────────────────────\n\nfunction generateRouterScript(): string {\n return `\n<script data-clawfire-router>\n(function() {\n function updateActiveNav() {\n var path = location.pathname;\n var links = document.querySelectorAll('#nav-links a[href]');\n for (var i = 0; i < links.length; i++) {\n var href = links[i].getAttribute('href');\n if (!href || href.startsWith('http')) continue;\n var isActive = (path === '/' && href === '/') || (href !== '/' && path.startsWith(href));\n links[i].setAttribute('data-active', isActive ? 'true' : 'false');\n }\n }\n\n function navigate(url) {\n var target = new URL(url, location.href);\n // Only handle same-origin, non-hash navigation\n if (target.origin !== location.origin) return false;\n if (target.pathname === location.pathname && target.hash) return false;\n\n fetch(target.pathname, {\n headers: { 'X-Clawfire-Partial': 'true' }\n })\n .then(function(res) {\n if (!res.ok) throw new Error('Page not found');\n return res.json();\n })\n .then(function(data) {\n // Update page content\n var container = document.getElementById('clawfire-page');\n if (container) {\n container.innerHTML = data.html;\n // Execute scripts in new content\n var scripts = container.querySelectorAll('script');\n for (var i = 0; i < scripts.length; i++) {\n var newScript = document.createElement('script');\n if (scripts[i].src) {\n newScript.src = scripts[i].src;\n } else {\n newScript.textContent = scripts[i].textContent;\n }\n scripts[i].parentNode.replaceChild(newScript, scripts[i]);\n }\n }\n // Update title\n if (data.meta && data.meta.title) {\n document.title = data.meta.title;\n }\n // Update URL\n history.pushState(null, '', target.pathname);\n // Update nav active state\n updateActiveNav();\n // Dispatch event for page scripts\n document.dispatchEvent(new CustomEvent('clawfire:navigate', { detail: { path: data.path } }));\n // Scroll to top\n window.scrollTo(0, 0);\n })\n .catch(function(err) {\n // Fallback: full page navigation\n location.href = url;\n });\n\n return true;\n }\n\n // Intercept link clicks\n document.addEventListener('click', function(e) {\n var anchor = e.target.closest ? e.target.closest('a[href]') : null;\n if (!anchor) return;\n var href = anchor.getAttribute('href');\n if (!href) return;\n // Skip external links, new-tab links, modified clicks\n if (href.startsWith('http') || href.startsWith('//')) return;\n if (anchor.target === '_blank') return;\n if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;\n if (anchor.hasAttribute('download')) return;\n\n e.preventDefault();\n navigate(href);\n });\n\n // Handle back/forward\n window.addEventListener('popstate', function() {\n navigate(location.pathname);\n });\n\n // Set initial active nav state\n updateActiveNav();\n})();\n</script>`;\n}\n\n// ─── Dev Server ──────────────────────────────────────────────────────\n\nexport class DevServer {\n private frontendServer: http.Server | null = null;\n private apiServer: http.Server | null = null;\n private router: ClawfireRouter;\n private watcher: FileWatcher | null = null;\n private frontendSseClients: SSEClient[] = [];\n private apiSseClients: SSEClient[] = [];\n private sseIdCounter = 0;\n private options: Required<DevServerOptions>;\n private routesDir: string;\n private schemasDir: string;\n private publicDir: string;\n private pagesDir: string;\n private componentsDir: string;\n private playgroundHtml = \"\";\n private importCounter = 0;\n private isReloading = false;\n private pageCompiler: PageCompiler;\n private envManager: EnvManager;\n private firebaseSetup: FirebaseSetup;\n\n constructor(options: DevServerOptions = {}) {\n this.options = {\n projectDir: options.projectDir || process.cwd(),\n port: options.port || 3000,\n apiPort: options.apiPort || 3456,\n routerOptions: options.routerOptions || {},\n hotReload: options.hotReload !== false,\n debounceMs: options.debounceMs || 150,\n onSetupRoutes: options.onSetupRoutes || (() => {}),\n };\n\n this.routesDir = resolve(this.options.projectDir, \"app/routes\");\n this.schemasDir = resolve(this.options.projectDir, \"app/schemas\");\n this.publicDir = resolve(this.options.projectDir, \"public\");\n this.pagesDir = resolve(this.options.projectDir, \"app/pages\");\n this.componentsDir = resolve(this.options.projectDir, \"app/components\");\n this.pageCompiler = new PageCompiler(this.options.projectDir);\n this.envManager = new EnvManager(this.options.projectDir);\n this.firebaseSetup = new FirebaseSetup(this.options.projectDir);\n\n this.router = createRouter({\n cors: [\"*\"],\n rateLimit: 0,\n ...this.options.routerOptions,\n });\n }\n\n // ─── Lifecycle ──────────────────────────────────────────────────────\n\n async start(): Promise<void> {\n await this.loadRoutes();\n this.regeneratePlayground();\n\n // Create API server (port 3456)\n this.apiServer = http.createServer((req, res) => this.handleApiRequest(req, res));\n\n // Create Frontend server (port 3000)\n this.frontendServer = http.createServer((req, res) => this.handleFrontendRequest(req, res));\n\n if (this.options.hotReload) {\n this.startWatcher();\n }\n\n // Start API server first\n await new Promise<void>((resolve, reject) => {\n this.apiServer!.listen(this.options.apiPort, () => resolve());\n this.apiServer!.on(\"error\", reject);\n });\n\n // Then start frontend server\n await new Promise<void>((resolve, reject) => {\n this.frontendServer!.listen(this.options.port, () => resolve());\n this.frontendServer!.on(\"error\", reject);\n });\n\n this.printStartupBanner();\n }\n\n async stop(): Promise<void> {\n this.watcher?.close();\n this.firebaseSetup.destroy();\n\n for (const client of [...this.frontendSseClients, ...this.apiSseClients]) {\n client.res.end();\n }\n this.frontendSseClients = [];\n this.apiSseClients = [];\n this.router.destroy();\n\n if (this.apiServer) {\n await new Promise<void>((resolve) => {\n this.apiServer!.close(() => resolve());\n });\n }\n if (this.frontendServer) {\n await new Promise<void>((resolve) => {\n this.frontendServer!.close(() => resolve());\n });\n }\n }\n\n // ─── Route Loading ─────────────────────────────────────────────────\n\n private async loadRoutes(): Promise<void> {\n this.router.destroy();\n this.router = createRouter({\n cors: [\"*\"],\n rateLimit: 0,\n ...this.options.routerOptions,\n });\n\n if (this.options.onSetupRoutes) {\n await this.options.onSetupRoutes(this.router);\n if (this.router.getRoutes().length > 0) return;\n }\n\n if (!existsSync(this.routesDir)) return;\n\n const discovered = discoverRoutes(this.routesDir);\n for (const route of discovered) {\n try {\n const fullPath = resolve(this.routesDir, route.filePath);\n const fileUrl = pathToFileURL(fullPath).href;\n const mod = await import(`${fileUrl}?v=${++this.importCounter}`);\n const contract = mod.default as APIContract;\n if (contract && typeof contract.handler === \"function\" && contract.input && contract.output && contract.meta) {\n this.router.register(route.apiPath, contract);\n }\n } catch (err) {\n logger.warn(`Failed to load route: ${route.filePath}`, err);\n }\n }\n }\n\n private async reloadRoutes(event: WatchEvent): Promise<void> {\n if (this.isReloading) return;\n this.isReloading = true;\n\n const relPath = relative(this.options.projectDir, event.filePath);\n const timestamp = new Date().toLocaleTimeString();\n\n console.log(`\\n \\x1b[33m[${timestamp}]\\x1b[0m \\x1b[36m${relPath}\\x1b[0m changed`);\n console.log(\" Reloading routes...\");\n\n try {\n await this.loadRoutes();\n this.regeneratePlayground();\n clearFirebaseStatusCache();\n\n const routeCount = this.router.getRoutes().length;\n console.log(` \\x1b[32m✓\\x1b[0m ${routeCount} routes loaded`);\n\n // Notify API SSE clients (playground)\n this.broadcastSSE(this.apiSseClients, {\n type: event.type,\n file: relPath,\n timestamp: event.timestamp,\n routes: routeCount,\n });\n\n // Also notify frontend SSE clients for route changes\n this.broadcastSSE(this.frontendSseClients, {\n type: event.type,\n file: relPath,\n timestamp: event.timestamp,\n routes: routeCount,\n });\n } catch (err) {\n console.log(` \\x1b[31m✗\\x1b[0m Reload failed:`, err);\n const errorData = {\n type: \"error\",\n file: relPath,\n message: err instanceof Error ? err.message : \"Unknown error\",\n };\n this.broadcastSSE(this.apiSseClients, errorData);\n this.broadcastSSE(this.frontendSseClients, errorData);\n } finally {\n this.isReloading = false;\n }\n }\n\n // ─── Frontend Change Handler ───────────────────────────────────────\n\n private handleFrontendChange(event: WatchEvent): void {\n const relPath = relative(this.options.projectDir, event.filePath);\n const timestamp = new Date().toLocaleTimeString();\n\n if (event.type === \"css-change\") {\n console.log(`\\n \\x1b[33m[${timestamp}]\\x1b[0m \\x1b[35m${relPath}\\x1b[0m CSS updated`);\n this.broadcastSSE(this.frontendSseClients, {\n type: \"css-change\",\n file: relPath,\n timestamp: event.timestamp,\n });\n } else {\n console.log(`\\n \\x1b[33m[${timestamp}]\\x1b[0m \\x1b[35m${relPath}\\x1b[0m changed → reload`);\n this.broadcastSSE(this.frontendSseClients, {\n type: event.type,\n file: relPath,\n timestamp: event.timestamp,\n });\n }\n }\n\n // ─── File Watcher ──────────────────────────────────────────────────\n\n private startWatcher(): void {\n this.watcher = new FileWatcher(this.options.debounceMs);\n\n // routes\n if (existsSync(this.routesDir)) {\n this.watcher.watchDir(this.routesDir, \"route-change\");\n }\n\n // schemas\n if (existsSync(this.schemasDir)) {\n this.watcher.watchDir(this.schemasDir, \"schema-change\");\n }\n\n // config file\n const configFile = resolve(this.options.projectDir, \"clawfire.config.ts\");\n if (existsSync(configFile)) {\n this.watcher.watchFile(configFile, \"config-change\");\n }\n\n // public/ directory (frontend hot reload)\n if (existsSync(this.publicDir)) {\n this.watcher.watchDirFrontend(this.publicDir);\n }\n\n // pages/ directory\n if (existsSync(this.pagesDir)) {\n this.watcher.watchDir(this.pagesDir, \"page-change\");\n }\n\n // components/ directory\n if (existsSync(this.componentsDir)) {\n this.watcher.watchDir(this.componentsDir, \"component-change\");\n }\n\n // API/schema/config changes → route reload\n this.watcher.on(\"route-change\", (event: WatchEvent) => this.reloadRoutes(event));\n this.watcher.on(\"schema-change\", (event: WatchEvent) => this.reloadRoutes(event));\n this.watcher.on(\"config-change\", (event: WatchEvent) => this.reloadRoutes(event));\n\n // Frontend changes → browser notification (CSS hot / full reload)\n this.watcher.on(\"frontend-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n this.watcher.on(\"css-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n\n // Page/component changes → full reload broadcast\n this.watcher.on(\"page-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n this.watcher.on(\"component-change\", (event: WatchEvent) => this.handleFrontendChange(event));\n }\n\n // ─── SSE (Server-Sent Events) ──────────────────────────────────────\n\n private handleSSE(req: http.IncomingMessage, res: http.ServerResponse, clients: SSEClient[]): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n\n const clientId = ++this.sseIdCounter;\n const client: SSEClient = { id: clientId, res };\n clients.push(client);\n\n res.write(`data: ${JSON.stringify({ type: \"connected\", id: clientId })}\\n\\n`);\n\n req.on(\"close\", () => {\n const idx = clients.indexOf(client);\n if (idx !== -1) clients.splice(idx, 1);\n });\n }\n\n private broadcastSSE(clients: SSEClient[], data: Record<string, unknown>): void {\n const message = `data: ${JSON.stringify(data)}\\n\\n`;\n for (const client of clients) {\n try {\n client.res.write(message);\n } catch {\n // disconnected client\n }\n }\n }\n\n // ─── Playground ────────────────────────────────────────────────────\n\n private regeneratePlayground(): void {\n const baseHtml = generatePlaygroundHtml({\n title: \"Clawfire Dev Playground\",\n apiBaseUrl: `http://localhost:${this.options.apiPort}`,\n });\n\n const dashboardHtml = generateDashboardHtml({\n apiPort: this.options.apiPort,\n });\n\n // Tab bar + tab switching script\n const tabBar = `\n<div id=\"clawfire-tab-bar\" style=\"position:sticky;top:0;z-index:9999;background:#0a0a0a;border-bottom:1px solid #2a2a2a;padding:0 16px;display:flex;gap:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;\">\n <button onclick=\"switchTab('apis')\" id=\"tab-btn-apis\" style=\"padding:10px 20px;background:transparent;border:none;border-bottom:2px solid #f97316;color:#f97316;font-size:14px;font-weight:600;cursor:pointer;\">APIs</button>\n <button onclick=\"switchTab('dashboard')\" id=\"tab-btn-dashboard\" style=\"padding:10px 20px;background:transparent;border:none;border-bottom:2px solid transparent;color:#a3a3a3;font-size:14px;font-weight:600;cursor:pointer;\">Dashboard</button>\n</div>`;\n\n const tabScript = `\n<script>\nfunction switchTab(tab) {\n var apis = document.getElementById('tab-apis');\n var dashboard = document.getElementById('tab-dashboard');\n var btnApis = document.getElementById('tab-btn-apis');\n var btnDash = document.getElementById('tab-btn-dashboard');\n\n if (tab === 'apis') {\n apis.style.display = 'block';\n dashboard.style.display = 'none';\n btnApis.style.borderBottomColor = '#f97316';\n btnApis.style.color = '#f97316';\n btnDash.style.borderBottomColor = 'transparent';\n btnDash.style.color = '#a3a3a3';\n } else {\n apis.style.display = 'none';\n dashboard.style.display = 'block';\n btnApis.style.borderBottomColor = 'transparent';\n btnApis.style.color = '#a3a3a3';\n btnDash.style.borderBottomColor = '#f97316';\n btnDash.style.color = '#f97316';\n // Lazy-load dashboard data on first click\n if (window._loadDashboard) window._loadDashboard();\n }\n}\n</script>`;\n\n const liveReloadScript = `\n<script>\n(function() {\n var banner = document.createElement('div');\n banner.id = 'dev-banner';\n banner.style.cssText = 'position:fixed;bottom:0;left:0;right:0;padding:6px 16px;background:#1a1a2e;color:#f97316;font-size:12px;font-family:monospace;z-index:9999;display:flex;align-items:center;gap:8px;border-top:1px solid #2a2a2a;';\n banner.innerHTML = '<span style=\"width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;\" id=\"dev-dot\"></span><span>Clawfire Dev Server</span><span style=\"color:#666;margin-left:auto;\" id=\"dev-status\">Connected</span>';\n document.body.appendChild(banner);\n\n var reconnectTimer;\n function connect() {\n var es = new EventSource('http://localhost:${this.options.apiPort}/__dev/events');\n es.onopen = function() {\n document.getElementById('dev-dot').style.background = '#22c55e';\n document.getElementById('dev-status').textContent = 'Connected';\n if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }\n };\n es.onmessage = function(e) {\n try {\n var data = JSON.parse(e.data);\n if (data.type === 'connected') return;\n if (data.type === 'error') {\n document.getElementById('dev-dot').style.background = '#ef4444';\n document.getElementById('dev-status').textContent = 'Error: ' + (data.message || 'reload failed');\n return;\n }\n document.getElementById('dev-dot').style.background = '#eab308';\n document.getElementById('dev-status').textContent = 'Reloading...';\n setTimeout(function() { window.location.reload(); }, 300);\n } catch(err) {}\n };\n es.onerror = function() {\n es.close();\n document.getElementById('dev-dot').style.background = '#ef4444';\n document.getElementById('dev-status').textContent = 'Disconnected — reconnecting...';\n reconnectTimer = setTimeout(connect, 2000);\n };\n }\n connect();\n})();\n</script>`;\n\n // Inject: tab bar after <body>, wrap playground in tab-apis, add dashboard tab\n let html = baseHtml;\n // Insert tab bar right after <body...>\n html = html.replace(/<body[^>]*>/, (match) => `${match}\\n${tabBar}`);\n // Wrap existing content: find the first element after body and wrap everything until </body>\n // Strategy: wrap all body content in tab-apis div, then append dashboard\n const bodyOpenMatch = html.match(/<body[^>]*>/);\n if (bodyOpenMatch) {\n const bodyOpenEnd = html.indexOf(bodyOpenMatch[0]) + bodyOpenMatch[0].length;\n const tabBarEnd = html.indexOf(\"</div>\", bodyOpenEnd) + \"</div>\".length; // end of tab bar\n const bodyClose = html.lastIndexOf(\"</body>\");\n\n const beforeTabBar = html.slice(0, tabBarEnd);\n const playgroundContent = html.slice(tabBarEnd, bodyClose);\n const afterBody = html.slice(bodyClose);\n\n html = beforeTabBar +\n `\\n<div id=\"tab-apis\">${playgroundContent}</div>` +\n `\\n<div id=\"tab-dashboard\" style=\"display:none;\">${dashboardHtml}</div>` +\n `\\n${tabScript}` +\n `\\n${liveReloadScript}\\n` +\n afterBody;\n } else {\n // Fallback: just append scripts\n html = html.replace(\"</body>\", `${liveReloadScript}\\n</body>`);\n }\n\n this.playgroundHtml = html;\n }\n\n // ─── Script Injection ──────────────────────────────────────────────\n\n /**\n * Inject HMR and (optionally) client-side router scripts before </body>.\n */\n private injectScripts(html: string, includeRouter: boolean): string {\n const hmr = generateHmrScript(this.options.port);\n const router = includeRouter ? generateRouterScript() : \"\";\n const scripts = router + hmr;\n\n if (html.includes(\"</body>\")) {\n return html.replace(\"</body>\", scripts + \"\\n</body>\");\n }\n return html + scripts;\n }\n\n // ─── Static File Serving ──────────────────────────────────────────\n\n private serveStaticFile(filePath: string, res: http.ServerResponse): boolean {\n if (!existsSync(filePath)) return false;\n\n try {\n const content = readFileSync(filePath);\n const ext = extname(filePath).slice(1).toLowerCase();\n const mime = MIME_TYPES[ext] || \"application/octet-stream\";\n\n // HTML files get HMR script injected\n if (ext === \"html\") {\n const html = content.toString(\"utf-8\");\n const injected = this.injectScripts(html, false);\n res.writeHead(200, { \"Content-Type\": mime });\n res.end(injected);\n return true;\n }\n\n res.writeHead(200, { \"Content-Type\": mime });\n res.end(content);\n return true;\n } catch {\n return false;\n }\n }\n\n // ─── API Proxy ────────────────────────────────────────────────────\n\n private proxyToApiServer(req: http.IncomingMessage, res: http.ServerResponse): void {\n const proxyReq = http.request(\n {\n hostname: \"127.0.0.1\",\n port: this.options.apiPort,\n path: req.url,\n method: req.method,\n headers: {\n ...req.headers,\n host: `localhost:${this.options.apiPort}`,\n },\n },\n (proxyRes) => {\n res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);\n proxyRes.pipe(res, { end: true });\n },\n );\n\n proxyReq.on(\"error\", (err) => {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: { code: \"PROXY_ERROR\", message: \"API server unavailable\" } }));\n });\n\n req.pipe(proxyReq, { end: true });\n }\n\n // ─── Frontend Request Handler ─────────────────────────────────────\n\n private handleFrontendRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url || \"/\", `http://localhost:${this.options.port}`);\n\n // 1. SSE endpoint for frontend HMR\n if (url.pathname === \"/__dev/events\") {\n this.handleSSE(req, res, this.frontendSseClients);\n return;\n }\n\n // 2. Proxy /api/* requests to API server\n if (url.pathname.startsWith(\"/api\")) {\n this.proxyToApiServer(req, res);\n return;\n }\n\n // 3. Proxy /__dev/* (except /events handled above) and /__playground to API server\n if (url.pathname.startsWith(\"/__\")) {\n this.proxyToApiServer(req, res);\n return;\n }\n\n // 4. Non-HTML file extensions → serve from public/\n const ext = extname(url.pathname);\n if (ext && STATIC_EXTENSIONS.has(ext)) {\n const filePath = resolve(this.publicDir, url.pathname.slice(1));\n if (filePath.startsWith(this.publicDir) && this.serveStaticFile(filePath, res)) {\n return;\n }\n // Static file not found\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n // 5. Page system\n if (this.pageCompiler.isActive()) {\n const isPartial = req.headers[\"x-clawfire-partial\"] === \"true\";\n const pagePath = this.pageCompiler.resolve(url.pathname);\n\n if (pagePath) {\n try {\n if (isPartial) {\n // SPA navigation — return JSON with page content only\n const partial = this.pageCompiler.compilePartial(pagePath, url.pathname);\n res.writeHead(200, { \"Content-Type\": \"application/json; charset=utf-8\" });\n res.end(JSON.stringify(partial));\n } else {\n // Full page render with layouts + components + script injection\n const compiled = this.pageCompiler.compile(pagePath);\n const html = this.injectScripts(compiled.html, true);\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n }\n } catch (err) {\n logger.warn(`Page compilation error: ${url.pathname}`, err);\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(`<h1>500 — Page Compilation Error</h1><pre>${err instanceof Error ? err.message : \"Unknown error\"}</pre>`);\n }\n return;\n }\n\n // 404 page\n const page404 = this.pageCompiler.resolve404();\n if (page404) {\n try {\n if (isPartial) {\n const partial = this.pageCompiler.compilePartial(page404, url.pathname);\n res.writeHead(404, { \"Content-Type\": \"application/json; charset=utf-8\" });\n res.end(JSON.stringify(partial));\n } else {\n const compiled = this.pageCompiler.compile(page404);\n const html = this.injectScripts(compiled.html, true);\n res.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n }\n } catch {\n res.writeHead(404);\n res.end(\"Not found\");\n }\n return;\n }\n }\n\n // 6. Serve static files from public/ (for paths without extension, e.g. directory index)\n const requestedPath = url.pathname === \"/\" ? \"index.html\" : url.pathname.slice(1);\n const filePath = resolve(this.publicDir, requestedPath);\n\n // Security: prevent directory traversal\n if (!filePath.startsWith(this.publicDir)) {\n res.writeHead(403);\n res.end(\"Forbidden\");\n return;\n }\n\n if (this.serveStaticFile(filePath, res)) {\n return;\n }\n\n // 7. SPA fallback: serve index.html for unmatched routes\n const indexPath = resolve(this.publicDir, \"index.html\");\n if (existsSync(indexPath)) {\n this.serveStaticFile(indexPath, res);\n return;\n }\n\n res.writeHead(404);\n res.end(\"Not found\");\n }\n\n // ─── API Request Handler ──────────────────────────────────────────\n\n private handleApiRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url || \"/\", `http://localhost:${this.options.apiPort}`);\n\n // SSE endpoint for playground\n if (url.pathname === \"/__dev/events\") {\n this.handleSSE(req, res, this.apiSseClients);\n return;\n }\n\n // Playground\n if (url.pathname === \"/\" || url.pathname === \"/__playground\") {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(this.playgroundHtml);\n return;\n }\n\n // API requests\n if (url.pathname.startsWith(\"/api\")) {\n // CORS\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"POST, GET, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", async () => {\n let parsed = {};\n try { parsed = body ? JSON.parse(body) : {}; } catch {}\n\n await this.router.handleRequest(\n {\n method: req.method,\n path: url.pathname,\n body: parsed,\n headers: req.headers as Record<string, string>,\n ip: req.socket.remoteAddress || \"127.0.0.1\",\n },\n {\n set(h: Record<string, string>) {\n for (const [k, v] of Object.entries(h)) {\n try { res.setHeader(k, v); } catch {}\n }\n },\n status(code: number) {\n return {\n json(data: unknown) {\n res.writeHead(code, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n },\n send(data: string) { res.writeHead(code); res.end(data); },\n end() { res.writeHead(code); res.end(); },\n };\n },\n },\n );\n });\n return;\n }\n\n // Dev dashboard endpoints\n if (url.pathname.startsWith(\"/__dev/\")) {\n this.handleDevEndpoint(req, res, url);\n return;\n }\n\n // 404\n res.writeHead(404);\n res.end(\"Not found\");\n }\n\n // ─── Startup Banner ────────────────────────────────────────────────\n\n private printStartupBanner(): void {\n const routes = this.router.getRoutes();\n const watching = this.options.hotReload;\n const pagesActive = this.pageCompiler.isActive();\n\n console.log(\"\");\n console.log(\" \\x1b[1m\\x1b[33m⚡ Clawfire Dev Server\\x1b[0m\");\n console.log(\" \\x1b[2m─────────────────────────────────────\\x1b[0m\");\n console.log(` \\x1b[36mApp\\x1b[0m : http://localhost:${this.options.port}`);\n console.log(` \\x1b[36mAPI\\x1b[0m : http://localhost:${this.options.apiPort}/api/...`);\n console.log(` \\x1b[36mPlayground\\x1b[0m : http://localhost:${this.options.apiPort}`);\n console.log(` \\x1b[36mPages\\x1b[0m : ${pagesActive ? \"\\x1b[32mON\\x1b[0m (app/pages/)\" : \"\\x1b[2mOFF\\x1b[0m\"}`);\n console.log(\"\");\n console.log(` \\x1b[32mRoutes (${routes.length})\\x1b[0m:`);\n for (const route of routes) {\n const auth = route.contract.meta.auth || \"public\";\n const authColor = auth === \"public\" ? \"32\" : auth === \"authenticated\" ? \"34\" : auth === \"role\" ? \"33\" : \"31\";\n console.log(` POST /api\\x1b[1m${route.path}\\x1b[0m \\x1b[${authColor}m[${auth}]\\x1b[0m \\x1b[2m${route.contract.meta.description}\\x1b[0m`);\n }\n console.log(\"\");\n if (watching) {\n const watchDirs = [\"app/routes/\", \"app/schemas/\", \"public/\"];\n if (pagesActive) watchDirs.push(\"app/pages/\", \"app/components/\");\n console.log(` \\x1b[35mHot Reload\\x1b[0m : \\x1b[32mON\\x1b[0m`);\n console.log(` \\x1b[2mWatching: ${watchDirs.join(\", \")}\\x1b[0m`);\n } else {\n console.log(` \\x1b[35mHot Reload\\x1b[0m : OFF`);\n }\n console.log(`\\n \\x1b[2mPress Ctrl+C to stop\\x1b[0m\\n`);\n }\n\n // ─── Dev Dashboard Endpoints ──────────────────────────────────────\n\n private handleDevEndpoint(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n url: URL,\n ): void {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const sendJson = (data: unknown, status = 200) => {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n };\n\n // GET /__dev/firebase-status\n if (url.pathname === \"/__dev/firebase-status\" && req.method === \"GET\") {\n checkFirebaseStatus(this.options.projectDir)\n .then((status) => sendJson(status))\n .catch((err) => sendJson({ error: err.message }, 500));\n return;\n }\n\n // GET /__dev/config\n if (url.pathname === \"/__dev/config\" && req.method === \"GET\") {\n sendJson(this.readProjectConfig());\n return;\n }\n\n // POST /__dev/config — update config field values\n if (url.pathname === \"/__dev/config\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const data = JSON.parse(body);\n if (data.action === \"set\" && data.key && data.value !== undefined) {\n this.updateProjectConfig(data.key, String(data.value));\n clearFirebaseStatusCache();\n sendJson({ ok: true });\n } else if (data.action === \"set-multiple\" && data.fields) {\n for (const [key, value] of Object.entries(data.fields)) {\n this.updateProjectConfig(key, String(value));\n }\n clearFirebaseStatusCache();\n sendJson({ ok: true });\n } else {\n sendJson({ error: \"Invalid action\" }, 400);\n }\n } catch (err) {\n sendJson({ error: err instanceof Error ? err.message : \"Failed\" }, 400);\n }\n });\n return;\n }\n\n // GET /__dev/firebase-sdk-config — auto-fill from Firebase CLI\n if (url.pathname === \"/__dev/firebase-sdk-config\" && req.method === \"GET\") {\n fetchFirebaseSdkConfig(this.options.projectDir)\n .then((config) => sendJson(config))\n .catch((err) => sendJson({ error: err instanceof Error ? err.message : \"Failed to fetch SDK config\" }, 500));\n return;\n }\n\n // GET /__dev/env\n if (url.pathname === \"/__dev/env\" && req.method === \"GET\") {\n try {\n sendJson(this.envManager.read());\n } catch (err) {\n sendJson({ error: err instanceof Error ? err.message : \"Read failed\" }, 500);\n }\n return;\n }\n\n // POST /__dev/env\n if (url.pathname === \"/__dev/env\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const data = JSON.parse(body);\n if (data.action === \"set\") {\n this.envManager.set(data.key, data.value, data.description);\n sendJson({ ok: true });\n } else if (data.action === \"delete\") {\n this.envManager.delete(data.key);\n sendJson({ ok: true });\n } else {\n sendJson({ error: \"Invalid action\" }, 400);\n }\n } catch (err) {\n sendJson({ error: err instanceof Error ? err.message : \"Failed\" }, 400);\n }\n });\n return;\n }\n\n // ─── Setup Wizard Endpoints ─────────────────────────────────────\n\n // GET /__dev/setup/status\n if (url.pathname === \"/__dev/setup/status\" && req.method === \"GET\") {\n this.firebaseSetup.getStatus()\n .then((status) => sendJson(status))\n .catch((err) => sendJson({ error: err instanceof Error ? err.message : \"Failed\" }, 500));\n return;\n }\n\n // POST /__dev/setup/install-cli\n if (url.pathname === \"/__dev/setup/install-cli\" && req.method === \"POST\") {\n this.firebaseSetup.installCli()\n .then((result) => sendJson(result))\n .catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : \"Failed\" }, 500));\n return;\n }\n\n // POST /__dev/setup/login — opens a real terminal window\n if (url.pathname === \"/__dev/setup/login\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n let reauth = false;\n try {\n const data = JSON.parse(body);\n reauth = !!data.reauth;\n } catch {\n // default: not reauth\n }\n const result = this.firebaseSetup.openLoginTerminal(reauth);\n sendJson(result);\n });\n return;\n }\n\n // GET /__dev/setup/projects\n if (url.pathname === \"/__dev/setup/projects\" && req.method === \"GET\") {\n this.firebaseSetup.listProjects()\n .then((result) => sendJson(result))\n .catch((err) => sendJson({ projects: [], error: err instanceof Error ? err.message : \"Failed\" }, 500));\n return;\n }\n\n // POST /__dev/setup/select-project\n if (url.pathname === \"/__dev/setup/select-project\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const data = JSON.parse(body);\n if (!data.projectId) {\n sendJson({ success: false, message: \"projectId is required\" }, 400);\n return;\n }\n this.firebaseSetup.selectProject(data.projectId)\n .then((result) => {\n clearFirebaseStatusCache();\n sendJson(result);\n })\n .catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : \"Failed\" }, 500));\n } catch {\n sendJson({ success: false, message: \"Invalid JSON body\" }, 400);\n }\n });\n return;\n }\n\n // Unknown /__dev/ endpoint\n res.writeHead(404);\n res.end(\"Not found\");\n }\n\n // ─── Config Reader ────────────────────────────────────────────────\n\n private readProjectConfig(): { fields: Array<{ key: string; value: string; isPlaceholder: boolean }> } {\n const configPath = resolve(this.options.projectDir, \"clawfire.config.ts\");\n const fields: Array<{ key: string; value: string; isPlaceholder: boolean }> = [];\n\n if (!existsSync(configPath)) {\n return { fields };\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\");\n\n // Parse key-value pairs from the config file\n // Match patterns like: apiKey: \"value\", authDomain: \"value\", etc.\n const kvPattern = /(\\w+)\\s*:\\s*[\"'`]([^\"'`]*)[\"'`]/g;\n let match: RegExpExecArray | null;\n\n while ((match = kvPattern.exec(content)) !== null) {\n const key = match[1];\n const value = match[2];\n const isPlaceholder = /^YOUR_/i.test(value) || /^CHANGE_ME$/i.test(value) || /^TODO$/i.test(value);\n fields.push({ key, value, isPlaceholder });\n }\n } catch {\n // ignore read errors\n }\n\n return { fields };\n }\n\n /** Update a single key's value in clawfire.config.ts */\n private updateProjectConfig(key: string, value: string): void {\n const configPath = resolve(this.options.projectDir, \"clawfire.config.ts\");\n if (!existsSync(configPath)) {\n throw new Error(\"clawfire.config.ts not found\");\n }\n\n let content = readFileSync(configPath, \"utf-8\");\n\n // Match key: \"value\" or key: 'value' patterns\n const pattern = new RegExp(\n `(${this.escapeRegex(key)}\\\\s*:\\\\s*)[\"'\\`][^\"'\\`]*[\"'\\`]`,\n );\n\n if (!pattern.test(content)) {\n throw new Error(`Key \"${key}\" not found in config`);\n }\n\n content = content.replace(pattern, `$1\"${value.replace(/\"/g, '\\\\\"')}\"`);\n writeFileSync(configPath, content, \"utf-8\");\n }\n\n private escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n}\n\n/**\n * Create and start dev server (one-line helper)\n */\nexport async function startDevServer(options?: DevServerOptions): Promise<DevServer> {\n const server = new DevServer(options);\n await server.start();\n\n const shutdown = async () => {\n console.log(\"\\n Shutting down...\");\n await server.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n return server;\n}\n","/**\n * Clawfire Schema & Contract System\n *\n * 모든 API는 input/output schema + meta + handler로 구성된 \"계약(Contract)\"으로 정의됩니다.\n * Zod를 사용하여 타입 안전성과 런타임 검증을 동시에 보장합니다.\n */\nimport { z, type ZodType, type ZodObject, type ZodRawShape } from \"zod\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\n/** 인증 컨텍스트 */\nexport interface AuthContext {\n uid: string;\n email?: string;\n emailVerified?: boolean;\n role?: string;\n customClaims?: Record<string, unknown>;\n token?: string;\n}\n\n/** API 핸들러에 전달되는 컨텍스트 */\nexport interface HandlerContext {\n auth: AuthContext | null;\n /** 재인증 여부 (민감 작업용) */\n reauthenticated?: boolean;\n /** 원본 요청 헤더 */\n headers?: Record<string, string>;\n /** 요청 IP */\n ip?: string;\n}\n\n/** 권한 수준 */\nexport type AuthLevel = \"public\" | \"authenticated\" | \"role\" | \"reauth\";\n\n/** API 메타데이터 */\nexport interface APIMeta {\n /** API 설명 (AI/Playground용) */\n description: string;\n /** 태그 (그룹화용) */\n tags?: string[];\n /** 인증 요구 수준 */\n auth?: AuthLevel;\n /** 필요 역할 (auth가 'role'일 때) */\n roles?: string[];\n /** 재인증 필요 여부 */\n reauth?: boolean;\n /** Rate limit (초당 요청 수) */\n rateLimit?: number;\n /** 비활성화 여부 */\n deprecated?: boolean;\n /** 예시 입력값 */\n exampleInput?: unknown;\n /** 예시 출력값 */\n exampleOutput?: unknown;\n}\n\n/** API 계약 정의 */\nexport interface APIContract<\n TInput extends ZodType = ZodType,\n TOutput extends ZodType = ZodType,\n> {\n /** 입력 스키마 */\n input: TInput;\n /** 출력 스키마 */\n output: TOutput;\n /** 메타데이터 */\n meta: APIMeta;\n /** 핸들러 함수 */\n handler: (\n input: z.infer<TInput>,\n ctx: HandlerContext,\n ) => Promise<z.infer<TOutput>>;\n}\n\n/** 모델 필드 정의 */\nexport interface ModelField {\n type: \"string\" | \"number\" | \"boolean\" | \"timestamp\" | \"array\" | \"map\" | \"reference\" | \"geopoint\";\n required?: boolean;\n description?: string;\n default?: unknown;\n /** 배열 아이템 타입 */\n items?: ModelField;\n /** 맵 값 타입 */\n values?: ModelField;\n /** 참조 대상 컬렉션 */\n ref?: string;\n /** enum 값 리스트 */\n enum?: string[];\n}\n\n/** 모델 정의 */\nexport interface ModelDefinition {\n /** 컬렉션 이름 */\n collection: string;\n /** 필드 정의 */\n fields: Record<string, ModelField>;\n /** 서브컬렉션 */\n subcollections?: Record<string, ModelDefinition>;\n /** 인덱스 */\n indexes?: ModelIndex[];\n /** 보안 규칙 */\n rules?: ModelRules;\n /** 타임스탬프 자동 생성 */\n timestamps?: boolean;\n /** 소프트 삭제 */\n softDelete?: boolean;\n}\n\n/** Firestore 인덱스 */\nexport interface ModelIndex {\n fields: Array<{ field: string; order?: \"asc\" | \"desc\" }>;\n}\n\n/** 모델 보안 규칙 */\nexport interface ModelRules {\n read?: AuthLevel;\n create?: AuthLevel;\n update?: AuthLevel;\n delete?: AuthLevel;\n readRoles?: string[];\n createRoles?: string[];\n updateRoles?: string[];\n deleteRoles?: string[];\n /** 소유자만 읽기/쓰기 가능 필드 */\n ownerField?: string;\n}\n\n// ─── Builders ────────────────────────────────────────────────────────\n\n/**\n * API 계약 정의\n *\n * @example\n * ```ts\n * export default defineAPI({\n * input: z.object({ name: z.string() }),\n * output: z.object({ id: z.string(), name: z.string() }),\n * meta: { description: \"상품 생성\", auth: \"authenticated\" },\n * handler: async (input, ctx) => {\n * const id = await db.create(\"products\", input);\n * return { id, ...input };\n * }\n * });\n * ```\n */\nexport function defineAPI<\n TInput extends ZodType,\n TOutput extends ZodType,\n>(contract: APIContract<TInput, TOutput>): APIContract<TInput, TOutput> {\n return contract;\n}\n\n/**\n * 모델(Firestore 컬렉션) 정의\n *\n * @example\n * ```ts\n * export const Product = defineModel({\n * collection: \"products\",\n * fields: {\n * name: { type: \"string\", required: true },\n * price: { type: \"number\", required: true },\n * tags: { type: \"array\", items: { type: \"string\" } },\n * },\n * timestamps: true,\n * rules: { read: \"public\", create: \"authenticated\" }\n * });\n * ```\n */\nexport function defineModel(definition: ModelDefinition): ModelDefinition {\n return {\n timestamps: true,\n ...definition,\n };\n}\n\n// ─── Schema Utilities ────────────────────────────────────────────────\n\n/** Zod 스키마에서 JSON Schema 생성 (Playground/문서용) */\nexport function zodToJsonSchema(schema: ZodType): Record<string, unknown> {\n return extractZodShape(schema);\n}\n\nfunction extractZodShape(schema: ZodType): Record<string, unknown> {\n const def = (schema as any)._def;\n\n if (!def) return { type: \"unknown\" };\n\n switch (def.typeName) {\n case \"ZodObject\": {\n const shape = (schema as ZodObject<ZodRawShape>).shape;\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, value] of Object.entries(shape)) {\n properties[key] = extractZodShape(value as ZodType);\n if (!(value as any).isOptional?.()) {\n const innerDef = (value as any)._def;\n if (innerDef?.typeName !== \"ZodOptional\" && innerDef?.typeName !== \"ZodDefault\") {\n required.push(key);\n }\n }\n }\n\n return { type: \"object\", properties, ...(required.length > 0 ? { required } : {}) };\n }\n case \"ZodString\":\n return { type: \"string\", ...(def.checks?.length ? extractStringChecks(def.checks) : {}) };\n case \"ZodNumber\":\n return { type: \"number\" };\n case \"ZodBoolean\":\n return { type: \"boolean\" };\n case \"ZodArray\":\n return { type: \"array\", items: extractZodShape(def.type) };\n case \"ZodEnum\":\n return { type: \"string\", enum: def.values };\n case \"ZodOptional\":\n return { ...extractZodShape(def.innerType), optional: true };\n case \"ZodDefault\":\n return { ...extractZodShape(def.innerType), default: def.defaultValue() };\n case \"ZodNullable\":\n return { ...extractZodShape(def.innerType), nullable: true };\n case \"ZodLiteral\":\n return { type: typeof def.value, const: def.value };\n case \"ZodUnion\":\n return { oneOf: def.options.map((o: ZodType) => extractZodShape(o)) };\n case \"ZodRecord\":\n return { type: \"object\", additionalProperties: extractZodShape(def.valueType) };\n case \"ZodDate\":\n return { type: \"string\", format: \"date-time\" };\n default:\n return { type: \"unknown\" };\n }\n}\n\nfunction extractStringChecks(checks: Array<{ kind: string; value?: unknown }>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const check of checks) {\n switch (check.kind) {\n case \"min\": result.minLength = check.value; break;\n case \"max\": result.maxLength = check.value; break;\n case \"email\": result.format = \"email\"; break;\n case \"url\": result.format = \"uri\"; break;\n case \"uuid\": result.format = \"uuid\"; break;\n }\n }\n return result;\n}\n\n/** 모델 정의에서 Zod 스키마 자동 생성 */\nexport function modelToZodSchema(model: ModelDefinition): ZodObject<ZodRawShape> {\n const shape: ZodRawShape = {};\n\n for (const [key, field] of Object.entries(model.fields)) {\n let fieldSchema: ZodType = fieldToZod(field);\n if (!field.required) {\n fieldSchema = fieldSchema.optional();\n }\n shape[key] = fieldSchema;\n }\n\n if (model.timestamps) {\n shape.createdAt = z.string().datetime().optional();\n shape.updatedAt = z.string().datetime().optional();\n }\n\n if (model.softDelete) {\n shape.deletedAt = z.string().datetime().nullable().optional();\n }\n\n return z.object(shape);\n}\n\nfunction fieldToZod(field: ModelField): ZodType {\n switch (field.type) {\n case \"string\":\n if (field.enum) return z.enum(field.enum as [string, ...string[]]);\n return z.string();\n case \"number\":\n return z.number();\n case \"boolean\":\n return z.boolean();\n case \"timestamp\":\n return z.string().datetime();\n case \"array\":\n if (field.items) return z.array(fieldToZod(field.items));\n return z.array(z.unknown());\n case \"map\":\n if (field.values) return z.record(z.string(), fieldToZod(field.values));\n return z.record(z.string(), z.unknown());\n case \"reference\":\n return z.string(); // 참조는 문서 경로 문자열\n case \"geopoint\":\n return z.object({ latitude: z.number(), longitude: z.number() });\n default:\n return z.unknown();\n }\n}\n\n// ─── Manifest ────────────────────────────────────────────────────────\n\n/** API 매니페스트 항목 */\nexport interface ManifestEntry {\n path: string;\n method: \"POST\"; // Clawfire는 모두 POST\n meta: APIMeta;\n inputSchema: Record<string, unknown>;\n outputSchema: Record<string, unknown>;\n}\n\n/** 전체 매니페스트 */\nexport interface Manifest {\n version: string;\n generatedAt: string;\n apis: ManifestEntry[];\n models: Record<string, ModelDefinition>;\n}\n\n/** 계약에서 매니페스트 항목 생성 */\nexport function contractToManifest(\n path: string,\n contract: APIContract,\n): ManifestEntry {\n return {\n path,\n method: \"POST\",\n meta: contract.meta,\n inputSchema: zodToJsonSchema(contract.input),\n outputSchema: zodToJsonSchema(contract.output),\n };\n}\n","/**\n * Clawfire Error System\n *\n * 표준화된 에러 코드와 구조로 일관된 에러 응답을 제공합니다.\n */\n\nexport type ClawfireErrorCode =\n | \"VALIDATION_ERROR\"\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"RATE_LIMITED\"\n | \"REAUTH_REQUIRED\"\n | \"INTERNAL_ERROR\"\n | \"SERVICE_UNAVAILABLE\";\n\nconst HTTP_STATUS_MAP: Record<ClawfireErrorCode, number> = {\n VALIDATION_ERROR: 400,\n UNAUTHORIZED: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n RATE_LIMITED: 429,\n REAUTH_REQUIRED: 401,\n INTERNAL_ERROR: 500,\n SERVICE_UNAVAILABLE: 503,\n};\n\nexport class ClawfireError extends Error {\n readonly code: ClawfireErrorCode;\n readonly statusCode: number;\n readonly details?: unknown;\n\n constructor(code: ClawfireErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = \"ClawfireError\";\n this.code = code;\n this.statusCode = HTTP_STATUS_MAP[code];\n this.details = details;\n }\n\n toJSON() {\n return {\n error: {\n code: this.code,\n message: this.message,\n ...(this.details ? { details: this.details } : {}),\n },\n };\n }\n}\n\n/** 편의 팩토리 함수 */\nexport const Errors = {\n validation: (message: string, details?: unknown) =>\n new ClawfireError(\"VALIDATION_ERROR\", message, details),\n\n unauthorized: (message = \"Authentication required\") =>\n new ClawfireError(\"UNAUTHORIZED\", message),\n\n forbidden: (message = \"Insufficient permissions\") =>\n new ClawfireError(\"FORBIDDEN\", message),\n\n notFound: (message = \"Resource not found\") =>\n new ClawfireError(\"NOT_FOUND\", message),\n\n conflict: (message: string) =>\n new ClawfireError(\"CONFLICT\", message),\n\n rateLimited: (message = \"Too many requests\") =>\n new ClawfireError(\"RATE_LIMITED\", message),\n\n reauthRequired: (message = \"Re-authentication required for this action\") =>\n new ClawfireError(\"REAUTH_REQUIRED\", message),\n\n internal: (message = \"Internal server error\") =>\n new ClawfireError(\"INTERNAL_ERROR\", message),\n\n unavailable: (message = \"Service temporarily unavailable\") =>\n new ClawfireError(\"SERVICE_UNAVAILABLE\", message),\n};\n","/**\n * Clawfire Logger\n *\n * 보안 로깅 (민감 정보 마스킹 포함)\n */\n\n/** 마스킹할 필드명 패턴 */\nconst SENSITIVE_FIELDS = [\n \"password\",\n \"token\",\n \"secret\",\n \"apiKey\",\n \"api_key\",\n \"authorization\",\n \"credit_card\",\n \"creditCard\",\n \"ssn\",\n \"cardNumber\",\n \"card_number\",\n \"cvv\",\n \"pin\",\n];\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nlet currentLevel: LogLevel = \"info\";\n\nexport function setLogLevel(level: LogLevel) {\n currentLevel = level;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];\n}\n\n/** 민감 필드 마스킹 */\nexport function maskSensitive(obj: unknown): unknown {\n if (obj === null || obj === undefined) return obj;\n if (typeof obj === \"string\") return obj;\n if (typeof obj !== \"object\") return obj;\n\n if (Array.isArray(obj)) {\n return obj.map(maskSensitive);\n }\n\n const masked: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_FIELDS.some((f) => key.toLowerCase().includes(f.toLowerCase()))) {\n masked[key] = \"***MASKED***\";\n } else if (typeof value === \"object\" && value !== null) {\n masked[key] = maskSensitive(value);\n } else {\n masked[key] = value;\n }\n }\n return masked;\n}\n\nfunction formatLog(level: LogLevel, message: string, data?: unknown): string {\n const timestamp = new Date().toISOString();\n const prefix = `[${timestamp}] [CLAWFIRE] [${level.toUpperCase()}]`;\n if (data !== undefined) {\n return `${prefix} ${message} ${JSON.stringify(maskSensitive(data))}`;\n }\n return `${prefix} ${message}`;\n}\n\nexport const logger = {\n debug(message: string, data?: unknown) {\n if (shouldLog(\"debug\")) console.debug(formatLog(\"debug\", message, data));\n },\n info(message: string, data?: unknown) {\n if (shouldLog(\"info\")) console.info(formatLog(\"info\", message, data));\n },\n warn(message: string, data?: unknown) {\n if (shouldLog(\"warn\")) console.warn(formatLog(\"warn\", message, data));\n },\n error(message: string, data?: unknown) {\n if (shouldLog(\"error\")) console.error(formatLog(\"error\", message, data));\n },\n};\n","/**\n * Clawfire Auth Helpers\n *\n * Firebase Auth 통합: 토큰 검증, 역할 관리, 재인증\n */\nimport type { AuthContext, AuthLevel } from \"../core/schema.js\";\nimport { Errors } from \"../core/errors.js\";\n\n// ─── Server-side Auth (Admin SDK) ────────────────────────────────────\n\n/**\n * Firebase ID 토큰에서 AuthContext 추출 (서버사이드)\n */\nexport async function verifyToken(\n auth: any, // firebase-admin Auth instance\n idToken: string,\n): Promise<AuthContext> {\n const decoded = await auth.verifyIdToken(idToken);\n return {\n uid: decoded.uid,\n email: decoded.email,\n emailVerified: decoded.email_verified,\n role: decoded.role || decoded.customClaims?.role,\n customClaims: decoded,\n token: idToken,\n };\n}\n\n/**\n * 재인증 토큰 검증 (최근 5분 이내 인증)\n */\nexport async function verifyReauth(\n auth: any,\n idToken: string,\n maxAgeSeconds = 300,\n): Promise<AuthContext> {\n const decoded = await auth.verifyIdToken(idToken, true);\n const authTime = decoded.auth_time * 1000;\n const now = Date.now();\n\n if (now - authTime > maxAgeSeconds * 1000) {\n throw Errors.reauthRequired(\n `Re-authentication required. Last auth was ${Math.floor((now - authTime) / 1000)}s ago.`,\n );\n }\n\n return {\n uid: decoded.uid,\n email: decoded.email,\n emailVerified: decoded.email_verified,\n role: decoded.role || decoded.customClaims?.role,\n customClaims: decoded,\n token: idToken,\n };\n}\n\n/**\n * 요청에서 Authorization 헤더의 Bearer 토큰 추출\n */\nexport function extractBearerToken(authHeader?: string): string | null {\n if (!authHeader) return null;\n const parts = authHeader.split(\" \");\n if (parts.length !== 2 || parts[0] !== \"Bearer\") return null;\n return parts[1];\n}\n\n/**\n * 인증 수준 체크\n */\nexport function checkAuthLevel(\n authCtx: AuthContext | null,\n level: AuthLevel,\n roles?: string[],\n reauthenticated?: boolean,\n): void {\n switch (level) {\n case \"public\":\n return; // 누구나 접근 가능\n\n case \"authenticated\":\n if (!authCtx) throw Errors.unauthorized();\n return;\n\n case \"role\":\n if (!authCtx) throw Errors.unauthorized();\n if (!roles || roles.length === 0) return;\n if (!authCtx.role || !roles.includes(authCtx.role)) {\n throw Errors.forbidden(`Required role: ${roles.join(\" or \")}`);\n }\n return;\n\n case \"reauth\":\n if (!authCtx) throw Errors.unauthorized();\n if (!reauthenticated) {\n throw Errors.reauthRequired();\n }\n return;\n }\n}\n\n// ─── Custom Claims / Role Management ─────────────────────────────────\n\n/**\n * 사용자에게 역할(Role) 설정\n */\nexport async function setUserRole(\n auth: any,\n uid: string,\n role: string,\n): Promise<void> {\n const user = await auth.getUser(uid);\n const currentClaims = user.customClaims || {};\n await auth.setCustomUserClaims(uid, { ...currentClaims, role });\n}\n\n/**\n * 사용자의 역할 조회\n */\nexport async function getUserRole(auth: any, uid: string): Promise<string | undefined> {\n const user = await auth.getUser(uid);\n return user.customClaims?.role;\n}\n\n/**\n * 사용자에게 커스텀 클레임 설정\n */\nexport async function setCustomClaims(\n auth: any,\n uid: string,\n claims: Record<string, unknown>,\n): Promise<void> {\n const user = await auth.getUser(uid);\n const currentClaims = user.customClaims || {};\n await auth.setCustomUserClaims(uid, { ...currentClaims, ...claims });\n}\n\n// ─── Client-side Auth Helpers ────────────────────────────────────────\n\n/**\n * 클라이언트 사이드 Auth 헬퍼 (firebase/auth 래핑)\n */\nexport function createClientAuth(firebaseAuth: any) {\n return {\n /** 현재 사용자 조회 */\n getCurrentUser(): any | null {\n return firebaseAuth.currentUser;\n },\n\n /** ID 토큰 취득 */\n async getIdToken(forceRefresh = false): Promise<string | null> {\n const user = firebaseAuth.currentUser;\n if (!user) return null;\n return user.getIdToken(forceRefresh);\n },\n\n /** 인증 상태 변경 리스너 */\n onAuthStateChanged(callback: (user: any | null) => void): () => void {\n return firebaseAuth.onAuthStateChanged(callback);\n },\n\n /** 로그아웃 */\n async signOut(): Promise<void> {\n await firebaseAuth.signOut();\n },\n\n /** 원시 auth 접근 */\n raw: firebaseAuth,\n };\n}\n\nexport type ClientAuth = ReturnType<typeof createClientAuth>;\n","/**\n * Clawfire File-based Router\n *\n * app/routes/ 디렉터리 구조를 자동으로 API 경로로 매핑합니다.\n *\n * 파일 구조 → API 경로:\n * app/routes/products/list.ts → /api/products/list\n * app/routes/products/create.ts → /api/products/create\n * app/routes/auth/login.ts → /api/auth/login\n * app/routes/users/[id]/get.ts → /api/users/:id/get\n */\nimport { z } from \"zod\";\nimport type { APIContract, HandlerContext, AuthContext, Manifest, ManifestEntry } from \"../core/schema.js\";\nimport { contractToManifest, zodToJsonSchema } from \"../core/schema.js\";\nimport { ClawfireError, Errors } from \"../core/errors.js\";\nimport { logger } from \"../core/logger.js\";\nimport { checkAuthLevel, extractBearerToken, verifyToken, verifyReauth } from \"../firebase/auth.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface Route {\n path: string;\n contract: APIContract;\n}\n\nexport interface RouterOptions {\n /** Firebase Admin Auth 인스턴스 */\n auth?: any;\n /** Firebase Admin Firestore 인스턴스 */\n firestore?: any;\n /** CORS 허용 도메인 */\n cors?: string[];\n /** 전역 rate limit */\n rateLimit?: number;\n /** 전역 미들웨어 */\n middleware?: Middleware[];\n /** 플레이그라운드 활성화 */\n playground?: boolean;\n}\n\nexport type Middleware = (\n input: unknown,\n ctx: HandlerContext,\n next: () => Promise<unknown>,\n) => Promise<unknown>;\n\n// ─── Rate Limiter (in-memory) ────────────────────────────────────────\n\nclass RateLimiter {\n private store = new Map<string, { count: number; resetAt: number }>();\n private defaultLimit: number;\n\n constructor(defaultLimit: number) {\n this.defaultLimit = defaultLimit;\n }\n\n check(key: string, limit?: number): boolean {\n const max = limit || this.defaultLimit;\n const now = Date.now();\n const entry = this.store.get(key);\n\n if (!entry || now > entry.resetAt) {\n this.store.set(key, { count: 1, resetAt: now + 60000 }); // 1분 윈도우\n return true;\n }\n\n if (entry.count >= max) {\n return false;\n }\n\n entry.count++;\n return true;\n }\n\n // 오래된 엔트리 정리\n cleanup() {\n const now = Date.now();\n for (const [key, entry] of this.store) {\n if (now > entry.resetAt) this.store.delete(key);\n }\n }\n}\n\n// ─── Router ──────────────────────────────────────────────────────────\n\nexport class ClawfireRouter {\n private routes = new Map<string, Route>();\n private options: RouterOptions;\n private rateLimiter: RateLimiter;\n private cleanupInterval?: ReturnType<typeof setInterval>;\n\n constructor(options: RouterOptions = {}) {\n this.options = options;\n this.rateLimiter = new RateLimiter(options.rateLimit || 100);\n this.cleanupInterval = setInterval(() => this.rateLimiter.cleanup(), 60000);\n }\n\n /** 라우트 등록 */\n register(path: string, contract: APIContract): this {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n this.routes.set(normalizedPath, { path: normalizedPath, contract });\n logger.debug(`Route registered: ${normalizedPath}`);\n return this;\n }\n\n /** 여러 라우트 한 번에 등록 */\n registerAll(routes: Record<string, APIContract>): this {\n for (const [path, contract] of Object.entries(routes)) {\n this.register(path, contract);\n }\n return this;\n }\n\n /** 매니페스트 생성 */\n getManifest(): Manifest {\n const apis: ManifestEntry[] = [];\n for (const [path, route] of this.routes) {\n apis.push(contractToManifest(path, route.contract));\n }\n\n return {\n version: \"1.0.0\",\n generatedAt: new Date().toISOString(),\n apis,\n models: {},\n };\n }\n\n /** HTTP 요청 핸들러 (Firebase Functions에서 사용) */\n async handleRequest(\n req: { path?: string; url?: string; body?: unknown; headers?: Record<string, string>; ip?: string; method?: string },\n res: { status: (code: number) => { json: (data: unknown) => void; send: (data: string) => void; end: () => void }; set: (headers: Record<string, string>) => void },\n ): Promise<void> {\n // CORS 처리\n const corsOrigins = this.options.cors || [];\n const origin = req.headers?.origin || req.headers?.Origin || \"\";\n\n if (corsOrigins.length > 0 && corsOrigins.includes(origin)) {\n res.set({\n \"Access-Control-Allow-Origin\": origin,\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Max-Age\": \"86400\",\n });\n } else if (corsOrigins.length === 0) {\n // CORS 기본 deny — 설정 없으면 허용하지 않음\n res.set({\n \"Access-Control-Allow-Origin\": \"\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n });\n }\n\n // OPTIONS (preflight)\n if (req.method === \"OPTIONS\") {\n res.status(204).end();\n return;\n }\n\n // 안전 헤더\n res.set({\n \"X-Content-Type-Options\": \"nosniff\",\n \"X-Frame-Options\": \"DENY\",\n \"X-XSS-Protection\": \"1; mode=block\",\n \"Content-Type\": \"application/json\",\n });\n\n // 경로 추출 — /api/xxx/yyy → /xxx/yyy\n let routePath = req.path || req.url || \"\";\n if (routePath.startsWith(\"/api\")) {\n routePath = routePath.slice(4);\n }\n\n // 시스템 엔드포인트 (GET 허용)\n if (routePath === \"/__manifest\") {\n res.status(200).json(this.getManifest());\n return;\n }\n\n // POST만 허용 (시스템 엔드포인트 제외)\n if (req.method && req.method !== \"POST\") {\n res.status(405).json({ error: { code: \"METHOD_NOT_ALLOWED\", message: \"Only POST is allowed\" } });\n return;\n }\n\n // 라우트 매칭\n const route = this.matchRoute(routePath);\n if (!route) {\n res.status(404).json({ error: { code: \"NOT_FOUND\", message: `API not found: ${routePath}` } });\n return;\n }\n\n try {\n // Rate limit\n const clientKey = req.ip || \"unknown\";\n const rateLimit = route.contract.meta.rateLimit || this.options.rateLimit;\n if (rateLimit && !this.rateLimiter.check(clientKey, rateLimit)) {\n throw Errors.rateLimited();\n }\n\n // Auth 처리\n let authCtx: AuthContext | null = null;\n let reauthenticated = false;\n const authHeader = req.headers?.authorization || req.headers?.Authorization;\n\n if (authHeader && this.options.auth) {\n const token = extractBearerToken(authHeader as string);\n if (token) {\n if (route.contract.meta.reauth || route.contract.meta.auth === \"reauth\") {\n authCtx = await verifyReauth(this.options.auth, token);\n reauthenticated = true;\n } else {\n authCtx = await verifyToken(this.options.auth, token);\n }\n }\n }\n\n // 권한 체크\n if (route.contract.meta.auth) {\n checkAuthLevel(authCtx, route.contract.meta.auth, route.contract.meta.roles, reauthenticated);\n }\n\n // 입력 검증\n const rawInput = req.body || {};\n const parsed = route.contract.input.safeParse(rawInput);\n if (!parsed.success) {\n throw Errors.validation(\"Invalid input\", parsed.error.flatten());\n }\n\n // 핸들러 컨텍스트\n const ctx: HandlerContext = {\n auth: authCtx,\n reauthenticated,\n headers: req.headers as Record<string, string>,\n ip: req.ip,\n };\n\n // 미들웨어 + 핸들러 실행\n let result: unknown;\n if (this.options.middleware && this.options.middleware.length > 0) {\n result = await this.runMiddleware(\n this.options.middleware,\n parsed.data,\n ctx,\n () => route.contract.handler(parsed.data, ctx),\n );\n } else {\n result = await route.contract.handler(parsed.data, ctx);\n }\n\n // 성공 응답\n res.status(200).json({ data: result });\n } catch (err) {\n if (err instanceof ClawfireError) {\n logger.warn(`API error [${err.code}]: ${err.message}`);\n res.status(err.statusCode).json(err.toJSON());\n } else {\n logger.error(\"Unhandled error\", err);\n res.status(500).json({\n error: {\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred\",\n },\n });\n }\n }\n }\n\n /** 라우트 매칭 (동적 파라미터 지원) */\n private matchRoute(path: string): Route | undefined {\n // 정확한 매칭\n const exact = this.routes.get(path);\n if (exact) return exact;\n\n // 동적 파라미터 매칭 (/users/:id/get → /users/abc123/get)\n for (const [routePath, route] of this.routes) {\n if (this.matchDynamicRoute(routePath, path)) {\n return route;\n }\n }\n\n return undefined;\n }\n\n private matchDynamicRoute(pattern: string, actual: string): boolean {\n const patternParts = pattern.split(\"/\").filter(Boolean);\n const actualParts = actual.split(\"/\").filter(Boolean);\n if (patternParts.length !== actualParts.length) return false;\n\n return patternParts.every(\n (part, i) => part.startsWith(\":\") || part.startsWith(\"[\") || part === actualParts[i],\n );\n }\n\n /** 미들웨어 체인 실행 */\n private async runMiddleware(\n middlewares: Middleware[],\n input: unknown,\n ctx: HandlerContext,\n handler: () => Promise<unknown>,\n ): Promise<unknown> {\n let index = 0;\n const next = async (): Promise<unknown> => {\n if (index >= middlewares.length) {\n return handler();\n }\n const mw = middlewares[index++];\n return mw(input, ctx, next);\n };\n return next();\n }\n\n /** 등록된 라우트 목록 */\n getRoutes(): Route[] {\n return Array.from(this.routes.values());\n }\n\n /** 리소스 정리 */\n destroy() {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n }\n}\n\n/**\n * 라우터 생성 헬퍼\n */\nexport function createRouter(options?: RouterOptions): ClawfireRouter {\n return new ClawfireRouter(options);\n}\n","/**\n * Clawfire Route Discovery\n *\n * 파일 시스템에서 라우트 자동 발견 (빌드 타임 & 런타임)\n */\nimport { resolve, relative, join } from \"path\";\nimport { existsSync, readdirSync, statSync } from \"fs\";\n\nexport interface DiscoveredRoute {\n /** 파일 경로 (상대) */\n filePath: string;\n /** API 경로 (/products/list) */\n apiPath: string;\n /** 동적 파라미터 이름 */\n params: string[];\n}\n\n/**\n * routes 디렉터리에서 라우트 파일 자동 발견\n *\n * @param routesDir - routes 디렉터리 절대 경로\n * @returns 발견된 라우트 목록\n *\n * @example\n * ```\n * app/routes/products/list.ts → { apiPath: \"/products/list\", params: [] }\n * app/routes/products/[id]/get.ts → { apiPath: \"/products/:id/get\", params: [\"id\"] }\n * app/routes/health.ts → { apiPath: \"/health\", params: [] }\n * ```\n */\nexport function discoverRoutes(routesDir: string): DiscoveredRoute[] {\n if (!existsSync(routesDir)) {\n return [];\n }\n\n const routes: DiscoveredRoute[] = [];\n scanDirectory(routesDir, routesDir, routes);\n return routes.sort((a, b) => a.apiPath.localeCompare(b.apiPath));\n}\n\nfunction scanDirectory(baseDir: string, currentDir: string, routes: DiscoveredRoute[]): void {\n const entries = readdirSync(currentDir);\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n // 숨김 디렉터리, node_modules 무시\n if (entry.startsWith(\".\") || entry === \"node_modules\") continue;\n scanDirectory(baseDir, fullPath, routes);\n } else if (stat.isFile()) {\n // .ts, .js 파일만\n if (!entry.endsWith(\".ts\") && !entry.endsWith(\".js\")) continue;\n // index, _로 시작하는 파일 무시\n if (entry.startsWith(\"_\")) continue;\n // .d.ts 무시\n if (entry.endsWith(\".d.ts\")) continue;\n\n const relativePath = relative(baseDir, fullPath);\n const route = filePathToRoute(relativePath);\n routes.push(route);\n }\n }\n}\n\nfunction filePathToRoute(filePath: string): DiscoveredRoute {\n const params: string[] = [];\n\n // 확장자 제거\n let routePath = filePath.replace(/\\.(ts|js)$/, \"\");\n\n // Windows 경로 → POSIX\n routePath = routePath.replace(/\\\\/g, \"/\");\n\n // index 파일은 디렉터리 자체\n if (routePath.endsWith(\"/index\") || routePath === \"index\") {\n routePath = routePath.replace(/\\/?index$/, \"\");\n }\n\n // [param] → :param 변환\n routePath = routePath.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n params.push(param);\n return `:${param}`;\n });\n\n // 앞에 / 추가\n const apiPath = `/${routePath}`;\n\n return {\n filePath,\n apiPath,\n params,\n };\n}\n\n/**\n * 라우트 파일에서 import하여 라우터에 등록하는 코드 생성 (빌드 타임)\n */\nexport function generateRouteImports(routes: DiscoveredRoute[], routesDir: string): string {\n const lines: string[] = [\n '// AUTO-GENERATED by Clawfire — DO NOT EDIT',\n '// This file is regenerated whenever routes change.',\n '',\n 'import { createRouter } from \"clawfire/functions\";',\n '',\n ];\n\n routes.forEach((route, i) => {\n const importPath = `./${route.filePath.replace(/\\.(ts|js)$/, \".js\")}`;\n lines.push(`import route_${i} from \"${importPath}\";`);\n });\n\n lines.push('');\n lines.push('export function registerAllRoutes(router: ReturnType<typeof createRouter>) {');\n\n routes.forEach((route, i) => {\n lines.push(` router.register(\"${route.apiPath}\", route_${i});`);\n });\n\n lines.push(' return router;');\n lines.push('}');\n\n return lines.join('\\n');\n}\n","/**\n * Clawfire Playground\n *\n * 웹 기반 API 탐색기: API 목록, 인증 테스트, 요청/응답 뷰어\n * 단일 HTML 파일로 생성되어 Firebase Hosting에 배포됩니다.\n */\n\nexport function generatePlaygroundHtml(options?: {\n title?: string;\n apiBaseUrl?: string;\n}): string {\n const title = options?.title || \"Clawfire Playground\";\n const apiBaseUrl = options?.apiBaseUrl || \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${title}</title>\n <style>\n :root {\n --bg: #0a0a0a;\n --surface: #141414;\n --surface2: #1e1e1e;\n --border: #2a2a2a;\n --text: #e5e5e5;\n --text2: #a3a3a3;\n --accent: #f97316;\n --accent2: #fb923c;\n --green: #22c55e;\n --red: #ef4444;\n --blue: #3b82f6;\n --yellow: #eab308;\n --radius: 8px;\n --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --mono: 'JetBrains Mono', 'Fira Code', monospace;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; }\n\n .layout { display: grid; grid-template-columns: 320px 1fr; min-height: 100vh; }\n .sidebar { background: var(--surface); border-right: 1px solid var(--border); overflow-y: auto; }\n .main { padding: 24px; overflow-y: auto; }\n\n .logo { padding: 20px; border-bottom: 1px solid var(--border); }\n .logo h1 { font-size: 20px; font-weight: 700; color: var(--accent); }\n .logo p { font-size: 12px; color: var(--text2); margin-top: 4px; }\n\n .auth-section { padding: 16px; border-bottom: 1px solid var(--border); }\n .auth-section label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 6px; }\n .auth-input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: var(--radius); color: var(--text); font-family: var(--mono); font-size: 12px; }\n .auth-status { font-size: 11px; margin-top: 6px; }\n .auth-status.ok { color: var(--green); }\n .auth-status.no { color: var(--text2); }\n\n .search { padding: 12px 16px; border-bottom: 1px solid var(--border); }\n .search input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: var(--radius); color: var(--text); font-size: 13px; }\n\n .api-list { padding: 8px 0; }\n .api-group { padding: 4px 0; }\n .api-group-title { padding: 8px 16px; font-size: 11px; color: var(--text2); text-transform: uppercase;\n letter-spacing: 0.5px; font-weight: 600; }\n .api-item { padding: 8px 16px; cursor: pointer; transition: background 0.15s; display: flex; align-items: center;\n gap: 8px; font-size: 13px; }\n .api-item:hover { background: var(--surface2); }\n .api-item.active { background: var(--surface2); border-left: 2px solid var(--accent); }\n .api-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; }\n .badge-public { background: #22c55e20; color: var(--green); }\n .badge-auth { background: #3b82f620; color: var(--blue); }\n .badge-role { background: #eab30820; color: var(--yellow); }\n .badge-reauth { background: #ef444420; color: var(--red); }\n\n .panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 16px; }\n .panel-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center;\n justify-content: space-between; }\n .panel-header h2 { font-size: 16px; font-weight: 600; }\n .panel-body { padding: 16px; }\n\n .field { margin-bottom: 12px; }\n .field label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 4px; }\n .field-type { font-size: 11px; color: var(--text2); font-family: var(--mono); }\n .field-required { color: var(--red); font-size: 11px; }\n\n textarea, input[type=\"text\"] { width: 100%; padding: 10px 14px; background: var(--surface2);\n border: 1px solid var(--border); border-radius: var(--radius); color: var(--text);\n font-family: var(--mono); font-size: 13px; resize: vertical; }\n textarea { min-height: 200px; }\n\n .btn { padding: 10px 20px; border: none; border-radius: var(--radius); font-size: 14px;\n font-weight: 600; cursor: pointer; transition: all 0.15s; }\n .btn-primary { background: var(--accent); color: white; }\n .btn-primary:hover { background: var(--accent2); }\n .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }\n\n .response-section { margin-top: 16px; }\n .status-badge { font-size: 12px; padding: 4px 8px; border-radius: 4px; font-weight: 600; }\n .status-ok { background: #22c55e20; color: var(--green); }\n .status-err { background: #ef444420; color: var(--red); }\n pre { background: var(--surface2); padding: 16px; border-radius: var(--radius); overflow-x: auto;\n font-family: var(--mono); font-size: 13px; line-height: 1.5; white-space: pre-wrap; }\n\n .schema-info { font-size: 12px; color: var(--text2); line-height: 1.6; }\n .schema-info code { background: var(--surface2); padding: 2px 6px; border-radius: 4px; font-family: var(--mono);\n font-size: 11px; }\n\n .empty-state { text-align: center; padding: 80px 40px; color: var(--text2); }\n .empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }\n\n .timer { font-size: 12px; color: var(--text2); font-family: var(--mono); }\n\n @media (max-width: 768px) {\n .layout { grid-template-columns: 1fr; }\n .sidebar { max-height: 40vh; }\n }\n </style>\n</head>\n<body>\n <div class=\"layout\">\n <div class=\"sidebar\">\n <div class=\"logo\">\n <h1>Clawfire</h1>\n <p>API Playground</p>\n </div>\n <div class=\"auth-section\">\n <label>Bearer Token</label>\n <input type=\"text\" class=\"auth-input\" id=\"token-input\" placeholder=\"Paste your ID token...\">\n <div class=\"auth-status no\" id=\"auth-status\">Not authenticated</div>\n </div>\n <div class=\"search\">\n <input type=\"text\" id=\"search-input\" placeholder=\"Search APIs...\">\n </div>\n <div class=\"api-list\" id=\"api-list\"></div>\n </div>\n <div class=\"main\" id=\"main-content\">\n <div class=\"empty-state\">\n <h2>Select an API</h2>\n <p>Choose an API from the sidebar to test it.</p>\n </div>\n </div>\n </div>\n\n <script>\n const BASE_URL = ${JSON.stringify(apiBaseUrl)} || window.location.origin;\n let manifest = null;\n let selectedApi = null;\n\n async function loadManifest() {\n try {\n const res = await fetch(BASE_URL + '/api/__manifest', { method: 'POST' });\n manifest = await res.json();\n renderApiList(manifest.apis);\n } catch (e) {\n document.getElementById('api-list').innerHTML =\n '<div style=\"padding:16px;color:var(--red);font-size:13px;\">Failed to load API manifest. Make sure your server is running.</div>';\n }\n }\n\n function renderApiList(apis) {\n const groups = {};\n apis.forEach(api => {\n const parts = api.path.split('/').filter(Boolean);\n const group = parts.length > 1 ? parts[0] : 'root';\n if (!groups[group]) groups[group] = [];\n groups[group].push(api);\n });\n\n const el = document.getElementById('api-list');\n el.innerHTML = Object.entries(groups).map(([group, items]) =>\n '<div class=\"api-group\">' +\n '<div class=\"api-group-title\">' + group + '</div>' +\n items.map(api => {\n const auth = api.meta.auth || 'public';\n const badgeClass = 'badge-' + auth;\n return '<div class=\"api-item\" onclick=\"selectApi(\\\\'' + api.path + '\\\\')\">' +\n '<span class=\"api-badge ' + badgeClass + '\">' + auth.toUpperCase() + '</span>' +\n '<span>' + api.path + '</span>' +\n '</div>';\n }).join('') +\n '</div>'\n ).join('');\n }\n\n function selectApi(path) {\n selectedApi = manifest.apis.find(a => a.path === path);\n if (!selectedApi) return;\n\n document.querySelectorAll('.api-item').forEach(el => el.classList.remove('active'));\n event.currentTarget?.classList.add('active');\n\n const main = document.getElementById('main-content');\n const exampleInput = selectedApi.meta.exampleInput\n ? JSON.stringify(selectedApi.meta.exampleInput, null, 2)\n : generateExampleFromSchema(selectedApi.inputSchema);\n\n main.innerHTML =\n '<div class=\"panel\">' +\n '<div class=\"panel-header\">' +\n '<h2>POST ' + selectedApi.path + '</h2>' +\n '<span class=\"api-badge badge-' + (selectedApi.meta.auth || 'public') + '\">' +\n (selectedApi.meta.auth || 'public').toUpperCase() + '</span>' +\n '</div>' +\n '<div class=\"panel-body\">' +\n '<p style=\"color:var(--text2);margin-bottom:16px;\">' + (selectedApi.meta.description || '') + '</p>' +\n (selectedApi.meta.tags ? '<div style=\"margin-bottom:12px;\">' + selectedApi.meta.tags.map(t =>\n '<span style=\"background:var(--surface2);padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px;\">' + t + '</span>'\n ).join('') + '</div>' : '') +\n '<div class=\"schema-info\" style=\"margin-bottom:16px;\">' +\n '<strong>Input Schema:</strong><br>' + renderSchemaInfo(selectedApi.inputSchema) +\n '</div>' +\n '<div class=\"schema-info\" style=\"margin-bottom:16px;\">' +\n '<strong>Output Schema:</strong><br>' + renderSchemaInfo(selectedApi.outputSchema) +\n '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"panel\">' +\n '<div class=\"panel-header\"><h2>Request</h2></div>' +\n '<div class=\"panel-body\">' +\n '<textarea id=\"req-body\" placeholder=\"Request JSON body\">' + exampleInput + '</textarea>' +\n '<div style=\"margin-top:12px;display:flex;align-items:center;gap:12px;\">' +\n '<button class=\"btn btn-primary\" onclick=\"sendRequest()\">Send Request</button>' +\n '<span class=\"timer\" id=\"timer\"></span>' +\n '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"response-section\" id=\"response-section\"></div>';\n }\n\n function renderSchemaInfo(schema) {\n if (!schema || !schema.properties) return '<code>void</code>';\n return Object.entries(schema.properties).map(([key, prop]) => {\n const required = schema.required?.includes(key);\n const type = prop.type || 'unknown';\n const enumVals = prop.enum ? ' (' + prop.enum.join(', ') + ')' : '';\n return '<code>' + key + '</code>: <span class=\"field-type\">' + type + enumVals + '</span>' +\n (required ? ' <span class=\"field-required\">required</span>' : ' <span style=\"color:var(--text2);font-size:11px;\">optional</span>');\n }).join('<br>');\n }\n\n function generateExampleFromSchema(schema) {\n if (!schema || !schema.properties) return '{}';\n const obj = {};\n for (const [key, prop] of Object.entries(schema.properties)) {\n if (prop.enum) { obj[key] = prop.enum[0]; continue; }\n switch (prop.type) {\n case 'string': obj[key] = prop.format === 'email' ? 'user@example.com' : 'string'; break;\n case 'number': obj[key] = 0; break;\n case 'boolean': obj[key] = false; break;\n case 'array': obj[key] = []; break;\n case 'object': obj[key] = {}; break;\n default: obj[key] = null;\n }\n }\n return JSON.stringify(obj, null, 2);\n }\n\n async function sendRequest() {\n if (!selectedApi) return;\n const body = document.getElementById('req-body').value;\n const token = document.getElementById('token-input').value;\n const timer = document.getElementById('timer');\n const section = document.getElementById('response-section');\n\n let parsed;\n try { parsed = JSON.parse(body); } catch {\n section.innerHTML = '<div class=\"panel\"><div class=\"panel-body\"><pre style=\"color:var(--red)\">Invalid JSON</pre></div></div>';\n return;\n }\n\n const start = performance.now();\n timer.textContent = 'Sending...';\n\n try {\n const headers = { 'Content-Type': 'application/json' };\n if (token) headers['Authorization'] = 'Bearer ' + token;\n\n const res = await fetch(BASE_URL + '/api' + selectedApi.path, {\n method: 'POST', headers, body: JSON.stringify(parsed)\n });\n const elapsed = Math.round(performance.now() - start);\n timer.textContent = elapsed + 'ms';\n\n const json = await res.json();\n const isOk = res.ok;\n\n section.innerHTML =\n '<div class=\"panel\">' +\n '<div class=\"panel-header\">' +\n '<h2>Response</h2>' +\n '<span class=\"status-badge ' + (isOk ? 'status-ok' : 'status-err') + '\">' +\n res.status + ' ' + res.statusText + '</span>' +\n '</div>' +\n '<div class=\"panel-body\"><pre>' + syntaxHighlight(JSON.stringify(json, null, 2)) + '</pre></div>' +\n '</div>';\n } catch (e) {\n const elapsed = Math.round(performance.now() - start);\n timer.textContent = elapsed + 'ms';\n section.innerHTML =\n '<div class=\"panel\"><div class=\"panel-body\"><pre style=\"color:var(--red)\">Network error: ' + e.message + '</pre></div></div>';\n }\n }\n\n function syntaxHighlight(json) {\n return json.replace(/(\"(\\\\\\\\u[a-fA-F0-9]{4}|\\\\\\\\[^u]|[^\\\\\\\\\"])*\"(\\\\s*:)?)|\\\\b(true|false|null)\\\\b|-?\\\\d+(\\\\.\\\\d+)?([eE][+-]?\\\\d+)?/g,\n function(match) {\n let cls = 'color:#eab308';\n if (/^\"/.test(match)) {\n if (/:$/.test(match)) cls = 'color:#3b82f6';\n else cls = 'color:#22c55e';\n } else if (/true|false/.test(match)) cls = 'color:#f97316';\n else if (/null/.test(match)) cls = 'color:#ef4444';\n return '<span style=\"' + cls + '\">' + match + '</span>';\n }\n );\n }\n\n // Search\n document.getElementById('search-input')?.addEventListener('input', (e) => {\n if (!manifest) return;\n const q = e.target.value.toLowerCase();\n const filtered = manifest.apis.filter(a => a.path.toLowerCase().includes(q) || a.meta.description?.toLowerCase().includes(q));\n renderApiList(filtered);\n });\n\n // Token status\n document.getElementById('token-input')?.addEventListener('input', (e) => {\n const el = document.getElementById('auth-status');\n if (e.target.value) {\n el.textContent = 'Token set';\n el.className = 'auth-status ok';\n } else {\n el.textContent = 'Not authenticated';\n el.className = 'auth-status no';\n }\n });\n\n loadManifest();\n </script>\n</body>\n</html>`;\n}\n","/**\n * Clawfire File Watcher\n *\n * Node.js fs.watch 기반 파일 변경 감지 (외부 의존성 없음)\n * macOS/Windows에서 recursive 지원, Linux에서는 디렉터리별 개별 감시\n */\nimport { watch, existsSync, statSync, readdirSync } from \"fs\";\nimport { join, extname } from \"path\";\nimport { EventEmitter } from \"events\";\n\nexport type WatchEventType = \"route-change\" | \"schema-change\" | \"config-change\" | \"frontend-change\" | \"css-change\" | \"page-change\" | \"component-change\" | \"any-change\";\n\nexport interface WatchEvent {\n type: WatchEventType;\n filePath: string;\n timestamp: number;\n}\n\nexport class FileWatcher extends EventEmitter {\n private watchers: ReturnType<typeof watch>[] = [];\n private debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n private debounceMs: number;\n\n constructor(debounceMs = 150) {\n super();\n this.debounceMs = debounceMs;\n }\n\n /**\n * 디렉터리 감시 시작\n */\n watchDir(dir: string, eventType: WatchEventType): this {\n if (!existsSync(dir)) return this;\n\n try {\n // macOS/Windows: recursive 옵션 지원\n const watcher = watch(dir, { recursive: true }, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n this.emitDebounced(filePath, eventType);\n });\n\n watcher.on(\"error\", (err) => {\n // Linux에서 recursive 미지원 시 fallback\n if ((err as any).code === \"ERR_FEATURE_UNAVAILABLE_ON_PLATFORM\") {\n this.watchDirRecursiveManual(dir, eventType);\n }\n });\n\n this.watchers.push(watcher);\n } catch {\n // fallback: 수동 재귀 감시\n this.watchDirRecursiveManual(dir, eventType);\n }\n\n return this;\n }\n\n /**\n * 단일 파일 감시\n */\n watchFile(filePath: string, eventType: WatchEventType): this {\n if (!existsSync(filePath)) return this;\n\n const watcher = watch(filePath, (event) => {\n this.emitDebounced(filePath, eventType);\n });\n\n this.watchers.push(watcher);\n return this;\n }\n\n /**\n * 프론트엔드 디렉터리 감시 (확장자별 이벤트 타입 자동 결정)\n */\n watchDirFrontend(dir: string): this {\n if (!existsSync(dir)) return this;\n\n try {\n const watcher = watch(dir, { recursive: true }, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n const ext = extname(filePath);\n const eventType: WatchEventType = ext === \".css\" ? \"css-change\" : \"frontend-change\";\n this.emitDebounced(filePath, eventType);\n });\n\n watcher.on(\"error\", (err) => {\n if ((err as any).code === \"ERR_FEATURE_UNAVAILABLE_ON_PLATFORM\") {\n this.watchDirFrontendRecursiveManual(dir);\n }\n });\n\n this.watchers.push(watcher);\n } catch {\n this.watchDirFrontendRecursiveManual(dir);\n }\n\n return this;\n }\n\n /**\n * Linux fallback: 프론트엔드 디렉터리 수동 재귀 감시\n */\n private watchDirFrontendRecursiveManual(dir: string): void {\n if (!existsSync(dir)) return;\n\n const watcher = watch(dir, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n const ext = extname(filePath);\n const eventType: WatchEventType = ext === \".css\" ? \"css-change\" : \"frontend-change\";\n this.emitDebounced(filePath, eventType);\n });\n this.watchers.push(watcher);\n\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith(\".\") && entry.name !== \"node_modules\") {\n this.watchDirFrontendRecursiveManual(join(dir, entry.name));\n }\n }\n } catch {}\n }\n\n /**\n * Linux fallback: 디렉터리 수동 재귀 감시\n */\n private watchDirRecursiveManual(dir: string, eventType: WatchEventType): void {\n if (!existsSync(dir)) return;\n\n const watcher = watch(dir, (event, filename) => {\n if (!filename) return;\n const filePath = join(dir, filename);\n if (!this.isWatchedFile(filePath)) return;\n this.emitDebounced(filePath, eventType);\n });\n this.watchers.push(watcher);\n\n // 서브디렉터리도 감시\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith(\".\") && entry.name !== \"node_modules\") {\n this.watchDirRecursiveManual(join(dir, entry.name), eventType);\n }\n }\n } catch {}\n }\n\n /**\n * 감시 대상 파일인지 확인\n */\n private isWatchedFile(filePath: string): boolean {\n const ext = extname(filePath);\n return [\".ts\", \".tsx\", \".js\", \".jsx\", \".json\", \".html\", \".css\", \".svg\", \".png\", \".jpg\", \".jpeg\", \".gif\", \".ico\", \".webp\", \".woff\", \".woff2\"].includes(ext);\n }\n\n /**\n * 디바운스 이벤트 발생\n */\n private emitDebounced(filePath: string, type: WatchEventType): void {\n const existing = this.debounceTimers.get(filePath);\n if (existing) clearTimeout(existing);\n\n this.debounceTimers.set(\n filePath,\n setTimeout(() => {\n this.debounceTimers.delete(filePath);\n const event: WatchEvent = { type, filePath, timestamp: Date.now() };\n this.emit(\"change\", event);\n this.emit(type, event);\n }, this.debounceMs),\n );\n }\n\n /**\n * 모든 감시 중지\n */\n close(): void {\n for (const watcher of this.watchers) {\n watcher.close();\n }\n this.watchers = [];\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer);\n }\n this.debounceTimers.clear();\n this.removeAllListeners();\n }\n}\n","/**\n * Clawfire Page Compiler\n *\n * File-based page routing with layouts and components.\n * Resolves URLs to page files, processes components (<c-name />),\n * applies nested layouts (<slot />), and extracts metadata.\n */\nimport { resolve, join, relative, dirname, basename, extname } from \"node:path\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface PageMeta {\n title?: string;\n description?: string;\n [key: string]: string | undefined;\n}\n\nexport interface CompiledPage {\n html: string;\n meta: PageMeta;\n filePath: string;\n layouts: string[];\n}\n\nexport interface PartialPage {\n html: string;\n meta: PageMeta;\n path: string;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────\n\nconst MAX_COMPONENT_DEPTH = 10;\nconst META_REGEX = /<!--\\s*@(\\w+):\\s*(.+?)\\s*-->/g;\nconst COMPONENT_REGEX = /<c-([a-z][a-z0-9-]*)\\s*\\/>/g;\nconst SLOT_MARKER = \"<slot />\";\n\n// ─── Page Compiler ───────────────────────────────────────────────────\n\nexport class PageCompiler {\n private pagesDir: string;\n private componentsDir: string;\n\n constructor(private projectDir: string) {\n this.pagesDir = resolve(projectDir, \"app/pages\");\n this.componentsDir = resolve(projectDir, \"app/components\");\n }\n\n /**\n * Check if the page system is active (app/pages/ exists)\n */\n isActive(): boolean {\n return existsSync(this.pagesDir);\n }\n\n /**\n * Resolve a URL pathname to a page file path.\n * Returns null if no matching page exists.\n *\n * / → app/pages/index.html\n * /about → app/pages/about.html OR app/pages/about/index.html\n * /todos → app/pages/todos/index.html OR app/pages/todos.html\n */\n resolve(pathname: string): string | null {\n if (!this.isActive()) return null;\n\n // Normalize: strip trailing slash, default to /\n const clean = pathname === \"/\" ? \"\" : pathname.replace(/\\/+$/, \"\");\n const segments = clean ? clean.split(\"/\").filter(Boolean) : [];\n\n // Build candidate paths\n const candidates: string[] = [];\n\n if (segments.length === 0) {\n candidates.push(join(this.pagesDir, \"index.html\"));\n } else {\n const pathPart = segments.join(\"/\");\n // Try direct file first, then directory/index\n candidates.push(join(this.pagesDir, `${pathPart}.html`));\n candidates.push(join(this.pagesDir, pathPart, \"index.html\"));\n }\n\n for (const candidate of candidates) {\n // Security: must be within pagesDir\n if (!candidate.startsWith(this.pagesDir)) continue;\n // Skip layout and special files\n const name = basename(candidate);\n if (name.startsWith(\"_\")) continue;\n if (existsSync(candidate)) return candidate;\n }\n\n return null;\n }\n\n /**\n * Resolve the 404 page file path.\n */\n resolve404(): string | null {\n const path404 = join(this.pagesDir, \"_404.html\");\n return existsSync(path404) ? path404 : null;\n }\n\n /**\n * Compile a full page with layouts and components.\n */\n compile(pagePath: string): CompiledPage {\n // 1. Read page file\n let pageHtml = readFileSync(pagePath, \"utf-8\");\n\n // 2. Extract metadata\n const meta = this.extractMeta(pageHtml);\n pageHtml = this.stripMeta(pageHtml);\n\n // 3. Process components in page\n pageHtml = this.processComponents(pageHtml);\n\n // 4. Wrap in page container div\n pageHtml = this.wrapPageContent(pageHtml);\n\n // 5. Collect layout chain\n const layouts = this.collectLayouts(pagePath);\n\n // 6. Apply layouts (innermost first)\n let html = pageHtml;\n for (const layoutPath of layouts) {\n let layoutHtml = readFileSync(layoutPath, \"utf-8\");\n layoutHtml = this.processComponents(layoutHtml);\n html = layoutHtml.replace(SLOT_MARKER, html);\n }\n\n // 7. Set <title> from metadata\n if (meta.title) {\n html = this.setTitle(html, meta.title);\n }\n\n return {\n html,\n meta,\n filePath: pagePath,\n layouts,\n };\n }\n\n /**\n * Compile a partial page for SPA navigation.\n * Returns only the page content (no layout) plus metadata.\n */\n compilePartial(pagePath: string, pathname: string): PartialPage {\n let pageHtml = readFileSync(pagePath, \"utf-8\");\n const meta = this.extractMeta(pageHtml);\n pageHtml = this.stripMeta(pageHtml);\n pageHtml = this.processComponents(pageHtml);\n\n return {\n html: pageHtml,\n meta,\n path: pathname,\n };\n }\n\n // ─── Internal Methods ────────────────────────────────────────────\n\n /**\n * Extract <!-- @key: value --> metadata from HTML.\n */\n private extractMeta(html: string): PageMeta {\n const meta: PageMeta = {};\n let match: RegExpExecArray | null;\n const regex = new RegExp(META_REGEX.source, META_REGEX.flags);\n while ((match = regex.exec(html)) !== null) {\n meta[match[1]] = match[2];\n }\n return meta;\n }\n\n /**\n * Strip metadata comments from HTML.\n */\n private stripMeta(html: string): string {\n return html.replace(META_REGEX, \"\").trim();\n }\n\n /**\n * Process <c-name /> component tags by replacing them with component file contents.\n * Supports nested components up to MAX_COMPONENT_DEPTH.\n */\n private processComponents(html: string, depth = 0): string {\n if (depth >= MAX_COMPONENT_DEPTH) return html;\n if (!COMPONENT_REGEX.test(html)) return html;\n\n // Reset regex state\n const regex = new RegExp(COMPONENT_REGEX.source, COMPONENT_REGEX.flags);\n\n const result = html.replace(regex, (_match, name: string) => {\n const componentPath = join(this.componentsDir, `${name}.html`);\n if (!existsSync(componentPath)) {\n return `<!-- component \"${name}\" not found -->`;\n }\n const content = readFileSync(componentPath, \"utf-8\").trim();\n // Recursively process nested components\n return this.processComponents(content, depth + 1);\n });\n\n return result;\n }\n\n /**\n * Wrap page content in the router target div.\n */\n private wrapPageContent(html: string): string {\n return `<div id=\"clawfire-page\">\\n${html}\\n</div>`;\n }\n\n /**\n * Collect layout files from page directory up to pages root.\n * Returns innermost layout first (closest to page).\n */\n private collectLayouts(pagePath: string): string[] {\n const layouts: string[] = [];\n let dir = dirname(pagePath);\n\n while (dir.startsWith(this.pagesDir)) {\n const layoutPath = join(dir, \"_layout.html\");\n if (existsSync(layoutPath)) {\n layouts.push(layoutPath);\n }\n // Stop at pages root\n if (dir === this.pagesDir) break;\n dir = dirname(dir);\n }\n\n // Reverse: outermost layout applied last (wraps everything)\n // We want to reduce from innermost out, so reverse:\n // page → innermost layout's <slot /> → outer layout's <slot />\n // The array is already [innermost, ..., outermost] from the walk.\n // We apply innermost first, which means page goes into innermost <slot />,\n // then that result goes into the next outer layout's <slot />, etc.\n return layouts;\n }\n\n /**\n * Set or update <title> tag in HTML.\n */\n private setTitle(html: string, title: string): string {\n if (html.includes(\"<title>\")) {\n return html.replace(/<title>[^<]*<\\/title>/, `<title>${title}</title>`);\n }\n if (html.includes(\"</head>\")) {\n return html.replace(\"</head>\", ` <title>${title}</title>\\n</head>`);\n }\n return html;\n }\n}\n","/**\n * Clawfire Environment Variable Manager\n *\n * Manages .env file and .env.descriptions.json for the dev dashboard.\n * Preserves comments and ordering in .env files.\n */\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface EnvVariable {\n key: string;\n value: string;\n description: string;\n isPlaceholder: boolean;\n line: number;\n}\n\nexport interface EnvData {\n variables: EnvVariable[];\n descriptions: Record<string, string>;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────\n\nconst KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/i;\nconst PLACEHOLDER_PATTERNS = [\n /^YOUR_/i,\n /^CHANGE_ME$/i,\n /^TODO$/i,\n /^REPLACE_/i,\n /^XXX/i,\n /^$/,\n];\n\n// ─── EnvManager ──────────────────────────────────────────────────────\n\nexport class EnvManager {\n private envPath: string;\n private descriptionsPath: string;\n\n constructor(projectDir: string) {\n this.envPath = resolve(projectDir, \".env\");\n this.descriptionsPath = resolve(projectDir, \".env.descriptions.json\");\n }\n\n /** Read all env variables with descriptions and placeholder detection */\n read(): EnvData {\n const descriptions = this.readDescriptions();\n const variables: EnvVariable[] = [];\n\n if (!existsSync(this.envPath)) {\n return { variables, descriptions };\n }\n\n const content = readFileSync(this.envPath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n // Skip empty lines and comments\n if (!line || line.startsWith(\"#\")) continue;\n\n const eqIdx = line.indexOf(\"=\");\n if (eqIdx === -1) continue;\n\n const key = line.slice(0, eqIdx).trim();\n const value = line.slice(eqIdx + 1).trim();\n\n if (!KEY_PATTERN.test(key)) continue;\n\n variables.push({\n key,\n value,\n description: descriptions[key] || \"\",\n isPlaceholder: isPlaceholder(value),\n line: i + 1,\n });\n }\n\n return { variables, descriptions };\n }\n\n /** Set or update an env variable */\n set(key: string, value: string, description?: string): void {\n if (!KEY_PATTERN.test(key)) {\n throw new Error(`Invalid key: \"${key}\" — must match ${KEY_PATTERN}`);\n }\n\n // Update .env file\n let lines: string[] = [];\n if (existsSync(this.envPath)) {\n lines = readFileSync(this.envPath, \"utf-8\").split(\"\\n\");\n }\n\n let found = false;\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (trimmed.startsWith(\"#\") || !trimmed) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx === -1) continue;\n const existingKey = trimmed.slice(0, eqIdx).trim();\n if (existingKey === key) {\n lines[i] = `${key}=${value}`;\n found = true;\n break;\n }\n }\n\n if (!found) {\n // Add to end, ensure trailing newline separation\n if (lines.length > 0 && lines[lines.length - 1].trim() !== \"\") {\n lines.push(`${key}=${value}`);\n } else {\n // Replace trailing empty line\n lines.push(`${key}=${value}`);\n }\n }\n\n writeFileSync(this.envPath, lines.join(\"\\n\"), \"utf-8\");\n\n // Update description if provided\n if (description !== undefined) {\n const descriptions = this.readDescriptions();\n descriptions[key] = description;\n this.writeDescriptions(descriptions);\n }\n }\n\n /** Delete an env variable */\n delete(key: string): void {\n if (!existsSync(this.envPath)) return;\n\n const lines = readFileSync(this.envPath, \"utf-8\").split(\"\\n\");\n const filtered = lines.filter((line) => {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\") || !trimmed) return true;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx === -1) return true;\n return trimmed.slice(0, eqIdx).trim() !== key;\n });\n\n writeFileSync(this.envPath, filtered.join(\"\\n\"), \"utf-8\");\n\n // Remove description\n const descriptions = this.readDescriptions();\n if (key in descriptions) {\n delete descriptions[key];\n this.writeDescriptions(descriptions);\n }\n }\n\n // ─── Private ─────────────────────────────────────────────────────\n\n private readDescriptions(): Record<string, string> {\n if (!existsSync(this.descriptionsPath)) return {};\n try {\n return JSON.parse(readFileSync(this.descriptionsPath, \"utf-8\"));\n } catch {\n return {};\n }\n }\n\n private writeDescriptions(descriptions: Record<string, string>): void {\n writeFileSync(\n this.descriptionsPath,\n JSON.stringify(descriptions, null, 2) + \"\\n\",\n \"utf-8\",\n );\n }\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction isPlaceholder(value: string): boolean {\n return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));\n}\n","/**\n * Clawfire Firebase Status Checker\n *\n * Checks Firebase project health via file inspection and CLI commands.\n * Results are cached for 30 seconds to avoid repeated CLI calls.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execFile } from \"node:child_process\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport type ServiceStatus = \"configured\" | \"placeholder\" | \"missing\";\n\nexport interface ServiceInfo {\n name: string;\n status: ServiceStatus;\n detail?: string;\n}\n\nexport interface FirebaseEnvironmentStatus {\n /** CLI info */\n cli: {\n installed: boolean;\n version: string;\n authenticated: boolean;\n user: string;\n };\n /** Active Firebase project */\n project: {\n id: string;\n hasFirebaseJson: boolean;\n };\n /** Per-service status */\n services: ServiceInfo[];\n /** Config placeholder warnings */\n configWarnings: string[];\n /** Timestamp of this check */\n timestamp: number;\n}\n\n// ─── Cache ───────────────────────────────────────────────────────────\n\nlet cachedStatus: FirebaseEnvironmentStatus | null = null;\nlet cacheTime = 0;\nconst CACHE_TTL = 30_000; // 30 seconds\n\n// ─── Main ────────────────────────────────────────────────────────────\n\nexport async function checkFirebaseStatus(\n projectDir: string,\n): Promise<FirebaseEnvironmentStatus> {\n const now = Date.now();\n if (cachedStatus && now - cacheTime < CACHE_TTL) {\n return cachedStatus;\n }\n\n const [fileStatus, cliStatus] = await Promise.all([\n checkFiles(projectDir),\n checkCli(projectDir),\n ]);\n\n const status: FirebaseEnvironmentStatus = {\n cli: cliStatus,\n project: fileStatus.project,\n services: fileStatus.services,\n configWarnings: fileStatus.configWarnings,\n timestamp: now,\n };\n\n cachedStatus = status;\n cacheTime = now;\n return status;\n}\n\n/** Clear the status cache (useful after config changes) */\nexport function clearFirebaseStatusCache(): void {\n cachedStatus = null;\n cacheTime = 0;\n}\n\n// ─── File Checks (sync, fast) ────────────────────────────────────────\n\ninterface FileCheckResult {\n project: { id: string; hasFirebaseJson: boolean };\n services: ServiceInfo[];\n configWarnings: string[];\n}\n\nfunction checkFiles(projectDir: string): FileCheckResult {\n const services: ServiceInfo[] = [];\n const configWarnings: string[] = [];\n\n // firebase.json\n const firebaseJsonPath = resolve(projectDir, \"firebase.json\");\n let hasFirebaseJson = false;\n let firebaseConfig: Record<string, unknown> = {};\n\n if (existsSync(firebaseJsonPath)) {\n hasFirebaseJson = true;\n try {\n firebaseConfig = JSON.parse(readFileSync(firebaseJsonPath, \"utf-8\"));\n } catch {\n // invalid JSON\n }\n }\n\n // Hosting\n if (firebaseConfig.hosting) {\n services.push({ name: \"Hosting\", status: \"configured\", detail: \"firebase.json\" });\n } else {\n services.push({ name: \"Hosting\", status: \"missing\" });\n }\n\n // Firestore\n const firestoreRulesPath = resolve(projectDir, \"firestore.rules\");\n if (firebaseConfig.firestore) {\n if (existsSync(firestoreRulesPath)) {\n services.push({ name: \"Firestore\", status: \"configured\", detail: \"Rules file found\" });\n } else {\n services.push({ name: \"Firestore\", status: \"placeholder\", detail: \"Configured but no rules file\" });\n }\n } else {\n services.push({ name: \"Firestore\", status: \"missing\" });\n }\n\n // Functions\n const functionsIndexPath = resolve(projectDir, \"functions/index.ts\");\n if (firebaseConfig.functions) {\n if (existsSync(functionsIndexPath)) {\n services.push({ name: \"Functions\", status: \"configured\", detail: \"functions/index.ts found\" });\n } else {\n services.push({ name: \"Functions\", status: \"placeholder\", detail: \"Configured but no entry file\" });\n }\n } else {\n services.push({ name: \"Functions\", status: \"missing\" });\n }\n\n // Storage\n const storageRulesPath = resolve(projectDir, \"storage.rules\");\n if (firebaseConfig.storage) {\n if (existsSync(storageRulesPath)) {\n services.push({ name: \"Storage\", status: \"configured\", detail: \"Rules file found\" });\n } else {\n services.push({ name: \"Storage\", status: \"placeholder\", detail: \"Configured but no rules file\" });\n }\n } else {\n services.push({ name: \"Storage\", status: \"missing\" });\n }\n\n // Firestore indexes\n const indexesPath = resolve(projectDir, \"firestore.indexes.json\");\n if (existsSync(indexesPath)) {\n services.push({ name: \"Indexes\", status: \"configured\", detail: \"firestore.indexes.json\" });\n } else if (firebaseConfig.firestore) {\n services.push({ name: \"Indexes\", status: \"placeholder\", detail: \"No indexes file\" });\n }\n\n // clawfire.config.ts — check for placeholder values\n const configPath = resolve(projectDir, \"clawfire.config.ts\");\n if (existsSync(configPath)) {\n try {\n const configContent = readFileSync(configPath, \"utf-8\");\n const placeholderMatches = configContent.match(/YOUR_[A-Z_]+/g);\n if (placeholderMatches) {\n for (const match of new Set(placeholderMatches)) {\n configWarnings.push(`Placeholder found: ${match}`);\n }\n }\n } catch {\n // ignore read errors\n }\n }\n\n // Project ID from .firebaserc\n let projectId = \"\";\n const firebasercPath = resolve(projectDir, \".firebaserc\");\n if (existsSync(firebasercPath)) {\n try {\n const rc = JSON.parse(readFileSync(firebasercPath, \"utf-8\"));\n projectId = rc?.projects?.default || \"\";\n } catch {\n // invalid JSON\n }\n }\n\n return {\n project: { id: projectId, hasFirebaseJson },\n services,\n configWarnings,\n };\n}\n\n// ─── CLI Checks (async, with timeouts) ───────────────────────────────\n\ninterface CliCheckResult {\n installed: boolean;\n version: string;\n authenticated: boolean;\n user: string;\n}\n\nasync function checkCli(projectDir: string): Promise<CliCheckResult> {\n const result: CliCheckResult = {\n installed: false,\n version: \"\",\n authenticated: false,\n user: \"\",\n };\n\n // Check firebase --version\n try {\n const version = await execWithTimeout(\"firebase\", [\"--version\"], projectDir, 5000);\n result.installed = true;\n result.version = version.trim();\n } catch {\n return result; // If CLI not installed, skip auth check\n }\n\n // Check firebase login status\n try {\n const loginOutput = await execWithTimeout(\n \"firebase\",\n [\"login:list\", \"--json\"],\n projectDir,\n 5000,\n );\n const loginData = JSON.parse(loginOutput);\n if (loginData?.result && Array.isArray(loginData.result) && loginData.result.length > 0) {\n result.authenticated = true;\n result.user = loginData.result[0]?.user?.email || loginData.result[0]?.email || \"\";\n }\n } catch {\n // Not authenticated or command failed\n }\n\n return result;\n}\n\n// ─── Firebase SDK Config (auto-fill) ─────────────────────────────────\n\nexport interface FirebaseSdkConfig {\n apiKey?: string;\n authDomain?: string;\n projectId?: string;\n storageBucket?: string;\n messagingSenderId?: string;\n appId?: string;\n}\n\n/**\n * Fetch Firebase web SDK config via CLI.\n * Requires: firebase CLI installed, logged in, active project set.\n *\n * Strategy:\n * 1. Try `firebase apps:sdkconfig web --json` (works if exactly 1 web app)\n * 2. If that fails, list all apps to find web app IDs\n * 3. If web apps found, retry with specific app ID\n * 4. If no web apps, return clear error\n */\nexport async function fetchFirebaseSdkConfig(\n projectDir: string,\n): Promise<FirebaseSdkConfig> {\n // Step 1: Try direct sdkconfig (works if project has exactly 1 web app)\n try {\n const output = await execWithTimeout(\n \"firebase\",\n [\"apps:sdkconfig\", \"web\", \"--json\"],\n projectDir,\n 15000,\n );\n\n const config = parseSdkConfigOutput(output);\n if (config) return config;\n } catch {\n // Fall through to Step 2\n }\n\n // Step 2: List all apps to find web app IDs\n let webApps: Array<{ appId: string; displayName: string }> = [];\n try {\n const appsOutput = await execWithTimeout(\n \"firebase\",\n [\"apps:list\", \"--json\"],\n projectDir,\n 15000,\n );\n const appsData = JSON.parse(appsOutput);\n const allApps = appsData?.result || [];\n webApps = allApps\n .filter((a: Record<string, unknown>) => a.platform === \"WEB\")\n .map((a: Record<string, unknown>) => ({\n appId: String(a.appId || \"\"),\n displayName: String(a.displayName || \"\"),\n }))\n .filter((a: { appId: string }) => a.appId);\n } catch {\n // Can't list apps — give generic error\n throw new Error(\n \"Failed to fetch Firebase SDK config. \" +\n \"Make sure you are logged in and have an active project selected.\"\n );\n }\n\n // Step 3: No web apps found\n if (webApps.length === 0) {\n throw new Error(\n \"No web app registered in this Firebase project. \" +\n \"Go to Firebase Console > Project Settings > General > Your apps > Add app (Web) to create one, \" +\n \"then click Auto-fill again.\"\n );\n }\n\n // Step 4: Retry with specific app ID\n const appId = webApps[0].appId;\n try {\n const output = await execWithTimeout(\n \"firebase\",\n [\"apps:sdkconfig\", \"web\", appId, \"--json\"],\n projectDir,\n 15000,\n );\n const config = parseSdkConfigOutput(output);\n if (config) return config;\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);\n }\n\n throw new Error(\"Could not parse Firebase SDK config from CLI output\");\n}\n\n/** Parse the output of `firebase apps:sdkconfig` */\nfunction parseSdkConfigOutput(output: string): FirebaseSdkConfig | null {\n try {\n const data = JSON.parse(output);\n\n // Firebase CLI returns { status: \"success\", result: { sdkConfig: { ... } } }\n if (data?.result?.sdkConfig) {\n return data.result.sdkConfig;\n }\n\n // Some CLI versions return fileContents with the config object as JS\n if (data?.result?.fileContents) {\n const contents = data.result.fileContents as string;\n const config: FirebaseSdkConfig = {};\n const extract = (key: string) => {\n const match = contents.match(new RegExp(`\"${key}\"\\\\s*:\\\\s*\"([^\"]+)\"`));\n return match ? match[1] : undefined;\n };\n config.apiKey = extract(\"apiKey\");\n config.authDomain = extract(\"authDomain\");\n config.projectId = extract(\"projectId\");\n config.storageBucket = extract(\"storageBucket\");\n config.messagingSenderId = extract(\"messagingSenderId\");\n config.appId = extract(\"appId\");\n if (config.apiKey || config.projectId) return config;\n }\n } catch {\n // JSON parse failed\n }\n return null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction execWithTimeout(\n command: string,\n args: string[],\n cwd: string,\n timeoutMs: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout, stderr) => {\n if (err) {\n // Include stderr in error message for better debugging\n const detail = stderr?.trim() || stdout?.trim() || \"\";\n const enriched = new Error(`${err.message}${detail ? \"\\n\" + detail : \"\"}`);\n reject(enriched);\n } else {\n resolve(stdout);\n }\n });\n // Extra safety: kill if timeout is reached\n const timer = setTimeout(() => {\n proc.kill(\"SIGTERM\");\n reject(new Error(\"Timeout\"));\n }, timeoutMs + 500);\n proc.on(\"exit\", () => clearTimeout(timer));\n });\n}\n","/**\n * Clawfire Dev Dashboard HTML Generator\n *\n * Generates the Dashboard tab content for the dev server.\n * Sections: Firebase Setup Wizard, Firebase Status, Config Overview (editable), Environment Variables.\n * All CSS/JS is inline — no external dependencies.\n * Data is lazy-loaded from /__dev/* endpoints.\n */\n\nexport interface DashboardHtmlOptions {\n apiPort: number;\n}\n\nexport function generateDashboardHtml(options: DashboardHtmlOptions): string {\n const { apiPort } = options;\n\n return `\n<div id=\"dashboard-content\" style=\"padding:24px;max-width:1200px;margin:0 auto;font-family:var(--font, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);\">\n\n <!-- Loading State -->\n <div id=\"dash-loading\" style=\"text-align:center;padding:60px 0;color:#a3a3a3;\">\n <div style=\"font-size:24px;margin-bottom:8px;\">Loading dashboard...</div>\n </div>\n\n <!-- Dashboard Sections (hidden until loaded) -->\n <div id=\"dash-loaded\" style=\"display:none;\">\n\n <!-- Section 0: Firebase Setup Wizard -->\n <div id=\"setup-wizard\" style=\"margin-bottom:32px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;\">Firebase Setup</h2>\n <div id=\"setup-card\" style=\"padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;\">\n\n <!-- Step indicators -->\n <div id=\"setup-steps\" style=\"display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;\">\n <div id=\"step-ind-1\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#1a1a2e;color:#f97316;border-right:1px solid #2a2a2a;\">\n 1. CLI Install\n </div>\n <div id=\"step-ind-2\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;\">\n 2. Login\n </div>\n <div id=\"step-ind-3\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;\">\n 3. Select Project\n </div>\n <div id=\"step-ind-4\" class=\"step-ind\" style=\"flex:1;padding:8px 12px;text-align:center;font-size:12px;font-weight:600;background:#0a0a0a;color:#666;\">\n 4. Auto-fill\n </div>\n </div>\n\n <!-- Step 1: CLI Install -->\n <div id=\"step-1\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step1-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Firebase CLI</div>\n <div id=\"step1-detail\" style=\"font-size:13px;color:#a3a3a3;\">Checking...</div>\n </div>\n </div>\n <div id=\"step1-action\" style=\"display:none;\">\n <button id=\"install-cli-btn\" onclick=\"installFirebaseCli()\" style=\"padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Install Firebase CLI\n </button>\n <div id=\"install-status\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n </div>\n\n <!-- Step 2: Login -->\n <div id=\"step-2\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step2-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Firebase Authentication</div>\n <div id=\"step2-detail\" style=\"font-size:13px;color:#a3a3a3;\">Checking...</div>\n </div>\n </div>\n <div id=\"step2-action\" style=\"display:none;\">\n <button id=\"login-btn\" onclick=\"startFirebaseLogin(false)\" style=\"padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Login to Firebase\n </button>\n <button id=\"reauth-btn\" onclick=\"startFirebaseLogin(true)\" style=\"display:none;padding:8px 20px;background:#eab308;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Re-authenticate\n </button>\n </div>\n <!-- Login waiting message -->\n <div id=\"login-waiting\" style=\"display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;\">\n <div style=\"display:flex;align-items:center;gap:8px;margin-bottom:8px;\">\n <span style=\"display:inline-block;width:14px;height:14px;border:2px solid #3b82f6;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;\"></span>\n <span style=\"color:#e5e5e5;font-size:13px;font-weight:600;\">Waiting for login...</span>\n </div>\n <div style=\"font-size:13px;color:#a3a3a3;\">A terminal window has been opened. Please complete the Firebase login there.</div>\n <div style=\"font-size:12px;color:#666;margin-top:6px;\">This page will update automatically when login is detected.</div>\n </div>\n <div id=\"login-result\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n\n <!-- Step 3: Select Project -->\n <div id=\"step-3\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step3-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Firebase Project</div>\n <div id=\"step3-detail\" style=\"font-size:13px;color:#a3a3a3;\">Checking...</div>\n </div>\n </div>\n <div id=\"step3-action\" style=\"display:none;\">\n <div style=\"display:flex;gap:8px;align-items:center;flex-wrap:wrap;\">\n <select id=\"project-select\" style=\"padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:13px;font-family:monospace;min-width:280px;outline:none;\">\n <option value=\"\">Loading projects...</option>\n </select>\n <button id=\"select-project-btn\" onclick=\"selectFirebaseProject()\" style=\"padding:8px 20px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Use This Project\n </button>\n <button onclick=\"refreshProjectList()\" style=\"padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;\" title=\"Refresh\">\n Refresh\n </button>\n </div>\n <div id=\"project-status\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n </div>\n\n <!-- Step 4: Done / Auto-fill -->\n <div id=\"step-4\" style=\"display:none;\">\n <div style=\"display:flex;align-items:center;gap:12px;margin-bottom:12px;\">\n <span id=\"step4-icon\" style=\"font-size:20px;\">&#9898;</span>\n <div>\n <div style=\"font-weight:600;color:#e5e5e5;font-size:15px;\">Auto-fill Config</div>\n <div id=\"step4-detail\" style=\"font-size:13px;color:#a3a3a3;\">Ready to auto-fill your clawfire.config.ts</div>\n </div>\n </div>\n <div id=\"step4-action\">\n <button id=\"autofill-wizard-btn\" onclick=\"autoFillConfig()\" style=\"padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">\n Auto-fill Config Now\n </button>\n <div id=\"autofill-wizard-status\" style=\"display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;\"></div>\n </div>\n </div>\n\n <!-- All done banner -->\n <div id=\"setup-done\" style=\"display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;\">\n <div style=\"font-size:16px;color:#22c55e;font-weight:700;margin-bottom:4px;\">Setup Complete</div>\n <div id=\"setup-done-detail\" style=\"font-size:13px;color:#a3a3a3;\"></div>\n <button onclick=\"startFirebaseLogin(true)\" style=\"margin-top:8px;padding:6px 14px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:12px;cursor:pointer;\">Re-authenticate</button>\n </div>\n </div>\n </div>\n\n <!-- Section 1: Firebase Status -->\n <div style=\"margin-bottom:32px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;\">Firebase Status</h2>\n\n <!-- CLI Banner -->\n <div id=\"cli-banner\" style=\"padding:12px 16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;margin-bottom:16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;\">\n <span id=\"cli-dot\" style=\"width:10px;height:10px;border-radius:50%;background:#666;display:inline-block;flex-shrink:0;\"></span>\n <span id=\"cli-text\" style=\"color:#e5e5e5;font-size:14px;\">Checking CLI...</span>\n <span id=\"cli-project\" style=\"color:#a3a3a3;font-size:12px;margin-left:auto;font-family:monospace;\"></span>\n </div>\n\n <!-- Service Cards Grid -->\n <div id=\"service-grid\" style=\"display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;\"></div>\n </div>\n\n <!-- Section 2: Config Overview -->\n <div style=\"margin-bottom:32px;\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;\">Config Overview</h2>\n <button id=\"autofill-btn\" onclick=\"autoFillConfig()\" style=\"padding:6px 14px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Auto-fill from Firebase</button>\n </div>\n <div id=\"autofill-status\" style=\"display:none;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;\"></div>\n <div id=\"config-section\" style=\"border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;\">\n <table id=\"config-table\" style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr style=\"border-bottom:1px solid #2a2a2a;\">\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;width:180px;\">Key</th>\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Value</th>\n <th style=\"padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:80px;\">Action</th>\n </tr>\n </thead>\n <tbody id=\"config-tbody\"></tbody>\n </table>\n <div id=\"config-empty\" style=\"display:none;padding:32px;text-align:center;color:#666;\">\n No clawfire.config.ts found.\n </div>\n </div>\n </div>\n\n <!-- Section 3: Environment Variables -->\n <div style=\"margin-bottom:32px;\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;\">\n <h2 style=\"font-size:18px;font-weight:700;color:#f97316;\">Environment Variables</h2>\n <button id=\"env-add-btn\" onclick=\"showEnvModal()\" style=\"padding:6px 14px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">+ Add Variable</button>\n </div>\n <div id=\"env-table-wrap\" style=\"border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;\">\n <table id=\"env-table\" style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n <thead>\n <tr style=\"border-bottom:1px solid #2a2a2a;\">\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Key</th>\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Value</th>\n <th style=\"padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;\">Description</th>\n <th style=\"padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:120px;\">Actions</th>\n </tr>\n </thead>\n <tbody id=\"env-tbody\"></tbody>\n </table>\n <div id=\"env-empty\" style=\"display:none;padding:32px;text-align:center;color:#666;\">\n No environment variables found. Click \"+ Add Variable\" to create one.\n </div>\n </div>\n </div>\n </div>\n\n <!-- Env Modal -->\n <div id=\"env-modal\" style=\"display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;align-items:center;justify-content:center;\">\n <div style=\"background:#1e1e1e;border:1px solid #2a2a2a;border-radius:12px;padding:24px;width:440px;max-width:90vw;\">\n <h3 id=\"modal-title\" style=\"font-size:16px;font-weight:700;color:#e5e5e5;margin-bottom:16px;\">Add Variable</h3>\n <div style=\"margin-bottom:12px;\">\n <label style=\"display:block;font-size:12px;color:#a3a3a3;margin-bottom:4px;\">Key</label>\n <input id=\"modal-key\" type=\"text\" placeholder=\"API_KEY\" style=\"width:100%;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;\" />\n </div>\n <div style=\"margin-bottom:12px;\">\n <label style=\"display:block;font-size:12px;color:#a3a3a3;margin-bottom:4px;\">Value</label>\n <input id=\"modal-value\" type=\"text\" placeholder=\"your-value-here\" style=\"width:100%;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;\" />\n </div>\n <div style=\"margin-bottom:20px;\">\n <label style=\"display:block;font-size:12px;color:#a3a3a3;margin-bottom:4px;\">Description (optional)</label>\n <input id=\"modal-desc\" type=\"text\" placeholder=\"What this variable is for\" style=\"width:100%;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:14px;outline:none;\" />\n </div>\n <div id=\"modal-error\" style=\"display:none;padding:8px 12px;background:#1c0808;border:1px solid #ef4444;border-radius:6px;color:#ef4444;font-size:12px;margin-bottom:12px;\"></div>\n <div style=\"display:flex;gap:8px;justify-content:flex-end;\">\n <button onclick=\"hideEnvModal()\" style=\"padding:8px 16px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;cursor:pointer;font-size:13px;\">Cancel</button>\n <button id=\"modal-save-btn\" onclick=\"saveEnvVar()\" style=\"padding:8px 16px;background:#f97316;border:none;border-radius:6px;color:#000;font-weight:600;cursor:pointer;font-size:13px;\">Save</button>\n </div>\n </div>\n </div>\n</div>\n\n<style>\n@keyframes spin { to { transform: rotate(360deg); } }\n#login-spinner { animation: spin 1s linear infinite; }\n</style>\n\n<script>\n(function() {\n var API = 'http://localhost:${apiPort}';\n var envData = [];\n var configData = [];\n var editingKey = null;\n var loginPollTimer = null;\n\n // ─── Load Dashboard Data ─────────────────────────────────────────\n window._loadDashboard = function() {\n if (window._dashboardLoaded) return;\n window._dashboardLoaded = true;\n Promise.all([\n fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }),\n fetch(API + '/__dev/config').then(function(r) { return r.json(); }),\n fetch(API + '/__dev/env').then(function(r) { return r.json(); }),\n fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),\n ]).then(function(results) {\n renderFirebaseStatus(results[0]);\n renderConfig(results[1]);\n renderEnvVars(results[2]);\n renderSetupWizard(results[3]);\n document.getElementById('dash-loading').style.display = 'none';\n document.getElementById('dash-loaded').style.display = 'block';\n }).catch(function(err) {\n document.getElementById('dash-loading').innerHTML =\n '<div style=\"color:#ef4444;\">Failed to load dashboard data</div>' +\n '<div style=\"color:#666;font-size:13px;margin-top:8px;\">' + err.message + '</div>';\n });\n };\n\n // ─── Setup Wizard ─────────────────────────────────────────────────\n\n function setStepIndicator(activeStep) {\n for (var i = 1; i <= 4; i++) {\n var el = document.getElementById('step-ind-' + i);\n if (i < activeStep) {\n el.style.background = '#0a1a0a';\n el.style.color = '#22c55e';\n } else if (i === activeStep) {\n el.style.background = '#1a1a2e';\n el.style.color = '#f97316';\n } else {\n el.style.background = '#0a0a0a';\n el.style.color = '#666';\n }\n }\n }\n\n function renderSetupWizard(status) {\n var GREEN = '\\\\u2705';\n var YELLOW = '\\\\u26A0\\\\uFE0F';\n var RED = '\\\\u274C';\n var CIRCLE = '\\\\u26AA';\n\n // Hide all steps first\n for (var i = 1; i <= 4; i++) {\n document.getElementById('step-' + i).style.display = 'none';\n }\n document.getElementById('setup-done').style.display = 'none';\n // Reset login UI state from previous interactions\n document.getElementById('login-waiting').style.display = 'none';\n document.getElementById('login-result').style.display = 'none';\n\n if (status.nextStep === 'done') {\n // All done!\n setStepIndicator(5);\n document.getElementById('setup-done').style.display = 'block';\n document.getElementById('setup-done-detail').textContent =\n 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;\n return;\n }\n\n // Step 1: CLI\n var step1 = document.getElementById('step-1');\n step1.style.display = 'block';\n if (status.cli.installed) {\n document.getElementById('step1-icon').textContent = GREEN;\n document.getElementById('step1-detail').textContent = 'Firebase CLI v' + status.cli.version + ' installed';\n document.getElementById('step1-action').style.display = 'none';\n } else {\n document.getElementById('step1-icon').textContent = RED;\n document.getElementById('step1-detail').textContent = 'Firebase CLI is not installed';\n document.getElementById('step1-action').style.display = 'block';\n }\n\n if (status.nextStep === 'install-cli') {\n setStepIndicator(1);\n return;\n }\n\n // Step 2: Auth\n var step2 = document.getElementById('step-2');\n step2.style.display = 'block';\n if (status.auth.authenticated) {\n document.getElementById('step2-icon').textContent = GREEN;\n document.getElementById('step2-detail').textContent = 'Logged in as ' + status.auth.user;\n document.getElementById('step2-action').style.display = 'block';\n document.getElementById('login-btn').style.display = 'none';\n document.getElementById('reauth-btn').style.display = 'inline-block';\n document.getElementById('login-waiting').style.display = 'none';\n } else {\n document.getElementById('step2-icon').textContent = RED;\n document.getElementById('step2-detail').textContent = 'Not logged in to Firebase';\n document.getElementById('step2-action').style.display = 'block';\n document.getElementById('login-btn').style.display = 'inline-block';\n document.getElementById('reauth-btn').style.display = 'none';\n document.getElementById('login-waiting').style.display = 'none';\n }\n\n if (status.nextStep === 'login') {\n setStepIndicator(2);\n return;\n }\n\n // Step 3: Project Selection\n var step3 = document.getElementById('step-3');\n step3.style.display = 'block';\n if (status.project.id) {\n document.getElementById('step3-icon').textContent = GREEN;\n document.getElementById('step3-detail').textContent = 'Active project: ' + status.project.id;\n document.getElementById('step3-action').style.display = 'block';\n } else {\n document.getElementById('step3-icon').textContent = YELLOW;\n document.getElementById('step3-detail').textContent = 'No project selected';\n document.getElementById('step3-action').style.display = 'block';\n }\n\n // Load project list\n loadProjectList(status.project.id);\n\n if (status.nextStep === 'select-project') {\n setStepIndicator(3);\n return;\n }\n\n // Step 4: Auto-fill\n var step4 = document.getElementById('step-4');\n step4.style.display = 'block';\n document.getElementById('step4-icon').textContent = CIRCLE;\n setStepIndicator(4);\n }\n\n // ─── Step 1: Install CLI ──────────────────────────────────────────\n window.installFirebaseCli = function() {\n var btn = document.getElementById('install-cli-btn');\n var status = document.getElementById('install-status');\n btn.disabled = true;\n btn.textContent = 'Installing...';\n status.style.display = 'block';\n status.textContent = 'Running npm install -g firebase-tools... This may take a minute.';\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';\n\n fetch(API + '/__dev/setup/install-cli', { method: 'POST' })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.success) {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n // Refresh wizard\n setTimeout(refreshSetupStatus, 1000);\n } else {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Install Firebase CLI';\n }\n })\n .catch(function(err) {\n status.textContent = 'Error: ' + err.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Install Firebase CLI';\n });\n };\n\n // ─── Step 2: Login (opens terminal window) ──────────────────────\n window.startFirebaseLogin = function(reauth) {\n var loginBtn = document.getElementById('login-btn');\n var reauthBtn = document.getElementById('reauth-btn');\n var waiting = document.getElementById('login-waiting');\n var result = document.getElementById('login-result');\n\n loginBtn.style.display = 'none';\n reauthBtn.style.display = 'none';\n waiting.style.display = 'block';\n result.style.display = 'none';\n\n fetch(API + '/__dev/setup/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ reauth: !!reauth })\n })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (!data.success) {\n waiting.style.display = 'none';\n result.textContent = data.message;\n result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n loginBtn.style.display = 'inline-block';\n return;\n }\n // Poll setup status until login is detected\n startLoginPoll();\n })\n .catch(function(err) {\n waiting.style.display = 'none';\n result.textContent = 'Failed: ' + err.message;\n result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n loginBtn.style.display = 'inline-block';\n });\n };\n\n function startLoginPoll() {\n if (loginPollTimer) clearInterval(loginPollTimer);\n loginPollTimer = setInterval(function() {\n fetch(API + '/__dev/setup/status')\n .then(function(r) { return r.json(); })\n .then(function(status) {\n if (status.auth.authenticated) {\n clearInterval(loginPollTimer);\n loginPollTimer = null;\n document.getElementById('login-waiting').style.display = 'none';\n var result = document.getElementById('login-result');\n result.textContent = 'Login successful! Logged in as ' + status.auth.user;\n result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n // Wait for token to fully settle, then refresh wizard + force-load projects\n setTimeout(function() {\n refreshSetupStatus();\n // Extra delay for project list — Firebase CLI needs time after fresh login\n setTimeout(function() { loadProjectList(''); }, 2500);\n }, 2000);\n }\n })\n .catch(function() {});\n }, 3000);\n }\n\n // ─── Step 3: Project Selection ────────────────────────────────────\n function loadProjectList(currentProjectId, retryCount) {\n retryCount = retryCount || 0;\n var select = document.getElementById('project-select');\n select.innerHTML = '<option value=\"\">Loading projects...</option>';\n select.disabled = true;\n\n fetch(API + '/__dev/setup/projects')\n .then(function(r) { return r.json(); })\n .then(function(data) {\n select.innerHTML = '';\n if (data.error) {\n // Auto-retry up to 2 times on error (token might not be ready yet after fresh login)\n if (retryCount < 2) {\n select.innerHTML = '<option value=\"\">Loading projects... (retry)</option>';\n setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);\n return;\n }\n select.innerHTML = '<option value=\"\">Error: ' + escHtml(data.error) + '</option>';\n return;\n }\n if (!data.projects || data.projects.length === 0) {\n if (retryCount < 2) {\n select.innerHTML = '<option value=\"\">Loading projects... (retry)</option>';\n setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);\n return;\n }\n select.innerHTML = '<option value=\"\">No projects found</option>';\n return;\n }\n\n select.innerHTML = '<option value=\"\">-- Select a project --</option>';\n data.projects.forEach(function(p) {\n var opt = document.createElement('option');\n opt.value = p.projectId;\n opt.textContent = p.displayName + ' (' + p.projectId + ')';\n if (p.projectId === currentProjectId) opt.selected = true;\n select.appendChild(opt);\n });\n select.disabled = false;\n })\n .catch(function(err) {\n if (retryCount < 2) {\n select.innerHTML = '<option value=\"\">Loading projects... (retry)</option>';\n setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);\n return;\n }\n select.innerHTML = '<option value=\"\">Failed to load</option>';\n });\n }\n\n window.refreshProjectList = function() {\n loadProjectList('');\n };\n\n window.selectFirebaseProject = function() {\n var select = document.getElementById('project-select');\n var btn = document.getElementById('select-project-btn');\n var status = document.getElementById('project-status');\n var projectId = select.value;\n\n if (!projectId) {\n status.textContent = 'Please select a project first.';\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';\n return;\n }\n\n btn.disabled = true;\n btn.textContent = 'Setting...';\n status.style.display = 'none';\n\n fetch(API + '/__dev/setup/select-project', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ projectId: projectId })\n })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.success) {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n setTimeout(refreshSetupStatus, 1000);\n } else {\n status.textContent = data.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Use This Project';\n }\n })\n .catch(function(err) {\n status.textContent = 'Error: ' + err.message;\n status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n btn.disabled = false;\n btn.textContent = 'Use This Project';\n });\n };\n\n // ─── Refresh Setup Status ─────────────────────────────────────────\n function refreshSetupStatus() {\n fetch(API + '/__dev/setup/status')\n .then(function(r) { return r.json(); })\n .then(function(status) { renderSetupWizard(status); })\n .catch(function() {});\n\n // Also refresh firebase status section\n fetch(API + '/__dev/firebase-status')\n .then(function(r) { return r.json(); })\n .then(function(data) { renderFirebaseStatus(data); })\n .catch(function() {});\n }\n\n // ─── Firebase Status ─────────────────────────────────────────────\n function renderFirebaseStatus(data) {\n var dot = document.getElementById('cli-dot');\n var text = document.getElementById('cli-text');\n var proj = document.getElementById('cli-project');\n\n if (!data.cli.installed) {\n dot.style.background = '#ef4444';\n text.textContent = 'Firebase CLI not installed';\n } else if (!data.cli.authenticated) {\n dot.style.background = '#eab308';\n text.textContent = 'Firebase CLI v' + data.cli.version + ' \\\\u2014 Not logged in';\n } else {\n dot.style.background = '#22c55e';\n text.textContent = 'Firebase CLI v' + data.cli.version + ' \\\\u2014 ' + data.cli.user;\n }\n\n if (data.project.id) {\n proj.textContent = 'Project: ' + data.project.id;\n } else {\n proj.textContent = 'No active project';\n proj.style.color = '#eab308';\n }\n\n // Service Cards\n var grid = document.getElementById('service-grid');\n grid.innerHTML = '';\n var statusColors = { configured: '#22c55e', placeholder: '#eab308', missing: '#666' };\n var statusLabels = { configured: 'Ready', placeholder: 'Needs Setup', missing: 'Not Configured' };\n\n data.services.forEach(function(svc) {\n var card = document.createElement('div');\n card.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;';\n card.innerHTML =\n '<div style=\"display:flex;align-items:center;gap:8px;margin-bottom:6px;\">' +\n '<span style=\"width:8px;height:8px;border-radius:50%;background:' + statusColors[svc.status] + ';display:inline-block;\"></span>' +\n '<span style=\"font-weight:600;color:#e5e5e5;\">' + svc.name + '</span>' +\n '</div>' +\n '<div style=\"font-size:12px;color:' + statusColors[svc.status] + ';\">' + statusLabels[svc.status] + '</div>' +\n (svc.detail ? '<div style=\"font-size:11px;color:#666;margin-top:4px;\">' + svc.detail + '</div>' : '');\n grid.appendChild(card);\n });\n\n if (data.configWarnings && data.configWarnings.length > 0) {\n var warningCard = document.createElement('div');\n warningCard.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #eab308;background:#1a1a0a;grid-column:1/-1;';\n warningCard.innerHTML =\n '<div style=\"font-weight:600;color:#eab308;margin-bottom:6px;\">Config Warnings</div>' +\n data.configWarnings.map(function(w) {\n return '<div style=\"font-size:12px;color:#eab308;font-family:monospace;\">' + escHtml(w) + '</div>';\n }).join('');\n grid.appendChild(warningCard);\n }\n }\n\n // ─── Config Overview (editable) ──────────────────────────────────\n function renderConfig(data) {\n var tbody = document.getElementById('config-tbody');\n var empty = document.getElementById('config-empty');\n var table = document.getElementById('config-table');\n\n configData = (data && data.fields) ? data.fields : [];\n\n if (configData.length === 0) {\n table.style.display = 'none';\n empty.style.display = 'block';\n return;\n }\n\n table.style.display = 'table';\n empty.style.display = 'none';\n tbody.innerHTML = '';\n\n configData.forEach(function(field) {\n var tr = document.createElement('tr');\n tr.style.borderBottom = '1px solid #2a2a2a';\n tr.id = 'cfg-row-' + field.key;\n var color = field.isPlaceholder ? '#eab308' : '#a3a3a3';\n var badge = field.isPlaceholder ? ' <span style=\"background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;\">PLACEHOLDER</span>' : '';\n tr.innerHTML =\n '<td style=\"padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;\">' + escHtml(field.key) + '</td>' +\n '<td style=\"padding:10px 16px;font-family:monospace;\">' +\n '<span id=\"cfg-val-' + field.key + '\" style=\"color:' + color + ';\">' + escHtml(field.value) + '</span>' +\n badge +\n '<input id=\"cfg-input-' + field.key + '\" type=\"text\" value=\"' + escHtml(field.value) + '\" style=\"display:none;width:100%;padding:4px 8px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:4px;color:#e5e5e5;font-family:monospace;font-size:13px;outline:none;\" />' +\n '</td>' +\n '<td style=\"padding:10px 16px;text-align:right;white-space:nowrap;\">' +\n '<button id=\"cfg-edit-' + field.key + '\" onclick=\"editConfigField(\\\\'' + escHtml(field.key) + '\\\\')\" style=\"background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;\">Edit</button>' +\n '<button id=\"cfg-save-' + field.key + '\" onclick=\"saveConfigField(\\\\'' + escHtml(field.key) + '\\\\')\" style=\"display:none;background:none;border:none;color:#22c55e;cursor:pointer;font-size:12px;padding:4px 8px;\">Save</button>' +\n '<button id=\"cfg-cancel-' + field.key + '\" onclick=\"cancelConfigEdit(\\\\'' + escHtml(field.key) + '\\\\')\" style=\"display:none;background:none;border:none;color:#a3a3a3;cursor:pointer;font-size:12px;padding:4px 8px;\">Cancel</button>' +\n '</td>';\n tbody.appendChild(tr);\n });\n }\n\n window.editConfigField = function(key) {\n var valSpan = document.getElementById('cfg-val-' + key);\n var input = document.getElementById('cfg-input-' + key);\n var editBtn = document.getElementById('cfg-edit-' + key);\n var saveBtn = document.getElementById('cfg-save-' + key);\n var cancelBtn = document.getElementById('cfg-cancel-' + key);\n if (!valSpan || !input) return;\n var badges = valSpan.parentNode.querySelectorAll('span[style*=\"PLACEHOLDER\"]');\n for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';\n valSpan.style.display = 'none';\n input.style.display = 'block';\n input.focus();\n input.select();\n editBtn.style.display = 'none';\n saveBtn.style.display = 'inline';\n cancelBtn.style.display = 'inline';\n };\n\n window.cancelConfigEdit = function(key) {\n var valSpan = document.getElementById('cfg-val-' + key);\n var input = document.getElementById('cfg-input-' + key);\n var editBtn = document.getElementById('cfg-edit-' + key);\n var saveBtn = document.getElementById('cfg-save-' + key);\n var cancelBtn = document.getElementById('cfg-cancel-' + key);\n if (!valSpan || !input) return;\n var badges = valSpan.parentNode.querySelectorAll('span[style*=\"border-radius\"]');\n for (var i = 0; i < badges.length; i++) badges[i].style.display = '';\n valSpan.style.display = '';\n input.style.display = 'none';\n input.value = valSpan.textContent;\n editBtn.style.display = 'inline';\n saveBtn.style.display = 'none';\n cancelBtn.style.display = 'none';\n };\n\n window.saveConfigField = function(key) {\n var input = document.getElementById('cfg-input-' + key);\n var saveBtn = document.getElementById('cfg-save-' + key);\n if (!input) return;\n var value = input.value;\n saveBtn.textContent = '...';\n fetch(API + '/__dev/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'set', key: key, value: value })\n }).then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }\n return fetch(API + '/__dev/config').then(function(r) { return r.json(); });\n })\n .then(function(data) { if (data) renderConfig(data); })\n .catch(function(err) { alert('Failed: ' + err.message); saveBtn.textContent = 'Save'; });\n };\n\n // ─── Auto-fill from Firebase ─────────────────────────────────────\n window.autoFillConfig = function() {\n var btn = document.getElementById('autofill-btn');\n var wizBtn = document.getElementById('autofill-wizard-btn');\n var status = document.getElementById('autofill-status');\n var wizStatus = document.getElementById('autofill-wizard-status');\n if (btn) { btn.disabled = true; btn.textContent = 'Fetching...'; }\n if (wizBtn) { wizBtn.disabled = true; wizBtn.textContent = 'Fetching...'; }\n if (status) status.style.display = 'none';\n if (wizStatus) wizStatus.style.display = 'none';\n\n fetch(API + '/__dev/firebase-sdk-config')\n .then(function(r) {\n if (!r.ok) {\n return r.json().catch(function() { return { error: 'Server error (' + r.status + ')' }; });\n }\n return r.json();\n })\n .then(function(data) {\n if (data.error) {\n showAutoFillResult(false, data.error);\n return;\n }\n\n var fields = {};\n if (data.apiKey) fields.apiKey = data.apiKey;\n if (data.authDomain) fields.authDomain = data.authDomain;\n if (data.projectId) fields.projectId = data.projectId;\n if (data.storageBucket) fields.storageBucket = data.storageBucket;\n if (data.appId) fields.appId = data.appId;\n\n var count = Object.keys(fields).length;\n if (count === 0) {\n showAutoFillResult(false, 'No web app config found. Create a Web app in Firebase Console first.');\n return;\n }\n\n return fetch(API + '/__dev/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'set-multiple', fields: fields })\n }).then(function(r) { return r.json(); })\n .then(function(result) {\n if (result.error) {\n showAutoFillResult(false, 'Error saving: ' + result.error);\n } else {\n showAutoFillResult(true, count + ' fields updated from Firebase project!');\n return fetch(API + '/__dev/config').then(function(r) { return r.json(); })\n .then(function(cfg) { renderConfig(cfg); });\n }\n });\n })\n .catch(function(err) {\n showAutoFillResult(false, 'Failed: ' + err.message);\n })\n .finally(function() {\n if (btn) { btn.disabled = false; btn.textContent = 'Auto-fill from Firebase'; }\n if (wizBtn) { wizBtn.disabled = false; wizBtn.textContent = 'Auto-fill Config Now'; }\n });\n };\n\n function showAutoFillResult(success, message) {\n var targets = ['autofill-status', 'autofill-wizard-status'];\n targets.forEach(function(id) {\n var el = document.getElementById(id);\n if (!el) return;\n el.textContent = message;\n if (success) {\n el.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';\n } else {\n el.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';\n }\n });\n }\n\n // ─── Environment Variables ───────────────────────────────────────\n function renderEnvVars(data) {\n envData = data.variables || [];\n var tbody = document.getElementById('env-tbody');\n var empty = document.getElementById('env-empty');\n var table = document.getElementById('env-table');\n\n if (envData.length === 0) {\n table.style.display = 'none';\n empty.style.display = 'block';\n return;\n }\n\n table.style.display = 'table';\n empty.style.display = 'none';\n tbody.innerHTML = '';\n\n envData.forEach(function(v) {\n var tr = document.createElement('tr');\n tr.style.borderBottom = '1px solid #2a2a2a';\n var maskedVal = v.value ? v.value.slice(0, 3) + '...' + v.value.slice(-2) : '';\n if (v.value.length <= 5) maskedVal = '***';\n var placeholderBadge = v.isPlaceholder ? ' <span style=\"background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;\">PLACEHOLDER</span>' : '';\n tr.innerHTML =\n '<td style=\"padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;\">' + escHtml(v.key) + placeholderBadge + '</td>' +\n '<td style=\"padding:10px 16px;color:#a3a3a3;font-family:monospace;\">' +\n '<span class=\"env-val\" data-key=\"' + escAttr(v.key) + '\" data-masked=\"' + escAttr(maskedVal) + '\" data-full=\"' + escAttr(v.value) + '\">' + escHtml(maskedVal) + '</span>' +\n ' <button onclick=\"toggleReveal(this)\" style=\"background:none;border:none;color:#666;cursor:pointer;font-size:11px;padding:2px 4px;\">reveal</button>' +\n '</td>' +\n '<td style=\"padding:10px 16px;color:#666;font-size:12px;\">' + escHtml(v.description || '') + '</td>' +\n '<td style=\"padding:10px 16px;text-align:right;white-space:nowrap;\">' +\n '<button onclick=\"editEnvVar(\\\\'' + escAttr(v.key) + '\\\\')\" style=\"background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;\">Edit</button>' +\n '<button onclick=\"deleteEnvVar(\\\\'' + escAttr(v.key) + '\\\\')\" style=\"background:none;border:none;color:#ef4444;cursor:pointer;font-size:12px;padding:4px 8px;\">Delete</button>' +\n '</td>';\n tbody.appendChild(tr);\n });\n }\n\n // ─── Env Modal ───────────────────────────────────────────────────\n window.showEnvModal = function(key) {\n editingKey = key || null;\n var modal = document.getElementById('env-modal');\n var title = document.getElementById('modal-title');\n var keyInput = document.getElementById('modal-key');\n var valInput = document.getElementById('modal-value');\n var descInput = document.getElementById('modal-desc');\n var errorEl = document.getElementById('modal-error');\n errorEl.style.display = 'none';\n\n if (editingKey) {\n title.textContent = 'Edit Variable';\n keyInput.value = editingKey;\n keyInput.readOnly = true;\n keyInput.style.opacity = '0.5';\n var existing = envData.find(function(v) { return v.key === editingKey; });\n valInput.value = existing ? existing.value : '';\n descInput.value = existing ? existing.description : '';\n } else {\n title.textContent = 'Add Variable';\n keyInput.value = '';\n keyInput.readOnly = false;\n keyInput.style.opacity = '1';\n valInput.value = '';\n descInput.value = '';\n }\n\n modal.style.display = 'flex';\n (editingKey ? valInput : keyInput).focus();\n };\n\n window.hideEnvModal = function() {\n document.getElementById('env-modal').style.display = 'none';\n editingKey = null;\n };\n\n window.editEnvVar = function(key) {\n showEnvModal(key);\n };\n\n window.saveEnvVar = function() {\n var key = document.getElementById('modal-key').value.trim();\n var value = document.getElementById('modal-value').value;\n var desc = document.getElementById('modal-desc').value.trim();\n var errorEl = document.getElementById('modal-error');\n\n if (!key) {\n errorEl.textContent = 'Key is required';\n errorEl.style.display = 'block';\n return;\n }\n if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) {\n errorEl.textContent = 'Invalid key format. Use UPPER_SNAKE_CASE.';\n errorEl.style.display = 'block';\n return;\n }\n\n fetch(API + '/__dev/env', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'set', key: key, value: value, description: desc || undefined })\n }).then(function(r) { return r.json(); })\n .then(function(data) {\n if (data.error) {\n errorEl.textContent = data.error;\n errorEl.style.display = 'block';\n return;\n }\n hideEnvModal();\n return fetch(API + '/__dev/env').then(function(r) { return r.json(); });\n })\n .then(function(data) { if (data) renderEnvVars(data); })\n .catch(function(err) {\n errorEl.textContent = err.message;\n errorEl.style.display = 'block';\n });\n };\n\n window.deleteEnvVar = function(key) {\n if (!confirm('Delete ' + key + '?')) return;\n fetch(API + '/__dev/env', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'delete', key: key })\n }).then(function() {\n return fetch(API + '/__dev/env').then(function(r) { return r.json(); });\n }).then(function(data) { renderEnvVars(data); })\n .catch(function() {});\n };\n\n window.toggleReveal = function(btn) {\n var span = btn.previousElementSibling;\n var masked = span.getAttribute('data-masked');\n var full = span.getAttribute('data-full');\n if (span.textContent === masked) {\n span.textContent = full;\n btn.textContent = 'hide';\n } else {\n span.textContent = masked;\n btn.textContent = 'reveal';\n }\n };\n\n // ─── Helpers ─────────────────────────────────────────────────────\n function escHtml(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;'); }\n function escAttr(s) { return String(s).replace(/&/g,'&amp;').replace(/'/g,\"\\\\\\\\'\").replace(/\"/g,'&quot;').replace(/</g,'&lt;'); }\n})();\n</script>`;\n}\n","/**\n * Clawfire Firebase Setup Orchestration\n *\n * Manages the full Firebase setup flow:\n * 1. Check/install Firebase CLI\n * 2. Open a real terminal for `firebase login` (TTY required)\n * 3. List available projects\n * 4. Select project → auto-fill config\n *\n * All operations are dev-only (localhost).\n */\nimport { execFile, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport { tmpdir, platform } from \"node:os\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\nexport interface SetupStatus {\n /** Firebase CLI */\n cli: {\n installed: boolean;\n version: string;\n };\n /** Auth state */\n auth: {\n authenticated: boolean;\n user: string;\n };\n /** Active project */\n project: {\n id: string;\n hasFirebaserc: boolean;\n };\n /** Overall readiness */\n ready: boolean;\n /** Current step the user should do next */\n nextStep: \"install-cli\" | \"login\" | \"select-project\" | \"done\";\n}\n\nexport interface FirebaseProject {\n projectId: string;\n displayName: string;\n projectNumber: string;\n state: string;\n}\n\n// ─── Firebase Setup Manager ──────────────────────────────────────────\n\nexport class FirebaseSetup {\n private projectDir: string;\n\n constructor(projectDir: string) {\n this.projectDir = projectDir;\n }\n\n // ─── Status Check ──────────────────────────────────────────────────\n\n async getStatus(): Promise<SetupStatus> {\n const status: SetupStatus = {\n cli: { installed: false, version: \"\" },\n auth: { authenticated: false, user: \"\" },\n project: { id: \"\", hasFirebaserc: false },\n ready: false,\n nextStep: \"install-cli\",\n };\n\n // 1. Check CLI\n try {\n const version = await this.execTimeout(\"firebase\", [\"--version\"], 5000);\n status.cli.installed = true;\n status.cli.version = version.trim();\n } catch {\n status.nextStep = \"install-cli\";\n return status;\n }\n\n // 2. Check auth — use login:list and also verify token works\n try {\n const loginOutput = await this.execTimeout(\n \"firebase\",\n [\"login:list\", \"--json\"],\n 5000,\n );\n const loginData = JSON.parse(loginOutput);\n if (loginData?.result && Array.isArray(loginData.result) && loginData.result.length > 0) {\n status.auth.authenticated = true;\n status.auth.user = loginData.result[0]?.user?.email || loginData.result[0]?.email || \"\";\n }\n } catch {\n // Not authenticated\n }\n\n if (!status.auth.authenticated) {\n status.nextStep = \"login\";\n return status;\n }\n\n // 3. Check active project\n const firebasercPath = resolve(this.projectDir, \".firebaserc\");\n if (existsSync(firebasercPath)) {\n status.project.hasFirebaserc = true;\n try {\n const rc = JSON.parse(readFileSync(firebasercPath, \"utf-8\"));\n status.project.id = rc?.projects?.default || \"\";\n } catch {\n // invalid JSON\n }\n }\n\n if (!status.project.id) {\n status.nextStep = \"select-project\";\n return status;\n }\n\n status.ready = true;\n status.nextStep = \"done\";\n return status;\n }\n\n // ─── CLI Install ───────────────────────────────────────────────────\n\n async installCli(): Promise<{ success: boolean; message: string }> {\n try {\n // Check if already installed\n try {\n await this.execTimeout(\"firebase\", [\"--version\"], 5000);\n return { success: true, message: \"Firebase CLI is already installed.\" };\n } catch {\n // Not installed, proceed\n }\n\n // Install via npm\n await this.execTimeout(\n \"npm\",\n [\"install\", \"-g\", \"firebase-tools\"],\n 120000, // 2 min timeout for install\n );\n\n // Verify installation\n try {\n const version = await this.execTimeout(\"firebase\", [\"--version\"], 5000);\n return { success: true, message: `Firebase CLI v${version.trim()} installed successfully.` };\n } catch {\n return { success: false, message: \"Installation completed but CLI not found in PATH. Try restarting your terminal.\" };\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n return { success: false, message: `Failed to install Firebase CLI: ${msg}` };\n }\n }\n\n // ─── Login via Terminal ───────────────────────────────────────────\n\n /**\n * Opens a new terminal window and runs `firebase login`.\n *\n * Firebase CLI requires a real TTY for interactive login.\n * Instead of trying to fake a TTY, we open an actual terminal.\n * The dashboard polls getStatus() to detect when login completes.\n *\n * macOS: Creates a .command file and opens it (Terminal.app)\n * Linux: Tries common terminal emulators\n * Windows: Opens a new cmd window\n */\n openLoginTerminal(reauth: boolean = false): { success: boolean; message: string } {\n const cmd = reauth ? \"firebase login --reauth\" : \"firebase login\";\n const os = platform();\n\n try {\n if (os === \"darwin\") {\n // macOS: .command files auto-open in Terminal.app — no special permissions needed\n // After login completes, osascript closes the terminal window matching our filename\n const scriptPath = join(tmpdir(), \"clawfire-firebase-login.command\");\n writeFileSync(scriptPath, [\n \"#!/bin/bash\",\n `cd \"${this.projectDir}\"`,\n cmd,\n 'echo \"\"',\n 'echo \"Login complete! Closing in 3 seconds...\"',\n \"sleep 3\",\n // Spawn osascript in background to close this specific terminal window, then exit\n '(sleep 1 && osascript -e \\'tell application \"Terminal\" to close (every window whose name contains \"clawfire-firebase-login\")\\' 2>/dev/null) &',\n \"exit 0\",\n ].join(\"\\n\"), { mode: 0o755 });\n\n const child = spawn(\"open\", [scriptPath], { detached: true, stdio: \"ignore\" });\n child.unref();\n } else if (os === \"win32\") {\n // Windows: open a new cmd window that auto-closes after login\n const child = spawn(\"cmd\", [\"/c\", \"start\", \"cmd\", \"/c\", `${cmd} && timeout /t 3 >nul`], {\n cwd: this.projectDir,\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n } else {\n // Linux: create script, try common terminal emulators\n const scriptPath = join(tmpdir(), \"clawfire-firebase-login.sh\");\n writeFileSync(scriptPath, [\n \"#!/bin/bash\",\n `cd \"${this.projectDir}\"`,\n cmd,\n 'echo \"\"',\n 'echo \"Login complete! Closing in 3 seconds...\"',\n \"sleep 3\",\n \"exit 0\",\n ].join(\"\\n\"), { mode: 0o755 });\n\n const terminals = [\n { cmd: \"x-terminal-emulator\", args: [\"-e\", scriptPath] },\n { cmd: \"gnome-terminal\", args: [\"--\", \"bash\", scriptPath] },\n { cmd: \"konsole\", args: [\"-e\", \"bash\", scriptPath] },\n { cmd: \"xfce4-terminal\", args: [\"-e\", scriptPath] },\n { cmd: \"xterm\", args: [\"-e\", scriptPath] },\n ];\n\n let opened = false;\n for (const t of terminals) {\n try {\n const child = spawn(t.cmd, t.args, { detached: true, stdio: \"ignore\" });\n child.unref();\n // If spawn didn't throw, it at least started\n child.on(\"error\", () => {});\n opened = true;\n break;\n } catch {\n continue;\n }\n }\n\n if (!opened) {\n return {\n success: false,\n message: `Could not find a terminal emulator. Please run \"${cmd}\" manually in your terminal.`,\n };\n }\n }\n\n return {\n success: true,\n message: \"Terminal window opened. Please complete the login in the new terminal.\",\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n return {\n success: false,\n message: `Failed to open terminal: ${msg}. Please run \"${cmd}\" manually in your terminal.`,\n };\n }\n }\n\n // ─── Project Listing ───────────────────────────────────────────────\n\n async listProjects(): Promise<{ projects: FirebaseProject[]; error?: string }> {\n try {\n const output = await this.execTimeout(\n \"firebase\",\n [\"projects:list\", \"--json\"],\n 30000, // 30s — can be slow on first call\n );\n\n const data = JSON.parse(output);\n\n if (data?.result && Array.isArray(data.result)) {\n const projects: FirebaseProject[] = data.result.map((p: Record<string, unknown>) => ({\n projectId: p.projectId || \"\",\n displayName: p.displayName || p.projectId || \"\",\n projectNumber: p.projectNumber || \"\",\n state: p.lifecycleState || p.state || \"ACTIVE\",\n }));\n return { projects };\n }\n\n return { projects: [], error: \"Unexpected response format\" };\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n // Common cases\n if (msg.includes(\"authenticate\") || msg.includes(\"login\") || msg.includes(\"credential\")) {\n return { projects: [], error: \"Session expired. Please re-authenticate first.\" };\n }\n if (msg.includes(\"Timeout\")) {\n return { projects: [], error: \"Request timed out. Please try again.\" };\n }\n return { projects: [], error: msg };\n }\n }\n\n // ─── Project Selection ─────────────────────────────────────────────\n\n async selectProject(projectId: string): Promise<{ success: boolean; message: string }> {\n if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {\n return { success: false, message: \"Invalid project ID format.\" };\n }\n\n try {\n // Set project via firebase use\n await this.execTimeout(\n \"firebase\",\n [\"use\", projectId],\n 10000,\n );\n\n // Also update .firebaserc directly for reliability\n const firebasercPath = resolve(this.projectDir, \".firebaserc\");\n let rc: Record<string, unknown> = {};\n if (existsSync(firebasercPath)) {\n try {\n rc = JSON.parse(readFileSync(firebasercPath, \"utf-8\"));\n } catch {\n rc = {};\n }\n }\n if (!rc.projects || typeof rc.projects !== \"object\") {\n rc.projects = {};\n }\n (rc.projects as Record<string, string>).default = projectId;\n writeFileSync(firebasercPath, JSON.stringify(rc, null, 2) + \"\\n\", \"utf-8\");\n\n return { success: true, message: `Active project set to \"${projectId}\".` };\n } catch {\n // If firebase use fails, still try writing .firebaserc directly\n try {\n const firebasercPath = resolve(this.projectDir, \".firebaserc\");\n const rc = {\n projects: { default: projectId },\n };\n writeFileSync(firebasercPath, JSON.stringify(rc, null, 2) + \"\\n\", \"utf-8\");\n return { success: true, message: `Active project set to \"${projectId}\" (via .firebaserc).` };\n } catch (writeErr) {\n const msg = writeErr instanceof Error ? writeErr.message : \"Unknown error\";\n return { success: false, message: `Failed to set project: ${msg}` };\n }\n }\n }\n\n // ─── Helpers ───────────────────────────────────────────────────────\n\n private execTimeout(command: string, args: string[], timeoutMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = execFile(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {\n if (err) {\n // Include stderr in error message for better debugging\n const detail = stderr?.trim() || stdout?.trim() || \"\";\n const enriched = new Error(`${err.message}${detail ? \"\\n\" + detail : \"\"}`);\n reject(enriched);\n } else {\n resolve(stdout);\n }\n });\n const timer = setTimeout(() => {\n proc.kill(\"SIGTERM\");\n reject(new Error(\"Timeout\"));\n }, timeoutMs + 500);\n proc.on(\"exit\", () => clearTimeout(timer));\n });\n }\n\n /** Cleanup resources */\n destroy(): void {\n // No persistent processes to clean up\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBA8HO,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAiOuB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["import_node_path","import_node_fs","import_fs","import_path","import_node_fs","import_node_path","import_node_fs","import_node_path","resolve","import_node_child_process","import_node_fs","import_node_path","resolve","http","resolve","filePath","isPlaceholder"]}