@aiviatic/kindling 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bootstrap/kindling.cmd +13 -13
- package/bootstrap/setup.ps1 +111 -98
- package/bootstrap/setup.sh +2 -2
- package/dist/{chunk-MW7UAGER.js → chunk-6VTQGOJR.js} +2 -2
- package/dist/{chunk-MW7UAGER.js.map → chunk-6VTQGOJR.js.map} +1 -1
- package/dist/{chunk-IS6LC3HK.js → chunk-DTSAPFB2.js} +3 -3
- package/dist/chunk-DTSAPFB2.js.map +1 -0
- package/dist/cli/main.js +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/ui/assets/{index-CoPlNDA-.js → index-BF3LDvt7.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/chunk-IS6LC3HK.js.map +0 -1
package/dist/ui/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Kindling</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BF3LDvt7.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-Bw_xLj6a.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../server/server.ts","../server/open-browser.ts"],"sourcesContent":["import { createServer as createHttpServer, type IncomingMessage, type ServerResponse, type Server } from 'node:http';\nimport { once } from 'node:events';\nimport { readFile } from 'node:fs/promises';\nimport { join, normalize, extname } from 'node:path';\nimport type { AddressInfo } from 'node:net';\nimport type { EngineEmitter } from '../engine/emitter';\nimport { StepId, type Config, type KindlingEvent, type InspectResult } from '../engine/contract';\nimport { expandTilde } from '../engine/expand-tilde';\n\n// Minimal command surface the server drives (the Engine satisfies this).\nexport interface ServerCommands {\n start(config: Config): unknown | Promise<unknown>;\n cancel(): void;\n retry(step: StepId): unknown | Promise<unknown>;\n /**\n * Read-only filesystem probe for the Configure-time \"existing project?\" detection (Story 7.2 /\n * `POST /inspect`). OPTIONAL so existing `ServerCommands` fakes (e.g. server.test.ts) don't break;\n * when absent the handler returns the neutral `{ isKindlingProject: false, installedBmadVersion: null }`.\n */\n inspect?(projectDir: string): Promise<InspectResult>;\n}\n\nexport interface StartServerOptions {\n emitter: EngineEmitter;\n commands: ServerCommands;\n /** Built UI directory (dist/ui) to serve statically; omit to skip static serving. */\n uiDir?: string;\n /** Called when the Welcome screen render-acks (POST /ack). The host uses this to write the\n * static welcome.html and exit the ephemeral server — only fired on success (3.7 lifecycle). */\n onWelcomeAck?: () => void;\n}\n\nexport interface RunningServer {\n server: Server;\n url: string;\n port: number;\n close(): Promise<void>;\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n};\n\nconst MAX_BODY_BYTES = 64 * 1024;\nconst KNOWN_STEPS = new Set<string>(Object.values(StepId));\n\n// Reads the request body with a hard size cap (throws past the cap → caller responds 413).\nasync function readBody(req: IncomingMessage): Promise<string> {\n let body = '';\n for await (const chunk of req) {\n body += String(chunk);\n if (body.length > MAX_BODY_BYTES) throw new Error('body too large');\n }\n return body;\n}\n\nasync function serveStatic(uiDir: string, pathname: string, res: ServerResponse): Promise<void> {\n const rel = pathname === '/' ? 'index.html' : pathname.replace(/^\\/+/, '');\n const filePath = normalize(join(uiDir, rel));\n // Path-traversal guard: the resolved path must stay within uiDir. (We serve only our own\n // built dist/ui — not attacker-writable — so symlink resolution is out of scope.)\n if (filePath !== normalize(uiDir) && !filePath.startsWith(normalize(uiDir) + '/')) {\n res.writeHead(403).end('forbidden');\n return;\n }\n try {\n const data = await readFile(filePath);\n res.writeHead(200, { 'Content-Type': MIME[extname(filePath)] ?? 'application/octet-stream' });\n res.end(data);\n } catch {\n res.writeHead(404).end('not found');\n }\n}\n\n// Stands up the localhost server: SSE event stream, command POSTs, and (optional) static UI.\n// Binds 127.0.0.1 on an ephemeral port (NFR6); resolves once listening.\nexport async function startServer(opts: StartServerOptions): Promise<RunningServer> {\n const { emitter, commands, uiDir, onWelcomeAck } = opts;\n const sseClients = new Set<ServerResponse>();\n\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const { pathname } = new URL(req.url ?? '/', 'http://127.0.0.1');\n\n // DNS-rebinding guard: only serve requests whose Host header is loopback. A page that rebinds\n // its own domain's DNS to 127.0.0.1 still sends that domain as Host, not 127.0.0.1 / localhost.\n const hostname = (req.headers.host ?? '').split(':')[0].toLowerCase();\n if (hostname !== '127.0.0.1' && hostname !== 'localhost') {\n res.writeHead(403).end('forbidden');\n return;\n }\n\n if (req.method === 'GET' && pathname === '/events') {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n // Data-only SSE framing (one stream per connection); the UI client uses `onmessage`.\n const send = (event: KindlingEvent): void => {\n if (!res.writableEnded) res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n };\n for (const event of emitter.events()) send(event); // replay backlog so late clients catch up\n const off = emitter.on(send);\n sseClients.add(res);\n req.on('close', () => {\n off();\n sseClients.delete(res);\n });\n return;\n }\n\n if (req.method === 'POST') {\n // CSRF guard: a cross-origin browser page can reach 127.0.0.1, but cannot set a custom\n // header without a CORS preflight (which this server never approves). The UI sends it.\n if (req.headers['x-kindling'] !== '1') {\n res.writeHead(403).end('forbidden');\n return;\n }\n if (pathname === '/start') {\n let body: string;\n try {\n body = await readBody(req);\n } catch {\n res.writeHead(413).end('body too large');\n return;\n }\n let config: Config | undefined;\n try {\n const p = JSON.parse(body || 'null') as Partial<Config> | null;\n // Shape-guard every field the engine dereferences (flags.ts reads projectDir; the\n // self-check/validation reads pins) so a malformed body is rejected as 400 rather\n // than reaching the engine and crashing after we've already sent 202.\n if (\n p &&\n typeof p === 'object' &&\n typeof p.projectDir === 'string' &&\n p.projectDir.length > 0 &&\n typeof p.projectName === 'string' &&\n p.projectName.length > 0 &&\n Array.isArray(p.ides) &&\n p.ides.length > 0 &&\n Array.isArray(p.modules) &&\n p.pins !== null &&\n typeof p.pins === 'object'\n ) {\n config = p as Config;\n }\n } catch {\n config = undefined;\n }\n if (!config) {\n res.writeHead(400).end('invalid config');\n return;\n }\n res.writeHead(202).end(); // progress flows over SSE; don't block on the run\n void Promise.resolve(commands.start(config)).catch(() => {});\n return;\n }\n if (pathname === '/inspect') {\n // Read-only, fixed-suffix probe (Story 7.2). Same CSRF guard + readBody cap as /start;\n // shape-guard the body → 400; else 200 + the InspectResult JSON. No engine, no writes,\n // no user-controlled traversal (the inspector only stats <dir>/_bmad + reads the fixed\n // <dir>/_bmad/_config/manifest.yaml). Both underlying reads never throw.\n let body: string;\n try {\n body = await readBody(req);\n } catch {\n res.writeHead(413).end('body too large');\n return;\n }\n let projectDir: string | undefined;\n try {\n const p = JSON.parse(body || 'null') as { projectDir?: unknown } | null;\n if (p && typeof p === 'object' && typeof p.projectDir === 'string' && p.projectDir.length > 0) {\n projectDir = p.projectDir;\n }\n } catch {\n projectDir = undefined;\n }\n if (!projectDir) {\n res.writeHead(400).end('invalid dir');\n return;\n }\n const result: InspectResult = commands.inspect\n ? await commands.inspect(expandTilde(projectDir))\n : { isKindlingProject: false, installedBmadVersion: null };\n res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }).end(JSON.stringify(result));\n return;\n }\n if (pathname === '/cancel') {\n commands.cancel();\n res.writeHead(202).end();\n return;\n }\n if (pathname === '/ack') {\n // The Welcome screen rendered — let the host exit the ephemeral server. Respond first so\n // the ack completes even if onWelcomeAck closes the server synchronously.\n res.writeHead(202).end();\n onWelcomeAck?.();\n return;\n }\n if (pathname === '/retry') {\n let body: string;\n try {\n body = await readBody(req);\n } catch {\n res.writeHead(413).end('body too large');\n return;\n }\n let step: string | undefined;\n try {\n step = (JSON.parse(body || '{}') as { step?: string }).step;\n } catch {\n step = undefined;\n }\n if (!step || !KNOWN_STEPS.has(step)) {\n res.writeHead(400).end('invalid step');\n return;\n }\n res.writeHead(202).end();\n void Promise.resolve(commands.retry(step as StepId)).catch(() => {});\n return;\n }\n }\n\n if (req.method === 'GET' && uiDir) {\n await serveStatic(uiDir, pathname, res);\n return;\n }\n\n res.writeHead(404).end('not found');\n };\n\n const server = createHttpServer((req, res) => {\n handler(req, res).catch(() => {\n if (!res.headersSent) res.writeHead(500).end('server error');\n else res.destroy();\n });\n });\n server.listen(0, '127.0.0.1');\n await once(server, 'listening');\n const { port } = server.address() as AddressInfo;\n\n return {\n server,\n port,\n url: `http://127.0.0.1:${port}/`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n for (const res of sseClients) res.end(); // SSE keeps sockets alive — close them first\n sseClients.clear();\n server.close((err) => (err ? reject(err) : resolve()));\n server.closeAllConnections(); // terminate any remaining keep-alive sockets (Node 18.2+)\n }),\n };\n}\n","import { exec as defaultExec, type ExecResult } from '../engine/exec';\n\nexport interface OpenBrowserOptions {\n exec?: (cmd: string, args: string[]) => Promise<ExecResult>;\n platform?: NodeJS.Platform;\n}\n\n// Best-effort: opens `url` in the default browser. NEVER throws; returns whether the open\n// command launched. The printed/returned URL is the real contract (FR5 — auto-open is decoration).\nexport async function openBrowser(url: string, opts: OpenBrowserOptions = {}): Promise<boolean> {\n const exec = opts.exec ?? defaultExec;\n const platform = opts.platform ?? process.platform;\n\n // `cmd.exe` is a real executable (spawns fine with shell:false); `start` is its builtin, and\n // its first quoted arg is the window title (empty here).\n const [cmd, args]: [string, string[]] =\n platform === 'darwin'\n ? ['open', [url]]\n : platform === 'win32'\n ? ['cmd', ['/c', 'start', '', url]]\n : ['xdg-open', [url]];\n\n try {\n const r = await exec(cmd, args);\n return r.code === 0;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["exec"]}
|