codebase-onboarder 0.7.0 → 1.0.0
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/README.md +115 -12
- package/bin/onboarder.js +0 -0
- package/cli/agent/command.d.ts +71 -0
- package/cli/agent/command.js +177 -0
- package/cli/agent/command.js.map +1 -0
- package/cli/agent/config.d.ts +31 -0
- package/cli/agent/config.js +98 -0
- package/cli/agent/config.js.map +1 -0
- package/cli/agent/contracts.d.ts +77 -0
- package/cli/agent/contracts.js +2 -0
- package/cli/agent/contracts.js.map +1 -0
- package/cli/agent/files.d.ts +19 -0
- package/cli/agent/files.js +109 -0
- package/cli/agent/files.js.map +1 -0
- package/cli/agent/gitSource.d.ts +18 -0
- package/cli/agent/gitSource.js +69 -0
- package/cli/agent/gitSource.js.map +1 -0
- package/cli/agent/github.d.ts +21 -0
- package/cli/agent/github.js +94 -0
- package/cli/agent/github.js.map +1 -0
- package/cli/agent/mcp.d.ts +15 -0
- package/cli/agent/mcp.js +78 -0
- package/cli/agent/mcp.js.map +1 -0
- package/cli/agent/process.d.ts +18 -0
- package/cli/agent/process.js +118 -0
- package/cli/agent/process.js.map +1 -0
- package/cli/agent/protocol.d.ts +3 -0
- package/cli/agent/protocol.js +26 -0
- package/cli/agent/protocol.js.map +1 -0
- package/cli/agent/runner.d.ts +34 -0
- package/cli/agent/runner.js +152 -0
- package/cli/agent/runner.js.map +1 -0
- package/cli/agent/stdio.d.ts +1 -0
- package/cli/agent/stdio.js +62 -0
- package/cli/agent/stdio.js.map +1 -0
- package/cli/agent/tools.d.ts +7 -0
- package/cli/agent/tools.js +201 -0
- package/cli/agent/tools.js.map +1 -0
- package/cli/chat/app.d.ts +16 -0
- package/cli/chat/app.js +345 -0
- package/cli/chat/app.js.map +1 -0
- package/cli/chat/commands.d.ts +11 -0
- package/cli/chat/commands.js +120 -0
- package/cli/chat/commands.js.map +1 -0
- package/cli/chat/composer.d.ts +75 -0
- package/cli/chat/composer.js +467 -0
- package/cli/chat/composer.js.map +1 -0
- package/cli/chat/controller.d.ts +60 -0
- package/cli/chat/controller.js +393 -0
- package/cli/chat/controller.js.map +1 -0
- package/cli/chat/logo.d.ts +3 -0
- package/cli/chat/logo.js +14 -0
- package/cli/chat/logo.js.map +1 -0
- package/cli/chat/models.d.ts +18 -0
- package/cli/chat/models.js +88 -0
- package/cli/chat/models.js.map +1 -0
- package/cli/chat/profile.d.ts +5 -0
- package/cli/chat/profile.js +98 -0
- package/cli/chat/profile.js.map +1 -0
- package/cli/chat/render.d.ts +40 -0
- package/cli/chat/render.js +259 -0
- package/cli/chat/render.js.map +1 -0
- package/cli/chat/screen.d.ts +26 -0
- package/cli/chat/screen.js +117 -0
- package/cli/chat/screen.js.map +1 -0
- package/cli/chat/source.d.ts +29 -0
- package/cli/chat/source.js +116 -0
- package/cli/chat/source.js.map +1 -0
- package/cli/chat/store.d.ts +43 -0
- package/cli/chat/store.js +94 -0
- package/cli/chat/store.js.map +1 -0
- package/cli/chat/text.d.ts +4 -0
- package/cli/chat/text.js +27 -0
- package/cli/chat/text.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +20 -1
- package/cli/explorer/app.js.map +1 -1
- package/cli/explorer/commands.js +5 -0
- package/cli/explorer/commands.js.map +1 -1
- package/cli/main.js +47 -13
- package/cli/main.js.map +1 -1
- package/docs/HERMES_RESEARCH.md +51 -0
- package/package.json +2 -1
- package/public/app.js +98 -24
- package/public/app.js.map +1 -1
- package/public/assets/onboarder-mark.svg +11 -0
- package/public/browser.d.ts +3 -0
- package/public/browser.js.map +1 -1
- package/public/index.html +24 -7
- package/public/js/about.js +2 -2
- package/public/js/about.js.map +1 -1
- package/public/js/aiDraft.d.ts +1 -0
- package/public/js/aiDraft.js +66 -17
- package/public/js/aiDraft.js.map +1 -1
- package/public/js/api.d.ts +8 -5
- package/public/js/api.js +45 -22
- package/public/js/api.js.map +1 -1
- package/public/js/deepAnalysisView.js +2 -2
- package/public/js/deepAnalysisView.js.map +1 -1
- package/public/js/diagramPane.d.ts +1 -0
- package/public/js/diagramPane.js +5 -0
- package/public/js/diagramPane.js.map +1 -1
- package/public/js/docsView.js +6 -6
- package/public/js/docsView.js.map +1 -1
- package/public/js/dom.d.ts +8 -0
- package/public/js/dom.js.map +1 -1
- package/public/js/llm.d.ts +3 -1
- package/public/js/llm.js +15 -8
- package/public/js/llm.js.map +1 -1
- package/public/js/repoFiles.js +1 -1
- package/public/js/repoFiles.js.map +1 -1
- package/public/js/reviewView.js +1 -1
- package/public/js/reviewView.js.map +1 -1
- package/public/styles.css +25 -0
- package/server/gitDiff.d.ts +1 -1
- package/server/hermesChat.d.ts +13 -0
- package/server/hermesChat.js +145 -0
- package/server/hermesChat.js.map +1 -0
- package/server/http.d.ts +5 -4
- package/server/http.js +0 -4
- package/server/http.js.map +1 -1
- package/server/mcp/server.d.ts +1 -1
- package/server/mcp/server.js +2 -2
- package/server/mcp/server.js.map +1 -1
- package/server/router.js +7 -3
- package/server/router.js.map +1 -1
- package/shared/diagram/aiMermaid.js +16 -13
- package/shared/diagram/aiMermaid.js.map +1 -1
package/server/mcp/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/mcp/server.ts"],"names":[],"mappings":"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","sourcesContent":["// The MCP server: JSON-RPC 2.0 over stdio, one JSON object per line.\n//\n// Why no SDK: the project has no dependencies, and `package.json` says so. MCP's\n// stdio transport is a framing convention (one line-delimited JSON-RPC message\n// per message, on the child's stdin/stdout), not a wire protocol with magic — so\n// implementing it directly is a few dozen lines and keeps the promise that this\n// repo installs with nothing. If the SDK ever becomes worth it, this file is the\n// only thing that changes; `tools.js` and `analysis.js` know nothing about JSON.\n//\n// The rules that matter for correctness:\n// * stdout belongs to the protocol. Anything else printed there corrupts the\n// stream, so the logger is redirected to stderr for the lifetime of the\n// child (see `runner.js`).\n// * Requests are answered in order. MCP clients pipeline, and answering a\n// `tools/call` out of order makes some clients discard the later result.\n// * A tool that throws is answered with a *result* carrying `isError: true`,\n// not a JSON-RPC error. That is what puts the message in front of the model\n// so it can correct itself; a protocol error just fails the turn.\n\nimport { toolDefinitions, callTool, findTool } from './tools.js';\nimport { ToolError } from './analysis.js';\n\nexport const PROTOCOL_VERSION = '2025-06-18';\nexport const SERVER_INFO = {\n name: 'onboarder',\n version: '1.0.0',\n title: 'Onboarder',\n instructions:\n 'Onboarder explains a codebase. Start with onboarder_scan on a repository path; '\n + 'it returns a written overview, the entry points, the most depended-upon files, and '\n + 'any dependency cycles. Then use onboarder_tour for a reading order, onboarder_search '\n + 'to find things by name, onboarder_explain_file for one file, and onboarder_read_file '\n + 'for raw source. Every tool also takes `path`; omit it to keep working on the last '\n + 'repository scanned. The repository is read, never written.',\n};\n\n// JSON-RPC error codes. Only the first two are protocol-level; the rest are\n// conventional in MCP and keep a misbehaving client from parsing a server bug as\n// a server bug it caused.\nconst PARSE_ERROR = -32700;\nconst INVALID_REQUEST = -32600;\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n\nfunction result(id, value) {\n return { jsonrpc: '2.0', id, result: value };\n}\n\nfunction failure(id, code, message, data?: unknown) {\n const error: { code: number; message: string; data?: unknown } = { code, message };\n if (data !== undefined) error.data = data;\n return { jsonrpc: '2.0', id, error };\n}\n\n// A tool error and an internal error are different things. A `ToolError` is a\n// sentence the model can act on — \"that path is outside the repo\" — so it becomes\n// an isError result with that sentence intact. Anything else is a bug, and saying\n// so plainly beats pretending it was the caller's fault.\nasync function callOne(name, args) {\n const tool = findTool(name);\n if (!tool) {\n return {\n isError: true,\n content: [{ type: 'text', text: `No tool called \"${name}\". Call tools/list to see what is available.` }],\n };\n }\n try {\n const value = await tool.run(args || {});\n // MCP content is a list of blocks. A tool that returns structured data sends\n // it as JSON text plus the native `structuredContent` field, which is what\n // typed clients read; the text block is what everything else falls back to.\n return {\n content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],\n structuredContent: value,\n isError: false,\n };\n } catch (err) {\n if (err instanceof ToolError) {\n return { isError: true, content: [{ type: 'text', text: err.message }] };\n }\n // Unexpected. Report the message, not the stack: the caller is a model on the\n // other end of a pipe, and a stack trace is noise. The full error is on the\n // child process's stderr, which is where a human debugging this will look.\n console.error('[onboarder-mcp] tool failed:', err);\n return {\n isError: true,\n content: [{ type: 'text', text: `The tool failed unexpectedly: ${err?.message || String(err)}` }],\n };\n }\n}\n\n// The dispatcher is exported because the Streamable HTTP transport in `./http.js`\n// needs to answer the same messages without going through a line of stdio. The\n// protocol logic lives in exactly one place; a second transport must not become a\n// second implementation of `tools/list`.\nexport { handle };\n\nasync function handle(msg) {\n // A request must name a method and must declare the protocol version. Anything\n // else is not a request at all, and replying to it would invent an id to answer\n // under — so this returns nothing and the line is dropped.\n //\n // `null` rather than `undefined` is the contract with the loop below: both mean\n // \"say nothing\", but only one of them survives being awaited and compared.\n if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0' || typeof msg.method !== 'string') {\n return null;\n }\n const { method, id, params } = msg;\n\n // A notification has no id and expects no answer. Returning `null` is how the\n // loop knows to stay silent, which matters: replying to a notification is\n // itself a protocol violation.\n const isNotification = id === undefined || id === null;\n\n switch (method) {\n case 'initialize':\n return isNotification ? undefined : result(id, {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: { tools: { listChanged: false } },\n serverInfo: SERVER_INFO,\n });\n\n // The client's post-handshake ping. Answering it is what tells the client the\n // handshake completed and the tool loop is live.\n case 'notifications/initialized':\n return null;\n\n case 'ping':\n return isNotification ? undefined : result(id, {});\n\n case 'tools/list':\n return isNotification ? undefined : result(id, { tools: toolDefinitions() });\n\n case 'tools/call':\n if (isNotification) return null;\n if (!params?.name || typeof params.name !== 'string') {\n return failure(id, INVALID_PARAMS, 'tools/call needs a tool name.');\n }\n return result(id, await callOne(params.name, params.arguments));\n\n // A client asking for resources or prompts is not an error — this server has\n // none, and saying so is more useful than METHOD_NOT_FOUND. A harness that\n // probes both at startup gets a clean answer instead of a warning.\n case 'resources/list':\n return result(id, { resources: [] });\n\n case 'prompts/list':\n return result(id, { prompts: [] });\n\n case 'resources/templates/list':\n return result(id, { resourceTemplates: [] });\n\n default:\n if (isNotification) return null;\n return failure(id, METHOD_NOT_FOUND, `No method called \"${method}\".`);\n }\n}\n\n// One line in, one line out. A message that will not parse gets a parse error\n// with `id: null`, which is what the spec requires — the client cannot know which\n// request the bad line belonged to.\nfunction parseLine(line) {\n try {\n const msg = JSON.parse(line);\n if (!msg || typeof msg !== 'object' || Array.isArray(msg)) {\n return { bad: failure(null, INVALID_REQUEST, 'A message must be a JSON-RPC object.') };\n }\n return { msg };\n } catch (err) {\n return { bad: failure(null, PARSE_ERROR, `Could not parse the message: ${err.message}`) };\n }\n}\n\n/**\n * Run the server until stdin closes. `input` and `output` are parameters so tests\n * can drive a whole session through in-memory streams.\n */\nexport function serveMcp({ input = process.stdin, output = process.stdout }: Record<string, any> = {}) {\n let buffer = '';\n // Messages are handled strictly in order, but not awaited inline: a long scan\n // must not block the event loop from reading the next line, or a client that\n // pipelines would sit with a full buffer. The promise chain is what preserves\n // the ordering while letting reads continue.\n let chain = Promise.resolve();\n let closed = false;\n\n const send = (message) => {\n if (closed) return;\n output.write(JSON.stringify(message) + '\\n');\n };\n\n const enqueue = (line) => {\n chain = chain.then(async () => {\n const { msg, bad } = parseLine(line);\n if (bad) { send(bad); return; }\n const response = await handle(msg);\n if (response) send(response);\n }).catch((err) => {\n // `handle` already turns tool failures into results. Reaching here means\n // something threw outside that, so the only honest move is to log it and\n // keep serving — a dead server helps nobody recover.\n console.error('[onboarder-mcp] dispatch failed:', err);\n });\n };\n\n input.setEncoding?.('utf8');\n input.on('data', (chunk) => {\n buffer += chunk;\n let nl;\n // A partial line at the end of a chunk stays in the buffer for the next one.\n while ((nl = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line) enqueue(line);\n }\n });\n\n input.on('end', () => {\n // A last line without a trailing newline is still a message. Dropping it\n // would lose the final request of a session — which is often the only one.\n const line = buffer.trim();\n buffer = '';\n if (line) enqueue(line);\n // Let the queue finish before the process exits, so a reply written just\n // after stdin closed still reaches the client.\n chain.finally(() => {\n if (output === process.stdout) process.exitCode = 0;\n });\n });\n\n input.on('error', (err) => {\n console.error('[onboarder-mcp] stdin error:', err);\n });\n\n return {\n stop() { closed = true; },\n // Awaiting the queue is how a test knows every reply has been written, and\n // how a shutdown can drain in-flight work instead of cutting it off.\n settled() { return chain; },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/mcp/server.ts"],"names":[],"mappings":"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","sourcesContent":["// The MCP server: JSON-RPC 2.0 over stdio, one JSON object per line.\n//\n// Why no SDK: the project has no dependencies, and `package.json` says so. MCP's\n// stdio transport is a framing convention (one line-delimited JSON-RPC message\n// per message, on the child's stdin/stdout), not a wire protocol with magic — so\n// implementing it directly is a few dozen lines and keeps the promise that this\n// repo installs with nothing. If the SDK ever becomes worth it, this file is the\n// only thing that changes; `tools.js` and `analysis.js` know nothing about JSON.\n//\n// The rules that matter for correctness:\n// * stdout belongs to the protocol. Anything else printed there corrupts the\n// stream, so the logger is redirected to stderr for the lifetime of the\n// child (see `runner.js`).\n// * Requests are answered in order. MCP clients pipeline, and answering a\n// `tools/call` out of order makes some clients discard the later result.\n// * A tool that throws is answered with a *result* carrying `isError: true`,\n// not a JSON-RPC error. That is what puts the message in front of the model\n// so it can correct itself; a protocol error just fails the turn.\n\nimport { toolDefinitions, callTool, findTool } from './tools.js';\nimport { ToolError } from './analysis.js';\n\nexport const PROTOCOL_VERSION = '2025-06-18';\nexport const SERVER_INFO = {\n name: 'onboarder',\n version: '1.0.0',\n title: 'Onboarder',\n instructions:\n 'Onboarder explains a codebase. Start with onboarder_scan on a repository path; '\n + 'it returns a written overview, the entry points, the most depended-upon files, and '\n + 'any dependency cycles. Then use onboarder_tour for a reading order, onboarder_search '\n + 'to find things by name, onboarder_explain_file for one file, and onboarder_read_file '\n + 'for raw source. Every tool also takes `path`; omit it to keep working on the last '\n + 'repository scanned. The repository is read, never written.',\n};\n\n// JSON-RPC error codes. Only the first two are protocol-level; the rest are\n// conventional in MCP and keep a misbehaving client from parsing a server bug as\n// a server bug it caused.\nconst PARSE_ERROR = -32700;\nconst INVALID_REQUEST = -32600;\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n\nfunction result(id, value) {\n return { jsonrpc: '2.0', id, result: value };\n}\n\nfunction failure(id, code, message, data?: unknown) {\n const error: { code: number; message: string; data?: unknown } = { code, message };\n if (data !== undefined) error.data = data;\n return { jsonrpc: '2.0', id, error };\n}\n\n// A tool error and an internal error are different things. A `ToolError` is a\n// sentence the model can act on — \"that path is outside the repo\" — so it becomes\n// an isError result with that sentence intact. Anything else is a bug, and saying\n// so plainly beats pretending it was the caller's fault.\nasync function callOne(name, args) {\n const tool = findTool(name);\n if (!tool) {\n return {\n isError: true,\n content: [{ type: 'text', text: `No tool called \"${name}\". Call tools/list to see what is available.` }],\n };\n }\n try {\n const value = await tool.run(args || {});\n // MCP content is a list of blocks. A tool that returns structured data sends\n // it as JSON text plus the native `structuredContent` field, which is what\n // typed clients read; the text block is what everything else falls back to.\n return {\n content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],\n structuredContent: value,\n isError: false,\n };\n } catch (err) {\n if (err instanceof ToolError) {\n return { isError: true, content: [{ type: 'text', text: err.message }] };\n }\n // Unexpected. Report the message, not the stack: the caller is a model on the\n // other end of a pipe, and a stack trace is noise. The full error is on the\n // child process's stderr, which is where a human debugging this will look.\n console.error('[onboarder-mcp] tool failed:', err);\n return {\n isError: true,\n content: [{ type: 'text', text: `The tool failed unexpectedly: ${err?.message || String(err)}` }],\n };\n }\n}\n\n// The dispatcher is exported because the Streamable HTTP transport in `./http.js`\n// needs to answer the same messages without going through a line of stdio. The\n// protocol logic lives in exactly one place; a second transport must not become a\n// second implementation of `tools/list`.\nexport { handle };\n\nasync function handle(msg) {\n // A request must name a method and must declare the protocol version. Anything\n // else is not a request at all, and replying to it would invent an id to answer\n // under — so this returns nothing and the line is dropped.\n //\n // `null` rather than `undefined` is the contract with the loop below: both mean\n // \"say nothing\", but only one of them survives being awaited and compared.\n if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0' || typeof msg.method !== 'string') {\n return null;\n }\n const { method, id, params } = msg;\n\n // A notification has no id and expects no answer. Returning `null` is how the\n // loop knows to stay silent, which matters: replying to a notification is\n // itself a protocol violation.\n const isNotification = id === undefined || id === null;\n\n switch (method) {\n case 'initialize':\n return isNotification ? undefined : result(id, {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: { tools: { listChanged: false } },\n serverInfo: SERVER_INFO,\n });\n\n // The client's post-handshake ping. Answering it is what tells the client the\n // handshake completed and the tool loop is live.\n case 'notifications/initialized':\n return null;\n\n case 'ping':\n return isNotification ? undefined : result(id, {});\n\n case 'tools/list':\n return isNotification ? undefined : result(id, { tools: toolDefinitions() });\n\n case 'tools/call':\n if (isNotification) return null;\n if (!params?.name || typeof params.name !== 'string') {\n return failure(id, INVALID_PARAMS, 'tools/call needs a tool name.');\n }\n return result(id, await callOne(params.name, params.arguments));\n\n // A client asking for resources or prompts is not an error — this server has\n // none, and saying so is more useful than METHOD_NOT_FOUND. A harness that\n // probes both at startup gets a clean answer instead of a warning.\n case 'resources/list':\n return result(id, { resources: [] });\n\n case 'prompts/list':\n return result(id, { prompts: [] });\n\n case 'resources/templates/list':\n return result(id, { resourceTemplates: [] });\n\n default:\n if (isNotification) return null;\n return failure(id, METHOD_NOT_FOUND, `No method called \"${method}\".`);\n }\n}\n\n// One line in, one line out. A message that will not parse gets a parse error\n// with `id: null`, which is what the spec requires — the client cannot know which\n// request the bad line belonged to.\nfunction parseLine(line) {\n try {\n const msg = JSON.parse(line);\n if (!msg || typeof msg !== 'object' || Array.isArray(msg)) {\n return { bad: failure(null, INVALID_REQUEST, 'A message must be a JSON-RPC object.') };\n }\n return { msg };\n } catch (err) {\n return { bad: failure(null, PARSE_ERROR, `Could not parse the message: ${err.message}`) };\n }\n}\n\n/**\n * Run the server until stdin closes. `input` and `output` are parameters so tests\n * can drive a whole session through in-memory streams.\n */\nexport function serveMcp({ input = process.stdin, output = process.stdout, dispatch = handle }: Record<string, any> = {}) {\n let buffer = '';\n // Messages are handled strictly in order, but not awaited inline: a long scan\n // must not block the event loop from reading the next line, or a client that\n // pipelines would sit with a full buffer. The promise chain is what preserves\n // the ordering while letting reads continue.\n let chain = Promise.resolve();\n let closed = false;\n\n const send = (message) => {\n if (closed) return;\n output.write(JSON.stringify(message) + '\\n');\n };\n\n const enqueue = (line) => {\n chain = chain.then(async () => {\n const { msg, bad } = parseLine(line);\n if (bad) { send(bad); return; }\n const response = await dispatch(msg);\n if (response) send(response);\n }).catch((err) => {\n // `handle` already turns tool failures into results. Reaching here means\n // something threw outside that, so the only honest move is to log it and\n // keep serving — a dead server helps nobody recover.\n console.error('[onboarder-mcp] dispatch failed:', err);\n });\n };\n\n input.setEncoding?.('utf8');\n input.on('data', (chunk) => {\n buffer += chunk;\n let nl;\n // A partial line at the end of a chunk stays in the buffer for the next one.\n while ((nl = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, nl).trim();\n buffer = buffer.slice(nl + 1);\n if (line) enqueue(line);\n }\n });\n\n input.on('end', () => {\n // A last line without a trailing newline is still a message. Dropping it\n // would lose the final request of a session — which is often the only one.\n const line = buffer.trim();\n buffer = '';\n if (line) enqueue(line);\n // Let the queue finish before the process exits, so a reply written just\n // after stdin closed still reaches the client.\n chain.finally(() => {\n if (output === process.stdout) process.exitCode = 0;\n });\n });\n\n input.on('error', (err) => {\n console.error('[onboarder-mcp] stdin error:', err);\n });\n\n return {\n stop() { closed = true; },\n // Awaiting the queue is how a test knows every reply has been written, and\n // how a shutdown can drain in-flight work instead of cutting it off.\n settled() { return chain; },\n };\n}\n"]}
|
package/server/router.js
CHANGED
|
@@ -15,6 +15,7 @@ import { handleCleanup, handleScan } from './apiScan.js';
|
|
|
15
15
|
import { sendError, sendJSON, readBody } from './http.js';
|
|
16
16
|
import { crossOriginReason, rebindingReason } from './httpGuards.js';
|
|
17
17
|
import { proxyChat } from './llmProxy.js';
|
|
18
|
+
import { hermesWeb } from './hermesChat.js';
|
|
18
19
|
import { serveStatic } from './static.js';
|
|
19
20
|
import { handleSearch } from './apiSearch.js';
|
|
20
21
|
import { handleBlame } from './apiGitBlame.js';
|
|
@@ -57,11 +58,14 @@ const ROUTES = [
|
|
|
57
58
|
run: ({ res, url }) => handleFile(res, url.searchParams.get('scan'), url.searchParams.get('path')),
|
|
58
59
|
},
|
|
59
60
|
{
|
|
60
|
-
//
|
|
61
|
-
//
|
|
61
|
+
// Hermes owns its server-side login. API mode forwards the browser's key
|
|
62
|
+
// to its chosen endpoint. Both share the same streaming response contract.
|
|
62
63
|
method: 'POST', path: '/api/explain', body: true,
|
|
63
|
-
run: ({ res, body }) =>
|
|
64
|
+
run: ({ res, body, config }) => body?.connection === 'hermes'
|
|
65
|
+
? (config.hermes || hermesWeb).chat(res, body) : proxyChat(res, body),
|
|
64
66
|
},
|
|
67
|
+
{ method: 'GET', path: '/api/hermes', sameOrigin: true,
|
|
68
|
+
run: ({ res, config }) => (config.hermes || hermesWeb).status(res) },
|
|
65
69
|
{
|
|
66
70
|
method: 'POST', path: '/api/doc', body: true,
|
|
67
71
|
run: ({ res, body }) => handleDocs(res, body),
|
package/server/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/server/router.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { Settings } from '../shared/contracts.js';\n\ninterface RouteContext { req: IncomingMessage; res: ServerResponse; url: URL; body: any; rest: string; config: any; settings: Settings }\ninterface Route { method: string; path?: string; prefix?: string; body?: boolean; sameOrigin?: boolean; run(context: RouteContext): unknown }\n// The route table and the gates in front of it.\n//\n// Everything arrives here: one function decides whether a request is allowed to\n// be answered at all, then which handler answers it. The routes are a list rather\n// than a ladder of `if` statements so that the whole surface of the server is\n// visible in one screen, and everything else is a static file.\n//\n// There are three concerns. Local mode relies on `Host`, `Origin`, and\n// `Sec-Fetch-Site` to stop another page and DNS rebinding from driving it.\n// Self-hosted mode adds real access-key authentication: local browser requests\n// stay open, remote browsers get a signed session, and API clients use Bearer.\n\nimport { handleDocs } from './apiDocs.js';\nimport { handleFile } from './apiFile.js';\nimport { handleCleanup, handleScan } from './apiScan.js';\nimport { sendError, sendJSON, readBody } from './http.js';\nimport { crossOriginReason, rebindingReason } from './httpGuards.js';\nimport { proxyChat } from './llmProxy.js';\nimport { serveStatic } from './static.js';\nimport { handleSearch } from './apiSearch.js';\nimport { handleBlame } from './apiGitBlame.js';\nimport { handleDiff, handleDiffRefs } from './apiDiff.js';\nimport { handleReview } from './apiReview.js';\nimport { handleToolsInstall, handleToolsRun, handleToolsStatus } from './apiTools.js';\nimport { handleMcpStart, handleMcpStatus, handleMcpStop, handleMcpCommand } from './apiMcp.js';\nimport { handleGetSettings, handleRotateAccessKey, handleUpdateSettings } from './apiSettings.js';\nimport { handleAuthStatus, handleLogin, handleLogout } from './apiAuth.js';\nimport { accessKeysMatch, allowedHosts, authReason, bearerToken, DEFAULT_SETTINGS } from './config.js';\nimport { hasValidSession } from './auth.js';\n\nconst ROUTES: Route[] = [\n { method: 'POST', path: '/api/review', body: true, run: ({ res, body }) => handleReview(res, body) },\n {\n method: 'GET', path: '/api/diff/refs', sameOrigin: true,\n run: ({ res, url }) => handleDiffRefs(res, url.searchParams.get('scan')),\n },\n {\n method: 'GET', path: '/api/diff', sameOrigin: true,\n run: ({ res, url }) => handleDiff(res, url.searchParams.get('scan'), url.searchParams),\n },\n {\n method: 'POST', path: '/api/search', body: true,\n run: ({ res, body }) => handleSearch(res, body),\n },\n {\n method: 'GET', path: '/api/blame', sameOrigin: true,\n run: ({ res, url }) => handleBlame(res, url.searchParams.get('scan'), url.searchParams.get('path')),\n },\n {\n method: 'POST', path: '/api/scan', body: true,\n run: ({ res, body, config }) => handleScan(res, body, config),\n },\n {\n method: 'DELETE', prefix: '/api/scan/',\n run: ({ res, rest }) => handleCleanup(res, decodeURIComponent(rest)),\n },\n {\n method: 'GET', path: '/api/file', sameOrigin: true,\n run: ({ res, url }) => handleFile(res, url.searchParams.get('scan'), url.searchParams.get('path')),\n },\n {\n // The proxy is the handler; there is no wrapper to write. It forwards the\n // browser's key to the endpoint the browser chose and never keeps either.\n method: 'POST', path: '/api/explain', body: true,\n run: ({ res, body }) => proxyChat(res, body),\n },\n {\n method: 'POST', path: '/api/doc', body: true,\n run: ({ res, body }) => handleDocs(res, body),\n },\n {\n // Reports which analyzers are installed — environment information a foreign\n // page has no business reading, so it is guarded like /api/file.\n method: 'GET', path: '/api/tools', sameOrigin: true,\n run: ({ res }) => handleToolsStatus(res),\n },\n {\n // Runs real analyzers over a scanned root. `body: true` applies the size\n // limit; the session check in the handler is the capability gate.\n method: 'POST', path: '/api/tools/run', body: true,\n run: ({ res, body }) => handleToolsRun(res, body),\n },\n {\n // Installs a missing analyzer from the GUI. Same-origin like every POST;\n // the tool name is validated against the registry and the install plans\n // are fixed data, so the request body never reaches a command line.\n method: 'POST', path: '/api/tools/install', body: true,\n run: ({ res, body }) => handleToolsInstall(res, body),\n },\n {\n // The MCP button's state, and the command an agent harness is configured with.\n // The config is not secret, but it is a path into this machine and a foreign\n // page has no business reading either one.\n method: 'GET', path: '/api/mcp', sameOrigin: true,\n run: ({ req, res, config }) => handleMcpStatus(req, res, config),\n },\n {\n method: 'GET', path: '/api/mcp/command', sameOrigin: true,\n run: ({ req, res, config }) => handleMcpCommand(req, res, config),\n },\n {\n // Starting and stopping a process is a side effect on the person's machine, so\n // these take a body like every other POST and are checked like every other POST.\n method: 'POST', path: '/api/mcp/start', body: true,\n run: ({ req, res, config }) => handleMcpStart(req, res, config),\n },\n {\n method: 'POST', path: '/api/mcp/stop', body: true,\n run: ({ req, res, config }) => handleMcpStop(req, res, config),\n },\n {\n method: 'GET', path: '/api/health',\n run: ({ res }) => sendJSON(res, 200, { ok: true }),\n },\n {\n method: 'GET', path: '/api/auth/status', sameOrigin: true,\n run: ({ req, res, settings }) => handleAuthStatus(req, res, settings),\n },\n {\n method: 'POST', path: '/api/auth/login', body: true, sameOrigin: true,\n run: ({ req, res, body, settings }) => handleLogin(req, res, body, settings),\n },\n {\n method: 'POST', path: '/api/auth/logout', body: true, sameOrigin: true,\n run: ({ req, res }) => handleLogout(req, res),\n },\n {\n // The settings drawer and the CLI read the same public shape: everything\n // about the configuration except the access key itself.\n method: 'GET', path: '/api/settings', sameOrigin: true,\n run: ({ req, res, config }) => handleGetSettings(req, res, config),\n },\n {\n // PATCH semantics over a small allow-listed key set; the validation that\n // rejects a bad port or a lawless mode lives in `server/config.js` and is\n // shared with the CLI wizard, so both surfaces enforce one schema.\n method: 'PUT', path: '/api/settings', body: true,\n run: ({ res, body, config }) => handleUpdateSettings(res, body, config),\n },\n {\n // The only way the key changes: generated on the server, shown once in\n // this response, never readable again. In self-hosted mode this endpoint\n // is itself behind the current key, so rotation requires possession.\n method: 'POST', path: '/api/settings/access-key', body: true,\n run: ({ req, res, config }) => handleRotateAccessKey(req, res, config),\n },\n];\n\n// The table itself, for the test that walks it. Exported read-only: the routes\n// are decided here, not assembled by whoever imports this.\nexport const routes = Object.freeze(ROUTES.map((r) => Object.freeze({ ...r })));\n\nexport function matchRoute(method, pathname) {\n for (const route of ROUTES) {\n if (route.method !== method) continue;\n if (route.path === pathname) return { route, rest: '' };\n if (route.prefix && pathname.startsWith(route.prefix)) {\n return { route, rest: pathname.slice(route.prefix.length) };\n }\n }\n return null;\n}\n\n// `config` carries the directories the handlers need — the project root to scan\n// for the demo, and the two roots static serving maps into. Passed in rather\n// than resolved here so a test can point the server somewhere else, and so this\n// module has nothing to say about where it was installed.\n//\n// Settings arrive the same way: `config.getSettings` is read on every request,\n// so a key rotated or a domain changed through the API takes effect on the next\n// request without a restart. A bare `createServer()` (the tests) has no getter\n// and sees the local-mode defaults — never somebody's home directory.\nasync function liveSettings(config) {\n if (typeof config?.getSettings !== 'function') return DEFAULT_SETTINGS;\n try {\n return await config.getSettings();\n } catch {\n return null; // unreadable config: fail closed, every /api call gets a 500\n }\n}\n\nexport function createRouter(config) {\n return async function handleRequest(req, res) {\n try {\n const url = new URL(req.url, 'http://127.0.0.1');\n\n const settings = await liveSettings(config);\n if (!settings) {\n return sendError(res, 500, 'The settings file could not be read — run `onboarder doctor` to find out why.');\n }\n\n const wrongHost = rebindingReason(req, allowedHosts(settings));\n if (wrongHost) {\n const scope = settings.mode === 'self-hosted'\n ? 'Onboarder only answers to its configured host and domain. '\n : 'Onboarder only answers to localhost. ';\n return sendError(res, 403, scope + wrongHost);\n }\n\n const found = matchRoute(req.method, url.pathname);\n const publicAuthRoute = ['/api/health', '/api/auth/status', '/api/auth/login', '/api/auth/logout'].includes(url.pathname);\n if (req.method !== 'GET' || found?.route.sameOrigin) {\n const foreign = crossOriginReason(req);\n if (foreign) {\n return sendError(res, 403, 'That request did not come from Onboarder’s own page. ' + foreign);\n }\n }\n\n // Bearer clients keep their existing API contract. A browser gets a signed,\n // HttpOnly session from the login form instead of storing the raw key.\n const bearerClient = accessKeysMatch(settings.accessKey, bearerToken(req));\n const browserAuthenticated = hasValidSession(req, settings);\n const authDenied = authReason(req, settings);\n const locallyExempt = !authDenied;\n const authenticated = bearerClient || browserAuthenticated || locallyExempt;\n const remoteSelfHosted = settings.mode === 'self-hosted' && Boolean(authDenied);\n\n if (remoteSelfHosted && !authenticated && !publicAuthRoute) {\n // API callers keep a machine-readable 401. A browser navigation gets the\n // themed sign-in document so the user never has to paste JSON into a tab.\n const accepts = String(req.headers?.accept || '');\n if (req.method === 'GET' && (url.pathname === '/' || accepts.includes('text/html'))) {\n res.statusCode = 200;\n return await serveStatic(res, '/login.html', config);\n }\n return sendError(res, 401, authDenied || 'Sign in with the Onboarder access key first.');\n }\n\n if (url.pathname === '/api/auth/logout') {\n // Always clear the browser cookie, even if it had already expired.\n return handleLogout(req, res);\n }\n\n if (found) {\n const body = found.route.body ? await readBody(req) : null;\n return await found.route.run({ req, res, url, body, rest: found.rest, config, settings });\n }\n\n if (req.method === 'GET') return await serveStatic(res, url.pathname, config);\n\n sendError(res, 404, 'Nothing lives at ' + url.pathname + '.');\n } catch (err) {\n // The last resort. Handlers turn their own expected failures into a status\n // that says something useful; anything reaching here is a bug or a broken\n // request body, and the message is more use to the person than \"500\".\n sendError(res, 500, err.message || 'Something went sideways.');\n }\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/server/router.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { Settings } from '../shared/contracts.js';\n\ninterface RouteContext { req: IncomingMessage; res: ServerResponse; url: URL; body: any; rest: string; config: any; settings: Settings }\ninterface Route { method: string; path?: string; prefix?: string; body?: boolean; sameOrigin?: boolean; run(context: RouteContext): unknown }\n// The route table and the gates in front of it.\n//\n// Everything arrives here: one function decides whether a request is allowed to\n// be answered at all, then which handler answers it. The routes are a list rather\n// than a ladder of `if` statements so that the whole surface of the server is\n// visible in one screen, and everything else is a static file.\n//\n// There are three concerns. Local mode relies on `Host`, `Origin`, and\n// `Sec-Fetch-Site` to stop another page and DNS rebinding from driving it.\n// Self-hosted mode adds real access-key authentication: local browser requests\n// stay open, remote browsers get a signed session, and API clients use Bearer.\n\nimport { handleDocs } from './apiDocs.js';\nimport { handleFile } from './apiFile.js';\nimport { handleCleanup, handleScan } from './apiScan.js';\nimport { sendError, sendJSON, readBody } from './http.js';\nimport { crossOriginReason, rebindingReason } from './httpGuards.js';\nimport { proxyChat } from './llmProxy.js';\nimport { hermesWeb } from './hermesChat.js';\nimport { serveStatic } from './static.js';\nimport { handleSearch } from './apiSearch.js';\nimport { handleBlame } from './apiGitBlame.js';\nimport { handleDiff, handleDiffRefs } from './apiDiff.js';\nimport { handleReview } from './apiReview.js';\nimport { handleToolsInstall, handleToolsRun, handleToolsStatus } from './apiTools.js';\nimport { handleMcpStart, handleMcpStatus, handleMcpStop, handleMcpCommand } from './apiMcp.js';\nimport { handleGetSettings, handleRotateAccessKey, handleUpdateSettings } from './apiSettings.js';\nimport { handleAuthStatus, handleLogin, handleLogout } from './apiAuth.js';\nimport { accessKeysMatch, allowedHosts, authReason, bearerToken, DEFAULT_SETTINGS } from './config.js';\nimport { hasValidSession } from './auth.js';\n\nconst ROUTES: Route[] = [\n { method: 'POST', path: '/api/review', body: true, run: ({ res, body }) => handleReview(res, body) },\n {\n method: 'GET', path: '/api/diff/refs', sameOrigin: true,\n run: ({ res, url }) => handleDiffRefs(res, url.searchParams.get('scan')),\n },\n {\n method: 'GET', path: '/api/diff', sameOrigin: true,\n run: ({ res, url }) => handleDiff(res, url.searchParams.get('scan'), url.searchParams),\n },\n {\n method: 'POST', path: '/api/search', body: true,\n run: ({ res, body }) => handleSearch(res, body),\n },\n {\n method: 'GET', path: '/api/blame', sameOrigin: true,\n run: ({ res, url }) => handleBlame(res, url.searchParams.get('scan'), url.searchParams.get('path')),\n },\n {\n method: 'POST', path: '/api/scan', body: true,\n run: ({ res, body, config }) => handleScan(res, body, config),\n },\n {\n method: 'DELETE', prefix: '/api/scan/',\n run: ({ res, rest }) => handleCleanup(res, decodeURIComponent(rest)),\n },\n {\n method: 'GET', path: '/api/file', sameOrigin: true,\n run: ({ res, url }) => handleFile(res, url.searchParams.get('scan'), url.searchParams.get('path')),\n },\n {\n // Hermes owns its server-side login. API mode forwards the browser's key\n // to its chosen endpoint. Both share the same streaming response contract.\n method: 'POST', path: '/api/explain', body: true,\n run: ({ res, body, config }) => body?.connection === 'hermes'\n ? (config.hermes || hermesWeb).chat(res, body) : proxyChat(res, body),\n },\n { method: 'GET', path: '/api/hermes', sameOrigin: true,\n run: ({ res, config }) => (config.hermes || hermesWeb).status(res) },\n {\n method: 'POST', path: '/api/doc', body: true,\n run: ({ res, body }) => handleDocs(res, body),\n },\n {\n // Reports which analyzers are installed — environment information a foreign\n // page has no business reading, so it is guarded like /api/file.\n method: 'GET', path: '/api/tools', sameOrigin: true,\n run: ({ res }) => handleToolsStatus(res),\n },\n {\n // Runs real analyzers over a scanned root. `body: true` applies the size\n // limit; the session check in the handler is the capability gate.\n method: 'POST', path: '/api/tools/run', body: true,\n run: ({ res, body }) => handleToolsRun(res, body),\n },\n {\n // Installs a missing analyzer from the GUI. Same-origin like every POST;\n // the tool name is validated against the registry and the install plans\n // are fixed data, so the request body never reaches a command line.\n method: 'POST', path: '/api/tools/install', body: true,\n run: ({ res, body }) => handleToolsInstall(res, body),\n },\n {\n // The MCP button's state, and the command an agent harness is configured with.\n // The config is not secret, but it is a path into this machine and a foreign\n // page has no business reading either one.\n method: 'GET', path: '/api/mcp', sameOrigin: true,\n run: ({ req, res, config }) => handleMcpStatus(req, res, config),\n },\n {\n method: 'GET', path: '/api/mcp/command', sameOrigin: true,\n run: ({ req, res, config }) => handleMcpCommand(req, res, config),\n },\n {\n // Starting and stopping a process is a side effect on the person's machine, so\n // these take a body like every other POST and are checked like every other POST.\n method: 'POST', path: '/api/mcp/start', body: true,\n run: ({ req, res, config }) => handleMcpStart(req, res, config),\n },\n {\n method: 'POST', path: '/api/mcp/stop', body: true,\n run: ({ req, res, config }) => handleMcpStop(req, res, config),\n },\n {\n method: 'GET', path: '/api/health',\n run: ({ res }) => sendJSON(res, 200, { ok: true }),\n },\n {\n method: 'GET', path: '/api/auth/status', sameOrigin: true,\n run: ({ req, res, settings }) => handleAuthStatus(req, res, settings),\n },\n {\n method: 'POST', path: '/api/auth/login', body: true, sameOrigin: true,\n run: ({ req, res, body, settings }) => handleLogin(req, res, body, settings),\n },\n {\n method: 'POST', path: '/api/auth/logout', body: true, sameOrigin: true,\n run: ({ req, res }) => handleLogout(req, res),\n },\n {\n // The settings drawer and the CLI read the same public shape: everything\n // about the configuration except the access key itself.\n method: 'GET', path: '/api/settings', sameOrigin: true,\n run: ({ req, res, config }) => handleGetSettings(req, res, config),\n },\n {\n // PATCH semantics over a small allow-listed key set; the validation that\n // rejects a bad port or a lawless mode lives in `server/config.js` and is\n // shared with the CLI wizard, so both surfaces enforce one schema.\n method: 'PUT', path: '/api/settings', body: true,\n run: ({ res, body, config }) => handleUpdateSettings(res, body, config),\n },\n {\n // The only way the key changes: generated on the server, shown once in\n // this response, never readable again. In self-hosted mode this endpoint\n // is itself behind the current key, so rotation requires possession.\n method: 'POST', path: '/api/settings/access-key', body: true,\n run: ({ req, res, config }) => handleRotateAccessKey(req, res, config),\n },\n];\n\n// The table itself, for the test that walks it. Exported read-only: the routes\n// are decided here, not assembled by whoever imports this.\nexport const routes = Object.freeze(ROUTES.map((r) => Object.freeze({ ...r })));\n\nexport function matchRoute(method, pathname) {\n for (const route of ROUTES) {\n if (route.method !== method) continue;\n if (route.path === pathname) return { route, rest: '' };\n if (route.prefix && pathname.startsWith(route.prefix)) {\n return { route, rest: pathname.slice(route.prefix.length) };\n }\n }\n return null;\n}\n\n// `config` carries the directories the handlers need — the project root to scan\n// for the demo, and the two roots static serving maps into. Passed in rather\n// than resolved here so a test can point the server somewhere else, and so this\n// module has nothing to say about where it was installed.\n//\n// Settings arrive the same way: `config.getSettings` is read on every request,\n// so a key rotated or a domain changed through the API takes effect on the next\n// request without a restart. A bare `createServer()` (the tests) has no getter\n// and sees the local-mode defaults — never somebody's home directory.\nasync function liveSettings(config) {\n if (typeof config?.getSettings !== 'function') return DEFAULT_SETTINGS;\n try {\n return await config.getSettings();\n } catch {\n return null; // unreadable config: fail closed, every /api call gets a 500\n }\n}\n\nexport function createRouter(config) {\n return async function handleRequest(req, res) {\n try {\n const url = new URL(req.url, 'http://127.0.0.1');\n\n const settings = await liveSettings(config);\n if (!settings) {\n return sendError(res, 500, 'The settings file could not be read — run `onboarder doctor` to find out why.');\n }\n\n const wrongHost = rebindingReason(req, allowedHosts(settings));\n if (wrongHost) {\n const scope = settings.mode === 'self-hosted'\n ? 'Onboarder only answers to its configured host and domain. '\n : 'Onboarder only answers to localhost. ';\n return sendError(res, 403, scope + wrongHost);\n }\n\n const found = matchRoute(req.method, url.pathname);\n const publicAuthRoute = ['/api/health', '/api/auth/status', '/api/auth/login', '/api/auth/logout'].includes(url.pathname);\n if (req.method !== 'GET' || found?.route.sameOrigin) {\n const foreign = crossOriginReason(req);\n if (foreign) {\n return sendError(res, 403, 'That request did not come from Onboarder’s own page. ' + foreign);\n }\n }\n\n // Bearer clients keep their existing API contract. A browser gets a signed,\n // HttpOnly session from the login form instead of storing the raw key.\n const bearerClient = accessKeysMatch(settings.accessKey, bearerToken(req));\n const browserAuthenticated = hasValidSession(req, settings);\n const authDenied = authReason(req, settings);\n const locallyExempt = !authDenied;\n const authenticated = bearerClient || browserAuthenticated || locallyExempt;\n const remoteSelfHosted = settings.mode === 'self-hosted' && Boolean(authDenied);\n\n if (remoteSelfHosted && !authenticated && !publicAuthRoute) {\n // API callers keep a machine-readable 401. A browser navigation gets the\n // themed sign-in document so the user never has to paste JSON into a tab.\n const accepts = String(req.headers?.accept || '');\n if (req.method === 'GET' && (url.pathname === '/' || accepts.includes('text/html'))) {\n res.statusCode = 200;\n return await serveStatic(res, '/login.html', config);\n }\n return sendError(res, 401, authDenied || 'Sign in with the Onboarder access key first.');\n }\n\n if (url.pathname === '/api/auth/logout') {\n // Always clear the browser cookie, even if it had already expired.\n return handleLogout(req, res);\n }\n\n if (found) {\n const body = found.route.body ? await readBody(req) : null;\n return await found.route.run({ req, res, url, body, rest: found.rest, config, settings });\n }\n\n if (req.method === 'GET') return await serveStatic(res, url.pathname, config);\n\n sendError(res, 404, 'Nothing lives at ' + url.pathname + '.');\n } catch (err) {\n // The last resort. Handlers turn their own expected failures into a status\n // that says something useful; anything reaching here is a bug or a broken\n // request body, and the message is more use to the person than \"500\".\n sendError(res, 500, err.message || 'Something went sideways.');\n }\n };\n}\n"]}
|
|
@@ -40,21 +40,24 @@ export function matchNodeText(text, files) {
|
|
|
40
40
|
const t = String(text || '').trim();
|
|
41
41
|
if (!t)
|
|
42
42
|
return null;
|
|
43
|
-
const exact = files.find((f) => t === f.path || t.endsWith('/' + f.path)
|
|
43
|
+
const exact = files.find((f) => t === f.path || t.endsWith('/' + f.path));
|
|
44
44
|
if (exact)
|
|
45
45
|
return exact.path;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const dup = files.filter((x) => x.name === f.name);
|
|
53
|
-
if (dup.length > 1)
|
|
54
|
-
return null;
|
|
46
|
+
const containsLabel = (label) => {
|
|
47
|
+
let at = t.indexOf(label);
|
|
48
|
+
while (at !== -1) {
|
|
49
|
+
if (!/[\w./-]/.test(t[at - 1] || '') && !/[\w./-]/.test(t[at + label.length] || ''))
|
|
50
|
+
return true;
|
|
51
|
+
at = t.indexOf(label, at + 1);
|
|
55
52
|
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
53
|
+
return false;
|
|
54
|
+
};
|
|
55
|
+
// Models often annotate the full path with a role or dependency count.
|
|
56
|
+
// Prefer that path before considering potentially duplicated basenames.
|
|
57
|
+
const paths = files.filter(f => containsLabel(f.path));
|
|
58
|
+
if (paths.length)
|
|
59
|
+
return paths.length === 1 ? paths[0].path : null;
|
|
60
|
+
const names = files.filter(f => containsLabel(f.name));
|
|
61
|
+
return names.length === 1 ? names[0].path : null;
|
|
59
62
|
}
|
|
60
63
|
//# sourceMappingURL=aiMermaid.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiMermaid.js","sourceRoot":"","sources":["../../src/shared/diagram/aiMermaid.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,4EAA4E;AAC5E,uDAAuD;AAEvD,yEAAyE;AACzE,2EAA2E;AAC3E,mCAAmC;AACnC,MAAM,UAAU,gBAAgB,CAAC,IAAI;IACnC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEnD,sDAAsD;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IAEzD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC;IAC1D,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,sEAAsE;IACtE,qEAAqE;IACrE,MAAM,YAAY,GAAG,kFAAkF,CAAC;IACxG,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,KAAK,CAAC,GAAG,EAAE,CAAC;YACZ,SAAS;QACX,CAAC;QACD,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM;QACnC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,iCAAiC;IAChD,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACtF,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,4EAA4E;AAC5E,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,IAAI,EAAE,KAAK;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"aiMermaid.js","sourceRoot":"","sources":["../../src/shared/diagram/aiMermaid.ts"],"names":[],"mappings":"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","sourcesContent":["// Turns an LLM's raw answer into trustworthy Mermaid source.\n// Models wrap, apologize, and explain — this strips all of that back to the\n// diagram itself plus the one-line caption we ask for.\n\n// Pulls `{ caption, body }` out of `text`; returns null when there is no\n// flowchart to be found. `body` starts at the `flowchart`/`graph` line and\n// has trailing prose trimmed away.\nexport function extractAIDiagram(text) {\n if (!text || typeof text !== 'string') return null;\n\n // Fences gone first — ```mermaid and plain ``` alike.\n const unfenced = text.replace(/```(?:mermaid)?/gi, '\\n');\n\n const capMatch = unfenced.match(/^%%\\s*caption:\\s*(.+)$/m);\n const caption = capMatch ? capMatch[1].trim() : '';\n\n const start = unfenced.search(/^\\s*(flowchart|graph)\\b/m);\n if (start === -1) return null;\n\n const lines = unfenced.slice(start).split('\\n');\n // Trim trailing prose: walk back past anything that doesn't look like\n // Mermaid. Labels, edges, subgraph blocks and classDefs all survive.\n const MERMAID_HINT = /-->|---|\\[|\\]|\\(|\\)|\\bsubgraph\\b|\\bend\\b|classDef|^class\\b|%%|:::|^\\s*[\\w$]+\\s*:/;\n while (lines.length) {\n const tail = lines[lines.length - 1].trim();\n if (!tail) {\n lines.pop();\n continue;\n }\n if (MERMAID_HINT.test(tail)) break;\n lines.pop(); // a sentence, not a diagram line\n }\n\n const body = lines.join('\\n').trim();\n const meaningful = lines.filter((l) => l.trim() && !l.trim().startsWith('%%')).length;\n if (meaningful < 2) return null;\n\n return { caption, body };\n}\n\n// AI nodes carry no stable ids, so clicks are matched by their text: a full\n// path hit first, then a unique-basename hit. Imperfect and honest about it.\nexport function matchNodeText(text, files) {\n const t = String(text || '').trim();\n if (!t) return null;\n const exact = files.find((f) => t === f.path || t.endsWith('/' + f.path));\n if (exact) return exact.path;\n const containsLabel = (label: string) => {\n let at = t.indexOf(label);\n while (at !== -1) {\n if (!/[\\w./-]/.test(t[at - 1] || '') && !/[\\w./-]/.test(t[at + label.length] || '')) return true;\n at = t.indexOf(label, at + 1);\n }\n return false;\n };\n // Models often annotate the full path with a role or dependency count.\n // Prefer that path before considering potentially duplicated basenames.\n const paths = files.filter(f => containsLabel(f.path));\n if (paths.length) return paths.length === 1 ? paths[0].path : null;\n const names = files.filter(f => containsLabel(f.name));\n return names.length === 1 ? names[0].path : null;\n}\n"]}
|