@runcontext/ui 0.5.11 → 0.6.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/erickittelson/Code/RunContext/runcontext/packages/ui/dist/index.cjs","../src/server.ts","../src/routes/api/brief.ts","../src/routes/api/sources.ts","../src/routes/api/upload.ts","../src/routes/api/pipeline.ts","../src/events.ts","../src/routes/api/products.ts","../src/routes/api/auth.ts","../src/routes/api/suggest-brief.ts","../src/routes/ws.ts"],"names":["PRODUCT_NAME_RE","execFile"],"mappings":"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","file":"/Users/erickittelson/Code/RunContext/runcontext/packages/ui/dist/index.cjs","sourcesContent":[null,"import { Hono } from 'hono';\nimport { serve } from '@hono/node-server';\nimport { cors } from 'hono/cors';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as url from 'node:url';\nimport { briefRoutes } from './routes/api/brief.js';\nimport { sourcesRoutes } from './routes/api/sources.js';\nimport { uploadRoutes } from './routes/api/upload.js';\nimport { pipelineRoutes } from './routes/api/pipeline.js';\nimport { productsRoutes } from './routes/api/products.js';\nimport { authRoutes } from './routes/api/auth.js';\nimport { suggestBriefRoutes } from './routes/api/suggest-brief.js';\nimport { attachWebSocket } from './routes/ws.js';\nimport { setupBus } from './events.js';\n\nexport interface UIServerOptions {\n rootDir: string;\n contextDir: string;\n port: number;\n host: string;\n}\n\nconst __dirname = path.dirname(url.fileURLToPath(import.meta.url));\nconst staticDir = path.resolve(__dirname, '..', 'static');\n\nexport function createApp(opts: UIServerOptions): Hono {\n const app = new Hono();\n\n app.use('*', cors({\n origin: (origin) => {\n // Allow requests with no origin (e.g. same-origin, curl, server-to-server)\n if (!origin) return origin;\n // Allow localhost on any port\n if (/^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(origin)) {\n return origin;\n }\n return null;\n },\n }));\n\n app.route('', briefRoutes(opts.contextDir));\n app.route('', sourcesRoutes(opts.rootDir, opts.contextDir));\n app.route('', uploadRoutes(opts.contextDir));\n app.route('', pipelineRoutes(opts.rootDir, opts.contextDir));\n app.route('', productsRoutes(opts.contextDir));\n app.route('', authRoutes(opts.rootDir));\n app.route('', suggestBriefRoutes(opts.rootDir));\n\n app.get('/api/health', (c) => c.json({ ok: true }));\n\n app.post('/api/session', (c) => {\n const id = setupBus.createSession();\n return c.json({ sessionId: id });\n });\n\n // Static file serving (CSS, JS)\n app.get('/static/:filename', (c) => {\n const filename = c.req.param('filename');\n if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {\n return c.text('Not found', 404);\n }\n const filePath = path.join(staticDir, filename);\n if (!fs.existsSync(filePath)) return c.text('Not found', 404);\n\n const ext = path.extname(filename);\n const contentType =\n ext === '.css' ? 'text/css'\n : ext === '.js' ? 'application/javascript'\n : 'application/octet-stream';\n\n return c.body(fs.readFileSync(filePath), 200, { 'Content-Type': contentType });\n });\n\n app.get('/setup', (c) => {\n return c.html(setupPageHTML());\n });\n\n app.get('/', (c) => c.redirect('/setup'));\n\n return app;\n}\n\nfunction setupPageHTML(): string {\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\" />\n <title>RunContext — Build Your Data Product</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap\" rel=\"stylesheet\" />\n <link rel=\"stylesheet\" href=\"/static/uxd.css\" />\n <link rel=\"stylesheet\" href=\"/static/setup.css\" />\n</head>\n<body>\n <div class=\"app-shell\">\n <!-- Sidebar -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-brand\">\n <svg class=\"brand-chevron\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M4 4l8 8-8 8\" stroke=\"#c9a55a\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M12 4l8 8-8 8\" stroke=\"#c9a55a\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\"/>\n </svg>\n <span class=\"brand-text\">\n <span class=\"brand-run\">Run</span><span class=\"brand-context\">Context</span>\n </span>\n <span class=\"brand-badge\">Local</span>\n </div>\n <nav class=\"sidebar-nav\">\n <a class=\"nav-item active\" data-nav=\"setup\">\n <span>Setup</span>\n </a>\n <a class=\"nav-item locked\" data-nav=\"planes\">\n <span>Semantic Planes</span>\n <svg class=\"lock-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/>\n <path d=\"M7 11V7a5 5 0 0110 0v4\"/>\n </svg>\n </a>\n <a class=\"nav-item locked\" data-nav=\"analytics\">\n <span>Analytics</span>\n <svg class=\"lock-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/>\n <path d=\"M7 11V7a5 5 0 0110 0v4\"/>\n </svg>\n </a>\n <a class=\"nav-item\" data-nav=\"mcp\">\n <span class=\"status-dot\" id=\"mcp-status-dot\"></span>\n <span>MCP Server</span>\n <span class=\"nav-detail\" id=\"mcp-status-text\">checking...</span>\n </a>\n <a class=\"nav-item locked\" data-nav=\"settings\">\n <span>Settings</span>\n <svg class=\"lock-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/>\n <path d=\"M7 11V7a5 5 0 0110 0v4\"/>\n </svg>\n </a>\n </nav>\n <div class=\"sidebar-status\">\n <div class=\"status-row\">\n <span class=\"status-dot\" id=\"db-status-dot\"></span>\n <span id=\"db-status-text\">No database</span>\n </div>\n <div class=\"status-row\">\n <span class=\"status-dot\" id=\"mcp-server-dot\"></span>\n <span id=\"mcp-server-text\">MCP stopped</span>\n </div>\n <div class=\"status-row\" id=\"tier-row\">\n <span class=\"tier-badge\" id=\"tier-badge\">Free</span>\n </div>\n </div>\n </aside>\n\n <!-- Header -->\n <header class=\"app-header\">\n <div class=\"header-stepper\" id=\"stepper\"></div>\n </header>\n\n <!-- Main Content -->\n <main class=\"main-content\">\n <div class=\"content-wrapper\" id=\"wizard-content\"></div>\n </main>\n\n <!-- Footer -->\n <footer class=\"app-footer\">\n <span>Powered by RunContext &middot; Open Semantic Interchange</span>\n </footer>\n </div>\n\n <!-- Locked tooltip (hidden by default) -->\n <div class=\"locked-tooltip\" id=\"locked-tooltip\" style=\"display:none\">\n <p>Available on RunContext Cloud</p>\n <a href=\"https://runcontext.dev/pricing\" target=\"_blank\" rel=\"noopener\">Learn more</a>\n </div>\n\n <script src=\"/static/setup.js\"></script>\n</body>\n</html>`;\n}\n\nexport function startUIServer(opts: UIServerOptions): Promise<void> {\n return new Promise((resolve, reject) => {\n const app = createApp(opts);\n\n const server = serve({\n fetch: app.fetch,\n port: opts.port,\n hostname: opts.host,\n }, (info) => {\n console.log(`RunContext UI running at http://${opts.host === '0.0.0.0' ? 'localhost' : opts.host}:${info.port}/setup`);\n resolve();\n });\n\n attachWebSocket(server as unknown as import('node:http').Server);\n\n server.on('error', reject);\n });\n}\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { stringify, parse } from 'yaml';\nimport { validateBrief, ContextBriefSchema, PRODUCT_NAME_RE } from '@runcontext/core';\n\nexport function briefRoutes(contextDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/brief', async (c) => {\n const body = await c.req.json();\n const validation = validateBrief(body);\n if (!validation.ok) {\n return c.json({ error: 'Invalid brief', details: validation.errors }, 400);\n }\n const brief = ContextBriefSchema.parse(body);\n brief.created_at = brief.created_at || new Date().toISOString();\n const briefPath = path.join(contextDir, `${brief.product_name}.context-brief.yaml`);\n fs.mkdirSync(contextDir, { recursive: true });\n fs.writeFileSync(briefPath, stringify(brief), 'utf-8');\n return c.json({ ok: true, path: `${brief.product_name}.context-brief.yaml` });\n });\n\n app.get('/api/brief/:name', async (c) => {\n const name = c.req.param('name');\n if (!PRODUCT_NAME_RE.test(name)) {\n return c.json({ error: 'Invalid product name' }, 400);\n }\n const briefPath = path.join(contextDir, `${name}.context-brief.yaml`);\n if (!fs.existsSync(briefPath)) return c.json({ error: 'Not found' }, 404);\n return c.json(parse(fs.readFileSync(briefPath, 'utf-8')));\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as yaml from 'yaml';\n\nexport interface DetectedSource {\n name: string;\n adapter: string;\n origin: string;\n status: 'detected' | 'connected' | 'error';\n}\n\n// ---------------------------------------------------------------------------\n// Lightweight MCP discovery (reads IDE config files for database servers)\n// ---------------------------------------------------------------------------\n\ninterface McpServerEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n type?: string;\n url?: string;\n headers?: Record<string, string>;\n}\n\n/** Known MCP server name patterns → adapter type */\nconst NAME_PATTERNS: Record<string, string> = {\n duckdb: 'duckdb', motherduck: 'duckdb',\n postgres: 'postgres', postgresql: 'postgres', neon: 'postgres', supabase: 'postgres',\n mysql: 'mysql', sqlite: 'sqlite', snowflake: 'snowflake',\n bigquery: 'bigquery', clickhouse: 'clickhouse', databricks: 'databricks',\n mssql: 'mssql', 'sql-server': 'mssql', redshift: 'postgres',\n};\n\n/** Known MCP package names → adapter type */\nconst PACKAGE_PATTERNS: Record<string, string> = {\n '@motherduck/mcp': 'duckdb', 'mcp-server-duckdb': 'duckdb',\n 'mcp-server-postgres': 'postgres', 'mcp-server-postgresql': 'postgres',\n '@neon/mcp': 'postgres', '@supabase/mcp': 'postgres',\n 'mcp-server-mysql': 'mysql', 'mcp-server-sqlite': 'sqlite',\n 'mcp-server-snowflake': 'snowflake', 'mcp-server-bigquery': 'bigquery',\n 'mcp-server-clickhouse': 'clickhouse', 'mcp-server-databricks': 'databricks',\n 'mcp-server-mssql': 'mssql', 'mcp-server-redshift': 'postgres',\n};\n\nfunction readJsonSafe(filePath: string): unknown | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n let raw = fs.readFileSync(filePath, 'utf-8');\n // Strip control characters that break JSON.parse (but keep \\n, \\r, \\t)\n raw = raw.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]/g, '');\n // Try parsing as-is first (most configs are valid JSON)\n try {\n return JSON.parse(raw);\n } catch {\n // Strip JSONC comments: only // at line start or after whitespace (not inside strings like URLs)\n const cleaned = raw\n .replace(/^\\s*\\/\\/.*$/gm, '')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/,(\\s*[}\\]])/g, '$1');\n return JSON.parse(cleaned);\n }\n } catch {\n return null;\n }\n}\n\nfunction getConfigLocations(cwd: string): Array<{ ide: string; path: string }> {\n const home = os.homedir();\n const locations: Array<{ ide: string; path: string }> = [];\n\n // Claude Code\n locations.push({ ide: 'claude-code', path: path.join(cwd, '.mcp.json') });\n locations.push({ ide: 'claude-code', path: path.join(home, '.claude.json') });\n locations.push({ ide: 'claude-code', path: path.join(home, '.claude', 'mcp_servers.json') });\n\n // Claude Desktop\n if (process.platform === 'darwin') {\n locations.push({ ide: 'claude-desktop', path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json') });\n } else if (process.platform === 'win32') {\n const appData = process.env.APPDATA ?? path.join(home, 'AppData', 'Roaming');\n locations.push({ ide: 'claude-desktop', path: path.join(appData, 'Claude', 'claude_desktop_config.json') });\n } else {\n locations.push({ ide: 'claude-desktop', path: path.join(home, '.config', 'claude', 'claude_desktop_config.json') });\n }\n\n // Cursor\n locations.push({ ide: 'cursor', path: path.join(cwd, '.cursor', 'mcp.json') });\n locations.push({ ide: 'cursor', path: path.join(home, '.cursor', 'mcp.json') });\n if (process.platform === 'darwin') {\n locations.push({ ide: 'cursor', path: path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json') });\n }\n\n // VS Code\n locations.push({ ide: 'vscode', path: path.join(cwd, '.vscode', 'mcp.json') });\n\n // Windsurf\n locations.push({ ide: 'windsurf', path: path.join(cwd, '.windsurf', 'mcp.json') });\n if (process.platform === 'darwin') {\n locations.push({ ide: 'windsurf', path: path.join(home, 'Library', 'Application Support', 'Windsurf', 'User', 'globalStorage', 'windsurf.mcp', 'mcp.json') });\n }\n\n return locations;\n}\n\nfunction detectAdapterType(serverName: string, entry: McpServerEntry): string | null {\n const nameLower = serverName.toLowerCase();\n\n // Check server name patterns\n for (const [pattern, adapter] of Object.entries(NAME_PATTERNS)) {\n if (nameLower.includes(pattern)) return adapter;\n }\n\n // Check package name in args (command-based servers)\n const args = entry.args ?? [];\n const allArgs = [entry.command ?? '', ...args].join(' ').toLowerCase();\n for (const [pkg, adapter] of Object.entries(PACKAGE_PATTERNS)) {\n if (allArgs.includes(pkg.toLowerCase())) return adapter;\n }\n\n // Check URL for HTTP-type servers (e.g. mcp.neon.tech → neon → postgres)\n if (entry.url) {\n const urlLower = entry.url.toLowerCase();\n for (const [pattern, adapter] of Object.entries(NAME_PATTERNS)) {\n if (urlLower.includes(pattern)) return adapter;\n }\n }\n\n return null;\n}\n\nfunction discoverMcpDatabases(cwd: string): DetectedSource[] {\n const results: DetectedSource[] = [];\n const seen = new Set<string>();\n\n try {\n const locations = getConfigLocations(cwd);\n\n for (const loc of locations) {\n const json = readJsonSafe(loc.path) as Record<string, unknown> | null;\n if (!json) continue;\n\n // Extract mcpServers from various config formats\n const servers = (json.mcpServers ?? json.mcp_servers ?? json.servers ?? json) as Record<string, McpServerEntry>;\n if (!servers || typeof servers !== 'object') continue;\n\n for (const [serverName, entry] of Object.entries(servers)) {\n if (!entry || typeof entry !== 'object') continue;\n\n const adapterType = detectAdapterType(serverName, entry);\n if (!adapterType) continue;\n\n const key = `${adapterType}:${serverName}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n // Build a friendly label\n const dbInfo = extractDbName(entry);\n const label = dbInfo\n ? `${serverName} (${adapterType}${dbInfo ? ' — ' + dbInfo : ''})`\n : `${serverName} (${adapterType})`;\n\n results.push({\n name: label,\n adapter: adapterType,\n origin: `mcp:${loc.ide}/${serverName}`,\n status: 'detected',\n });\n }\n }\n } catch {\n // Discovery is best-effort\n }\n\n return results;\n}\n\nfunction extractDbName(entry: McpServerEntry): string {\n const args = entry.args ?? [];\n // Look for database name in common arg patterns\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n // --database, --db, --dbname flags\n if ((arg === '--database' || arg === '--db' || arg === '--dbname') && args[i + 1]) {\n return args[i + 1];\n }\n // Connection URL\n if (arg && /^(postgres|mysql|mssql|clickhouse):\\/\\//.test(arg)) {\n try {\n const u = new URL(arg);\n return u.pathname.replace(/^\\//, '') || u.hostname;\n } catch { /* ignore */ }\n }\n }\n // Check env vars for connection info\n if (entry.env) {\n for (const [key, val] of Object.entries(entry.env)) {\n if (/^(DATABASE_URL|POSTGRES_URL|PG_CONNECTION)/.test(key) && val) {\n try {\n const u = new URL(val);\n return u.pathname.replace(/^\\//, '') || u.hostname;\n } catch { /* ignore */ }\n }\n }\n }\n return '';\n}\n\n// ---------------------------------------------------------------------------\n// Routes\n// ---------------------------------------------------------------------------\n\nexport function sourcesRoutes(rootDir: string, contextDir: string): Hono {\n const app = new Hono();\n\n app.get('/api/sources', (c) => {\n const sources: DetectedSource[] = [];\n\n // Read data_sources from runcontext.config.yaml\n try {\n const configPath = path.join(rootDir, 'runcontext.config.yaml');\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, 'utf-8');\n const config = yaml.parse(raw);\n if (config?.data_sources && typeof config.data_sources === 'object') {\n for (const [name, ds] of Object.entries(config.data_sources)) {\n const src = ds as { adapter?: string; connection?: string; path?: string };\n sources.push({\n name,\n adapter: src.adapter ?? 'auto',\n origin: `config:${name}`,\n status: 'detected',\n });\n }\n }\n }\n } catch {\n // ignore config read errors\n }\n\n // Check environment variables for common databases\n const envChecks: Array<{ env: string; adapter: string; name: string }> = [\n { env: 'DATABASE_URL', adapter: 'auto', name: 'Database (DATABASE_URL)' },\n { env: 'POSTGRES_URL', adapter: 'postgres', name: 'PostgreSQL' },\n { env: 'PG_CONNECTION_STRING', adapter: 'postgres', name: 'PostgreSQL' },\n { env: 'SNOWFLAKE_ACCOUNT', adapter: 'snowflake', name: 'Snowflake' },\n { env: 'BIGQUERY_PROJECT', adapter: 'bigquery', name: 'BigQuery' },\n { env: 'CLICKHOUSE_URL', adapter: 'clickhouse', name: 'ClickHouse' },\n { env: 'DATABRICKS_HOST', adapter: 'databricks', name: 'Databricks' },\n ];\n\n for (const check of envChecks) {\n if (process.env[check.env]) {\n sources.push({\n name: check.name,\n adapter: check.adapter,\n origin: `env:${check.env}`,\n status: 'detected',\n });\n }\n }\n\n // Check for local DuckDB files\n const duckdbFiles = ['*.duckdb', '*.db', '*.ddb'];\n for (const pattern of duckdbFiles) {\n const ext = pattern.replace('*', '');\n try {\n const files = fs.readdirSync(rootDir).filter((f) => f.endsWith(ext));\n for (const file of files) {\n sources.push({\n name: `DuckDB: ${file}`,\n adapter: 'duckdb',\n origin: `file:${file}`,\n status: 'detected',\n });\n }\n } catch {\n // ignore read errors\n }\n }\n\n // Check for SQLite files\n try {\n const sqliteFiles = fs.readdirSync(rootDir).filter((f) => f.endsWith('.sqlite') || f.endsWith('.sqlite3'));\n for (const file of sqliteFiles) {\n sources.push({\n name: `SQLite: ${file}`,\n adapter: 'sqlite',\n origin: `file:${file}`,\n status: 'detected',\n });\n }\n } catch {\n // ignore\n }\n\n // MCP discovery: scan IDE configs for database MCP servers\n const mcpSources = discoverMcpDatabases(rootDir);\n for (const mcp of mcpSources) {\n // Skip duplicates already found via config/env/files\n const alreadyFound = sources.some((s) =>\n s.origin === mcp.origin || (s.adapter === mcp.adapter && s.name === mcp.name)\n );\n if (!alreadyFound) {\n sources.push(mcp);\n }\n }\n\n return c.json(sources);\n });\n\n app.post('/api/sources', async (c) => {\n const body = await c.req.json<{ connection: string; name?: string }>();\n const { connection, name = 'default' } = body;\n\n if (!connection) {\n return c.json({ error: 'connection is required' }, 400);\n }\n\n // Detect adapter from URL prefix\n let adapter = 'auto';\n if (connection.startsWith('postgres://') || connection.startsWith('postgresql://')) {\n adapter = 'postgres';\n } else if (connection.startsWith('mysql://')) {\n adapter = 'mysql';\n } else if (connection.startsWith('mssql://') || connection.startsWith('sqlserver://')) {\n adapter = 'mssql';\n } else if (connection.startsWith('clickhouse://')) {\n adapter = 'clickhouse';\n }\n\n // Read existing config or create new\n const configPath = path.join(rootDir, 'runcontext.config.yaml');\n let config: Record<string, unknown> = {};\n try {\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, 'utf-8');\n config = yaml.parse(raw) ?? {};\n }\n } catch {\n // start fresh\n }\n\n if (!config.data_sources || typeof config.data_sources !== 'object') {\n config.data_sources = {};\n }\n\n const dataSources = config.data_sources as Record<string, { adapter: string; connection: string }>;\n dataSources[name] = { adapter, connection };\n\n fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');\n\n const created: DetectedSource = {\n name,\n adapter,\n origin: `config:${name}`,\n status: 'detected',\n };\n\n return c.json(created, 201);\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { PRODUCT_NAME_RE } from '@runcontext/core';\nconst MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\nconst ALLOWED_EXTENSIONS = ['.md', '.txt', '.pdf', '.csv', '.json', '.yaml', '.yml', '.sql', '.html'];\n\nexport function uploadRoutes(contextDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/upload/:productName', async (c) => {\n const productName = c.req.param('productName');\n if (!PRODUCT_NAME_RE.test(productName)) {\n return c.json({ error: 'Invalid product name' }, 400);\n }\n\n const formData = await c.req.formData();\n const file = formData.get('file');\n if (!file || !(file instanceof File)) {\n return c.json({ error: 'No file provided' }, 400);\n }\n\n if (file.size > MAX_FILE_SIZE) {\n return c.json({ error: 'File too large (max 10MB)' }, 400);\n }\n\n const ext = path.extname(file.name).toLowerCase();\n if (!ALLOWED_EXTENSIONS.includes(ext)) {\n return c.json({ error: `File type ${ext} not allowed` }, 400);\n }\n\n // Sanitize filename\n const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, '_');\n const docsDir = path.join(contextDir, 'products', productName, 'docs');\n fs.mkdirSync(docsDir, { recursive: true });\n\n const buffer = Buffer.from(await file.arrayBuffer());\n fs.writeFileSync(path.join(docsDir, safeName), buffer);\n\n return c.json({ ok: true, filename: safeName });\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport { execFile as execFileCb } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { promisify } from 'node:util';\nimport { fileURLToPath } from 'node:url';\nimport { parse as parseYaml } from 'yaml';\nimport { setupBus } from '../../events.js';\n\nconst execFile = promisify(execFileCb);\n\n/**\n * Resolve the CLI entry point. Prefers the local monorepo build\n * (so `context setup` in dev always uses the local code), falling\n * back to the currently-running process argv, then npx.\n */\nfunction resolveCliBin(): { cmd: string; prefix: string[] } {\n // 1. Try to find the local CLI dist relative to this package\n // (packages/ui → packages/cli/dist/index.js)\n try {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n const localCli = join(thisDir, '..', '..', '..', 'cli', 'dist', 'index.js');\n if (existsSync(localCli)) {\n return { cmd: process.execPath, prefix: [localCli] };\n }\n } catch { /* ignore */ }\n\n // 2. If this process was started via the CLI binary, reuse it\n if (process.argv[1] && existsSync(process.argv[1])) {\n return { cmd: process.execPath, prefix: [process.argv[1]] };\n }\n\n // 3. Fall back to npx (published CLI)\n return { cmd: 'npx', prefix: ['--yes', '@runcontext/cli'] };\n}\n\nexport type PipelineStage =\n | 'introspect'\n | 'scaffold'\n | 'enrich-silver'\n | 'enrich-gold'\n | 'verify'\n | 'autofix'\n | 'agent-instructions';\n\nexport type StageStatus = 'pending' | 'running' | 'done' | 'error' | 'skipped';\n\nexport interface PipelineStageState {\n stage: PipelineStage;\n status: StageStatus;\n summary?: string;\n error?: string;\n startedAt?: string;\n completedAt?: string;\n}\n\nexport interface PipelineRun {\n id: string;\n productName: string;\n targetTier: 'bronze' | 'silver' | 'gold';\n status: 'running' | 'done' | 'error';\n stages: PipelineStageState[];\n createdAt: string;\n}\n\nconst ALL_STAGES: PipelineStage[] = [\n 'introspect',\n 'scaffold',\n 'enrich-silver',\n 'enrich-gold',\n 'verify',\n 'autofix',\n 'agent-instructions',\n];\n\n// In-memory store for pipeline runs\nconst runs = new Map<string, PipelineRun>();\n\nfunction stagesForTier(tier: 'bronze' | 'silver' | 'gold'): PipelineStage[] {\n const base: PipelineStage[] = ['introspect', 'scaffold'];\n if (tier === 'silver' || tier === 'gold') base.push('enrich-silver');\n if (tier === 'gold') base.push('enrich-gold');\n base.push('verify', 'autofix', 'agent-instructions');\n return base;\n}\n\nexport function pipelineRoutes(rootDir: string, contextDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/pipeline/start', async (c) => {\n const body = await c.req.json();\n const { productName, targetTier, dataSource, sessionId } = body;\n\n if (!productName || !targetTier) {\n return c.json({ error: 'productName and targetTier required' }, 400);\n }\n\n if (!['bronze', 'silver', 'gold'].includes(targetTier)) {\n return c.json({ error: 'targetTier must be bronze, silver, or gold' }, 400);\n }\n\n // Validate productName: alphanumeric, hyphens, underscores only\n const safeNamePattern = /^[a-zA-Z0-9_-]+$/;\n if (!safeNamePattern.test(productName)) {\n return c.json({ error: 'productName must contain only letters, numbers, hyphens, and underscores' }, 400);\n }\n\n // Validate dataSource if provided\n if (dataSource && !safeNamePattern.test(dataSource)) {\n return c.json({ error: 'dataSource must contain only letters, numbers, hyphens, and underscores' }, 400);\n }\n\n const id = randomUUID();\n const activeStages = stagesForTier(targetTier);\n const skippedStages = ALL_STAGES.filter((s) => !activeStages.includes(s));\n\n const run: PipelineRun = {\n id,\n productName,\n targetTier,\n status: 'running',\n stages: ALL_STAGES.map((stage) => ({\n stage,\n status: skippedStages.includes(stage) ? 'skipped' : 'pending',\n })),\n createdAt: new Date().toISOString(),\n };\n\n runs.set(id, run);\n\n // Start the pipeline asynchronously (non-blocking)\n executePipeline(run, rootDir, contextDir, dataSource, sessionId).catch((err) => {\n run.status = 'error';\n const currentStage = run.stages.find((s) => s.status === 'running');\n if (currentStage) {\n currentStage.status = 'error';\n currentStage.error = err instanceof Error ? err.message : String(err);\n }\n });\n\n return c.json({ id, status: 'running' });\n });\n\n app.get('/api/pipeline/status/:id', (c) => {\n const run = runs.get(c.req.param('id'));\n if (!run) return c.json({ error: 'Not found' }, 404);\n return c.json(run);\n });\n\n app.get('/api/mcp-config', (c) => {\n // Read runcontext.config.yaml to find a data source connection string\n let connection: string | undefined;\n try {\n const configPath = join(rootDir, 'runcontext.config.yaml');\n const configRaw = readFileSync(configPath, 'utf-8');\n const config = parseYaml(configRaw) as Record<string, unknown>;\n const dataSources = config?.data_sources as\n | Record<string, { connection?: string; path?: string }>\n | undefined;\n if (dataSources) {\n const firstKey = Object.keys(dataSources)[0];\n if (firstKey) {\n connection = dataSources[firstKey].connection || dataSources[firstKey].path;\n }\n }\n } catch {\n // Config file missing or unparseable – proceed without db entry\n }\n\n const cli = resolveCliBin();\n const mcpServers: Record<string, unknown> = {\n runcontext: {\n command: cli.cmd,\n args: [...cli.prefix, 'serve'],\n cwd: rootDir,\n },\n };\n\n if (connection) {\n mcpServers['runcontext-db'] = {\n command: 'npx',\n args: ['--yes', '@runcontext/db', '--url', connection],\n };\n }\n\n return c.json({ mcpServers });\n });\n\n return app;\n}\n\nfunction buildCliArgs(\n stage: PipelineStage,\n dataSource?: string,\n): string[] {\n switch (stage) {\n case 'introspect': {\n const args = ['introspect'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'scaffold':\n return ['build'];\n case 'enrich-silver': {\n const args = ['enrich', '--target', 'silver', '--apply'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'enrich-gold': {\n const args = ['enrich', '--target', 'gold', '--apply'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'verify':\n return ['verify'];\n case 'autofix':\n return ['fix'];\n case 'agent-instructions':\n return ['build'];\n }\n}\n\nfunction extractSummary(stdout: string): string {\n const lines = stdout.trim().split('\\n').filter(Boolean);\n return lines.slice(-3).join('\\n') || 'completed';\n}\n\nasync function executePipeline(\n run: PipelineRun,\n rootDir: string,\n contextDir: string,\n dataSource?: string,\n sessionId?: string,\n): Promise<void> {\n for (const stage of run.stages) {\n if (stage.status === 'skipped') continue;\n\n stage.status = 'running';\n stage.startedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'running' },\n });\n }\n\n try {\n const cliArgs = buildCliArgs(stage.stage, dataSource);\n const cli = resolveCliBin();\n const { stdout } = await execFile(cli.cmd, [...cli.prefix, ...cliArgs], {\n cwd: rootDir,\n timeout: 120_000,\n env: {\n ...process.env,\n NODE_OPTIONS: '--max-old-space-size=4096 --no-deprecation',\n },\n });\n stage.status = 'done';\n stage.summary = extractSummary(stdout);\n stage.completedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'done', summary: stage.summary },\n });\n }\n } catch (err: unknown) {\n // If the command produced stdout despite failing, treat warnings-only\n // exits as success (e.g. verify with SSL deprecation warnings)\n const execErr = err as { stdout?: string; stderr?: string; code?: number };\n if (execErr.stdout && execErr.stdout.trim().length > 0) {\n stage.status = 'done';\n stage.summary = extractSummary(execErr.stdout);\n stage.completedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'done', summary: stage.summary },\n });\n }\n continue;\n }\n stage.status = 'error';\n stage.error = err instanceof Error ? err.message : String(err);\n stage.completedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'error', error: stage.error },\n });\n }\n run.status = 'error';\n return;\n }\n }\n\n run.status = 'done';\n}\n","import { EventEmitter } from 'node:events';\nimport { randomUUID } from 'node:crypto';\n\nexport interface SetupEvent {\n type: string;\n sessionId: string;\n payload: Record<string, unknown>;\n}\n\n// Agent/CLI -> Wizard event types\nexport type AgentEventType =\n | 'setup:step' // Navigate wizard to a step\n | 'setup:field' // Update a form field value\n | 'pipeline:stage' // Stage status change\n | 'pipeline:detail' // Stage detail update\n | 'tier:update' // Tier scorecard changed\n | 'enrich:progress' // Enrichment checklist item updated\n | 'enrich:log'; // Activity log entry\n\n// Wizard -> Agent/CLI event types\nexport type WizardEventType =\n | 'user:field' // User edited a form field\n | 'user:confirm' // User clicked Continue/Approve\n | 'user:retry' // User clicked Retry\n | 'user:cancel'; // User cancelled\n\nclass SetupEventBus extends EventEmitter {\n private sessions = new Map<string, { createdAt: string }>();\n\n createSession(): string {\n const id = randomUUID();\n this.sessions.set(id, { createdAt: new Date().toISOString() });\n return id;\n }\n\n hasSession(id: string): boolean {\n return this.sessions.has(id);\n }\n\n removeSession(id: string): void {\n this.sessions.delete(id);\n }\n\n emitEvent(event: SetupEvent): void {\n this.emit('event', event);\n this.emit(event.type, event);\n }\n}\n\nexport const setupBus = new SetupEventBus();\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parse } from 'yaml';\n\nexport interface ExistingProduct {\n name: string;\n description?: string;\n sensitivity?: string;\n hasBrief: boolean;\n}\n\nexport function productsRoutes(contextDir: string): Hono {\n const app = new Hono();\n\n app.get('/api/products', (c) => {\n const productsDir = path.join(contextDir, 'products');\n if (!fs.existsSync(productsDir)) {\n return c.json([]);\n }\n\n const products: ExistingProduct[] = [];\n const dirs = fs.readdirSync(productsDir).filter((name) => {\n const fullPath = path.join(productsDir, name);\n return fs.statSync(fullPath).isDirectory() && !name.startsWith('.');\n });\n\n for (const name of dirs) {\n const briefPath = path.join(productsDir, name, 'context-brief.yaml');\n let description: string | undefined;\n let sensitivity: string | undefined;\n let hasBrief = false;\n\n if (fs.existsSync(briefPath)) {\n hasBrief = true;\n try {\n const brief = parse(fs.readFileSync(briefPath, 'utf-8'));\n description = brief?.description;\n sensitivity = brief?.sensitivity;\n } catch {\n // ignore parse errors\n }\n }\n\n products.push({ name, description, sensitivity, hasBrief });\n }\n\n return c.json(products);\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport {\n createDefaultRegistry,\n CredentialStore,\n} from '@runcontext/core';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nexport function authRoutes(rootDir: string): Hono {\n const app = new Hono();\n const registry = createDefaultRegistry();\n const store = new CredentialStore();\n\n // List all supported providers with CLI detection status\n app.get('/api/auth/providers', async (c) => {\n const providers = await Promise.all(\n registry.getAll().map(async (p) => {\n const cli = await p.detectCli();\n return {\n id: p.id,\n displayName: p.displayName,\n adapters: p.adapters,\n cliInstalled: cli.installed,\n cliAuthenticated: cli.authenticated,\n };\n }),\n );\n return c.json(providers);\n });\n\n // Start authentication with a provider\n app.post('/api/auth/start', async (c) => {\n const { provider: providerId } = await c.req.json();\n const provider = registry.get(providerId);\n if (!provider) {\n return c.json({ error: `Unknown provider: ${providerId}` }, 400);\n }\n\n // First, try listing databases without full re-auth.\n // If the provider already has valid stored credentials (e.g. \"CLI authenticated\"),\n // this avoids re-running neonctl auth which can open a broken OAuth browser window.\n try {\n const databases = await provider.listDatabases();\n if (databases.length > 0) {\n return c.json({ ok: true, provider: providerId, databases });\n }\n } catch { /* fall through to full authenticate */ }\n\n // Full authentication flow (may open browser for OAuth)\n const result = await provider.authenticate();\n if (!result.ok) {\n return c.json({ error: result.error }, 401);\n }\n\n // List databases after successful auth — pass the fresh token\n const databases = await provider.listDatabases(result.token);\n\n return c.json({\n ok: true,\n provider: providerId,\n databases,\n });\n });\n\n // Select a database and save credentials\n app.post('/api/auth/select-db', async (c) => {\n const { provider: providerId, database } = await c.req.json();\n const provider = registry.get(providerId);\n if (!provider) {\n return c.json({ error: `Unknown provider: ${providerId}` }, 400);\n }\n\n // Use token from database metadata (set during listDatabases) or re-authenticate\n let token = database.metadata?.token as string | undefined;\n if (!token) {\n const authResult = await provider.authenticate();\n if (!authResult.ok) {\n return c.json({ error: authResult.error }, 401);\n }\n token = authResult.token;\n }\n\n // Build and test connection\n database.metadata = { ...database.metadata, token };\n const connStr = await provider.getConnectionString(database);\n\n try {\n const { createAdapter } = await import('@runcontext/core');\n const adapter = await createAdapter({\n adapter: database.adapter,\n connection: connStr,\n });\n await adapter.connect();\n await adapter.query('SELECT 1');\n await adapter.disconnect();\n } catch (err) {\n return c.json({ error: `Connection failed: ${(err as Error).message}` }, 400);\n }\n\n // Save credential\n const credKey = `${providerId}:${database.id}`;\n await store.save({\n provider: providerId,\n key: credKey,\n token: token!,\n refreshToken: undefined,\n expiresAt: undefined,\n metadata: {\n host: database.host,\n database: database.name,\n ...database.metadata,\n token: undefined,\n },\n });\n\n // Update config\n const configPath = path.join(rootDir, 'runcontext.config.yaml');\n let config: Record<string, any> = {};\n if (fs.existsSync(configPath)) {\n try {\n config = parseYaml(fs.readFileSync(configPath, 'utf-8')) ?? {};\n } catch { /* start fresh */ }\n }\n config.data_sources = config.data_sources ?? {};\n const sourceName = (database.name || database.database || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');\n config.data_sources[sourceName] = { adapter: database.adapter, connection: connStr };\n fs.writeFileSync(configPath, stringifyYaml(config), 'utf-8');\n\n return c.json({ ok: true, auth: credKey });\n });\n\n // List stored credentials\n app.get('/api/auth/credentials', async (c) => {\n const keys = await store.list();\n return c.json(keys);\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFile = promisify(execFileCb);\n\nexport function suggestBriefRoutes(rootDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/suggest-brief', async (c) => {\n const body = await c.req.json<{\n source?: {\n name?: string;\n adapter?: string;\n host?: string;\n metadata?: Record<string, unknown>;\n };\n }>();\n\n const source = body.source || {};\n const meta = source.metadata || {};\n\n // --- Derive product name ---\n // Use project name, db name, or fallback\n const projectName = (meta.project as string) || '';\n const dbName = source.name || '';\n const rawName = projectName || dbName || 'my-data';\n // Sanitize to alphanumeric + hyphens\n const productName = rawName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\n // --- Derive description ---\n const branch = (meta.branch as string) || 'main';\n const adapter = source.adapter || 'database';\n const region = (meta.region as string) || '';\n const org = (meta.org as string) || '';\n\n let description = `Semantic context for the ${rawName} ${adapter} database`;\n if (branch !== 'main') description += ` (${branch} branch)`;\n if (org && org !== 'Personal') description += `, managed by ${org}`;\n description += '.';\n\n // --- Git user info ---\n let ownerName = '';\n let ownerEmail = '';\n let ownerTeam = '';\n try {\n const { stdout: name } = await execFile('git', ['config', 'user.name'], {\n cwd: rootDir,\n timeout: 3000,\n });\n ownerName = name.trim();\n } catch { /* ignore */ }\n try {\n const { stdout: email } = await execFile('git', ['config', 'user.email'], {\n cwd: rootDir,\n timeout: 3000,\n });\n ownerEmail = email.trim();\n } catch { /* ignore */ }\n\n // Try to derive team from email domain or org\n if (org && org !== 'Personal') {\n ownerTeam = org;\n } else if (ownerEmail) {\n const domain = ownerEmail.split('@')[1];\n if (domain && !['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com'].includes(domain)) {\n // Use domain name as team hint\n ownerTeam = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1);\n }\n }\n\n return c.json({\n product_name: productName,\n description,\n owner: {\n name: ownerName,\n email: ownerEmail,\n team: ownerTeam,\n },\n sensitivity: 'internal',\n });\n });\n\n return app;\n}\n","import { WebSocketServer, WebSocket } from 'ws';\nimport type { Server } from 'node:http';\nimport { setupBus, type SetupEvent } from '../events.js';\n\nexport function attachWebSocket(server: Server): void {\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n wss.on('connection', (ws, req) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const sessionId = url.searchParams.get('session');\n\n if (!sessionId) {\n ws.close(4001, 'session query param required');\n return;\n }\n\n // Auto-create session if it does not exist\n if (!setupBus.hasSession(sessionId)) {\n setupBus.createSession();\n }\n\n // Forward bus events to this WebSocket client\n const onEvent = (event: SetupEvent) => {\n if (event.sessionId !== sessionId) return;\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(event));\n }\n };\n setupBus.on('event', onEvent);\n\n // Receive messages from this client and broadcast via bus\n ws.on('message', (raw) => {\n try {\n const msg = JSON.parse(raw.toString()) as SetupEvent;\n msg.sessionId = sessionId;\n setupBus.emitEvent(msg);\n } catch {\n // ignore malformed messages\n }\n });\n\n ws.on('close', () => {\n setupBus.off('event', onEvent);\n });\n });\n}\n"]}
1
+ {"version":3,"sources":["/Users/erickittelson/Code/RunContext/runcontext/packages/ui/dist/index.cjs","../src/server.ts","../src/routes/api/brief.ts","../src/routes/api/sources.ts","../src/routes/api/upload.ts","../src/routes/api/pipeline.ts","../src/events.ts","../src/routes/api/products.ts","../src/routes/api/auth.ts","../src/routes/api/suggest-brief.ts","../src/routes/ws.ts"],"names":["PRODUCT_NAME_RE","execFile"],"mappings":"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","file":"/Users/erickittelson/Code/RunContext/runcontext/packages/ui/dist/index.cjs","sourcesContent":[null,"import { Hono } from 'hono';\nimport { serve } from '@hono/node-server';\nimport { cors } from 'hono/cors';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as url from 'node:url';\nimport { briefRoutes } from './routes/api/brief.js';\nimport { sourcesRoutes } from './routes/api/sources.js';\nimport { uploadRoutes } from './routes/api/upload.js';\nimport { pipelineRoutes } from './routes/api/pipeline.js';\nimport { productsRoutes } from './routes/api/products.js';\nimport { authRoutes } from './routes/api/auth.js';\nimport { suggestBriefRoutes } from './routes/api/suggest-brief.js';\nimport { attachWebSocket } from './routes/ws.js';\nimport { setupBus } from './events.js';\n\nexport interface UIServerOptions {\n rootDir: string;\n contextDir: string;\n port: number;\n host: string;\n}\n\nconst __dirname = path.dirname(url.fileURLToPath(import.meta.url));\nconst staticDir = path.resolve(__dirname, '..', 'static');\n\nexport function createApp(opts: UIServerOptions): Hono {\n const app = new Hono();\n\n app.use('*', cors({\n origin: (origin) => {\n // Allow requests with no origin (e.g. same-origin, curl, server-to-server)\n if (!origin) return origin;\n // Allow localhost on any port\n if (/^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(origin)) {\n return origin;\n }\n return null;\n },\n }));\n\n app.route('', briefRoutes(opts.contextDir));\n app.route('', sourcesRoutes(opts.rootDir, opts.contextDir));\n app.route('', uploadRoutes(opts.contextDir));\n app.route('', pipelineRoutes(opts.rootDir, opts.contextDir));\n app.route('', productsRoutes(opts.contextDir));\n app.route('', authRoutes(opts.rootDir));\n app.route('', suggestBriefRoutes(opts.rootDir));\n\n app.get('/api/health', (c) => c.json({ ok: true }));\n\n app.post('/api/session', (c) => {\n const id = setupBus.createSession();\n return c.json({ sessionId: id });\n });\n\n // Static file serving (CSS, JS)\n app.get('/static/:filename', (c) => {\n const filename = c.req.param('filename');\n if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {\n return c.text('Not found', 404);\n }\n const filePath = path.join(staticDir, filename);\n if (!fs.existsSync(filePath)) return c.text('Not found', 404);\n\n const ext = path.extname(filename);\n const contentType =\n ext === '.css' ? 'text/css'\n : ext === '.js' ? 'application/javascript'\n : 'application/octet-stream';\n\n return c.body(fs.readFileSync(filePath), 200, { 'Content-Type': contentType });\n });\n\n app.get('/setup', (c) => {\n return c.html(setupPageHTML());\n });\n\n app.get('/planes', (c) => {\n return c.html(pageHTML({\n title: 'Semantic Planes',\n activePage: 'planes',\n contentId: 'page-content',\n\n }));\n });\n\n app.get('/analytics', (c) => {\n return c.html(pageHTML({\n title: 'Analytics',\n activePage: 'analytics',\n contentId: 'page-content',\n\n }));\n });\n\n app.get('/settings', (c) => {\n return c.html(pageHTML({\n title: 'Settings',\n activePage: 'settings',\n contentId: 'page-content',\n\n }));\n });\n\n app.get('/', (c) => c.redirect('/setup'));\n\n return app;\n}\n\ninterface PageHTMLOptions {\n title: string;\n activePage: 'setup' | 'planes' | 'analytics' | 'settings';\n contentId: string;\n}\n\nfunction sidebarHTML(activePage: PageHTMLOptions['activePage']): string {\n const nav = (page: PageHTMLOptions['activePage'], href: string, label: string) => {\n const isActive = activePage === page;\n return `<a class=\"nav-item${isActive ? ' active' : ''}\" href=\"${href}\">\n <span>${label}</span>\n </a>`;\n };\n\n return `<aside class=\"sidebar\">\n <div class=\"sidebar-brand\">\n <svg class=\"brand-chevron\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M4 4l8 8-8 8\" stroke=\"#c9a55a\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M12 4l8 8-8 8\" stroke=\"#c9a55a\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\"/>\n </svg>\n <span class=\"brand-text\">\n <span class=\"brand-run\">Run</span><span class=\"brand-context\">Context</span>\n </span>\n <span class=\"brand-badge\">Local</span>\n </div>\n <nav class=\"sidebar-nav\">\n ${nav('setup', '/setup', 'Setup')}\n ${nav('planes', '/planes', 'Semantic Planes')}\n ${nav('analytics', '/analytics', 'Analytics')}\n <div class=\"nav-item mcp-toggle\" id=\"mcp-nav-toggle\" title=\"Click to start/stop MCP server\" style=\"cursor:pointer\">\n <span class=\"status-dot\" id=\"mcp-status-dot\"></span>\n <span>MCP Server</span>\n <span class=\"nav-detail\" id=\"mcp-status-text\">checking...</span>\n </div>\n ${nav('settings', '/settings', 'Settings')}\n </nav>\n <div class=\"sidebar-status\">\n <div class=\"status-row\">\n <span class=\"status-dot\" id=\"db-status-dot\"></span>\n <span id=\"db-status-text\">No database</span>\n </div>\n <div class=\"status-row mcp-toggle\" id=\"mcp-toggle-row\" title=\"Click to start/stop MCP server\">\n <span class=\"status-dot\" id=\"mcp-server-dot\"></span>\n <span id=\"mcp-server-text\">MCP stopped</span>\n </div>\n <div class=\"status-row\" id=\"tier-row\">\n <span class=\"tier-badge\" id=\"tier-badge\">Free</span>\n </div>\n </div>\n <div class=\"sidebar-security\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"var(--rc-color-status-success)\" stroke-width=\"2\">\n <path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/>\n </svg>\n <span>Local-only processing</span>\n </div>\n </aside>`;\n}\n\nfunction footerHTML(): string {\n return `<footer class=\"app-footer\">\n <span>Powered by <a href=\"https://runcontext.dev\" target=\"_blank\" rel=\"noopener\">RunContext</a></span>\n <span class=\"footer-links\">\n <a href=\"https://docs.runcontext.dev\" target=\"_blank\" rel=\"noopener\">Docs</a>\n <span class=\"footer-sep\">&middot;</span>\n <a href=\"https://runcontext.dev/pricing\" target=\"_blank\" rel=\"noopener\">Cloud</a>\n <span class=\"footer-sep\">&middot;</span>\n <a href=\"https://github.com/Quiet-Victory-Labs/runcontext\" target=\"_blank\" rel=\"noopener\">GitHub</a>\n </span>\n </footer>`;\n}\n\nfunction pageHTML(opts: PageHTMLOptions): string {\n const isSetup = opts.activePage === 'setup';\n const headerContent = isSetup\n ? `<div class=\"header-stepper\" id=\"stepper\"></div>`\n : `<h1 class=\"header-title\">${opts.title}</h1>`;\n const lockedTooltip = isSetup\n ? `\\n <!-- Locked tooltip (hidden by default) -->\n <div class=\"locked-tooltip\" id=\"locked-tooltip\" style=\"display:none\">\n <p><strong>Cloud Feature</strong></p>\n <p>This feature is available on RunContext Cloud with team collaboration, hosted endpoints, and analytics.</p>\n <a href=\"https://runcontext.dev/pricing\" target=\"_blank\" rel=\"noopener\">View plans →</a>\n </div>`\n : '';\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\" />\n <title>RunContext — ${opts.title}</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap\" rel=\"stylesheet\" />\n <link rel=\"stylesheet\" href=\"/static/uxd.css\" />\n <link rel=\"stylesheet\" href=\"/static/setup.css\" />\n</head>\n<body data-page=\"${opts.activePage}\">\n <div class=\"app-shell\">\n <!-- Sidebar -->\n ${sidebarHTML(opts.activePage)}\n\n <!-- Header -->\n <header class=\"app-header\">\n ${headerContent}\n </header>\n\n <!-- Main Content -->\n <main class=\"main-content\">\n <div class=\"content-wrapper\" id=\"${opts.contentId}\"></div>\n </main>\n\n <!-- Footer -->\n ${footerHTML()}\n </div>\n${lockedTooltip}\n <script src=\"/static/app.js\"></script>\n</body>\n</html>`;\n}\n\nfunction setupPageHTML(): string {\n return pageHTML({\n title: 'Build Your Context Layer',\n activePage: 'setup',\n contentId: 'wizard-content',\n });\n}\n\nexport function startUIServer(opts: UIServerOptions): Promise<void> {\n return new Promise((resolve, reject) => {\n const app = createApp(opts);\n\n const server = serve({\n fetch: app.fetch,\n port: opts.port,\n hostname: opts.host,\n }, (info) => {\n console.log(`RunContext UI running at http://${opts.host === '0.0.0.0' ? 'localhost' : opts.host}:${info.port}/setup`);\n resolve();\n });\n\n attachWebSocket(server as unknown as import('node:http').Server);\n\n server.on('error', reject);\n });\n}\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { stringify, parse } from 'yaml';\nimport { validateBrief, ContextBriefSchema, PRODUCT_NAME_RE } from '@runcontext/core';\n\nexport function briefRoutes(contextDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/brief', async (c) => {\n const body = await c.req.json();\n const validation = validateBrief(body);\n if (!validation.ok) {\n return c.json({ error: 'Invalid brief', details: validation.errors }, 400);\n }\n const brief = ContextBriefSchema.parse(body);\n brief.created_at = brief.created_at || new Date().toISOString();\n const briefPath = path.join(contextDir, `${brief.product_name}.context-brief.yaml`);\n fs.mkdirSync(contextDir, { recursive: true });\n fs.writeFileSync(briefPath, stringify(brief), 'utf-8');\n return c.json({ ok: true, path: `${brief.product_name}.context-brief.yaml` });\n });\n\n app.get('/api/brief/:name', async (c) => {\n const name = c.req.param('name');\n if (!PRODUCT_NAME_RE.test(name)) {\n return c.json({ error: 'Invalid product name' }, 400);\n }\n const briefPath = path.join(contextDir, `${name}.context-brief.yaml`);\n if (!fs.existsSync(briefPath)) return c.json({ error: 'Not found' }, 404);\n return c.json(parse(fs.readFileSync(briefPath, 'utf-8')));\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as yaml from 'yaml';\n\nexport interface DetectedSource {\n name: string;\n adapter: string;\n origin: string;\n status: 'detected' | 'connected' | 'error';\n}\n\n// ---------------------------------------------------------------------------\n// Lightweight MCP discovery (reads IDE config files for database servers)\n// ---------------------------------------------------------------------------\n\ninterface McpServerEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n type?: string;\n url?: string;\n headers?: Record<string, string>;\n}\n\n/** Known MCP server name patterns → adapter type */\nconst NAME_PATTERNS: Record<string, string> = {\n duckdb: 'duckdb', motherduck: 'duckdb',\n postgres: 'postgres', postgresql: 'postgres', neon: 'postgres', supabase: 'postgres',\n mysql: 'mysql', sqlite: 'sqlite', snowflake: 'snowflake',\n bigquery: 'bigquery', clickhouse: 'clickhouse', databricks: 'databricks',\n mssql: 'mssql', 'sql-server': 'mssql', redshift: 'postgres',\n};\n\n/** Known MCP package names → adapter type */\nconst PACKAGE_PATTERNS: Record<string, string> = {\n '@motherduck/mcp': 'duckdb', 'mcp-server-duckdb': 'duckdb',\n 'mcp-server-postgres': 'postgres', 'mcp-server-postgresql': 'postgres',\n '@neon/mcp': 'postgres', '@supabase/mcp': 'postgres',\n 'mcp-server-mysql': 'mysql', 'mcp-server-sqlite': 'sqlite',\n 'mcp-server-snowflake': 'snowflake', 'mcp-server-bigquery': 'bigquery',\n 'mcp-server-clickhouse': 'clickhouse', 'mcp-server-databricks': 'databricks',\n 'mcp-server-mssql': 'mssql', 'mcp-server-redshift': 'postgres',\n};\n\nfunction readJsonSafe(filePath: string): unknown | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n let raw = fs.readFileSync(filePath, 'utf-8');\n // Strip control characters that break JSON.parse (but keep \\n, \\r, \\t)\n raw = raw.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]/g, '');\n // Try parsing as-is first (most configs are valid JSON)\n try {\n return JSON.parse(raw);\n } catch {\n // Strip JSONC comments: only // at line start or after whitespace (not inside strings like URLs)\n const cleaned = raw\n .replace(/^\\s*\\/\\/.*$/gm, '')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/,(\\s*[}\\]])/g, '$1');\n return JSON.parse(cleaned);\n }\n } catch {\n return null;\n }\n}\n\nfunction getConfigLocations(cwd: string): Array<{ ide: string; path: string }> {\n const home = os.homedir();\n const locations: Array<{ ide: string; path: string }> = [];\n\n // Claude Code\n locations.push({ ide: 'claude-code', path: path.join(cwd, '.mcp.json') });\n locations.push({ ide: 'claude-code', path: path.join(home, '.claude.json') });\n locations.push({ ide: 'claude-code', path: path.join(home, '.claude', 'mcp_servers.json') });\n\n // Claude Desktop\n if (process.platform === 'darwin') {\n locations.push({ ide: 'claude-desktop', path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json') });\n } else if (process.platform === 'win32') {\n const appData = process.env.APPDATA ?? path.join(home, 'AppData', 'Roaming');\n locations.push({ ide: 'claude-desktop', path: path.join(appData, 'Claude', 'claude_desktop_config.json') });\n } else {\n locations.push({ ide: 'claude-desktop', path: path.join(home, '.config', 'claude', 'claude_desktop_config.json') });\n }\n\n // Cursor\n locations.push({ ide: 'cursor', path: path.join(cwd, '.cursor', 'mcp.json') });\n locations.push({ ide: 'cursor', path: path.join(home, '.cursor', 'mcp.json') });\n if (process.platform === 'darwin') {\n locations.push({ ide: 'cursor', path: path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json') });\n }\n\n // VS Code\n locations.push({ ide: 'vscode', path: path.join(cwd, '.vscode', 'mcp.json') });\n\n // Windsurf\n locations.push({ ide: 'windsurf', path: path.join(cwd, '.windsurf', 'mcp.json') });\n if (process.platform === 'darwin') {\n locations.push({ ide: 'windsurf', path: path.join(home, 'Library', 'Application Support', 'Windsurf', 'User', 'globalStorage', 'windsurf.mcp', 'mcp.json') });\n }\n\n return locations;\n}\n\nfunction detectAdapterType(serverName: string, entry: McpServerEntry): string | null {\n const nameLower = serverName.toLowerCase();\n\n // Check server name patterns\n for (const [pattern, adapter] of Object.entries(NAME_PATTERNS)) {\n if (nameLower.includes(pattern)) return adapter;\n }\n\n // Check package name in args (command-based servers)\n const args = entry.args ?? [];\n const allArgs = [entry.command ?? '', ...args].join(' ').toLowerCase();\n for (const [pkg, adapter] of Object.entries(PACKAGE_PATTERNS)) {\n if (allArgs.includes(pkg.toLowerCase())) return adapter;\n }\n\n // Check URL for HTTP-type servers (e.g. mcp.neon.tech → neon → postgres)\n if (entry.url) {\n const urlLower = entry.url.toLowerCase();\n for (const [pattern, adapter] of Object.entries(NAME_PATTERNS)) {\n if (urlLower.includes(pattern)) return adapter;\n }\n }\n\n return null;\n}\n\nfunction discoverMcpDatabases(cwd: string): DetectedSource[] {\n const results: DetectedSource[] = [];\n const seen = new Set<string>();\n\n try {\n const locations = getConfigLocations(cwd);\n\n for (const loc of locations) {\n const json = readJsonSafe(loc.path) as Record<string, unknown> | null;\n if (!json) continue;\n\n // Extract mcpServers from various config formats\n const servers = (json.mcpServers ?? json.mcp_servers ?? json.servers ?? json) as Record<string, McpServerEntry>;\n if (!servers || typeof servers !== 'object') continue;\n\n for (const [serverName, entry] of Object.entries(servers)) {\n if (!entry || typeof entry !== 'object') continue;\n\n const adapterType = detectAdapterType(serverName, entry);\n if (!adapterType) continue;\n\n const key = `${adapterType}:${serverName}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n // Build a friendly label\n const dbInfo = extractDbName(entry);\n const label = dbInfo\n ? `${serverName} (${adapterType}${dbInfo ? ' — ' + dbInfo : ''})`\n : `${serverName} (${adapterType})`;\n\n results.push({\n name: label,\n adapter: adapterType,\n origin: `mcp:${loc.ide}/${serverName}`,\n status: 'detected',\n });\n }\n }\n } catch {\n // Discovery is best-effort\n }\n\n return results;\n}\n\nfunction extractDbName(entry: McpServerEntry): string {\n const args = entry.args ?? [];\n // Look for database name in common arg patterns\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n // --database, --db, --dbname flags\n if ((arg === '--database' || arg === '--db' || arg === '--dbname') && args[i + 1]) {\n return args[i + 1];\n }\n // Connection URL\n if (arg && /^(postgres|mysql|mssql|clickhouse):\\/\\//.test(arg)) {\n try {\n const u = new URL(arg);\n return u.pathname.replace(/^\\//, '') || u.hostname;\n } catch { /* ignore */ }\n }\n }\n // Check env vars for connection info\n if (entry.env) {\n for (const [key, val] of Object.entries(entry.env)) {\n if (/^(DATABASE_URL|POSTGRES_URL|PG_CONNECTION)/.test(key) && val) {\n try {\n const u = new URL(val);\n return u.pathname.replace(/^\\//, '') || u.hostname;\n } catch { /* ignore */ }\n }\n }\n }\n return '';\n}\n\n// ---------------------------------------------------------------------------\n// Routes\n// ---------------------------------------------------------------------------\n\nexport function sourcesRoutes(rootDir: string, contextDir: string): Hono {\n const app = new Hono();\n\n app.get('/api/sources', (c) => {\n const sources: DetectedSource[] = [];\n\n // Read data_sources from runcontext.config.yaml\n try {\n const configPath = path.join(rootDir, 'runcontext.config.yaml');\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, 'utf-8');\n const config = yaml.parse(raw);\n if (config?.data_sources && typeof config.data_sources === 'object') {\n for (const [name, ds] of Object.entries(config.data_sources)) {\n const src = ds as { adapter?: string; connection?: string; path?: string };\n sources.push({\n name,\n adapter: src.adapter ?? 'auto',\n origin: `config:${name}`,\n status: 'detected',\n });\n }\n }\n }\n } catch {\n // ignore config read errors\n }\n\n // Check environment variables for common databases\n const envChecks: Array<{ env: string; adapter: string; name: string }> = [\n { env: 'DATABASE_URL', adapter: 'auto', name: 'Database (DATABASE_URL)' },\n { env: 'POSTGRES_URL', adapter: 'postgres', name: 'PostgreSQL' },\n { env: 'PG_CONNECTION_STRING', adapter: 'postgres', name: 'PostgreSQL' },\n { env: 'SNOWFLAKE_ACCOUNT', adapter: 'snowflake', name: 'Snowflake' },\n { env: 'BIGQUERY_PROJECT', adapter: 'bigquery', name: 'BigQuery' },\n { env: 'CLICKHOUSE_URL', adapter: 'clickhouse', name: 'ClickHouse' },\n { env: 'DATABRICKS_HOST', adapter: 'databricks', name: 'Databricks' },\n ];\n\n for (const check of envChecks) {\n if (process.env[check.env]) {\n sources.push({\n name: check.name,\n adapter: check.adapter,\n origin: `env:${check.env}`,\n status: 'detected',\n });\n }\n }\n\n // Check for local DuckDB files\n const duckdbFiles = ['*.duckdb', '*.db', '*.ddb'];\n for (const pattern of duckdbFiles) {\n const ext = pattern.replace('*', '');\n try {\n const files = fs.readdirSync(rootDir).filter((f) => f.endsWith(ext));\n for (const file of files) {\n sources.push({\n name: `DuckDB: ${file}`,\n adapter: 'duckdb',\n origin: `file:${file}`,\n status: 'detected',\n });\n }\n } catch {\n // ignore read errors\n }\n }\n\n // Check for SQLite files\n try {\n const sqliteFiles = fs.readdirSync(rootDir).filter((f) => f.endsWith('.sqlite') || f.endsWith('.sqlite3'));\n for (const file of sqliteFiles) {\n sources.push({\n name: `SQLite: ${file}`,\n adapter: 'sqlite',\n origin: `file:${file}`,\n status: 'detected',\n });\n }\n } catch {\n // ignore\n }\n\n // MCP discovery: scan IDE configs for database MCP servers\n const mcpSources = discoverMcpDatabases(rootDir);\n for (const mcp of mcpSources) {\n // Skip duplicates already found via config/env/files\n const alreadyFound = sources.some((s) =>\n s.origin === mcp.origin || (s.adapter === mcp.adapter && s.name === mcp.name)\n );\n if (!alreadyFound) {\n sources.push(mcp);\n }\n }\n\n return c.json(sources);\n });\n\n app.post('/api/sources', async (c) => {\n const body = await c.req.json<{ connection: string; name?: string }>();\n const { connection, name = 'default' } = body;\n\n if (!connection) {\n return c.json({ error: 'connection is required' }, 400);\n }\n\n // Detect adapter from URL prefix\n let adapter = 'auto';\n if (connection.startsWith('postgres://') || connection.startsWith('postgresql://')) {\n adapter = 'postgres';\n } else if (connection.startsWith('mysql://')) {\n adapter = 'mysql';\n } else if (connection.startsWith('mssql://') || connection.startsWith('sqlserver://')) {\n adapter = 'mssql';\n } else if (connection.startsWith('clickhouse://')) {\n adapter = 'clickhouse';\n }\n\n // Read existing config or create new\n const configPath = path.join(rootDir, 'runcontext.config.yaml');\n let config: Record<string, unknown> = {};\n try {\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, 'utf-8');\n config = yaml.parse(raw) ?? {};\n }\n } catch {\n // start fresh\n }\n\n if (!config.data_sources || typeof config.data_sources !== 'object') {\n config.data_sources = {};\n }\n\n const dataSources = config.data_sources as Record<string, { adapter: string; connection: string }>;\n dataSources[name] = { adapter, connection };\n\n fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');\n\n const created: DetectedSource = {\n name,\n adapter,\n origin: `config:${name}`,\n status: 'detected',\n };\n\n return c.json(created, 201);\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { PRODUCT_NAME_RE } from '@runcontext/core';\nconst MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\nconst ALLOWED_EXTENSIONS = ['.md', '.txt', '.pdf', '.csv', '.json', '.yaml', '.yml', '.sql', '.html'];\n\nexport function uploadRoutes(contextDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/upload/:productName', async (c) => {\n const productName = c.req.param('productName');\n if (!PRODUCT_NAME_RE.test(productName)) {\n return c.json({ error: 'Invalid product name' }, 400);\n }\n\n const formData = await c.req.formData();\n const file = formData.get('file');\n if (!file || !(file instanceof File)) {\n return c.json({ error: 'No file provided' }, 400);\n }\n\n if (file.size > MAX_FILE_SIZE) {\n return c.json({ error: 'File too large (max 10MB)' }, 400);\n }\n\n const ext = path.extname(file.name).toLowerCase();\n if (!ALLOWED_EXTENSIONS.includes(ext)) {\n return c.json({ error: `File type ${ext} not allowed` }, 400);\n }\n\n // Sanitize filename\n const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, '_');\n const docsDir = path.join(contextDir, 'products', productName, 'docs');\n fs.mkdirSync(docsDir, { recursive: true });\n\n const buffer = Buffer.from(await file.arrayBuffer());\n fs.writeFileSync(path.join(docsDir, safeName), buffer);\n\n return c.json({ ok: true, filename: safeName });\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport { execFile as execFileCb, spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport { fileURLToPath } from 'node:url';\nimport { parse as parseYaml } from 'yaml';\nimport { setupBus } from '../../events.js';\nimport type { ChildProcess } from 'node:child_process';\n\nconst execFile = promisify(execFileCb);\n\n/**\n * Resolve the CLI entry point. Prefers the local monorepo build\n * (so `context setup` in dev always uses the local code), falling\n * back to the currently-running process argv, then npx.\n */\nfunction resolveCliBin(): { cmd: string; prefix: string[] } {\n // 1. Try to find the local CLI dist relative to this package\n // (packages/ui → packages/cli/dist/index.js)\n try {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n const localCli = join(thisDir, '..', '..', '..', 'cli', 'dist', 'index.js');\n if (existsSync(localCli)) {\n return { cmd: process.execPath, prefix: [localCli] };\n }\n } catch { /* ignore */ }\n\n // 2. Try relative to cwd (monorepo root → packages/cli/dist/index.js)\n const cwdCli = join(process.cwd(), 'packages', 'cli', 'dist', 'index.js');\n if (existsSync(cwdCli)) {\n return { cmd: process.execPath, prefix: [cwdCli] };\n }\n\n // 3. If this process was started via the CLI binary, reuse it\n if (process.argv[1] && existsSync(process.argv[1])) {\n return { cmd: process.execPath, prefix: [process.argv[1]] };\n }\n\n // 4. Fall back to npx (published CLI)\n return { cmd: 'npx', prefix: ['--yes', '@runcontext/cli'] };\n}\n\nexport type PipelineStage =\n | 'introspect'\n | 'scaffold'\n | 'enrich-silver'\n | 'enrich-gold'\n | 'verify'\n | 'autofix'\n | 'agent-instructions';\n\nexport type StageStatus = 'pending' | 'running' | 'done' | 'error' | 'skipped';\n\nexport interface PipelineStageState {\n stage: PipelineStage;\n status: StageStatus;\n summary?: string;\n error?: string;\n startedAt?: string;\n completedAt?: string;\n}\n\nexport interface PipelineRun {\n id: string;\n productName: string;\n targetTier: 'bronze' | 'silver' | 'gold';\n status: 'running' | 'done' | 'error';\n stages: PipelineStageState[];\n createdAt: string;\n}\n\nconst ALL_STAGES: PipelineStage[] = [\n 'introspect',\n 'scaffold',\n 'enrich-silver',\n 'enrich-gold',\n 'verify',\n 'autofix',\n 'agent-instructions',\n];\n\n// In-memory store for pipeline runs\nconst runs = new Map<string, PipelineRun>();\n\nfunction stagesForTier(tier: 'bronze' | 'silver' | 'gold'): PipelineStage[] {\n const base: PipelineStage[] = ['introspect', 'scaffold'];\n if (tier === 'silver' || tier === 'gold') base.push('enrich-silver');\n if (tier === 'gold') base.push('enrich-gold');\n base.push('verify', 'autofix', 'agent-instructions');\n return base;\n}\n\nexport function pipelineRoutes(rootDir: string, contextDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/pipeline/start', async (c) => {\n const body = await c.req.json();\n const { productName, targetTier, dataSource, sessionId } = body;\n\n if (!productName || !targetTier) {\n return c.json({ error: 'productName and targetTier required' }, 400);\n }\n\n if (!['bronze', 'silver', 'gold'].includes(targetTier)) {\n return c.json({ error: 'targetTier must be bronze, silver, or gold' }, 400);\n }\n\n // Validate productName: alphanumeric, hyphens, underscores only\n const safeNamePattern = /^[a-zA-Z0-9_-]+$/;\n if (!safeNamePattern.test(productName)) {\n return c.json({ error: 'productName must contain only letters, numbers, hyphens, and underscores' }, 400);\n }\n\n // Validate dataSource if provided\n if (dataSource && !safeNamePattern.test(dataSource)) {\n return c.json({ error: 'dataSource must contain only letters, numbers, hyphens, and underscores' }, 400);\n }\n\n const id = randomUUID();\n const activeStages = stagesForTier(targetTier);\n const skippedStages = ALL_STAGES.filter((s) => !activeStages.includes(s));\n\n const run: PipelineRun = {\n id,\n productName,\n targetTier,\n status: 'running',\n stages: ALL_STAGES.map((stage) => ({\n stage,\n status: skippedStages.includes(stage) ? 'skipped' : 'pending',\n })),\n createdAt: new Date().toISOString(),\n };\n\n runs.set(id, run);\n\n // Start the pipeline asynchronously (non-blocking)\n executePipeline(run, rootDir, contextDir, dataSource, sessionId).catch((err) => {\n run.status = 'error';\n const currentStage = run.stages.find((s) => s.status === 'running');\n if (currentStage) {\n currentStage.status = 'error';\n currentStage.error = err instanceof Error ? err.message : String(err);\n }\n });\n\n return c.json({ id, status: 'running' });\n });\n\n app.get('/api/pipeline/status/:id', (c) => {\n const run = runs.get(c.req.param('id'));\n if (!run) return c.json({ error: 'Not found' }, 404);\n return c.json(run);\n });\n\n // --- MCP server management ---\n let mcpProcess: ChildProcess | null = null;\n\n app.post('/api/mcp/start', (c) => {\n if (mcpProcess && !mcpProcess.killed) {\n return c.json({ ok: true, status: 'already_running' });\n }\n const cli = resolveCliBin();\n mcpProcess = spawn(cli.cmd, [...cli.prefix, 'serve'], {\n cwd: rootDir,\n stdio: ['pipe', 'pipe', 'ignore'],\n detached: false,\n env: { ...process.env, NODE_OPTIONS: '--no-deprecation' },\n });\n // Keep stdin open so the stdio MCP server doesn't exit on EOF\n mcpProcess.on('exit', () => { mcpProcess = null; });\n mcpProcess.on('error', () => { mcpProcess = null; });\n return c.json({ ok: true, status: 'started' });\n });\n\n app.post('/api/mcp/stop', (c) => {\n if (mcpProcess && !mcpProcess.killed) {\n mcpProcess.kill();\n mcpProcess = null;\n }\n return c.json({ ok: true, status: 'stopped' });\n });\n\n app.get('/api/mcp/status', (c) => {\n const running = mcpProcess !== null && !mcpProcess.killed;\n return c.json({ running });\n });\n\n app.get('/api/mcp-config', (c) => {\n const cli = resolveCliBin();\n const absRoot = resolve(rootDir);\n const mcpServers: Record<string, unknown> = {\n runcontext: {\n command: cli.cmd,\n args: [...cli.prefix, 'serve'],\n cwd: absRoot,\n },\n };\n\n return c.json({ mcpServers });\n });\n\n return app;\n}\n\nfunction buildCliArgs(\n stage: PipelineStage,\n dataSource?: string,\n): string[] {\n switch (stage) {\n case 'introspect': {\n const args = ['introspect'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'scaffold':\n return ['build'];\n case 'enrich-silver': {\n const args = ['enrich', '--target', 'silver', '--apply'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'enrich-gold': {\n const args = ['enrich', '--target', 'gold', '--apply'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'verify': {\n const args = ['verify'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'autofix': {\n const args = ['fix'];\n if (dataSource) args.push('--source', dataSource);\n return args;\n }\n case 'agent-instructions':\n return ['build'];\n }\n}\n\nfunction extractSummary(stdout: string): string {\n const lines = stdout.trim().split('\\n').filter(Boolean);\n return lines.slice(-3).join('\\n') || 'completed';\n}\n\nasync function executePipeline(\n run: PipelineRun,\n rootDir: string,\n contextDir: string,\n dataSource?: string,\n sessionId?: string,\n): Promise<void> {\n for (const stage of run.stages) {\n if (stage.status === 'skipped') continue;\n\n stage.status = 'running';\n stage.startedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'running' },\n });\n }\n\n try {\n const cliArgs = buildCliArgs(stage.stage, dataSource);\n const cli = resolveCliBin();\n const { stdout } = await execFile(cli.cmd, [...cli.prefix, ...cliArgs], {\n cwd: rootDir,\n timeout: 300_000,\n env: {\n ...process.env,\n NODE_OPTIONS: '--max-old-space-size=4096 --no-deprecation',\n },\n });\n stage.status = 'done';\n stage.summary = extractSummary(stdout);\n stage.completedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'done', summary: stage.summary },\n });\n }\n } catch (err: unknown) {\n // If the command produced stdout despite failing, treat warnings-only\n // exits as success (e.g. verify with SSL deprecation warnings)\n const execErr = err as { stdout?: string; stderr?: string; code?: number };\n if (execErr.stdout && execErr.stdout.trim().length > 0) {\n stage.status = 'done';\n stage.summary = extractSummary(execErr.stdout);\n stage.completedAt = new Date().toISOString();\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'done', summary: stage.summary },\n });\n }\n continue;\n }\n stage.status = 'error';\n const errDetail = execErr.stderr || (err instanceof Error ? err.message : String(err));\n stage.error = errDetail;\n stage.completedAt = new Date().toISOString();\n console.error(`[pipeline] Stage ${stage.stage} failed:`, errDetail);\n if (sessionId) {\n setupBus.emitEvent({\n type: 'pipeline:stage',\n sessionId,\n payload: { stage: stage.stage, status: 'error', error: stage.error },\n });\n }\n run.status = 'error';\n return;\n }\n }\n\n run.status = 'done';\n}\n","import { EventEmitter } from 'node:events';\nimport { randomUUID } from 'node:crypto';\n\nexport interface SetupEvent {\n type: string;\n sessionId: string;\n payload: Record<string, unknown>;\n}\n\n// Agent/CLI -> Wizard event types\nexport type AgentEventType =\n | 'setup:step' // Navigate wizard to a step\n | 'setup:field' // Update a form field value\n | 'pipeline:stage' // Stage status change\n | 'pipeline:detail' // Stage detail update\n | 'tier:update' // Tier scorecard changed\n | 'enrich:progress' // Enrichment checklist item updated\n | 'enrich:log'; // Activity log entry\n\n// Wizard -> Agent/CLI event types\nexport type WizardEventType =\n | 'user:field' // User edited a form field\n | 'user:confirm' // User clicked Continue/Approve\n | 'user:retry' // User clicked Retry\n | 'user:cancel'; // User cancelled\n\nclass SetupEventBus extends EventEmitter {\n private sessions = new Map<string, { createdAt: string }>();\n\n createSession(): string {\n const id = randomUUID();\n this.sessions.set(id, { createdAt: new Date().toISOString() });\n return id;\n }\n\n hasSession(id: string): boolean {\n return this.sessions.has(id);\n }\n\n removeSession(id: string): void {\n this.sessions.delete(id);\n }\n\n emitEvent(event: SetupEvent): void {\n this.emit('event', event);\n this.emit(event.type, event);\n }\n}\n\nexport const setupBus = new SetupEventBus();\n","import { Hono } from 'hono';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { parse } from 'yaml';\n\nconst execFile = promisify(execFileCb);\n\nexport interface ExistingProduct {\n name: string;\n description?: string;\n sensitivity?: string;\n tier?: string;\n tables?: number;\n columns?: number;\n hasBrief: boolean;\n}\n\nfunction detectTier(contextDir: string, sourceName: string): string {\n const rulesPath = path.join(contextDir, 'rules', `${sourceName}.rules.yaml`);\n const modelPath = path.join(contextDir, 'models', `${sourceName}.osi.yaml`);\n\n // Gold: needs real golden queries (not TODO), real guardrails, meaningful descriptions\n if (fs.existsSync(rulesPath)) {\n try {\n const rules = parse(fs.readFileSync(rulesPath, 'utf-8'));\n const realQueries = (rules?.golden_queries || []).filter((q: any) =>\n q.sql && !q.sql.includes('TODO') && !q.sql.includes('table_name') &&\n q.question && !q.question.includes('TODO')\n );\n const realGuardrails = (rules?.guardrail_filters || rules?.guardrails || []).filter((g: any) =>\n g.name && !g.name.includes('TODO')\n );\n if (realQueries.length >= 3 && realGuardrails.length >= 1) return 'gold';\n } catch { /* ignore */ }\n }\n\n // Silver: needs sample_values on at least 2 fields\n if (fs.existsSync(modelPath)) {\n try {\n const model = parse(fs.readFileSync(modelPath, 'utf-8'));\n let datasets: any[] = model?.tables || model?.models || [];\n if (datasets.length === 0 && Array.isArray(model?.semantic_model)) {\n for (const sm of model.semantic_model) {\n if (Array.isArray(sm.datasets)) datasets.push(...sm.datasets);\n }\n }\n let fieldsWithSamples = 0;\n for (const d of datasets) {\n for (const f of (d.columns || d.fields || [])) {\n if (f.sample_values?.length > 0) fieldsWithSamples++;\n }\n }\n if (fieldsWithSamples >= 2) return 'silver';\n } catch { /* ignore */ }\n }\n\n return 'bronze';\n}\n\nfunction countTablesAndColumns(contextDir: string, sourceName: string): { tables: number; columns: number } {\n const modelPath = path.join(contextDir, 'models', `${sourceName}.osi.yaml`);\n if (!fs.existsSync(modelPath)) return { tables: 0, columns: 0 };\n try {\n const model = parse(fs.readFileSync(modelPath, 'utf-8'));\n // Support both flat (tables/models) and nested (semantic_model[].datasets[]) formats\n let datasets: any[] = model?.tables || model?.models || [];\n if (datasets.length === 0 && Array.isArray(model?.semantic_model)) {\n for (const sm of model.semantic_model) {\n if (Array.isArray(sm.datasets)) datasets.push(...sm.datasets);\n }\n }\n const columns = datasets.reduce((sum: number, t: any) => sum + (t.columns?.length || t.fields?.length || 0), 0);\n return { tables: datasets.length, columns };\n } catch {\n return { tables: 0, columns: 0 };\n }\n}\n\nexport function productsRoutes(contextDir: string): Hono {\n const app = new Hono();\n\n app.get('/api/products', (c) => {\n if (!fs.existsSync(contextDir)) {\n return c.json([]);\n }\n\n const products: ExistingProduct[] = [];\n\n // Scan for *.context-brief.yaml files in the context dir\n const briefFiles = fs.readdirSync(contextDir).filter(f => f.endsWith('.context-brief.yaml'));\n\n for (const briefFile of briefFiles) {\n const briefPath = path.join(contextDir, briefFile);\n try {\n const brief = parse(fs.readFileSync(briefPath, 'utf-8'));\n const name = brief?.product_name || briefFile.replace('.context-brief.yaml', '');\n\n // Find associated model: check brief for data_source, then scan models dir\n const modelsDir = path.join(contextDir, 'models');\n let sourceName = '';\n const briefSource = brief?.data_sources?.[0]?.name || brief?.data_source || '';\n if (briefSource && fs.existsSync(path.join(modelsDir, `${briefSource}.osi.yaml`))) {\n sourceName = briefSource;\n } else if (fs.existsSync(modelsDir)) {\n const modelFiles = fs.readdirSync(modelsDir).filter(f => f.endsWith('.osi.yaml'));\n if (modelFiles.length > 0) {\n sourceName = modelFiles[0].replace('.osi.yaml', '');\n }\n }\n\n const { tables, columns } = sourceName\n ? countTablesAndColumns(contextDir, sourceName)\n : { tables: 0, columns: 0 };\n\n const tier = sourceName ? detectTier(contextDir, sourceName) : 'bronze';\n\n products.push({\n name,\n description: brief?.description,\n sensitivity: brief?.sensitivity,\n tier,\n tables,\n columns,\n hasBrief: true,\n });\n } catch {\n // ignore parse errors\n }\n }\n\n // Deduplicate by name (keep first)\n const seen = new Set<string>();\n const unique = products.filter(p => {\n if (seen.has(p.name)) return false;\n seen.add(p.name);\n return true;\n });\n\n return c.json(unique);\n });\n\n // Detail: full semantic plane content\n app.get('/api/products/:name/detail', (c) => {\n const modelsDir = path.join(contextDir, 'models');\n const rulesDir = path.join(contextDir, 'rules');\n const govDir = path.join(contextDir, 'governance');\n const glossaryDir = path.join(contextDir, 'glossary');\n const ownersDir = path.join(contextDir, 'owners');\n\n // Find model file\n const modelFiles = fs.existsSync(modelsDir)\n ? fs.readdirSync(modelsDir).filter(f => f.endsWith('.osi.yaml'))\n : [];\n const sourceName = modelFiles.length > 0 ? modelFiles[0].replace('.osi.yaml', '') : '';\n\n // Parse model\n let tables: any[] = [];\n let modelYaml = '';\n if (sourceName) {\n const modelPath = path.join(modelsDir, modelFiles[0]);\n modelYaml = fs.readFileSync(modelPath, 'utf-8');\n try {\n const model = parse(modelYaml);\n let datasets: any[] = model?.tables || model?.models || [];\n if (datasets.length === 0 && Array.isArray(model?.semantic_model)) {\n for (const sm of model.semantic_model) {\n if (Array.isArray(sm.datasets)) datasets.push(...sm.datasets);\n }\n }\n tables = datasets.map((d: any) => {\n const fields = d.columns || d.fields || [];\n return {\n name: d.name,\n description: d.description || '',\n fields: fields.map((f: any) => ({\n name: f.name,\n type: f.type || f.data_type || '',\n description: f.description || '',\n sampleValues: f.sample_values || [],\n semanticRole: f.semantic_role || '',\n })),\n };\n });\n } catch { /* ignore */ }\n }\n\n // Parse rules\n let rules: any = {};\n let rulesYaml = '';\n if (sourceName) {\n const rulesPath = path.join(rulesDir, `${sourceName}.rules.yaml`);\n if (fs.existsSync(rulesPath)) {\n rulesYaml = fs.readFileSync(rulesPath, 'utf-8');\n try { rules = parse(rulesYaml) || {}; } catch { /* ignore */ }\n }\n }\n\n // Parse governance\n let governance: any = {};\n let govYaml = '';\n if (sourceName) {\n const govPath = path.join(govDir, `${sourceName}.governance.yaml`);\n if (fs.existsSync(govPath)) {\n govYaml = fs.readFileSync(govPath, 'utf-8');\n try { governance = parse(govYaml) || {}; } catch { /* ignore */ }\n }\n }\n\n // Parse glossary\n const glossary: any[] = [];\n if (fs.existsSync(glossaryDir)) {\n for (const f of fs.readdirSync(glossaryDir).filter(f => f.endsWith('.term.yaml'))) {\n try {\n const term = parse(fs.readFileSync(path.join(glossaryDir, f), 'utf-8'));\n if (term) glossary.push(term);\n } catch { /* ignore */ }\n }\n }\n\n // Parse owners\n const owners: any[] = [];\n if (fs.existsSync(ownersDir)) {\n for (const f of fs.readdirSync(ownersDir).filter(f => f.endsWith('.owner.yaml'))) {\n try {\n const owner = parse(fs.readFileSync(path.join(ownersDir, f), 'utf-8'));\n if (owner) owners.push(owner);\n } catch { /* ignore */ }\n }\n }\n\n return c.json({\n tables,\n rules: {\n joinRules: rules.join_rules || [],\n goldenQueries: rules.golden_queries || [],\n guardrails: rules.guardrail_filters || rules.guardrails || [],\n grainStatements: rules.grain_statements || [],\n },\n governance,\n glossary,\n owners,\n yaml: {\n model: modelYaml.slice(0, 50000),\n rules: rulesYaml.slice(0, 20000),\n governance: govYaml.slice(0, 10000),\n },\n });\n });\n\n // Tier scorecard via CLI\n app.get('/api/tier', async (c) => {\n const cwdCli = path.join(process.cwd(), 'packages', 'cli', 'dist', 'index.js');\n const cliPath = fs.existsSync(cwdCli) ? cwdCli : null;\n if (!cliPath) return c.json({ tier: 'unknown', output: 'CLI not found' });\n\n try {\n const { stdout } = await execFile(process.execPath, [cliPath, 'tier'], {\n cwd: process.cwd(),\n timeout: 15_000,\n env: { ...process.env, NODE_OPTIONS: '--no-deprecation' },\n });\n const tierMatch = stdout.match(/(BRONZE|SILVER|GOLD)/i);\n return c.json({ tier: tierMatch ? tierMatch[1].toLowerCase() : 'unknown', output: stdout });\n } catch (err: any) {\n const stdout = err?.stdout || '';\n const tierMatch = stdout.match(/(BRONZE|SILVER|GOLD)/i);\n return c.json({ tier: tierMatch ? tierMatch[1].toLowerCase() : 'unknown', output: stdout || err.message });\n }\n });\n\n // Agent instructions\n app.get('/api/agent-instructions', (c) => {\n // Try to find the built agent instructions from the dist manifest\n const distInstructions = path.join(process.cwd(), 'dist', 'AGENT_INSTRUCTIONS.md');\n const cliInstructions = path.join(process.cwd(), 'packages', 'cli', 'assets', 'AGENT_INSTRUCTIONS.md');\n const instrPath = fs.existsSync(distInstructions) ? distInstructions : fs.existsSync(cliInstructions) ? cliInstructions : null;\n\n if (!instrPath) {\n return c.json({ instructions: null, error: 'Agent instructions not found' });\n }\n\n return c.json({ instructions: fs.readFileSync(instrPath, 'utf-8') });\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport {\n createDefaultRegistry,\n CredentialStore,\n} from '@runcontext/core';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nexport function authRoutes(rootDir: string): Hono {\n const app = new Hono();\n const registry = createDefaultRegistry();\n const store = new CredentialStore();\n\n // List all supported providers with CLI detection status\n app.get('/api/auth/providers', async (c) => {\n const providers = await Promise.all(\n registry.getAll().map(async (p) => {\n const cli = await p.detectCli();\n return {\n id: p.id,\n displayName: p.displayName,\n adapters: p.adapters,\n cliInstalled: cli.installed,\n cliAuthenticated: cli.authenticated,\n };\n }),\n );\n return c.json(providers);\n });\n\n // Start authentication with a provider\n app.post('/api/auth/start', async (c) => {\n const { provider: providerId } = await c.req.json();\n const provider = registry.get(providerId);\n if (!provider) {\n return c.json({ error: `Unknown provider: ${providerId}` }, 400);\n }\n\n // First, try listing databases without full re-auth.\n // If the provider already has valid stored credentials (e.g. \"CLI authenticated\"),\n // this avoids re-running neonctl auth which can open a broken OAuth browser window.\n try {\n const databases = await provider.listDatabases();\n if (databases.length > 0) {\n return c.json({ ok: true, provider: providerId, databases });\n }\n } catch { /* fall through to full authenticate */ }\n\n // Full authentication flow (may open browser for OAuth)\n const result = await provider.authenticate();\n if (!result.ok) {\n return c.json({ error: result.error }, 401);\n }\n\n // List databases after successful auth — pass the fresh token\n const databases = await provider.listDatabases(result.token);\n\n return c.json({\n ok: true,\n provider: providerId,\n databases,\n });\n });\n\n // Select a database and save credentials\n app.post('/api/auth/select-db', async (c) => {\n const { provider: providerId, database } = await c.req.json();\n const provider = registry.get(providerId);\n if (!provider) {\n return c.json({ error: `Unknown provider: ${providerId}` }, 400);\n }\n\n // Use token from database metadata (set during listDatabases) or re-authenticate\n let token = database.metadata?.token as string | undefined;\n if (!token) {\n const authResult = await provider.authenticate();\n if (!authResult.ok) {\n return c.json({ error: authResult.error }, 401);\n }\n token = authResult.token;\n }\n\n // Build and test connection\n database.metadata = { ...database.metadata, token };\n const connStr = await provider.getConnectionString(database);\n\n try {\n const { createAdapter } = await import('@runcontext/core');\n const adapter = await createAdapter({\n adapter: database.adapter,\n connection: connStr,\n });\n await adapter.connect();\n await adapter.query('SELECT 1');\n await adapter.disconnect();\n } catch (err) {\n return c.json({ error: `Connection failed: ${(err as Error).message}` }, 400);\n }\n\n // Save credential\n const credKey = `${providerId}:${database.id}`;\n await store.save({\n provider: providerId,\n key: credKey,\n token: token!,\n refreshToken: undefined,\n expiresAt: undefined,\n metadata: {\n host: database.host,\n database: database.name,\n ...database.metadata,\n token: undefined,\n },\n });\n\n // Update config\n const configPath = path.join(rootDir, 'runcontext.config.yaml');\n let config: Record<string, any> = {};\n if (fs.existsSync(configPath)) {\n try {\n config = parseYaml(fs.readFileSync(configPath, 'utf-8')) ?? {};\n } catch { /* start fresh */ }\n }\n // Remove stale entries (e.g. auth-only refs without connection strings)\n const existingSources = config.data_sources ?? {};\n for (const [key, val] of Object.entries(existingSources)) {\n if (val && typeof val === 'object' && !(val as any).connection && !(val as any).path) {\n delete existingSources[key];\n }\n }\n config.data_sources = existingSources;\n const sourceName = (database.name || database.database || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');\n config.data_sources[sourceName] = { adapter: database.adapter, connection: connStr };\n fs.writeFileSync(configPath, stringifyYaml(config), 'utf-8');\n\n return c.json({ ok: true, auth: credKey });\n });\n\n // List stored credentials\n app.get('/api/auth/credentials', async (c) => {\n const keys = await store.list();\n return c.json(keys);\n });\n\n return app;\n}\n","import { Hono } from 'hono';\nimport { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFile = promisify(execFileCb);\n\nexport function suggestBriefRoutes(rootDir: string): Hono {\n const app = new Hono();\n\n app.post('/api/suggest-brief', async (c) => {\n const body = await c.req.json<{\n source?: {\n name?: string;\n adapter?: string;\n host?: string;\n metadata?: Record<string, unknown>;\n };\n }>();\n\n const source = body.source || {};\n const meta = source.metadata || {};\n\n // --- Derive product name ---\n // Use project name, db name, or fallback\n const projectName = (meta.project as string) || '';\n const dbName = source.name || '';\n const rawName = projectName || dbName || 'my-data';\n // Sanitize to alphanumeric + hyphens\n const productName = rawName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\n // --- Derive description ---\n const branch = (meta.branch as string) || 'main';\n const adapter = source.adapter || 'database';\n const region = (meta.region as string) || '';\n const org = (meta.org as string) || '';\n\n let description = `Semantic context for the ${rawName} ${adapter} database`;\n if (branch !== 'main') description += ` (${branch} branch)`;\n if (org && org !== 'Personal') description += `, managed by ${org}`;\n description += '.';\n\n // --- Git user info ---\n let ownerName = '';\n let ownerEmail = '';\n let ownerTeam = '';\n try {\n const { stdout: name } = await execFile('git', ['config', 'user.name'], {\n cwd: rootDir,\n timeout: 3000,\n });\n ownerName = name.trim();\n } catch { /* ignore */ }\n try {\n const { stdout: email } = await execFile('git', ['config', 'user.email'], {\n cwd: rootDir,\n timeout: 3000,\n });\n ownerEmail = email.trim();\n } catch { /* ignore */ }\n\n // Try to derive team from email domain or org\n if (org && org !== 'Personal') {\n ownerTeam = org;\n } else if (ownerEmail) {\n const domain = ownerEmail.split('@')[1];\n if (domain && !['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com'].includes(domain)) {\n // Use domain name as team hint\n ownerTeam = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1);\n }\n }\n\n return c.json({\n product_name: productName,\n description,\n owner: {\n name: ownerName,\n email: ownerEmail,\n team: ownerTeam,\n },\n sensitivity: 'internal',\n });\n });\n\n return app;\n}\n","import { WebSocketServer, WebSocket } from 'ws';\nimport type { Server } from 'node:http';\nimport { setupBus, type SetupEvent } from '../events.js';\n\nexport function attachWebSocket(server: Server): void {\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n wss.on('connection', (ws, req) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const sessionId = url.searchParams.get('session');\n\n if (!sessionId) {\n ws.close(4001, 'session query param required');\n return;\n }\n\n // Auto-create session if it does not exist\n if (!setupBus.hasSession(sessionId)) {\n setupBus.createSession();\n }\n\n // Forward bus events to this WebSocket client\n const onEvent = (event: SetupEvent) => {\n if (event.sessionId !== sessionId) return;\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(event));\n }\n };\n setupBus.on('event', onEvent);\n\n // Receive messages from this client and broadcast via bus\n ws.on('message', (raw) => {\n try {\n const msg = JSON.parse(raw.toString()) as SetupEvent;\n msg.sessionId = sessionId;\n setupBus.emitEvent(msg);\n } catch {\n // ignore malformed messages\n }\n });\n\n ws.on('close', () => {\n setupBus.off('event', onEvent);\n });\n });\n}\n"]}