@runcontext/cli 0.4.1 → 0.4.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.
@@ -167,9 +167,18 @@ async function startStudioServer(opts) {
167
167
  const pages = await getPages();
168
168
  let pagePath = url.pathname === "/" ? "index.html" : url.pathname.replace(/^\//, "");
169
169
  if (!pagePath.endsWith(".html") && !pagePath.endsWith(".json")) {
170
- pagePath += ".html";
170
+ if (pages.has(pagePath + ".html")) {
171
+ pagePath += ".html";
172
+ } else {
173
+ pagePath = pagePath.replace(/\/$/, "") + "/index.html";
174
+ }
175
+ }
176
+ let page = pages.get(pagePath);
177
+ if (!page && pagePath.endsWith("/index.html")) {
178
+ res.writeHead(302, { Location: "/" });
179
+ res.end();
180
+ return;
171
181
  }
172
- const page = pages.get(pagePath);
173
182
  if (page) {
174
183
  const ct = pagePath.endsWith(".json") ? "application/json" : "text/html; charset=utf-8";
175
184
  res.writeHead(200, { "Content-Type": ct });
@@ -189,4 +198,4 @@ async function startStudioServer(opts) {
189
198
  export {
190
199
  startStudioServer
191
200
  };
192
- //# sourceMappingURL=server-DEKWPP3H.js.map
201
+ //# sourceMappingURL=server-IVDYRFDN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/studio/server.ts","../src/studio/sse.ts"],"sourcesContent":["import * as http from 'node:http';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport {\n compile,\n loadConfig,\n emitManifest,\n LintEngine,\n ALL_RULES,\n applyYamlEdit,\n previewYamlEdit,\n type Manifest,\n type Diagnostic,\n} from '@runcontext/core';\nimport { SSEManager } from './sse.js';\n\nexport interface StudioServerOptions {\n contextDir: string;\n rootDir: string;\n port: number;\n host: string;\n}\n\nexport async function startStudioServer(opts: StudioServerOptions): Promise<{\n server: http.Server;\n sse: SSEManager;\n recompileAndBroadcast: () => Promise<void>;\n}> {\n const { contextDir, rootDir, port, host } = opts;\n const config = loadConfig(rootDir);\n const sse = new SSEManager();\n\n let cachedPages: Map<string, string> | null = null;\n let cachedManifest: Manifest | null = null;\n\n async function recompile(): Promise<{ manifest: Manifest; diagnostics: Diagnostic[] }> {\n const { graph, diagnostics: compileDiags } = await compile({ contextDir, config, rootDir });\n const engine = new LintEngine();\n for (const rule of ALL_RULES) engine.register(rule);\n const lintDiags = engine.run(graph);\n const manifest = emitManifest(graph, config);\n cachedManifest = manifest;\n cachedPages = null; // invalidate\n return { manifest, diagnostics: [...compileDiags, ...lintDiags] };\n }\n\n async function recompileAndBroadcast(): Promise<void> {\n const { manifest, diagnostics } = await recompile();\n sse.broadcast('update', {\n tiers: manifest.tiers,\n diagnosticCount: diagnostics.length,\n diagnostics: diagnostics.slice(0, 50),\n });\n }\n\n async function getPages(): Promise<Map<string, string>> {\n if (cachedPages) return cachedPages;\n if (!cachedManifest) await recompile();\n const { generateSite } = await import('@runcontext/site');\n cachedPages = generateSite(cachedManifest!, config.site, { studioMode: true });\n return cachedPages;\n }\n\n // Initial compile\n await recompile();\n\n function parseBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {\n const MAX_BODY = 1_048_576; // 1 MB\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString();\n if (body.length > MAX_BODY) {\n reject(new Error('Payload too large'));\n req.destroy();\n }\n });\n req.on('end', () => {\n try {\n resolve(JSON.parse(body));\n } catch {\n reject(new Error('Invalid JSON'));\n }\n });\n req.on('error', reject);\n });\n }\n\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n // --- API routes ---\n if (url.pathname === '/api/events' && req.method === 'GET') {\n sse.addClient(res);\n return;\n }\n\n if (url.pathname === '/api/manifest' && req.method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(cachedManifest));\n return;\n }\n\n if (url.pathname === '/api/preview' && req.method === 'POST') {\n const body = await parseBody(req);\n const file = body.file;\n const dotPath = body.path;\n const value = body.value;\n if (typeof file !== 'string' || typeof dotPath !== 'string') {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing required fields: file, path');\n return;\n }\n const filePath = path.resolve(rootDir, file);\n const resolvedRoot = path.resolve(rootDir);\n if (!filePath.startsWith(resolvedRoot + path.sep) && filePath !== resolvedRoot) {\n res.writeHead(403);\n res.end('Forbidden');\n return;\n }\n const content = await fs.promises.readFile(filePath, 'utf-8');\n const preview = previewYamlEdit(content, dotPath, value);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ filename: file, ...preview }));\n return;\n }\n\n if (url.pathname === '/api/save' && req.method === 'POST') {\n const body = await parseBody(req);\n if (!Array.isArray(body.edits)) {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing required field: edits (array)');\n return;\n }\n const edits = body.edits as Array<{ file: string; path: string; value: unknown }>;\n const resolvedRoot = path.resolve(rootDir);\n const results: Array<{ file: string; ok: boolean }> = [];\n for (const edit of edits) {\n const filePath = path.resolve(rootDir, edit.file);\n if (!filePath.startsWith(resolvedRoot + path.sep) && filePath !== resolvedRoot) {\n results.push({ file: edit.file, ok: false });\n continue;\n }\n const content = await fs.promises.readFile(filePath, 'utf-8');\n const updated = applyYamlEdit(content, edit.path, edit.value);\n await fs.promises.writeFile(filePath, updated, 'utf-8');\n results.push({ file: edit.file, ok: true });\n }\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ results }));\n return;\n }\n\n // --- Static site pages ---\n const pages = await getPages();\n let pagePath = url.pathname === '/' ? 'index.html' : url.pathname.replace(/^\\//, '');\n if (!pagePath.endsWith('.html') && !pagePath.endsWith('.json')) {\n // Try exact path + .html, then directory index\n if (pages.has(pagePath + '.html')) {\n pagePath += '.html';\n } else {\n pagePath = pagePath.replace(/\\/$/, '') + '/index.html';\n }\n }\n // Redirect bare /models to /models/ index (or first model)\n let page = pages.get(pagePath);\n if (!page && pagePath.endsWith('/index.html')) {\n // No index page for this directory — redirect to site root\n res.writeHead(302, { Location: '/' });\n res.end();\n return;\n }\n if (page) {\n const ct = pagePath.endsWith('.json') ? 'application/json' : 'text/html; charset=utf-8';\n res.writeHead(200, { 'Content-Type': ct });\n res.end(page);\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n } catch (err) {\n res.writeHead(500, { 'Content-Type': 'text/plain' });\n res.end((err as Error).message);\n }\n });\n\n server.listen(port, host);\n\n return { server, sse, recompileAndBroadcast };\n}\n","import type { ServerResponse } from 'node:http';\n\nexport class SSEManager {\n private clients: Set<ServerResponse> = new Set();\n\n addClient(res: ServerResponse): void {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n res.write('data: {\"type\":\"connected\"}\\n\\n');\n this.clients.add(res);\n res.on('close', () => this.clients.delete(res));\n }\n\n broadcast(event: string, data: unknown): void {\n const payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n for (const client of this.clients) {\n try {\n client.write(payload);\n } catch {\n this.clients.delete(client);\n }\n }\n }\n}\n"],"mappings":"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","names":["resolve"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runcontext/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Tell your AI agent to build your semantic layer. CLI for introspecting databases, curating metadata, and serving context via MCP.",
5
5
  "license": "MIT",
6
6
  "author": "Eric Kittelson",
@@ -61,9 +61,9 @@
61
61
  "commander": "^14.0.0",
62
62
  "@clack/prompts": "^1.1.0",
63
63
  "yaml": "^2.7.0",
64
- "@runcontext/mcp": "^0.4.1",
65
- "@runcontext/core": "^0.4.1",
66
- "@runcontext/site": "^0.4.1"
64
+ "@runcontext/core": "^0.4.3",
65
+ "@runcontext/site": "^0.4.3",
66
+ "@runcontext/mcp": "^0.4.3"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node": "^25.3.3",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/studio/server.ts","../src/studio/sse.ts"],"sourcesContent":["import * as http from 'node:http';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport {\n compile,\n loadConfig,\n emitManifest,\n LintEngine,\n ALL_RULES,\n applyYamlEdit,\n previewYamlEdit,\n type Manifest,\n type Diagnostic,\n} from '@runcontext/core';\nimport { SSEManager } from './sse.js';\n\nexport interface StudioServerOptions {\n contextDir: string;\n rootDir: string;\n port: number;\n host: string;\n}\n\nexport async function startStudioServer(opts: StudioServerOptions): Promise<{\n server: http.Server;\n sse: SSEManager;\n recompileAndBroadcast: () => Promise<void>;\n}> {\n const { contextDir, rootDir, port, host } = opts;\n const config = loadConfig(rootDir);\n const sse = new SSEManager();\n\n let cachedPages: Map<string, string> | null = null;\n let cachedManifest: Manifest | null = null;\n\n async function recompile(): Promise<{ manifest: Manifest; diagnostics: Diagnostic[] }> {\n const { graph, diagnostics: compileDiags } = await compile({ contextDir, config, rootDir });\n const engine = new LintEngine();\n for (const rule of ALL_RULES) engine.register(rule);\n const lintDiags = engine.run(graph);\n const manifest = emitManifest(graph, config);\n cachedManifest = manifest;\n cachedPages = null; // invalidate\n return { manifest, diagnostics: [...compileDiags, ...lintDiags] };\n }\n\n async function recompileAndBroadcast(): Promise<void> {\n const { manifest, diagnostics } = await recompile();\n sse.broadcast('update', {\n tiers: manifest.tiers,\n diagnosticCount: diagnostics.length,\n diagnostics: diagnostics.slice(0, 50),\n });\n }\n\n async function getPages(): Promise<Map<string, string>> {\n if (cachedPages) return cachedPages;\n if (!cachedManifest) await recompile();\n const { generateSite } = await import('@runcontext/site');\n cachedPages = generateSite(cachedManifest!, config.site, { studioMode: true });\n return cachedPages;\n }\n\n // Initial compile\n await recompile();\n\n function parseBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {\n const MAX_BODY = 1_048_576; // 1 MB\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString();\n if (body.length > MAX_BODY) {\n reject(new Error('Payload too large'));\n req.destroy();\n }\n });\n req.on('end', () => {\n try {\n resolve(JSON.parse(body));\n } catch {\n reject(new Error('Invalid JSON'));\n }\n });\n req.on('error', reject);\n });\n }\n\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n // --- API routes ---\n if (url.pathname === '/api/events' && req.method === 'GET') {\n sse.addClient(res);\n return;\n }\n\n if (url.pathname === '/api/manifest' && req.method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(cachedManifest));\n return;\n }\n\n if (url.pathname === '/api/preview' && req.method === 'POST') {\n const body = await parseBody(req);\n const file = body.file;\n const dotPath = body.path;\n const value = body.value;\n if (typeof file !== 'string' || typeof dotPath !== 'string') {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing required fields: file, path');\n return;\n }\n const filePath = path.resolve(rootDir, file);\n const resolvedRoot = path.resolve(rootDir);\n if (!filePath.startsWith(resolvedRoot + path.sep) && filePath !== resolvedRoot) {\n res.writeHead(403);\n res.end('Forbidden');\n return;\n }\n const content = await fs.promises.readFile(filePath, 'utf-8');\n const preview = previewYamlEdit(content, dotPath, value);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ filename: file, ...preview }));\n return;\n }\n\n if (url.pathname === '/api/save' && req.method === 'POST') {\n const body = await parseBody(req);\n if (!Array.isArray(body.edits)) {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing required field: edits (array)');\n return;\n }\n const edits = body.edits as Array<{ file: string; path: string; value: unknown }>;\n const resolvedRoot = path.resolve(rootDir);\n const results: Array<{ file: string; ok: boolean }> = [];\n for (const edit of edits) {\n const filePath = path.resolve(rootDir, edit.file);\n if (!filePath.startsWith(resolvedRoot + path.sep) && filePath !== resolvedRoot) {\n results.push({ file: edit.file, ok: false });\n continue;\n }\n const content = await fs.promises.readFile(filePath, 'utf-8');\n const updated = applyYamlEdit(content, edit.path, edit.value);\n await fs.promises.writeFile(filePath, updated, 'utf-8');\n results.push({ file: edit.file, ok: true });\n }\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ results }));\n return;\n }\n\n // --- Static site pages ---\n const pages = await getPages();\n let pagePath = url.pathname === '/' ? 'index.html' : url.pathname.replace(/^\\//, '');\n if (!pagePath.endsWith('.html') && !pagePath.endsWith('.json')) {\n pagePath += '.html';\n }\n const page = pages.get(pagePath);\n if (page) {\n const ct = pagePath.endsWith('.json') ? 'application/json' : 'text/html; charset=utf-8';\n res.writeHead(200, { 'Content-Type': ct });\n res.end(page);\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n } catch (err) {\n res.writeHead(500, { 'Content-Type': 'text/plain' });\n res.end((err as Error).message);\n }\n });\n\n server.listen(port, host);\n\n return { server, sse, recompileAndBroadcast };\n}\n","import type { ServerResponse } from 'node:http';\n\nexport class SSEManager {\n private clients: Set<ServerResponse> = new Set();\n\n addClient(res: ServerResponse): void {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n res.write('data: {\"type\":\"connected\"}\\n\\n');\n this.clients.add(res);\n res.on('close', () => this.clients.delete(res));\n }\n\n broadcast(event: string, data: unknown): void {\n const payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n for (const client of this.clients) {\n try {\n client.write(payload);\n } catch {\n this.clients.delete(client);\n }\n }\n }\n}\n"],"mappings":"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","names":["resolve"]}