chisel-studio 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -4,6 +4,7 @@ interface StudioOptions {
4
4
  port?: number;
5
5
  host?: string;
6
6
  open?: boolean;
7
+ readOnly?: boolean;
7
8
  }
8
9
  interface StudioServer {
9
10
  start(): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ interface StudioOptions {
4
4
  port?: number;
5
5
  host?: string;
6
6
  open?: boolean;
7
+ readOnly?: boolean;
7
8
  }
8
9
  interface StudioServer {
9
10
  start(): Promise<void>;
package/dist/index.js CHANGED
@@ -174,13 +174,23 @@ function resolveUiDir() {
174
174
  if (fs.existsSync(path.join(fromSrc, "index.html"))) return fromSrc;
175
175
  return fromDist;
176
176
  }
177
- function createStudioApp(engine) {
177
+ function createStudioApp(engine, options) {
178
+ const readOnly = options?.readOnly ?? false;
178
179
  const app = new hono.Hono();
179
180
  app.onError((err, c) => {
180
181
  console.error(`[studio] ${c.req.method} ${c.req.path} error:`, err);
181
182
  return c.json({ error: err.message }, 500);
182
183
  });
183
184
  app.use("*", cors.cors());
185
+ app.get("/api/config", (c) => c.json({ readOnly }));
186
+ if (readOnly) {
187
+ app.use("/api/*", async (c, next) => {
188
+ if (c.req.method === "POST") {
189
+ return c.json({ error: "Studio is in read-only mode" }, 403);
190
+ }
191
+ await next();
192
+ });
193
+ }
184
194
  app.route("/api", createApiRoutes(engine));
185
195
  app.route("/api", createSseRoute(engine));
186
196
  app.get("*", createStaticHandler(resolveUiDir()));
@@ -192,7 +202,7 @@ function createStudio(engine, options = {}) {
192
202
  const port = options.port ?? 4040;
193
203
  const host = options.host ?? "localhost";
194
204
  const url = `http://${host}:${port}`;
195
- const app = createStudioApp(engine);
205
+ const app = createStudioApp(engine, { readOnly: options.readOnly });
196
206
  let server = null;
197
207
  return {
198
208
  get url() {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routes/api.ts","../src/routes/sse.ts","../src/static.ts","../src/server.ts","../src/index.ts"],"names":["Hono","streamSSE","join","existsSync","readFileSync","extname","cors","serve"],"mappings":"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","file":"index.js","sourcesContent":["import { Hono } from \"hono\";\nimport type { Engine } from \"chisel-engine\";\nimport type { RunStatus } from \"chisel-engine\";\n\nexport function createApiRoutes(engine: Engine): Hono {\n const app = new Hono();\n\n // Health check\n app.get(\"/health\", async (c) => {\n const health = await engine.health();\n return c.json(health, health.connected ? 200 : 503);\n });\n\n // List registered workflows\n app.get(\"/workflows\", (c) => {\n return c.json(engine.listWorkflows());\n });\n\n // List runs for a workflow\n app.get(\"/workflows/:id/runs\", async (c) => {\n const id = c.req.param(\"id\");\n const query = c.req.query();\n\n const result = await engine.listRuns(id, {\n limit: query.limit ? Number(query.limit) : undefined,\n cursor: query.cursor ? Number(query.cursor) : undefined,\n order: query.order as \"asc\" | \"desc\" | undefined,\n status: query.status as RunStatus | undefined,\n });\n\n return c.json(result);\n });\n\n // Get run detail\n app.get(\"/runs/:runId\", async (c) => {\n const runId = c.req.param(\"runId\");\n const run = await engine.getRun(runId);\n\n if (!run) {\n return c.json({ error: \"Run not found\" }, 404);\n }\n\n return c.json(run);\n });\n\n // Cancel a run\n app.post(\"/runs/:runId/cancel\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.cancelRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Retry a failed run\n app.post(\"/runs/:runId/retry\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.retryRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Trigger a workflow\n app.post(\"/workflows/:id/trigger\", async (c) => {\n const id = c.req.param(\"id\");\n\n try {\n const body = await c.req.json();\n const { runId } = await engine.trigger(id, body);\n return c.json({ runId }, 202);\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n return app;\n}\n","import { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { Engine, EngineEventName } from \"chisel-engine\";\n\nconst ENGINE_EVENTS: EngineEventName[] = [\n \"workflow:start\",\n \"workflow:complete\",\n \"workflow:fail\",\n \"step:start\",\n \"step:complete\",\n \"step:fail\",\n \"step:retry\",\n];\n\nexport function createSseRoute(engine: Engine): Hono {\n const app = new Hono();\n\n app.get(\"/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const handlers = new Map<string, (payload: unknown) => void>();\n\n for (const event of ENGINE_EVENTS) {\n const handler = (payload: unknown) => {\n stream\n .writeSSE({\n event,\n data: JSON.stringify(payload, (_key, value) =>\n value instanceof Error\n ? { message: value.message, name: value.name }\n : value\n ),\n })\n .catch(() => {});\n };\n handlers.set(event, handler);\n engine.on(event, handler as any);\n }\n\n // Heartbeat every 15 seconds\n const heartbeat = setInterval(() => {\n stream\n .writeSSE({\n event: \"heartbeat\",\n data: JSON.stringify({ time: Date.now() }),\n })\n .catch(() => {});\n }, 15_000);\n\n // Keep the stream alive until client disconnects\n try {\n while (true) {\n await stream.sleep(1000);\n }\n } finally {\n clearInterval(heartbeat);\n for (const [event, handler] of handlers) {\n engine.off(event as EngineEventName, handler as any);\n }\n }\n });\n });\n\n return app;\n}\n","import { readFileSync, existsSync } from \"fs\";\nimport { join, extname } from \"path\";\nimport type { Context } from \"hono\";\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"application/javascript; charset=utf-8\",\n \".mjs\": \"application/javascript; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport function createStaticHandler(uiDir: string) {\n // Pre-load index.html into memory (small file, hot path)\n const indexPath = join(uiDir, \"index.html\");\n let indexHtml = \"\";\n if (existsSync(indexPath)) {\n indexHtml = readFileSync(indexPath, \"utf-8\");\n }\n\n return async (c: Context) => {\n const reqPath = new URL(c.req.url).pathname;\n\n // Try serving static asset\n const filePath = join(uiDir, reqPath);\n if (existsSync(filePath) && reqPath !== \"/\") {\n try {\n const content = readFileSync(filePath);\n const ext = extname(filePath);\n const contentType = MIME_TYPES[ext] || \"application/octet-stream\";\n return new Response(content, {\n headers: {\n \"Content-Type\": contentType,\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n },\n });\n } catch {\n // Fall through to index.html\n }\n }\n\n // SPA fallback: serve index.html for all non-file routes\n if (!indexHtml) {\n return c.text(\"Studio UI not built. Run: bun run build:ui\", 500);\n }\n return c.html(indexHtml);\n };\n}\n","import { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Engine } from \"chisel-engine\";\nimport { createApiRoutes } from \"./routes/api\";\nimport { createSseRoute } from \"./routes/sse\";\nimport { createStaticHandler } from \"./static\";\n\nfunction resolveUiDir(): string {\n // When running from built output: __dirname is dist/, ui is dist/ui/\n const fromDist = join(__dirname, \"ui\");\n if (existsSync(join(fromDist, \"index.html\"))) return fromDist;\n\n // When running from source via bun/tsx: __dirname is src/, ui is ../dist/ui/\n const fromSrc = join(__dirname, \"..\", \"dist\", \"ui\");\n if (existsSync(join(fromSrc, \"index.html\"))) return fromSrc;\n\n // Fallback\n return fromDist;\n}\n\nexport function createStudioApp(engine: Engine): Hono {\n const app = new Hono();\n\n app.onError((err, c) => {\n console.error(`[studio] ${c.req.method} ${c.req.path} error:`, err);\n return c.json({ error: err.message }, 500);\n });\n\n // Enable CORS for development\n app.use(\"*\", cors());\n\n // API routes\n app.route(\"/api\", createApiRoutes(engine));\n\n // SSE events\n app.route(\"/api\", createSseRoute(engine));\n\n // Static SPA assets\n app.get(\"*\", createStaticHandler(resolveUiDir()));\n\n return app;\n}\n","import { serve } from \"@hono/node-server\";\nimport type { Engine } from \"chisel-engine\";\nimport { createStudioApp } from \"./server\";\nimport type { StudioOptions, StudioServer } from \"./types\";\n\nexport type { StudioOptions, StudioServer } from \"./types\";\n\nexport function createStudio(\n engine: Engine,\n options: StudioOptions = {}\n): StudioServer {\n const port = options.port ?? 4040;\n const host = options.host ?? \"localhost\";\n const url = `http://${host}:${port}`;\n\n const app = createStudioApp(engine);\n let server: ReturnType<typeof serve> | null = null;\n\n return {\n get url() {\n return url;\n },\n\n async start() {\n server = serve({ fetch: app.fetch, port, hostname: host });\n\n console.log(`Chisel Studio running at ${url}`);\n\n if (options.open) {\n const { exec } = await import(\"child_process\");\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${url}`);\n }\n },\n\n async stop() {\n if (server) {\n server.close();\n server = null;\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/routes/api.ts","../src/routes/sse.ts","../src/static.ts","../src/server.ts","../src/index.ts"],"names":["Hono","streamSSE","join","existsSync","readFileSync","extname","cors","serve"],"mappings":"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","file":"index.js","sourcesContent":["import { Hono } from \"hono\";\nimport type { Engine } from \"chisel-engine\";\nimport type { RunStatus } from \"chisel-engine\";\n\nexport function createApiRoutes(engine: Engine): Hono {\n const app = new Hono();\n\n // Health check\n app.get(\"/health\", async (c) => {\n const health = await engine.health();\n return c.json(health, health.connected ? 200 : 503);\n });\n\n // List registered workflows\n app.get(\"/workflows\", (c) => {\n return c.json(engine.listWorkflows());\n });\n\n // List runs for a workflow\n app.get(\"/workflows/:id/runs\", async (c) => {\n const id = c.req.param(\"id\");\n const query = c.req.query();\n\n const result = await engine.listRuns(id, {\n limit: query.limit ? Number(query.limit) : undefined,\n cursor: query.cursor ? Number(query.cursor) : undefined,\n order: query.order as \"asc\" | \"desc\" | undefined,\n status: query.status as RunStatus | undefined,\n });\n\n return c.json(result);\n });\n\n // Get run detail\n app.get(\"/runs/:runId\", async (c) => {\n const runId = c.req.param(\"runId\");\n const run = await engine.getRun(runId);\n\n if (!run) {\n return c.json({ error: \"Run not found\" }, 404);\n }\n\n return c.json(run);\n });\n\n // Cancel a run\n app.post(\"/runs/:runId/cancel\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.cancelRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Retry a failed run\n app.post(\"/runs/:runId/retry\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.retryRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Trigger a workflow\n app.post(\"/workflows/:id/trigger\", async (c) => {\n const id = c.req.param(\"id\");\n\n try {\n const body = await c.req.json();\n const { runId } = await engine.trigger(id, body);\n return c.json({ runId }, 202);\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n return app;\n}\n","import { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { Engine, EngineEventName } from \"chisel-engine\";\n\nconst ENGINE_EVENTS: EngineEventName[] = [\n \"workflow:start\",\n \"workflow:complete\",\n \"workflow:fail\",\n \"step:start\",\n \"step:complete\",\n \"step:fail\",\n \"step:retry\",\n];\n\nexport function createSseRoute(engine: Engine): Hono {\n const app = new Hono();\n\n app.get(\"/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const handlers = new Map<string, (payload: unknown) => void>();\n\n for (const event of ENGINE_EVENTS) {\n const handler = (payload: unknown) => {\n stream\n .writeSSE({\n event,\n data: JSON.stringify(payload, (_key, value) =>\n value instanceof Error\n ? { message: value.message, name: value.name }\n : value\n ),\n })\n .catch(() => {});\n };\n handlers.set(event, handler);\n engine.on(event, handler as any);\n }\n\n // Heartbeat every 15 seconds\n const heartbeat = setInterval(() => {\n stream\n .writeSSE({\n event: \"heartbeat\",\n data: JSON.stringify({ time: Date.now() }),\n })\n .catch(() => {});\n }, 15_000);\n\n // Keep the stream alive until client disconnects\n try {\n while (true) {\n await stream.sleep(1000);\n }\n } finally {\n clearInterval(heartbeat);\n for (const [event, handler] of handlers) {\n engine.off(event as EngineEventName, handler as any);\n }\n }\n });\n });\n\n return app;\n}\n","import { readFileSync, existsSync } from \"fs\";\nimport { join, extname } from \"path\";\nimport type { Context } from \"hono\";\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"application/javascript; charset=utf-8\",\n \".mjs\": \"application/javascript; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport function createStaticHandler(uiDir: string) {\n // Pre-load index.html into memory (small file, hot path)\n const indexPath = join(uiDir, \"index.html\");\n let indexHtml = \"\";\n if (existsSync(indexPath)) {\n indexHtml = readFileSync(indexPath, \"utf-8\");\n }\n\n return async (c: Context) => {\n const reqPath = new URL(c.req.url).pathname;\n\n // Try serving static asset\n const filePath = join(uiDir, reqPath);\n if (existsSync(filePath) && reqPath !== \"/\") {\n try {\n const content = readFileSync(filePath);\n const ext = extname(filePath);\n const contentType = MIME_TYPES[ext] || \"application/octet-stream\";\n return new Response(content, {\n headers: {\n \"Content-Type\": contentType,\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n },\n });\n } catch {\n // Fall through to index.html\n }\n }\n\n // SPA fallback: serve index.html for all non-file routes\n if (!indexHtml) {\n return c.text(\"Studio UI not built. Run: bun run build:ui\", 500);\n }\n return c.html(indexHtml);\n };\n}\n","import { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Engine } from \"chisel-engine\";\nimport { createApiRoutes } from \"./routes/api\";\nimport { createSseRoute } from \"./routes/sse\";\nimport { createStaticHandler } from \"./static\";\n\nfunction resolveUiDir(): string {\n // When running from built output: __dirname is dist/, ui is dist/ui/\n const fromDist = join(__dirname, \"ui\");\n if (existsSync(join(fromDist, \"index.html\"))) return fromDist;\n\n // When running from source via bun/tsx: __dirname is src/, ui is ../dist/ui/\n const fromSrc = join(__dirname, \"..\", \"dist\", \"ui\");\n if (existsSync(join(fromSrc, \"index.html\"))) return fromSrc;\n\n // Fallback\n return fromDist;\n}\n\nexport function createStudioApp(\n engine: Engine,\n options?: { readOnly?: boolean }\n): Hono {\n const readOnly = options?.readOnly ?? false;\n const app = new Hono();\n\n app.onError((err, c) => {\n console.error(`[studio] ${c.req.method} ${c.req.path} error:`, err);\n return c.json({ error: err.message }, 500);\n });\n\n // Enable CORS for development\n app.use(\"*\", cors());\n\n // Config endpoint\n app.get(\"/api/config\", (c) => c.json({ readOnly }));\n\n // Block mutations in read-only mode\n if (readOnly) {\n app.use(\"/api/*\", async (c, next) => {\n if (c.req.method === \"POST\") {\n return c.json({ error: \"Studio is in read-only mode\" }, 403);\n }\n await next();\n });\n }\n\n // API routes\n app.route(\"/api\", createApiRoutes(engine));\n\n // SSE events\n app.route(\"/api\", createSseRoute(engine));\n\n // Static SPA assets\n app.get(\"*\", createStaticHandler(resolveUiDir()));\n\n return app;\n}\n","import { serve } from \"@hono/node-server\";\nimport type { Engine } from \"chisel-engine\";\nimport { createStudioApp } from \"./server\";\nimport type { StudioOptions, StudioServer } from \"./types\";\n\nexport type { StudioOptions, StudioServer } from \"./types\";\n\nexport function createStudio(\n engine: Engine,\n options: StudioOptions = {}\n): StudioServer {\n const port = options.port ?? 4040;\n const host = options.host ?? \"localhost\";\n const url = `http://${host}:${port}`;\n\n const app = createStudioApp(engine, { readOnly: options.readOnly });\n let server: ReturnType<typeof serve> | null = null;\n\n return {\n get url() {\n return url;\n },\n\n async start() {\n server = serve({ fetch: app.fetch, port, hostname: host });\n\n console.log(`Chisel Studio running at ${url}`);\n\n if (options.open) {\n const { exec } = await import(\"child_process\");\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${url}`);\n }\n },\n\n async stop() {\n if (server) {\n server.close();\n server = null;\n }\n },\n };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -172,13 +172,23 @@ function resolveUiDir() {
172
172
  if (existsSync(join(fromSrc, "index.html"))) return fromSrc;
173
173
  return fromDist;
174
174
  }
175
- function createStudioApp(engine) {
175
+ function createStudioApp(engine, options) {
176
+ const readOnly = options?.readOnly ?? false;
176
177
  const app = new Hono();
177
178
  app.onError((err, c) => {
178
179
  console.error(`[studio] ${c.req.method} ${c.req.path} error:`, err);
179
180
  return c.json({ error: err.message }, 500);
180
181
  });
181
182
  app.use("*", cors());
183
+ app.get("/api/config", (c) => c.json({ readOnly }));
184
+ if (readOnly) {
185
+ app.use("/api/*", async (c, next) => {
186
+ if (c.req.method === "POST") {
187
+ return c.json({ error: "Studio is in read-only mode" }, 403);
188
+ }
189
+ await next();
190
+ });
191
+ }
182
192
  app.route("/api", createApiRoutes(engine));
183
193
  app.route("/api", createSseRoute(engine));
184
194
  app.get("*", createStaticHandler(resolveUiDir()));
@@ -190,7 +200,7 @@ function createStudio(engine, options = {}) {
190
200
  const port = options.port ?? 4040;
191
201
  const host = options.host ?? "localhost";
192
202
  const url = `http://${host}:${port}`;
193
- const app = createStudioApp(engine);
203
+ const app = createStudioApp(engine, { readOnly: options.readOnly });
194
204
  let server = null;
195
205
  return {
196
206
  get url() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routes/api.ts","../src/routes/sse.ts","../src/static.ts","../src/server.ts","../src/index.ts"],"names":["Hono","join","existsSync"],"mappings":"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","file":"index.mjs","sourcesContent":["import { Hono } from \"hono\";\nimport type { Engine } from \"chisel-engine\";\nimport type { RunStatus } from \"chisel-engine\";\n\nexport function createApiRoutes(engine: Engine): Hono {\n const app = new Hono();\n\n // Health check\n app.get(\"/health\", async (c) => {\n const health = await engine.health();\n return c.json(health, health.connected ? 200 : 503);\n });\n\n // List registered workflows\n app.get(\"/workflows\", (c) => {\n return c.json(engine.listWorkflows());\n });\n\n // List runs for a workflow\n app.get(\"/workflows/:id/runs\", async (c) => {\n const id = c.req.param(\"id\");\n const query = c.req.query();\n\n const result = await engine.listRuns(id, {\n limit: query.limit ? Number(query.limit) : undefined,\n cursor: query.cursor ? Number(query.cursor) : undefined,\n order: query.order as \"asc\" | \"desc\" | undefined,\n status: query.status as RunStatus | undefined,\n });\n\n return c.json(result);\n });\n\n // Get run detail\n app.get(\"/runs/:runId\", async (c) => {\n const runId = c.req.param(\"runId\");\n const run = await engine.getRun(runId);\n\n if (!run) {\n return c.json({ error: \"Run not found\" }, 404);\n }\n\n return c.json(run);\n });\n\n // Cancel a run\n app.post(\"/runs/:runId/cancel\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.cancelRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Retry a failed run\n app.post(\"/runs/:runId/retry\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.retryRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Trigger a workflow\n app.post(\"/workflows/:id/trigger\", async (c) => {\n const id = c.req.param(\"id\");\n\n try {\n const body = await c.req.json();\n const { runId } = await engine.trigger(id, body);\n return c.json({ runId }, 202);\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n return app;\n}\n","import { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { Engine, EngineEventName } from \"chisel-engine\";\n\nconst ENGINE_EVENTS: EngineEventName[] = [\n \"workflow:start\",\n \"workflow:complete\",\n \"workflow:fail\",\n \"step:start\",\n \"step:complete\",\n \"step:fail\",\n \"step:retry\",\n];\n\nexport function createSseRoute(engine: Engine): Hono {\n const app = new Hono();\n\n app.get(\"/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const handlers = new Map<string, (payload: unknown) => void>();\n\n for (const event of ENGINE_EVENTS) {\n const handler = (payload: unknown) => {\n stream\n .writeSSE({\n event,\n data: JSON.stringify(payload, (_key, value) =>\n value instanceof Error\n ? { message: value.message, name: value.name }\n : value\n ),\n })\n .catch(() => {});\n };\n handlers.set(event, handler);\n engine.on(event, handler as any);\n }\n\n // Heartbeat every 15 seconds\n const heartbeat = setInterval(() => {\n stream\n .writeSSE({\n event: \"heartbeat\",\n data: JSON.stringify({ time: Date.now() }),\n })\n .catch(() => {});\n }, 15_000);\n\n // Keep the stream alive until client disconnects\n try {\n while (true) {\n await stream.sleep(1000);\n }\n } finally {\n clearInterval(heartbeat);\n for (const [event, handler] of handlers) {\n engine.off(event as EngineEventName, handler as any);\n }\n }\n });\n });\n\n return app;\n}\n","import { readFileSync, existsSync } from \"fs\";\nimport { join, extname } from \"path\";\nimport type { Context } from \"hono\";\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"application/javascript; charset=utf-8\",\n \".mjs\": \"application/javascript; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport function createStaticHandler(uiDir: string) {\n // Pre-load index.html into memory (small file, hot path)\n const indexPath = join(uiDir, \"index.html\");\n let indexHtml = \"\";\n if (existsSync(indexPath)) {\n indexHtml = readFileSync(indexPath, \"utf-8\");\n }\n\n return async (c: Context) => {\n const reqPath = new URL(c.req.url).pathname;\n\n // Try serving static asset\n const filePath = join(uiDir, reqPath);\n if (existsSync(filePath) && reqPath !== \"/\") {\n try {\n const content = readFileSync(filePath);\n const ext = extname(filePath);\n const contentType = MIME_TYPES[ext] || \"application/octet-stream\";\n return new Response(content, {\n headers: {\n \"Content-Type\": contentType,\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n },\n });\n } catch {\n // Fall through to index.html\n }\n }\n\n // SPA fallback: serve index.html for all non-file routes\n if (!indexHtml) {\n return c.text(\"Studio UI not built. Run: bun run build:ui\", 500);\n }\n return c.html(indexHtml);\n };\n}\n","import { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Engine } from \"chisel-engine\";\nimport { createApiRoutes } from \"./routes/api\";\nimport { createSseRoute } from \"./routes/sse\";\nimport { createStaticHandler } from \"./static\";\n\nfunction resolveUiDir(): string {\n // When running from built output: __dirname is dist/, ui is dist/ui/\n const fromDist = join(__dirname, \"ui\");\n if (existsSync(join(fromDist, \"index.html\"))) return fromDist;\n\n // When running from source via bun/tsx: __dirname is src/, ui is ../dist/ui/\n const fromSrc = join(__dirname, \"..\", \"dist\", \"ui\");\n if (existsSync(join(fromSrc, \"index.html\"))) return fromSrc;\n\n // Fallback\n return fromDist;\n}\n\nexport function createStudioApp(engine: Engine): Hono {\n const app = new Hono();\n\n app.onError((err, c) => {\n console.error(`[studio] ${c.req.method} ${c.req.path} error:`, err);\n return c.json({ error: err.message }, 500);\n });\n\n // Enable CORS for development\n app.use(\"*\", cors());\n\n // API routes\n app.route(\"/api\", createApiRoutes(engine));\n\n // SSE events\n app.route(\"/api\", createSseRoute(engine));\n\n // Static SPA assets\n app.get(\"*\", createStaticHandler(resolveUiDir()));\n\n return app;\n}\n","import { serve } from \"@hono/node-server\";\nimport type { Engine } from \"chisel-engine\";\nimport { createStudioApp } from \"./server\";\nimport type { StudioOptions, StudioServer } from \"./types\";\n\nexport type { StudioOptions, StudioServer } from \"./types\";\n\nexport function createStudio(\n engine: Engine,\n options: StudioOptions = {}\n): StudioServer {\n const port = options.port ?? 4040;\n const host = options.host ?? \"localhost\";\n const url = `http://${host}:${port}`;\n\n const app = createStudioApp(engine);\n let server: ReturnType<typeof serve> | null = null;\n\n return {\n get url() {\n return url;\n },\n\n async start() {\n server = serve({ fetch: app.fetch, port, hostname: host });\n\n console.log(`Chisel Studio running at ${url}`);\n\n if (options.open) {\n const { exec } = await import(\"child_process\");\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${url}`);\n }\n },\n\n async stop() {\n if (server) {\n server.close();\n server = null;\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/routes/api.ts","../src/routes/sse.ts","../src/static.ts","../src/server.ts","../src/index.ts"],"names":["Hono","join","existsSync"],"mappings":"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","file":"index.mjs","sourcesContent":["import { Hono } from \"hono\";\nimport type { Engine } from \"chisel-engine\";\nimport type { RunStatus } from \"chisel-engine\";\n\nexport function createApiRoutes(engine: Engine): Hono {\n const app = new Hono();\n\n // Health check\n app.get(\"/health\", async (c) => {\n const health = await engine.health();\n return c.json(health, health.connected ? 200 : 503);\n });\n\n // List registered workflows\n app.get(\"/workflows\", (c) => {\n return c.json(engine.listWorkflows());\n });\n\n // List runs for a workflow\n app.get(\"/workflows/:id/runs\", async (c) => {\n const id = c.req.param(\"id\");\n const query = c.req.query();\n\n const result = await engine.listRuns(id, {\n limit: query.limit ? Number(query.limit) : undefined,\n cursor: query.cursor ? Number(query.cursor) : undefined,\n order: query.order as \"asc\" | \"desc\" | undefined,\n status: query.status as RunStatus | undefined,\n });\n\n return c.json(result);\n });\n\n // Get run detail\n app.get(\"/runs/:runId\", async (c) => {\n const runId = c.req.param(\"runId\");\n const run = await engine.getRun(runId);\n\n if (!run) {\n return c.json({ error: \"Run not found\" }, 404);\n }\n\n return c.json(run);\n });\n\n // Cancel a run\n app.post(\"/runs/:runId/cancel\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.cancelRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Retry a failed run\n app.post(\"/runs/:runId/retry\", async (c) => {\n const runId = c.req.param(\"runId\");\n\n try {\n await engine.retryRun(runId);\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n // Trigger a workflow\n app.post(\"/workflows/:id/trigger\", async (c) => {\n const id = c.req.param(\"id\");\n\n try {\n const body = await c.req.json();\n const { runId } = await engine.trigger(id, body);\n return c.json({ runId }, 202);\n } catch (error) {\n return c.json(\n { error: error instanceof Error ? error.message : String(error) },\n 400\n );\n }\n });\n\n return app;\n}\n","import { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { Engine, EngineEventName } from \"chisel-engine\";\n\nconst ENGINE_EVENTS: EngineEventName[] = [\n \"workflow:start\",\n \"workflow:complete\",\n \"workflow:fail\",\n \"step:start\",\n \"step:complete\",\n \"step:fail\",\n \"step:retry\",\n];\n\nexport function createSseRoute(engine: Engine): Hono {\n const app = new Hono();\n\n app.get(\"/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const handlers = new Map<string, (payload: unknown) => void>();\n\n for (const event of ENGINE_EVENTS) {\n const handler = (payload: unknown) => {\n stream\n .writeSSE({\n event,\n data: JSON.stringify(payload, (_key, value) =>\n value instanceof Error\n ? { message: value.message, name: value.name }\n : value\n ),\n })\n .catch(() => {});\n };\n handlers.set(event, handler);\n engine.on(event, handler as any);\n }\n\n // Heartbeat every 15 seconds\n const heartbeat = setInterval(() => {\n stream\n .writeSSE({\n event: \"heartbeat\",\n data: JSON.stringify({ time: Date.now() }),\n })\n .catch(() => {});\n }, 15_000);\n\n // Keep the stream alive until client disconnects\n try {\n while (true) {\n await stream.sleep(1000);\n }\n } finally {\n clearInterval(heartbeat);\n for (const [event, handler] of handlers) {\n engine.off(event as EngineEventName, handler as any);\n }\n }\n });\n });\n\n return app;\n}\n","import { readFileSync, existsSync } from \"fs\";\nimport { join, extname } from \"path\";\nimport type { Context } from \"hono\";\n\nconst MIME_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"application/javascript; charset=utf-8\",\n \".mjs\": \"application/javascript; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport function createStaticHandler(uiDir: string) {\n // Pre-load index.html into memory (small file, hot path)\n const indexPath = join(uiDir, \"index.html\");\n let indexHtml = \"\";\n if (existsSync(indexPath)) {\n indexHtml = readFileSync(indexPath, \"utf-8\");\n }\n\n return async (c: Context) => {\n const reqPath = new URL(c.req.url).pathname;\n\n // Try serving static asset\n const filePath = join(uiDir, reqPath);\n if (existsSync(filePath) && reqPath !== \"/\") {\n try {\n const content = readFileSync(filePath);\n const ext = extname(filePath);\n const contentType = MIME_TYPES[ext] || \"application/octet-stream\";\n return new Response(content, {\n headers: {\n \"Content-Type\": contentType,\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n },\n });\n } catch {\n // Fall through to index.html\n }\n }\n\n // SPA fallback: serve index.html for all non-file routes\n if (!indexHtml) {\n return c.text(\"Studio UI not built. Run: bun run build:ui\", 500);\n }\n return c.html(indexHtml);\n };\n}\n","import { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport type { Engine } from \"chisel-engine\";\nimport { createApiRoutes } from \"./routes/api\";\nimport { createSseRoute } from \"./routes/sse\";\nimport { createStaticHandler } from \"./static\";\n\nfunction resolveUiDir(): string {\n // When running from built output: __dirname is dist/, ui is dist/ui/\n const fromDist = join(__dirname, \"ui\");\n if (existsSync(join(fromDist, \"index.html\"))) return fromDist;\n\n // When running from source via bun/tsx: __dirname is src/, ui is ../dist/ui/\n const fromSrc = join(__dirname, \"..\", \"dist\", \"ui\");\n if (existsSync(join(fromSrc, \"index.html\"))) return fromSrc;\n\n // Fallback\n return fromDist;\n}\n\nexport function createStudioApp(\n engine: Engine,\n options?: { readOnly?: boolean }\n): Hono {\n const readOnly = options?.readOnly ?? false;\n const app = new Hono();\n\n app.onError((err, c) => {\n console.error(`[studio] ${c.req.method} ${c.req.path} error:`, err);\n return c.json({ error: err.message }, 500);\n });\n\n // Enable CORS for development\n app.use(\"*\", cors());\n\n // Config endpoint\n app.get(\"/api/config\", (c) => c.json({ readOnly }));\n\n // Block mutations in read-only mode\n if (readOnly) {\n app.use(\"/api/*\", async (c, next) => {\n if (c.req.method === \"POST\") {\n return c.json({ error: \"Studio is in read-only mode\" }, 403);\n }\n await next();\n });\n }\n\n // API routes\n app.route(\"/api\", createApiRoutes(engine));\n\n // SSE events\n app.route(\"/api\", createSseRoute(engine));\n\n // Static SPA assets\n app.get(\"*\", createStaticHandler(resolveUiDir()));\n\n return app;\n}\n","import { serve } from \"@hono/node-server\";\nimport type { Engine } from \"chisel-engine\";\nimport { createStudioApp } from \"./server\";\nimport type { StudioOptions, StudioServer } from \"./types\";\n\nexport type { StudioOptions, StudioServer } from \"./types\";\n\nexport function createStudio(\n engine: Engine,\n options: StudioOptions = {}\n): StudioServer {\n const port = options.port ?? 4040;\n const host = options.host ?? \"localhost\";\n const url = `http://${host}:${port}`;\n\n const app = createStudioApp(engine, { readOnly: options.readOnly });\n let server: ReturnType<typeof serve> | null = null;\n\n return {\n get url() {\n return url;\n },\n\n async start() {\n server = serve({ fetch: app.fetch, port, hostname: host });\n\n console.log(`Chisel Studio running at ${url}`);\n\n if (options.open) {\n const { exec } = await import(\"child_process\");\n const cmd =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${url}`);\n }\n },\n\n async stop() {\n if (server) {\n server.close();\n server = null;\n }\n },\n };\n}\n"]}