@runcontext/ui 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +706 -151
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +706 -151
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -8
- package/static/setup.css +1069 -300
- package/static/setup.js +1371 -285
- package/static/uxd.css +672 -0
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/erickittelson/Desktop/ContextKit/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/routes/api/products.ts"],"names":["Hono","PRODUCT_NAME_RE"],"mappings":"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","file":"/Users/erickittelson/Desktop/ContextKit/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';\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\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\n app.get('/api/health', (c) => c.json({ ok: true }));\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>ContextKit — Build Your Data Product</title>\n <link rel=\"stylesheet\" href=\"/static/setup.css\" />\n</head>\n<body>\n <div class=\"wizard\">\n <header class=\"wizard-header\">\n <h1>ContextKit</h1>\n <p class=\"tagline\">Build your data product. AI handles the rest.</p>\n </header>\n\n <div class=\"progress-bar\">\n <div class=\"progress-step active\" data-step=\"1\"><span class=\"step-num\">1</span><span class=\"step-label\">Product</span></div>\n <div class=\"progress-step\" data-step=\"2\"><span class=\"step-num\">2</span><span class=\"step-label\">Owner</span></div>\n <div class=\"progress-step\" data-step=\"3\"><span class=\"step-num\">3</span><span class=\"step-label\">Context</span></div>\n <div class=\"progress-step\" data-step=\"4\"><span class=\"step-num\">4</span><span class=\"step-label\">Review</span></div>\n <div class=\"progress-step\" data-step=\"5\"><span class=\"step-num\">5</span><span class=\"step-label\">Build</span></div>\n </div>\n\n <!-- Step 1: Product Name + Description -->\n <div class=\"step active\" id=\"step-1\">\n <h2>Name your data product</h2>\n <div class=\"field\">\n <label for=\"product-name\">Product Name</label>\n <input type=\"text\" id=\"product-name\" class=\"input\" placeholder=\"e.g. player-engagement\" />\n <p class=\"hint\">Letters, numbers, dashes, underscores only</p>\n </div>\n <div class=\"field\">\n <label for=\"description\">Description</label>\n <div class=\"textarea-wrapper\">\n <textarea id=\"description\" class=\"textarea\" rows=\"4\" placeholder=\"Describe what this data product covers...\"></textarea>\n <button type=\"button\" id=\"voice-btn\" class=\"btn-icon\" title=\"Voice input\">\\u{1F3A4}</button>\n </div>\n </div>\n <div class=\"step-actions\">\n <div></div>\n <button type=\"button\" class=\"btn btn-primary\" data-next>Next</button>\n </div>\n </div>\n\n <!-- Step 2: Owner -->\n <div class=\"step\" id=\"step-2\">\n <h2>Who owns this data?</h2>\n <div class=\"field\">\n <label for=\"owner-name\">Your Name</label>\n <input type=\"text\" id=\"owner-name\" class=\"input\" placeholder=\"e.g. Tyler\" />\n </div>\n <div class=\"field\">\n <label for=\"owner-team\">Team</label>\n <input type=\"text\" id=\"owner-team\" class=\"input\" placeholder=\"e.g. Analytics\" />\n </div>\n <div class=\"field\">\n <label for=\"owner-email\">Email</label>\n <input type=\"email\" id=\"owner-email\" class=\"input\" placeholder=\"e.g. tyler@company.com\" />\n </div>\n <div class=\"step-actions\">\n <button type=\"button\" class=\"btn btn-secondary\" data-prev>Back</button>\n <button type=\"button\" class=\"btn btn-primary\" data-next>Next</button>\n </div>\n </div>\n\n <!-- Step 3: Sensitivity + Sources + Upload -->\n <div class=\"step\" id=\"step-3\">\n <h2>Context & sensitivity</h2>\n <div class=\"field\">\n <label>Data Sensitivity</label>\n <div class=\"sensitivity-cards\">\n <div class=\"card\" data-sensitivity=\"public\">\n <strong>Public</strong>\n <p>Open data, no restrictions</p>\n </div>\n <div class=\"card selected\" data-sensitivity=\"internal\">\n <strong>Internal</strong>\n <p>Company use only</p>\n </div>\n <div class=\"card\" data-sensitivity=\"confidential\">\n <strong>Confidential</strong>\n <p>Need-to-know basis</p>\n </div>\n <div class=\"card\" data-sensitivity=\"restricted\">\n <strong>Restricted</strong>\n <p>Strict access controls</p>\n </div>\n </div>\n </div>\n\n <div class=\"field\">\n <label>Data Sources</label>\n <div id=\"sources-list\" class=\"source-cards\">\n <p class=\"muted\">Detecting data sources...</p>\n </div>\n </div>\n\n <div class=\"field\">\n <label>Documentation (optional)</label>\n <div id=\"upload-area\" class=\"upload-area\">\n <p>Drop files here or click to upload</p>\n <p class=\"hint\">Supports .md, .txt, .pdf, .csv, .json, .yaml, .sql</p>\n <input type=\"file\" id=\"file-input\" hidden multiple accept=\".md,.txt,.pdf,.csv,.json,.yaml,.yml,.sql,.html\" />\n </div>\n <div id=\"uploaded-files\"></div>\n </div>\n\n <div class=\"step-actions\">\n <button type=\"button\" class=\"btn btn-secondary\" data-prev>Back</button>\n <button type=\"button\" class=\"btn btn-primary\" data-next>Next</button>\n </div>\n </div>\n\n <!-- Step 4: Review -->\n <div class=\"step\" id=\"step-4\">\n <h2>Review your data product</h2>\n <div id=\"review-content\" class=\"review-content\"></div>\n <div class=\"step-actions\">\n <button type=\"button\" class=\"btn btn-secondary\" data-prev>Back</button>\n <button type=\"button\" class=\"btn btn-primary\" data-next>Build it</button>\n </div>\n </div>\n\n <!-- Step 5: Build Pipeline -->\n <div class=\"step\" id=\"step-5\">\n <h2>Building your semantic plane</h2>\n <div id=\"pipeline-timeline\" class=\"pipeline-timeline\"></div>\n <div id=\"pipeline-done\" class=\"pipeline-done\" style=\"display:none\">\n <p>Your semantic plane is live. AI agents can now query your data with context.</p>\n <p class=\"muted\">Powered by ContextKit \\u00B7 Open Semantic Interchange</p>\n </div>\n </div>\n\n <footer class=\"wizard-footer\">\n <p>Powered by ContextKit \\u00B7 Open Semantic Interchange</p>\n </footer>\n </div>\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(`ContextKit UI running at http://${opts.host === '0.0.0.0' ? 'localhost' : opts.host}:${info.port}/setup`);\n resolve();\n });\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 productDir = path.join(contextDir, 'products', brief.product_name);\n fs.mkdirSync(productDir, { recursive: true });\n fs.writeFileSync(path.join(productDir, 'context-brief.yaml'), stringify(brief), 'utf-8');\n return c.json({ ok: true, path: `products/${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, 'products', 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 path from 'node:path';\n\nexport interface DetectedSource {\n name: string;\n adapter: string;\n origin: string;\n status: 'detected' | 'connected' | 'error';\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 // 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 return c.json(sources);\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 { randomUUID } from 'node:crypto';\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 } = 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 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).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 return app;\n}\n\nasync function executePipeline(\n run: PipelineRun,\n _rootDir: string,\n _contextDir: string,\n _dataSource?: 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\n try {\n // TODO: Wire real setup step functions here\n // For now, each stage is a placeholder that completes immediately\n // Real implementation will call the corresponding setup step\n // e.g., for 'introspect': connect to data source and discover schema\n // e.g., for 'scaffold': generate .osi.yaml files from schema\n stage.status = 'done';\n stage.summary = `${stage.stage} completed`;\n stage.completedAt = new Date().toISOString();\n } catch (err) {\n stage.status = 'error';\n stage.error = err instanceof Error ? err.message : String(err);\n stage.completedAt = new Date().toISOString();\n run.status = 'error';\n return;\n }\n }\n\n run.status = 'done';\n}\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"]}
|
|
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 · 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 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 // Re-authenticate to get a fresh token\n const authResult = await provider.authenticate();\n if (!authResult.ok) {\n return c.json({ error: authResult.error }, 401);\n }\n\n // Build and test connection\n database.metadata = { ...database.metadata, token: authResult.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: authResult.token,\n refreshToken: authResult.ok ? authResult.refreshToken : undefined,\n expiresAt: authResult.ok ? authResult.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 config.data_sources.default = { adapter: database.adapter, auth: credKey };\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"]}
|