chain-insights 0.3.9 → 0.3.11

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.
Files changed (32) hide show
  1. package/dist/{app-BxojXjtB.cjs → app-DjJn3irw.cjs} +1 -1
  2. package/dist/{app-CRd39JJ8.mjs → app-norpwdou.mjs} +2 -2
  3. package/dist/{app-CRd39JJ8.mjs.map → app-norpwdou.mjs.map} +1 -1
  4. package/dist/{artifact-server-XbN16DwU.cjs → artifact-server-C6_gtIql.cjs} +1 -1
  5. package/dist/{artifact-server-CP6LXQ9d.mjs → artifact-server-DHPM0lxS.mjs} +2 -2
  6. package/dist/{artifact-server-CP6LXQ9d.mjs.map → artifact-server-DHPM0lxS.mjs.map} +1 -1
  7. package/dist/cli.cjs +18 -18
  8. package/dist/cli.mjs +18 -18
  9. package/dist/{config-Drgc2HuF.mjs → config-C6zM8Xir.mjs} +3 -3
  10. package/dist/{config-Drgc2HuF.mjs.map → config-C6zM8Xir.mjs.map} +1 -1
  11. package/dist/{config-BwVx19Og.cjs → config-CkW404Cs.cjs} +2 -2
  12. package/dist/index.cjs +3 -3
  13. package/dist/index.mjs +3 -3
  14. package/dist/{init-CKQ6F07J.mjs → init-uAmPfio2.mjs} +2 -2
  15. package/dist/{init-CKQ6F07J.mjs.map → init-uAmPfio2.mjs.map} +1 -1
  16. package/dist/{init-Dhw8F23z.cjs → init-vj2v5PMP.cjs} +1 -1
  17. package/dist/{mcp-endpoint-DHs1cRFH.mjs → mcp-endpoint-QQ5Lbqc2.mjs} +5 -2
  18. package/dist/mcp-endpoint-QQ5Lbqc2.mjs.map +1 -0
  19. package/dist/{mcp-endpoint-BaV8h_lq.cjs → mcp-endpoint-cQIZSjkK.cjs} +4 -1
  20. package/dist/mcp-proxy.cjs +3 -3
  21. package/dist/mcp-proxy.mjs +3 -3
  22. package/dist/{runner-CVnjpqc-.mjs → runner-B9fXAP0t.mjs} +2 -2
  23. package/dist/{runner-CVnjpqc-.mjs.map → runner-B9fXAP0t.mjs.map} +1 -1
  24. package/dist/{runner-bLy0pTr_.cjs → runner-CcZCrrkn.cjs} +1 -1
  25. package/dist/{schema-BFEWhzg7.mjs → schema-D_qwaQA5.mjs} +2 -2
  26. package/dist/{schema-BFEWhzg7.mjs.map → schema-D_qwaQA5.mjs.map} +1 -1
  27. package/dist/{schema-Vl9yuOFO.cjs → schema-Dr6JXSOF.cjs} +1 -1
  28. package/dist/{server-BXLX2j_A.mjs → server-86dyCsJO.mjs} +2 -2
  29. package/dist/{server-BXLX2j_A.mjs.map → server-86dyCsJO.mjs.map} +1 -1
  30. package/dist/{server-BqVdWath.cjs → server-B2NFmnCM.cjs} +1 -1
  31. package/package.json +1 -1
  32. package/dist/mcp-endpoint-DHs1cRFH.mjs.map +0 -1
@@ -100,7 +100,7 @@ function createApp() {
100
100
  ts: Date.now()
101
101
  }));
102
102
  app.get("/status", async (c) => {
103
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
103
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
104
104
  const config = await loadConfig();
105
105
  return c.json({
106
106
  dataDir: config.dataDir,
@@ -97,7 +97,7 @@ function createApp() {
97
97
  ts: Date.now()
98
98
  }));
99
99
  app.get("/status", async (c) => {
100
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
100
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
101
101
  const config = await loadConfig();
102
102
  return c.json({
103
103
  dataDir: config.dataDir,
@@ -152,4 +152,4 @@ function createApp() {
152
152
  //#endregion
153
153
  export { createApp as t };
154
154
 
155
- //# sourceMappingURL=app-CRd39JJ8.mjs.map
155
+ //# sourceMappingURL=app-norpwdou.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-CRd39JJ8.mjs","names":[],"sources":["../src/server/app.ts"],"sourcesContent":["import { Hono } from 'hono'\nimport { lstat, readFile, readdir, realpath } from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\n\nconst WORKSPACE_TREE_ROOTS = ['cases', 'reports', '.chain-insights/schema']\nconst WORKSPACE_TREE_MAX_DEPTH = 4\n\ninterface WorkspaceTreeEntry {\n path: string\n type: 'file' | 'directory' | 'symlink'\n size?: number\n}\n\nfunction withinRoot(root: string, target: string): boolean {\n const relative = path.relative(path.resolve(root), path.resolve(target))\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))\n}\n\nasync function realPathWithinRoot(root: string, target: string): Promise<boolean> {\n try {\n const [realRoot, realTarget] = await Promise.all([realpath(root), realpath(target)])\n return withinRoot(realRoot, realTarget)\n } catch {\n return false\n }\n}\n\nfunction toWorkspaceRelative(root: string, target: string): string {\n return path.relative(root, target).split(path.sep).join('/')\n}\n\nasync function listWorkspaceEntries(\n workspaceRoot: string,\n roots = WORKSPACE_TREE_ROOTS,\n maxDepth = WORKSPACE_TREE_MAX_DEPTH\n): Promise<WorkspaceTreeEntry[]> {\n const entries: WorkspaceTreeEntry[] = []\n const root = path.resolve(workspaceRoot)\n\n async function visit(target: string, depth: number): Promise<void> {\n const resolved = path.resolve(target)\n if (!withinRoot(root, resolved)) return\n\n let info: Awaited<ReturnType<typeof lstat>>\n try {\n info = await lstat(resolved)\n } catch {\n return\n }\n\n const type = info.isSymbolicLink() ? 'symlink' : info.isDirectory() ? 'directory' : info.isFile() ? 'file' : null\n if (!type) return\n\n const entry: WorkspaceTreeEntry = {\n path: toWorkspaceRelative(root, resolved),\n type,\n }\n if (type === 'file') entry.size = info.size\n entries.push(entry)\n\n if (type !== 'directory' || depth >= maxDepth) return\n if (!await realPathWithinRoot(root, resolved)) return\n\n let children: string[]\n try {\n children = await readdir(resolved)\n } catch {\n return\n }\n\n for (const child of children.sort()) {\n await visit(path.join(resolved, child), depth + 1)\n }\n }\n\n for (const rootName of roots) {\n const target = path.resolve(root, rootName)\n if (withinRoot(root, target)) await visit(target, 0)\n }\n\n return entries\n}\n\nasync function findVizHtml(vizId: string): Promise<string | null> {\n const home = os.homedir()\n const filename = `${vizId}.html`\n\n // 1. Check central standalone directory first (fast, single path)\n const centralPath = path.join(home, '.chain-insights', 'viz', filename)\n try {\n return await readFile(centralPath, 'utf-8')\n } catch { /* not found here, continue */ }\n\n // 2. Check per-case directory using vizId prefix (case-based vizs use <caseId>_<timestamp>)\n // The vizId for case-based vizs is formatted as <case-id>_<timestamp>,\n // so extract the case-id prefix to check its directory first.\n const underscoreIdx = vizId.lastIndexOf('_')\n if (underscoreIdx > 0) {\n const possibleCaseId = vizId.substring(0, underscoreIdx)\n const casePath = path.join(home, '.chain-insights', 'cases', possibleCaseId, 'viz', filename)\n try {\n return await readFile(casePath, 'utf-8')\n } catch { /* not found here, continue */ }\n }\n\n // 3. Fallback: scan all case directories (CONTEXT.md: ~/.chain-insights/cases/<case-id>/viz/)\n const casesDir = path.join(home, '.chain-insights', 'cases')\n try {\n const cases = await readdir(casesDir)\n for (const caseId of cases) {\n const casePath = path.join(casesDir, caseId, 'viz', filename)\n try {\n return await readFile(casePath, 'utf-8')\n } catch { /* not in this case dir */ }\n }\n } catch { /* cases dir doesn't exist */ }\n\n return null\n}\n\nfunction isSafeGraphReportFilename(filename: string): boolean {\n return (\n filename.endsWith('.graph.json') &&\n /^[A-Za-z0-9._-]+$/.test(filename) &&\n !filename.includes('..') &&\n !filename.includes('/') &&\n !filename.includes('\\\\')\n )\n}\n\nexport function createApp(): Hono {\n const app = new Hono()\n\n app.get('/health', (c) => c.json({ ok: true, ts: Date.now() }))\n\n app.get('/status', async (c) => {\n const { loadConfig } = await import('../config/index.js')\n const config = await loadConfig()\n return c.json({\n dataDir: config.dataDir,\n graphMcpMode: config.graphMcpMode,\n server: 'running',\n })\n })\n\n app.get('/viz/:id', async (c) => {\n const id = c.req.param('id')\n if (!/^[a-zA-Z0-9_-]+$/.test(id)) {\n return c.json({ error: 'Invalid visualization ID' }, 400)\n }\n const html = await findVizHtml(id)\n if (!html) {\n return c.json({ error: 'Visualization not found' }, 404)\n }\n return c.html(html)\n })\n\n app.get('/graph-reports/:filename', async (c) => {\n const filename = c.req.param('filename')\n if (!isSafeGraphReportFilename(filename)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const graphPath = path.resolve(paths.reportGraphsRoot, filename)\n if (!withinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n if (!await realPathWithinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n\n try {\n const graph = await readFile(graphPath, 'utf-8')\n return c.body(graph, 200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n } catch {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n })\n\n app.get('/graph-reports/*', (c) => {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n })\n\n app.get('/workspace/tree', async (c) => {\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS)\n return c.json({\n schema: 'chain-insights.workspace-tree.v1',\n root: paths.root,\n entries,\n })\n })\n\n app.onError((err, c) => {\n console.error(err)\n return c.json({ error: 'Internal server error' }, 500)\n })\n\n return app\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-norpwdou.mjs","names":[],"sources":["../src/server/app.ts"],"sourcesContent":["import { Hono } from 'hono'\nimport { lstat, readFile, readdir, realpath } from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\n\nconst WORKSPACE_TREE_ROOTS = ['cases', 'reports', '.chain-insights/schema']\nconst WORKSPACE_TREE_MAX_DEPTH = 4\n\ninterface WorkspaceTreeEntry {\n path: string\n type: 'file' | 'directory' | 'symlink'\n size?: number\n}\n\nfunction withinRoot(root: string, target: string): boolean {\n const relative = path.relative(path.resolve(root), path.resolve(target))\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))\n}\n\nasync function realPathWithinRoot(root: string, target: string): Promise<boolean> {\n try {\n const [realRoot, realTarget] = await Promise.all([realpath(root), realpath(target)])\n return withinRoot(realRoot, realTarget)\n } catch {\n return false\n }\n}\n\nfunction toWorkspaceRelative(root: string, target: string): string {\n return path.relative(root, target).split(path.sep).join('/')\n}\n\nasync function listWorkspaceEntries(\n workspaceRoot: string,\n roots = WORKSPACE_TREE_ROOTS,\n maxDepth = WORKSPACE_TREE_MAX_DEPTH\n): Promise<WorkspaceTreeEntry[]> {\n const entries: WorkspaceTreeEntry[] = []\n const root = path.resolve(workspaceRoot)\n\n async function visit(target: string, depth: number): Promise<void> {\n const resolved = path.resolve(target)\n if (!withinRoot(root, resolved)) return\n\n let info: Awaited<ReturnType<typeof lstat>>\n try {\n info = await lstat(resolved)\n } catch {\n return\n }\n\n const type = info.isSymbolicLink() ? 'symlink' : info.isDirectory() ? 'directory' : info.isFile() ? 'file' : null\n if (!type) return\n\n const entry: WorkspaceTreeEntry = {\n path: toWorkspaceRelative(root, resolved),\n type,\n }\n if (type === 'file') entry.size = info.size\n entries.push(entry)\n\n if (type !== 'directory' || depth >= maxDepth) return\n if (!await realPathWithinRoot(root, resolved)) return\n\n let children: string[]\n try {\n children = await readdir(resolved)\n } catch {\n return\n }\n\n for (const child of children.sort()) {\n await visit(path.join(resolved, child), depth + 1)\n }\n }\n\n for (const rootName of roots) {\n const target = path.resolve(root, rootName)\n if (withinRoot(root, target)) await visit(target, 0)\n }\n\n return entries\n}\n\nasync function findVizHtml(vizId: string): Promise<string | null> {\n const home = os.homedir()\n const filename = `${vizId}.html`\n\n // 1. Check central standalone directory first (fast, single path)\n const centralPath = path.join(home, '.chain-insights', 'viz', filename)\n try {\n return await readFile(centralPath, 'utf-8')\n } catch { /* not found here, continue */ }\n\n // 2. Check per-case directory using vizId prefix (case-based vizs use <caseId>_<timestamp>)\n // The vizId for case-based vizs is formatted as <case-id>_<timestamp>,\n // so extract the case-id prefix to check its directory first.\n const underscoreIdx = vizId.lastIndexOf('_')\n if (underscoreIdx > 0) {\n const possibleCaseId = vizId.substring(0, underscoreIdx)\n const casePath = path.join(home, '.chain-insights', 'cases', possibleCaseId, 'viz', filename)\n try {\n return await readFile(casePath, 'utf-8')\n } catch { /* not found here, continue */ }\n }\n\n // 3. Fallback: scan all case directories (CONTEXT.md: ~/.chain-insights/cases/<case-id>/viz/)\n const casesDir = path.join(home, '.chain-insights', 'cases')\n try {\n const cases = await readdir(casesDir)\n for (const caseId of cases) {\n const casePath = path.join(casesDir, caseId, 'viz', filename)\n try {\n return await readFile(casePath, 'utf-8')\n } catch { /* not in this case dir */ }\n }\n } catch { /* cases dir doesn't exist */ }\n\n return null\n}\n\nfunction isSafeGraphReportFilename(filename: string): boolean {\n return (\n filename.endsWith('.graph.json') &&\n /^[A-Za-z0-9._-]+$/.test(filename) &&\n !filename.includes('..') &&\n !filename.includes('/') &&\n !filename.includes('\\\\')\n )\n}\n\nexport function createApp(): Hono {\n const app = new Hono()\n\n app.get('/health', (c) => c.json({ ok: true, ts: Date.now() }))\n\n app.get('/status', async (c) => {\n const { loadConfig } = await import('../config/index.js')\n const config = await loadConfig()\n return c.json({\n dataDir: config.dataDir,\n graphMcpMode: config.graphMcpMode,\n server: 'running',\n })\n })\n\n app.get('/viz/:id', async (c) => {\n const id = c.req.param('id')\n if (!/^[a-zA-Z0-9_-]+$/.test(id)) {\n return c.json({ error: 'Invalid visualization ID' }, 400)\n }\n const html = await findVizHtml(id)\n if (!html) {\n return c.json({ error: 'Visualization not found' }, 404)\n }\n return c.html(html)\n })\n\n app.get('/graph-reports/:filename', async (c) => {\n const filename = c.req.param('filename')\n if (!isSafeGraphReportFilename(filename)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const graphPath = path.resolve(paths.reportGraphsRoot, filename)\n if (!withinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n if (!await realPathWithinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n\n try {\n const graph = await readFile(graphPath, 'utf-8')\n return c.body(graph, 200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n } catch {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n })\n\n app.get('/graph-reports/*', (c) => {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n })\n\n app.get('/workspace/tree', async (c) => {\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS)\n return c.json({\n schema: 'chain-insights.workspace-tree.v1',\n root: paths.root,\n entries,\n })\n })\n\n app.onError((err, c) => {\n console.error(err)\n return c.json({ error: 'Internal server error' }, 500)\n })\n\n return app\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const require_app = require("./app-BxojXjtB.cjs");
1
+ const require_app = require("./app-DjJn3irw.cjs");
2
2
  let _hono_node_server = require("@hono/node-server");
3
3
  let node_timers_promises = require("node:timers/promises");
4
4
  //#region src/mcp/artifact-server.ts
@@ -1,4 +1,4 @@
1
- import { t as createApp } from "./app-CRd39JJ8.mjs";
1
+ import { t as createApp } from "./app-norpwdou.mjs";
2
2
  import { serve } from "@hono/node-server";
3
3
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
4
4
  //#region src/mcp/artifact-server.ts
@@ -45,4 +45,4 @@ async function ensureArtifactServer(port) {
45
45
  //#endregion
46
46
  export { ensureArtifactServer };
47
47
 
48
- //# sourceMappingURL=artifact-server-CP6LXQ9d.mjs.map
48
+ //# sourceMappingURL=artifact-server-DHPM0lxS.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-server-CP6LXQ9d.mjs","names":["delay"],"sources":["../src/mcp/artifact-server.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { createApp } from '../server/app.js'\n\ntype ArtifactServer = ReturnType<typeof serve>\n\nconst servers = new Map<number, ArtifactServer>()\n\nasync function isHealthy(port: number): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 500)\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n })\n return response.ok\n } catch {\n return false\n } finally {\n clearTimeout(timeout)\n }\n}\n\nasync function waitUntilHealthy(port: number): Promise<void> {\n for (let attempt = 0; attempt < 20; attempt += 1) {\n if (await isHealthy(port)) return\n await delay(50)\n }\n throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`)\n}\n\nexport async function ensureArtifactServer(port: number): Promise<void> {\n if (servers.has(port)) return\n if (await isHealthy(port)) return\n\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1',\n port,\n })\n servers.set(port, server)\n server.on('error', (err) => {\n servers.delete(port)\n process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${(err as Error).message}\\n`)\n })\n\n try {\n await waitUntilHealthy(port)\n } catch (err) {\n servers.delete(port)\n server.close()\n throw err\n }\n}\n\nexport function closeArtifactServers(): void {\n for (const [port, server] of servers.entries()) {\n server.close()\n servers.delete(port)\n }\n}\n"],"mappings":";;;;AAMA,MAAM,0BAAU,IAAI,IAA4B;AAEhD,eAAe,UAAU,MAAgC;CACvD,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,GAAG;CACxD,IAAI;EAIF,QAAO,MAHgB,MAAM,oBAAoB,KAAK,UAAU,EAC9D,QAAQ,WAAW,OACrB,CAAC,GACe;CAClB,QAAQ;EACN,OAAO;CACT,UAAU;EACR,aAAa,OAAO;CACtB;AACF;AAEA,eAAe,iBAAiB,MAA6B;CAC3D,KAAK,IAAI,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;EAChD,IAAI,MAAM,UAAU,IAAI,GAAG;EAC3B,MAAMA,aAAM,EAAE;CAChB;CACA,MAAM,IAAI,MAAM,2DAA2D,MAAM;AACnF;AAEA,eAAsB,qBAAqB,MAA6B;CACtE,IAAI,QAAQ,IAAI,IAAI,GAAG;CACvB,IAAI,MAAM,UAAU,IAAI,GAAG;CAG3B,MAAM,SAAS,MAAM;EACnB,OAFU,UAED,EAAE;EACX,UAAU;EACV;CACF,CAAC;CACD,QAAQ,IAAI,MAAM,MAAM;CACxB,OAAO,GAAG,UAAU,QAAQ;EAC1B,QAAQ,OAAO,IAAI;EACnB,QAAQ,OAAO,MAAM,0DAA0D,KAAK,IAAK,IAAc,QAAQ,GAAG;CACpH,CAAC;CAED,IAAI;EACF,MAAM,iBAAiB,IAAI;CAC7B,SAAS,KAAK;EACZ,QAAQ,OAAO,IAAI;EACnB,OAAO,MAAM;EACb,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"artifact-server-DHPM0lxS.mjs","names":["delay"],"sources":["../src/mcp/artifact-server.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { createApp } from '../server/app.js'\n\ntype ArtifactServer = ReturnType<typeof serve>\n\nconst servers = new Map<number, ArtifactServer>()\n\nasync function isHealthy(port: number): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 500)\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n })\n return response.ok\n } catch {\n return false\n } finally {\n clearTimeout(timeout)\n }\n}\n\nasync function waitUntilHealthy(port: number): Promise<void> {\n for (let attempt = 0; attempt < 20; attempt += 1) {\n if (await isHealthy(port)) return\n await delay(50)\n }\n throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`)\n}\n\nexport async function ensureArtifactServer(port: number): Promise<void> {\n if (servers.has(port)) return\n if (await isHealthy(port)) return\n\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1',\n port,\n })\n servers.set(port, server)\n server.on('error', (err) => {\n servers.delete(port)\n process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${(err as Error).message}\\n`)\n })\n\n try {\n await waitUntilHealthy(port)\n } catch (err) {\n servers.delete(port)\n server.close()\n throw err\n }\n}\n\nexport function closeArtifactServers(): void {\n for (const [port, server] of servers.entries()) {\n server.close()\n servers.delete(port)\n }\n}\n"],"mappings":";;;;AAMA,MAAM,0BAAU,IAAI,IAA4B;AAEhD,eAAe,UAAU,MAAgC;CACvD,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,GAAG;CACxD,IAAI;EAIF,QAAO,MAHgB,MAAM,oBAAoB,KAAK,UAAU,EAC9D,QAAQ,WAAW,OACrB,CAAC,GACe;CAClB,QAAQ;EACN,OAAO;CACT,UAAU;EACR,aAAa,OAAO;CACtB;AACF;AAEA,eAAe,iBAAiB,MAA6B;CAC3D,KAAK,IAAI,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;EAChD,IAAI,MAAM,UAAU,IAAI,GAAG;EAC3B,MAAMA,aAAM,EAAE;CAChB;CACA,MAAM,IAAI,MAAM,2DAA2D,MAAM;AACnF;AAEA,eAAsB,qBAAqB,MAA6B;CACtE,IAAI,QAAQ,IAAI,IAAI,GAAG;CACvB,IAAI,MAAM,UAAU,IAAI,GAAG;CAG3B,MAAM,SAAS,MAAM;EACnB,OAFU,UAED,EAAE;EACX,UAAU;EACV;CACF,CAAC;CACD,QAAQ,IAAI,MAAM,MAAM;CACxB,OAAO,GAAG,UAAU,QAAQ;EAC1B,QAAQ,OAAO,IAAI;EACnB,QAAQ,OAAO,MAAM,0DAA0D,KAAK,IAAK,IAAc,QAAQ,GAAG;CACpH,CAAC;CAED,IAAI;EACF,MAAM,iBAAiB,IAAI;CAC7B,SAAS,KAAK;EACZ,QAAQ,OAAO,IAAI;EACnB,OAAO,MAAM;EACb,MAAM;CACR;AACF"}
package/dist/cli.cjs CHANGED
@@ -70,7 +70,7 @@ function optionalNumberArg(value, name) {
70
70
  throw new Error(`Invalid number for ${name}: ${String(value)}`);
71
71
  }
72
72
  async function withGraphMcpClient(name, fn) {
73
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
73
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
74
74
  const config = await loadConfig();
75
75
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Y_zqKqJT.cjs")).then((n) => n.client_exports);
76
76
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
@@ -91,7 +91,7 @@ function printMcpTextContent(result) {
91
91
  for (const item of result.content ?? []) if (item.type === "text") console.log(item.text);
92
92
  }
93
93
  async function printNetworkCapabilities(opts) {
94
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
94
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
95
95
  const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-DOa6EFO-.cjs"));
96
96
  const document = await fetchNetworkCapabilities(await loadConfig());
97
97
  if (opts.json) console.log(JSON.stringify(document, null, 2));
@@ -109,7 +109,7 @@ program.command("serve").description("Start local visualization server").option(
109
109
  try {
110
110
  const { requireWorkspaceRoot } = await Promise.resolve().then(() => require("./output-root-YIbl6PwF.cjs")).then((n) => n.output_root_exports);
111
111
  const workspaceRoot = requireWorkspaceRoot();
112
- const { startServer } = await Promise.resolve().then(() => require("./server-BqVdWath.cjs")).then((n) => n.server_exports);
112
+ const { startServer } = await Promise.resolve().then(() => require("./server-B2NFmnCM.cjs")).then((n) => n.server_exports);
113
113
  console.log(`Workspace: ${workspaceRoot}`);
114
114
  startServer(parseInt(opts.port, 10));
115
115
  } catch (err) {
@@ -118,7 +118,7 @@ program.command("serve").description("Start local visualization server").option(
118
118
  }
119
119
  });
120
120
  program.command("status").description("Show toolkit status and configuration").action(async () => {
121
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
121
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
122
122
  const { findActiveWorkspace, activeDataDir } = await Promise.resolve().then(() => require("./active-BVr55kvW.cjs")).then((n) => n.active_exports);
123
123
  const config = await loadConfig();
124
124
  const workspace = findActiveWorkspace();
@@ -175,7 +175,7 @@ program.command("obsidian").description("Manage the local Obsidian investigation
175
175
  }));
176
176
  program.command("debug").description("Configure Graph MCP debug mode").addCommand(new commander.Command("on").description("Enable Graph MCP debug mode without x402 payments").requiredOption("--token <token>", "Debug bearer token").option("--endpoint <url>", "Graph MCP endpoint").action(async (opts) => {
177
177
  try {
178
- const { saveConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
178
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
179
179
  await saveConfig({
180
180
  graphMcpMode: "debug",
181
181
  graphMcpAuthToken: opts.token,
@@ -190,7 +190,7 @@ program.command("debug").description("Configure Graph MCP debug mode").addComman
190
190
  }
191
191
  })).addCommand(new commander.Command("off").description("Disable Graph MCP debug mode and use paid x402 calls").action(async () => {
192
192
  try {
193
- const { saveConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
193
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
194
194
  await saveConfig({
195
195
  graphMcpMode: "paid",
196
196
  graphMcpAuthToken: ""
@@ -203,7 +203,7 @@ program.command("debug").description("Configure Graph MCP debug mode").addComman
203
203
  }
204
204
  })).addCommand(new commander.Command("status").description("Show Graph MCP payment/debug mode").action(async () => {
205
205
  try {
206
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
206
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
207
207
  const config = await loadConfig();
208
208
  console.log(`Graph MCP mode: ${config.graphMcpMode}`);
209
209
  console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
@@ -218,7 +218,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
218
218
  try {
219
219
  const normalizedKey = key.trim();
220
220
  if (!normalizedKey) throw new Error("Test access key is required");
221
- const { saveConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
221
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
222
222
  await saveConfig({
223
223
  graphMcpMode: "debug",
224
224
  graphMcpAuthToken: normalizedKey,
@@ -233,7 +233,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
233
233
  }
234
234
  })).addCommand(new commander.Command("clear").description("Remove the Graph MCP test access key and use paid x402 calls").action(async () => {
235
235
  try {
236
- const { saveConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
236
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
237
237
  await saveConfig({
238
238
  graphMcpMode: "paid",
239
239
  graphMcpAuthToken: ""
@@ -246,7 +246,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
246
246
  }
247
247
  })).addCommand(new commander.Command("status").description("Show Graph MCP test access key status").action(async () => {
248
248
  try {
249
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
249
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
250
250
  const config = await loadConfig();
251
251
  console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
252
252
  console.log(`Access key: ${config.graphMcpAuthToken?.trim() ? "configured" : "not configured"}`);
@@ -258,7 +258,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
258
258
  }));
259
259
  program.command("init").description("Initialize an investigation workspace").argument("[dir]", "Workspace directory to initialize", ".").option("--force", "Overwrite existing workspace files").action(async (dir, opts) => {
260
260
  try {
261
- const { initWorkspace } = await Promise.resolve().then(() => require("./init-Dhw8F23z.cjs"));
261
+ const { initWorkspace } = await Promise.resolve().then(() => require("./init-vj2v5PMP.cjs"));
262
262
  const result = await initWorkspace({
263
263
  targetDir: dir,
264
264
  force: opts.force
@@ -295,8 +295,8 @@ program.command("setup").description("Configure external MCP clients").addComman
295
295
  }
296
296
  }));
297
297
  program.command("config").description("Read or write configuration values").addCommand(new commander.Command("get").argument("<key>", "Config key to read").action(async (key) => {
298
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
299
- const { CONFIG_KEYS } = await Promise.resolve().then(() => require("./schema-Vl9yuOFO.cjs")).then((n) => n.schema_exports);
298
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
299
+ const { CONFIG_KEYS } = await Promise.resolve().then(() => require("./schema-Dr6JXSOF.cjs")).then((n) => n.schema_exports);
300
300
  if (!CONFIG_KEYS.includes(key)) {
301
301
  console.error(`Unknown config key: ${key}`);
302
302
  process.exit(1);
@@ -316,8 +316,8 @@ program.command("config").description("Read or write configuration values").addC
316
316
  }
317
317
  return;
318
318
  }
319
- const { loadConfig, saveConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
320
- const { CONFIG_KEYS, DEFAULT_CONFIG } = await Promise.resolve().then(() => require("./schema-Vl9yuOFO.cjs")).then((n) => n.schema_exports);
319
+ const { loadConfig, saveConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
320
+ const { CONFIG_KEYS, DEFAULT_CONFIG } = await Promise.resolve().then(() => require("./schema-Dr6JXSOF.cjs")).then((n) => n.schema_exports);
321
321
  const current = await loadConfig();
322
322
  if (!CONFIG_KEYS.includes(key)) {
323
323
  console.error(`Unknown config key: ${key}`);
@@ -409,7 +409,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
409
409
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-CJk1EL3L.cjs"));
410
410
  const { formatToolsTable } = await Promise.resolve().then(() => require("./format-9NLBykEL.cjs"));
411
411
  const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-Buq7YdUZ.cjs")).then((n) => n.tool_visibility_exports);
412
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
412
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
413
413
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Y_zqKqJT.cjs")).then((n) => n.client_exports);
414
414
  const config = await loadConfig();
415
415
  const graphMcpEndpoint = resolveGraphMcpEndpoint(config);
@@ -877,7 +877,7 @@ program.command("playbook").description("Run and manage investigation playbooks"
877
877
  console.error(`Invalid --from value: "${opts.from}". Must be a positive integer.`);
878
878
  process.exit(1);
879
879
  }
880
- const { PlaybookRunner } = await Promise.resolve().then(() => require("./runner-bLy0pTr_.cjs"));
880
+ const { PlaybookRunner } = await Promise.resolve().then(() => require("./runner-CcZCrrkn.cjs"));
881
881
  await PlaybookRunner.run(definition, {
882
882
  caseId: opts.case,
883
883
  from: fromN,
@@ -932,7 +932,7 @@ program.command("viz").description("Generate money flow visualization").argument
932
932
  caseId,
933
933
  dataFile: opts.data
934
934
  });
935
- const { startServer } = await Promise.resolve().then(() => require("./server-BqVdWath.cjs")).then((n) => n.server_exports);
935
+ const { startServer } = await Promise.resolve().then(() => require("./server-B2NFmnCM.cjs")).then((n) => n.server_exports);
936
936
  const port = parseInt(opts.port, 10);
937
937
  startServer(port);
938
938
  const url = `http://127.0.0.1:${port}/viz/${result.vizId}`;
package/dist/cli.mjs CHANGED
@@ -68,7 +68,7 @@ function optionalNumberArg(value, name) {
68
68
  throw new Error(`Invalid number for ${name}: ${String(value)}`);
69
69
  }
70
70
  async function withGraphMcpClient(name, fn) {
71
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
71
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
72
72
  const config = await loadConfig();
73
73
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-BgmHjBHQ.mjs").then((n) => n.r);
74
74
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
@@ -89,7 +89,7 @@ function printMcpTextContent(result) {
89
89
  for (const item of result.content ?? []) if (item.type === "text") console.log(item.text);
90
90
  }
91
91
  async function printNetworkCapabilities(opts) {
92
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
92
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
93
93
  const { fetchNetworkCapabilities, formatNetworkCapabilities } = await import("./capabilities-BCvkTkIu.mjs");
94
94
  const document = await fetchNetworkCapabilities(await loadConfig());
95
95
  if (opts.json) console.log(JSON.stringify(document, null, 2));
@@ -107,7 +107,7 @@ program.command("serve").description("Start local visualization server").option(
107
107
  try {
108
108
  const { requireWorkspaceRoot } = await import("./output-root-BRhzhhXZ.mjs").then((n) => n.t);
109
109
  const workspaceRoot = requireWorkspaceRoot();
110
- const { startServer } = await import("./server-BXLX2j_A.mjs").then((n) => n.t);
110
+ const { startServer } = await import("./server-86dyCsJO.mjs").then((n) => n.t);
111
111
  console.log(`Workspace: ${workspaceRoot}`);
112
112
  startServer(parseInt(opts.port, 10));
113
113
  } catch (err) {
@@ -116,7 +116,7 @@ program.command("serve").description("Start local visualization server").option(
116
116
  }
117
117
  });
118
118
  program.command("status").description("Show toolkit status and configuration").action(async () => {
119
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
119
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
120
120
  const { findActiveWorkspace, activeDataDir } = await import("./active-ByNgjuAg.mjs").then((n) => n.n);
121
121
  const config = await loadConfig();
122
122
  const workspace = findActiveWorkspace();
@@ -173,7 +173,7 @@ program.command("obsidian").description("Manage the local Obsidian investigation
173
173
  }));
174
174
  program.command("debug").description("Configure Graph MCP debug mode").addCommand(new Command("on").description("Enable Graph MCP debug mode without x402 payments").requiredOption("--token <token>", "Debug bearer token").option("--endpoint <url>", "Graph MCP endpoint").action(async (opts) => {
175
175
  try {
176
- const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
176
+ const { saveConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
177
177
  await saveConfig({
178
178
  graphMcpMode: "debug",
179
179
  graphMcpAuthToken: opts.token,
@@ -188,7 +188,7 @@ program.command("debug").description("Configure Graph MCP debug mode").addComman
188
188
  }
189
189
  })).addCommand(new Command("off").description("Disable Graph MCP debug mode and use paid x402 calls").action(async () => {
190
190
  try {
191
- const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
191
+ const { saveConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
192
192
  await saveConfig({
193
193
  graphMcpMode: "paid",
194
194
  graphMcpAuthToken: ""
@@ -201,7 +201,7 @@ program.command("debug").description("Configure Graph MCP debug mode").addComman
201
201
  }
202
202
  })).addCommand(new Command("status").description("Show Graph MCP payment/debug mode").action(async () => {
203
203
  try {
204
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
204
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
205
205
  const config = await loadConfig();
206
206
  console.log(`Graph MCP mode: ${config.graphMcpMode}`);
207
207
  console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
@@ -216,7 +216,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
216
216
  try {
217
217
  const normalizedKey = key.trim();
218
218
  if (!normalizedKey) throw new Error("Test access key is required");
219
- const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
219
+ const { saveConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
220
220
  await saveConfig({
221
221
  graphMcpMode: "debug",
222
222
  graphMcpAuthToken: normalizedKey,
@@ -231,7 +231,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
231
231
  }
232
232
  })).addCommand(new Command("clear").description("Remove the Graph MCP test access key and use paid x402 calls").action(async () => {
233
233
  try {
234
- const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
234
+ const { saveConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
235
235
  await saveConfig({
236
236
  graphMcpMode: "paid",
237
237
  graphMcpAuthToken: ""
@@ -244,7 +244,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
244
244
  }
245
245
  })).addCommand(new Command("status").description("Show Graph MCP test access key status").action(async () => {
246
246
  try {
247
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
247
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
248
248
  const config = await loadConfig();
249
249
  console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
250
250
  console.log(`Access key: ${config.graphMcpAuthToken?.trim() ? "configured" : "not configured"}`);
@@ -256,7 +256,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
256
256
  }));
257
257
  program.command("init").description("Initialize an investigation workspace").argument("[dir]", "Workspace directory to initialize", ".").option("--force", "Overwrite existing workspace files").action(async (dir, opts) => {
258
258
  try {
259
- const { initWorkspace } = await import("./init-CKQ6F07J.mjs");
259
+ const { initWorkspace } = await import("./init-uAmPfio2.mjs");
260
260
  const result = await initWorkspace({
261
261
  targetDir: dir,
262
262
  force: opts.force
@@ -293,8 +293,8 @@ program.command("setup").description("Configure external MCP clients").addComman
293
293
  }
294
294
  }));
295
295
  program.command("config").description("Read or write configuration values").addCommand(new Command("get").argument("<key>", "Config key to read").action(async (key) => {
296
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
297
- const { CONFIG_KEYS } = await import("./schema-BFEWhzg7.mjs").then((n) => n.r);
296
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
297
+ const { CONFIG_KEYS } = await import("./schema-D_qwaQA5.mjs").then((n) => n.r);
298
298
  if (!CONFIG_KEYS.includes(key)) {
299
299
  console.error(`Unknown config key: ${key}`);
300
300
  process.exit(1);
@@ -314,8 +314,8 @@ program.command("config").description("Read or write configuration values").addC
314
314
  }
315
315
  return;
316
316
  }
317
- const { loadConfig, saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
318
- const { CONFIG_KEYS, DEFAULT_CONFIG } = await import("./schema-BFEWhzg7.mjs").then((n) => n.r);
317
+ const { loadConfig, saveConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
318
+ const { CONFIG_KEYS, DEFAULT_CONFIG } = await import("./schema-D_qwaQA5.mjs").then((n) => n.r);
319
319
  const current = await loadConfig();
320
320
  if (!CONFIG_KEYS.includes(key)) {
321
321
  console.error(`Unknown config key: ${key}`);
@@ -407,7 +407,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
407
407
  const { loadSchema, saveSchema } = await import("./schema-cache-DwDvPy4e.mjs");
408
408
  const { formatToolsTable } = await import("./format-Bq94jSyw.mjs");
409
409
  const { visibleRemoteTools } = await import("./tool-visibility-BpyZHRBi.mjs").then((n) => n.n);
410
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
410
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
411
411
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-BgmHjBHQ.mjs").then((n) => n.r);
412
412
  const config = await loadConfig();
413
413
  const graphMcpEndpoint = resolveGraphMcpEndpoint(config);
@@ -875,7 +875,7 @@ program.command("playbook").description("Run and manage investigation playbooks"
875
875
  console.error(`Invalid --from value: "${opts.from}". Must be a positive integer.`);
876
876
  process.exit(1);
877
877
  }
878
- const { PlaybookRunner } = await import("./runner-CVnjpqc-.mjs");
878
+ const { PlaybookRunner } = await import("./runner-B9fXAP0t.mjs");
879
879
  await PlaybookRunner.run(definition, {
880
880
  caseId: opts.case,
881
881
  from: fromN,
@@ -930,7 +930,7 @@ program.command("viz").description("Generate money flow visualization").argument
930
930
  caseId,
931
931
  dataFile: opts.data
932
932
  });
933
- const { startServer } = await import("./server-BXLX2j_A.mjs").then((n) => n.t);
933
+ const { startServer } = await import("./server-86dyCsJO.mjs").then((n) => n.t);
934
934
  const port = parseInt(opts.port, 10);
935
935
  startServer(port);
936
936
  const url = `http://127.0.0.1:${port}/viz/${result.vizId}`;
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { r as graphMcpEndpointEnvOverride } from "./mcp-endpoint-DHs1cRFH.mjs";
3
- import { n as parseInvestigatorConfig, t as DEFAULT_CONFIG } from "./schema-BFEWhzg7.mjs";
2
+ import { r as graphMcpEndpointEnvOverride } from "./mcp-endpoint-QQ5Lbqc2.mjs";
3
+ import { n as parseInvestigatorConfig, t as DEFAULT_CONFIG } from "./schema-D_qwaQA5.mjs";
4
4
  import path from "node:path";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
6
  import os from "node:os";
@@ -74,4 +74,4 @@ async function resetConfigCache() {
74
74
  //#endregion
75
75
  export { saveConfig as i, loadConfig as n, resetConfigCache as r, config_exports as t };
76
76
 
77
- //# sourceMappingURL=config-Drgc2HuF.mjs.map
77
+ //# sourceMappingURL=config-C6zM8Xir.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-Drgc2HuF.mjs","names":[],"sources":["../src/config/index.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { DEFAULT_CONFIG, parseInvestigatorConfig, type InvestigatorConfig } from './schema.js'\nimport { graphMcpEndpointEnvOverride } from './mcp-endpoint.js'\n\n// Config path derived from HOME at call time so tests can override HOME.\nfunction configPath(): string {\n return path.join(os.homedir(), '.chain-insights', 'config.json')\n}\n\nlet _cached: InvestigatorConfig | null = null\n\nfunction applyRuntimeEnvOverrides(config: InvestigatorConfig): InvestigatorConfig {\n let graphMcpEndpoint: string | undefined\n try {\n graphMcpEndpoint = graphMcpEndpointEnvOverride()\n } catch (err) {\n throw new Error(`Invalid configuration in environment: ${(err as Error).message}`)\n }\n return graphMcpEndpoint\n ? parseInvestigatorConfig({ ...config, graphMcpEndpoint })\n : config\n}\n\nasync function loadStoredConfig(): Promise<InvestigatorConfig> {\n const cfgPath = configPath()\n let raw: string\n try {\n raw = await readFile(cfgPath, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return DEFAULT_CONFIG\n }\n throw new Error(`Unable to read config ${cfgPath}: ${(err as Error).message}`)\n }\n\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(raw) as unknown\n } catch (err) {\n throw new Error(`Invalid JSON in ${cfgPath}: ${(err as Error).message}`)\n }\n\n try {\n return parseInvestigatorConfig(parsedJson)\n } catch (err) {\n throw new Error(`Invalid configuration in ${cfgPath}: ${(err as Error).message}`)\n }\n}\n\nexport async function loadConfig(): Promise<InvestigatorConfig> {\n if (_cached) return _cached\n _cached = applyRuntimeEnvOverrides(await loadStoredConfig())\n return _cached\n}\n\nexport async function saveConfig(updates: Partial<InvestigatorConfig>): Promise<void> {\n const current = await loadStoredConfig()\n let next: InvestigatorConfig\n try {\n next = parseInvestigatorConfig({ ...current, ...updates })\n } catch (err) {\n throw new Error(`Invalid configuration update: ${(err as Error).message}`)\n }\n const p = configPath()\n await mkdir(path.dirname(p), { recursive: true })\n await writeFile(p, JSON.stringify(next, null, 2) + '\\n', { mode: 0o600 })\n _cached = applyRuntimeEnvOverrides(next)\n}\n\nexport async function resetConfigCache(): Promise<void> {\n _cached = null\n}\n"],"mappings":";;;;;;;;;;;;AAOA,SAAS,aAAqB;CAC5B,OAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,mBAAmB,aAAa;AACjE;AAEA,IAAI,UAAqC;AAEzC,SAAS,yBAAyB,QAAgD;CAChF,IAAI;CACJ,IAAI;EACF,mBAAmB,4BAA4B;CACjD,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,yCAA0C,IAAc,SAAS;CACnF;CACA,OAAO,mBACH,wBAAwB;EAAE,GAAG;EAAQ;CAAiB,CAAC,IACvD;AACN;AAEA,eAAe,mBAAgD;CAC7D,MAAM,UAAU,WAAW;CAC3B,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,SAAS,SAAS,MAAM;CACtC,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAC1C,OAAO;EAET,MAAM,IAAI,MAAM,yBAAyB,QAAQ,IAAK,IAAc,SAAS;CAC/E;CAEA,IAAI;CACJ,IAAI;EACF,aAAa,KAAK,MAAM,GAAG;CAC7B,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,mBAAmB,QAAQ,IAAK,IAAc,SAAS;CACzE;CAEA,IAAI;EACF,OAAO,wBAAwB,UAAU;CAC3C,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,4BAA4B,QAAQ,IAAK,IAAc,SAAS;CAClF;AACF;AAEA,eAAsB,aAA0C;CAC9D,IAAI,SAAS,OAAO;CACpB,UAAU,yBAAyB,MAAM,iBAAiB,CAAC;CAC3D,OAAO;AACT;AAEA,eAAsB,WAAW,SAAqD;CACpF,MAAM,UAAU,MAAM,iBAAiB;CACvC,IAAI;CACJ,IAAI;EACF,OAAO,wBAAwB;GAAE,GAAG;GAAS,GAAG;EAAQ,CAAC;CAC3D,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,iCAAkC,IAAc,SAAS;CAC3E;CACA,MAAM,IAAI,WAAW;CACrB,MAAM,MAAM,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CAChD,MAAM,UAAU,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;CACxE,UAAU,yBAAyB,IAAI;AACzC;AAEA,eAAsB,mBAAkC;CACtD,UAAU;AACZ"}
1
+ {"version":3,"file":"config-C6zM8Xir.mjs","names":[],"sources":["../src/config/index.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { DEFAULT_CONFIG, parseInvestigatorConfig, type InvestigatorConfig } from './schema.js'\nimport { graphMcpEndpointEnvOverride } from './mcp-endpoint.js'\n\n// Config path derived from HOME at call time so tests can override HOME.\nfunction configPath(): string {\n return path.join(os.homedir(), '.chain-insights', 'config.json')\n}\n\nlet _cached: InvestigatorConfig | null = null\n\nfunction applyRuntimeEnvOverrides(config: InvestigatorConfig): InvestigatorConfig {\n let graphMcpEndpoint: string | undefined\n try {\n graphMcpEndpoint = graphMcpEndpointEnvOverride()\n } catch (err) {\n throw new Error(`Invalid configuration in environment: ${(err as Error).message}`)\n }\n return graphMcpEndpoint\n ? parseInvestigatorConfig({ ...config, graphMcpEndpoint })\n : config\n}\n\nasync function loadStoredConfig(): Promise<InvestigatorConfig> {\n const cfgPath = configPath()\n let raw: string\n try {\n raw = await readFile(cfgPath, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return DEFAULT_CONFIG\n }\n throw new Error(`Unable to read config ${cfgPath}: ${(err as Error).message}`)\n }\n\n let parsedJson: unknown\n try {\n parsedJson = JSON.parse(raw) as unknown\n } catch (err) {\n throw new Error(`Invalid JSON in ${cfgPath}: ${(err as Error).message}`)\n }\n\n try {\n return parseInvestigatorConfig(parsedJson)\n } catch (err) {\n throw new Error(`Invalid configuration in ${cfgPath}: ${(err as Error).message}`)\n }\n}\n\nexport async function loadConfig(): Promise<InvestigatorConfig> {\n if (_cached) return _cached\n _cached = applyRuntimeEnvOverrides(await loadStoredConfig())\n return _cached\n}\n\nexport async function saveConfig(updates: Partial<InvestigatorConfig>): Promise<void> {\n const current = await loadStoredConfig()\n let next: InvestigatorConfig\n try {\n next = parseInvestigatorConfig({ ...current, ...updates })\n } catch (err) {\n throw new Error(`Invalid configuration update: ${(err as Error).message}`)\n }\n const p = configPath()\n await mkdir(path.dirname(p), { recursive: true })\n await writeFile(p, JSON.stringify(next, null, 2) + '\\n', { mode: 0o600 })\n _cached = applyRuntimeEnvOverrides(next)\n}\n\nexport async function resetConfigCache(): Promise<void> {\n _cached = null\n}\n"],"mappings":";;;;;;;;;;;;AAOA,SAAS,aAAqB;CAC5B,OAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,mBAAmB,aAAa;AACjE;AAEA,IAAI,UAAqC;AAEzC,SAAS,yBAAyB,QAAgD;CAChF,IAAI;CACJ,IAAI;EACF,mBAAmB,4BAA4B;CACjD,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,yCAA0C,IAAc,SAAS;CACnF;CACA,OAAO,mBACH,wBAAwB;EAAE,GAAG;EAAQ;CAAiB,CAAC,IACvD;AACN;AAEA,eAAe,mBAAgD;CAC7D,MAAM,UAAU,WAAW;CAC3B,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,SAAS,SAAS,MAAM;CACtC,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAC1C,OAAO;EAET,MAAM,IAAI,MAAM,yBAAyB,QAAQ,IAAK,IAAc,SAAS;CAC/E;CAEA,IAAI;CACJ,IAAI;EACF,aAAa,KAAK,MAAM,GAAG;CAC7B,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,mBAAmB,QAAQ,IAAK,IAAc,SAAS;CACzE;CAEA,IAAI;EACF,OAAO,wBAAwB,UAAU;CAC3C,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,4BAA4B,QAAQ,IAAK,IAAc,SAAS;CAClF;AACF;AAEA,eAAsB,aAA0C;CAC9D,IAAI,SAAS,OAAO;CACpB,UAAU,yBAAyB,MAAM,iBAAiB,CAAC;CAC3D,OAAO;AACT;AAEA,eAAsB,WAAW,SAAqD;CACpF,MAAM,UAAU,MAAM,iBAAiB;CACvC,IAAI;CACJ,IAAI;EACF,OAAO,wBAAwB;GAAE,GAAG;GAAS,GAAG;EAAQ,CAAC;CAC3D,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,iCAAkC,IAAc,SAAS;CAC3E;CACA,MAAM,IAAI,WAAW;CACrB,MAAM,MAAM,KAAK,QAAQ,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CAChD,MAAM,UAAU,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;CACxE,UAAU,yBAAyB,IAAI;AACzC;AAEA,eAAsB,mBAAkC;CACtD,UAAU;AACZ"}
@@ -1,6 +1,6 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_mcp_endpoint = require("./mcp-endpoint-BaV8h_lq.cjs");
3
- const require_schema = require("./schema-Vl9yuOFO.cjs");
2
+ const require_mcp_endpoint = require("./mcp-endpoint-cQIZSjkK.cjs");
3
+ const require_schema = require("./schema-Dr6JXSOF.cjs");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_chunk.__toESM(node_path, 1);
6
6
  let node_fs_promises = require("node:fs/promises");
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_config = require("./config-BwVx19Og.cjs");
3
- const require_app = require("./app-BxojXjtB.cjs");
4
- const require_server = require("./server-BqVdWath.cjs");
2
+ const require_config = require("./config-CkW404Cs.cjs");
3
+ const require_app = require("./app-DjJn3irw.cjs");
4
+ const require_server = require("./server-B2NFmnCM.cjs");
5
5
  const require_wallet = require("./wallet-gC2jxh7j.cjs");
6
6
  const require_tools = require("./tools-BhTI3Lmg.cjs");
7
7
  const require_topup_server = require("./topup-server-DhYlOOBM.cjs");
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { i as saveConfig, n as loadConfig, r as resetConfigCache } from "./config-Drgc2HuF.mjs";
2
- import { t as createApp } from "./app-CRd39JJ8.mjs";
3
- import { n as startServer } from "./server-BXLX2j_A.mjs";
1
+ import { i as saveConfig, n as loadConfig, r as resetConfigCache } from "./config-C6zM8Xir.mjs";
2
+ import { t as createApp } from "./app-norpwdou.mjs";
3
+ import { n as startServer } from "./server-86dyCsJO.mjs";
4
4
  import { a as setWalletPrivateKey, i as normalizeWalletPrivateKey, n as encryptKey, o as walletAddressFromPrivateKey, r as isWalletConfigured, t as decryptKey } from "./wallet-BL0fJC29.mjs";
5
5
  import { a as getWalletAccount, i as getBalanceUsdc, n as formatWalletBalance, o as getWalletBalanceText, r as getBalanceEth, t as buildTopupInfo } from "./tools-v6kcdojg.mjs";
6
6
  import { i as generateArtifactHtml, n as startTopupServer, t as getTopupUrl } from "./topup-server-R3dNp-p8.mjs";
@@ -1,4 +1,4 @@
1
- import { t as LOCAL_GRAPH_MCP_ENDPOINT } from "./mcp-endpoint-DHs1cRFH.mjs";
1
+ import { t as LOCAL_GRAPH_MCP_ENDPOINT } from "./mcp-endpoint-QQ5Lbqc2.mjs";
2
2
  import { assertNoVaultFileCollisions, scaffoldVault } from "./vault-z35Dohdq.mjs";
3
3
  import path from "node:path";
4
4
  import { access, mkdir, writeFile } from "node:fs/promises";
@@ -272,4 +272,4 @@ async function initWorkspace(options) {
272
272
  //#endregion
273
273
  export { initWorkspace };
274
274
 
275
- //# sourceMappingURL=init-CKQ6F07J.mjs.map
275
+ //# sourceMappingURL=init-uAmPfio2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"init-CKQ6F07J.mjs","names":[],"sources":["../src/workspace/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT } from '../config/mcp-endpoint.js'\nimport { assertNoVaultFileCollisions, scaffoldVault } from '../vault/index.js'\n\nexport interface InitWorkspaceOptions {\n targetDir: string\n force?: boolean\n}\n\nexport interface InitWorkspaceResult {\n workspaceRoot: string\n filesWritten: string[]\n}\n\nconst WORKSPACE_DIRS = [\n '.chain-insights',\n '.chain-insights/schema',\n '.chain-insights/runtime',\n '.chain-insights/runtime/logs',\n '.chain-insights/runtime-skill',\n 'cases',\n 'imports',\n 'reports',\n 'reports/graphs',\n 'reports/tables',\n 'templates',\n 'published',\n]\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction workspaceJson(workspaceRoot: string): string {\n return JSON.stringify({\n schema: 'chain-insights.workspace.v1',\n name: 'Chain Insights Investigations',\n workspace_root: workspaceRoot,\n default_network: 'bittensor',\n graph_mcp_endpoint: LOCAL_GRAPH_MCP_ENDPOINT,\n cases_dir: 'cases',\n imports_dir: 'imports',\n reports_dir: 'reports',\n templates_dir: 'templates',\n created_at: todayIso(),\n }, null, 2) + '\\n'\n}\n\nconst README = `# Chain Insights Investigation Vault\n\nThis is a Chain Insights AML investigation workspace and Obsidian-compatible vault.\nOpen this directory in Obsidian to review cases, entities, evidence, graphs, and\npublished investigation bundles alongside the Chain Insights runtime metadata.\n\n## Start\n\n\\`\\`\\`bash\nchain-insights mcp tools --refresh\nchain-insights wallet ready --check-only\n\\`\\`\\`\n\n## Layout\n\n\\`\\`\\`text\n.chain-insights/ Workspace metadata\ncases/ Case exports and notes\nimports/ External reports, CSVs, screenshots, raw notes\nreports/ Final or interim analyst reports\nreports/graphs/ Graph JSON for visualization\nreports/tables/ Compact tabular extracts\ntemplates/ Reusable case/report templates\npublished/ Obsidian-ready case exports and published bundles\n.chain-insights/schema/ Runtime graph schema captures\n.chain-insights/runtime/ Workspace-local runtime process state and debug logs\n.chain-insights/runtime-skill/ Workspace-specific agent schema notes\n.obsidian/ Obsidian vault configuration\nCanvases/ Obsidian canvases for graph review\nEntities/ Entity notes and indexes\nEvidence/ Evidence notes and indexes\n\\`\\`\\`\n`\n\nconst AGENTS = `# Agent Instructions\n\nYou are operating inside a Chain Insights investigation workspace.\n\n- Read README.md first.\n- If this directory is not initialized, run \\`cia init .\\` before investigation-producing commands.\n- Do not rerun init in an existing workspace unless replacing scaffolding with \\`--force\\`.\n- Read .chain-insights/runtime-skill/SKILL.md before graph queries.\n- Preserve full blockchain addresses exactly.\n- Do not guess the network for graph queries.\n- Capture or refresh graph schema before the first case query.\n- Save compact evidence with original graph field names.\n- Put canonical graph JSON in reports/graphs/ and analyst tables in reports/tables/.\n- Evidence files should summarize and point to graph/table outputs; do not paste large raw JSON blobs into evidence Markdown.\n- Investigation output must stay in this initialized workspace.\n- Never write cases, evidence, reports, graph JSON, HTML, schema captures, or logs to ~/.chain-insights.\n- Keep theories lightweight until evidence supports them.\n`\n\nconst CLAUDE = AGENTS\n\nconst CASE_BRIEF = `# Case Brief\n\n## Summary\n\nStatus:\nNetwork:\nCurrent Assessment:\n\n## Known Addresses\n\n## Claims To Validate\n\n## Evidence\n\n## Next Steps\n`\n\nconst IMPORTS_README = `# External Investigation Inputs\n\nPut user-provided or third-party investigation material here before turning it\ninto case evidence.\n\nExamples:\n\n- Exchange support exports\n- CSV extracts\n- Screenshots\n- Raw notes\n- Partner reports\n\nFiles in this directory are inputs, not verified evidence. When an import\nsupports a claim, summarize it into the case evidence manifest and reference\nthe original file path.\n`\n\nconst TEMPLATES_README = `# Reusable Workspace Templates\n\nStore local report, case, prompt, and evidence templates here.\n\nTemplates are optional workspace helpers. They are not evidence and should not\nbe treated as case state until copied into a case, evidence file, dossier, or\nreport.\n`\n\nconst RUNTIME_SKILL = `---\nname: chain-insights-runtime-schema\ndescription: Workspace-local Chain Insights runtime schema notes. Refresh this after connecting to a graph MCP endpoint.\n---\n\n# Runtime Graph Schema\n\nBefore the first investigation query, capture the live graph schema into:\n\n\\`\\`\\`text\n.chain-insights/schema/<network>.graph-schema.json\n\\`\\`\\`\n\nUse \\`graph_query_batch\\` for schema capture. Prefix current topology reads\nwith \\`USE live_topology\\`, historical topology reads with\n\\`USE archive_topology\\`, and fact reads with \\`USE facts\\`, for example:\n\n\\`\\`\\`bash\ncia mcp call graph_query_batch network=<network> 'queries=[{\"id\":\"node_labels\",\"query\":\"USE live_topology MATCH (n:Address) RETURN \\\"Address\\\" AS node_label, count(n) AS sample_count LIMIT 1\"},{\"id\":\"archive_flow_sample\",\"query\":\"USE archive_topology MATCH (:Address)-[f:FLOWS_TO]->(:Address) RETURN f.period_granularity AS granularity, f.amount_sum AS amount_sum LIMIT 20\"}]'\n\\`\\`\\`\n\nThen update this file with observed labels, relationship types, and allowed\nproperty names for the active network.\n\nRules:\n\n- Prefer \\`graph_query\\` and \\`graph_query_batch\\` for graph-language reads.\n- Use \\`USE live_topology\\` for recent topology, \\`USE archive_topology\\`\n for historical topology, and \\`USE facts\\` for labels, features,\n risk scores, assets, and enrichment. Address facts can be reached through\n relationships such as \\`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\\`.\n Archived money-flow topology is exposed as\n \\`(:Address)-[:FLOWS_TO]->(:Address)\\` with \\`period_granularity\\`,\n \\`period_start_date\\`, and \\`period_end_date\\` on the relationship.\n- Preserve source schema field names in evidence and generated data files.\n- Do not rename, reinterpret, or add unit labels to graph fields unless the\n schema or query result explicitly supports that interpretation.\n- Keep evidence compact: select only the fields needed to support the claim.\n Avoid storing whole node or relationship property blobs in evidence unless\n the purpose of the query is schema discovery or debugging.\n- When using BFS, fixed-depth traversal fallbacks, or any manual \\`FLOWS_TO\\`\n traversal, treat exchange hot wallets as terminal endpoints only. Do not\n expand from, through, or classify exchange nodes as deposit, suspect, or\n intermediate candidates. In Cypher, require every non-terminal traversal node\n to satisfy \\`is_exchange IS NULL\\`; only the final exchange endpoint should\n satisfy \\`is_exchange IS NOT NULL\\`.\n- Keep analysis products separate from evidence: graph JSON belongs under\n \\`reports/graphs/\\`, tabular extracts under \\`reports/tables/\\`, and analyst\n narrative under \\`reports/\\`.\n- Evidence Markdown should be a short provenance record with key facts and\n pointers. Large JSON belongs in \\`reports/tables/\\`, not inline in evidence.\n\nTrace tool chaining:\n\n1. Use \\`trace_victim_funds\\` when the user gives victim/source addresses.\n2. Pass returned \\`continuation.candidate_deposit_addresses\\` to\n \\`trace_deposit_sources\\`; do not make victim tracing run deposit traceback\n internally.\n3. Pass high-confidence \\`continuation.candidate_suspect_addresses\\` from\n deposit traceback to \\`trace_suspect_funds\\`.\n4. Use \\`trace_suspect_funds\\` when the user gives suspected scammer, mule,\n operator, or laundering-ring addresses. \\`incident_timestamp_ms\\` is\n optional.\n5. Use \\`address_risk\\` for single-address enrichment, and\n \\`graph_query_batch\\` only when the role-specific tools do not answer the\n exact question.\n\nAll trace tools return \\`chain-insights.trace.v1\\`. Preserve full addresses in\n\\`input.addresses\\`, \\`addresses[].address\\`, \\`edges[].from_address\\`,\n\\`edges[].to_address\\`, \\`paths[].addresses\\`, \\`candidate_labels[].address\\`,\nand \\`continuation\\` address lists.\n`\n\nconst SCHEMA_README = `# Runtime Schema Captures\n\nStore graph schema captures here, for example:\n\n\\`\\`\\`text\nbittensor.graph-schema.json\n\\`\\`\\`\n\nSchema captures should be generated before the first case query in a fresh\nworkspace, then referenced by evidence, reports, and runtime skill notes.\n`\n\nfunction workspaceFiles(workspaceRoot: string): Array<[string, string]> {\n return [\n ['.chain-insights/workspace.json', workspaceJson(workspaceRoot)],\n ['README.md', README],\n ['AGENTS.md', AGENTS],\n ['CLAUDE.md', CLAUDE],\n ['imports/README.md', IMPORTS_README],\n ['templates/README.md', TEMPLATES_README],\n ['templates/case-brief.md', CASE_BRIEF],\n ['.chain-insights/runtime-skill/SKILL.md', RUNTIME_SKILL],\n ['.chain-insights/schema/README.md', SCHEMA_README],\n ['.chain-insights/runtime/.keep', ''],\n ['.chain-insights/runtime/logs/.keep', ''],\n ]\n}\n\nasync function assertNoFileCollisions(workspaceRoot: string): Promise<void> {\n for (const [relativePath] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await access(filePath)\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n continue\n }\n throw err\n }\n }\n}\n\nexport async function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult> {\n const workspaceRoot = path.resolve(options.targetDir)\n if (!options.force) {\n await assertNoFileCollisions(workspaceRoot)\n await assertNoVaultFileCollisions(workspaceRoot)\n }\n\n for (const dir of WORKSPACE_DIRS) {\n await mkdir(path.join(workspaceRoot, dir), { recursive: true })\n }\n\n const filesWritten: string[] = []\n const flag = options.force ? 'w' : 'wx'\n for (const [relativePath, content] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await writeFile(filePath, content, { mode: 0o600, flag })\n filesWritten.push(relativePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n }\n throw err\n }\n }\n\n const vault = await scaffoldVault({ workspaceRoot, force: options.force })\n filesWritten.push(...vault.filesWritten)\n\n return { workspaceRoot, filesWritten }\n}\n"],"mappings":";;;;;AAeA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,WAAmB;CAC1B,wBAAO,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAEA,SAAS,cAAc,eAA+B;CACpD,OAAO,KAAK,UAAU;EACpB,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,aAAa;EACb,aAAa;EACb,eAAe;EACf,YAAY,SAAS;CACvB,GAAG,MAAM,CAAC,IAAI;AAChB;AAEA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCf,MAAM,SAAS;;;;;;;;;;;;;;;;;;AAmBf,MAAM,SAAS;AAEf,MAAM,aAAa;;;;;;;;;;;;;;;;AAiBnB,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEtB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAe,eAAgD;CACtE,OAAO;EACL,CAAC,kCAAkC,cAAc,aAAa,CAAC;EAC/D,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,qBAAqB,cAAc;EACpC,CAAC,uBAAuB,gBAAgB;EACxC,CAAC,2BAA2B,UAAU;EACtC,CAAC,0CAA0C,aAAa;EACxD,CAAC,oCAAoC,aAAa;EAClD,CAAC,iCAAiC,EAAE;EACpC,CAAC,sCAAsC,EAAE;CAC3C;AACF;AAEA,eAAe,uBAAuB,eAAsC;CAC1E,KAAK,MAAM,CAAC,iBAAiB,eAAe,aAAa,GAAG;EAC1D,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,OAAO,QAAQ;GACrB,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;EACtG,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C;GAEF,MAAM;EACR;CACF;AACF;AAEA,eAAsB,cAAc,SAA6D;CAC/F,MAAM,gBAAgB,KAAK,QAAQ,QAAQ,SAAS;CACpD,IAAI,CAAC,QAAQ,OAAO;EAClB,MAAM,uBAAuB,aAAa;EAC1C,MAAM,4BAA4B,aAAa;CACjD;CAEA,KAAK,MAAM,OAAO,gBAChB,MAAM,MAAM,KAAK,KAAK,eAAe,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CAGhE,MAAM,eAAyB,CAAC;CAChC,MAAM,OAAO,QAAQ,QAAQ,MAAM;CACnC,KAAK,MAAM,CAAC,cAAc,YAAY,eAAe,aAAa,GAAG;EACnE,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,UAAU,UAAU,SAAS;IAAE,MAAM;IAAO;GAAK,CAAC;GACxD,aAAa,KAAK,YAAY;EAChC,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;GAEtG,MAAM;EACR;CACF;CAEA,MAAM,QAAQ,MAAM,cAAc;EAAE;EAAe,OAAO,QAAQ;CAAM,CAAC;CACzE,aAAa,KAAK,GAAG,MAAM,YAAY;CAEvC,OAAO;EAAE;EAAe;CAAa;AACvC"}
1
+ {"version":3,"file":"init-uAmPfio2.mjs","names":[],"sources":["../src/workspace/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT } from '../config/mcp-endpoint.js'\nimport { assertNoVaultFileCollisions, scaffoldVault } from '../vault/index.js'\n\nexport interface InitWorkspaceOptions {\n targetDir: string\n force?: boolean\n}\n\nexport interface InitWorkspaceResult {\n workspaceRoot: string\n filesWritten: string[]\n}\n\nconst WORKSPACE_DIRS = [\n '.chain-insights',\n '.chain-insights/schema',\n '.chain-insights/runtime',\n '.chain-insights/runtime/logs',\n '.chain-insights/runtime-skill',\n 'cases',\n 'imports',\n 'reports',\n 'reports/graphs',\n 'reports/tables',\n 'templates',\n 'published',\n]\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction workspaceJson(workspaceRoot: string): string {\n return JSON.stringify({\n schema: 'chain-insights.workspace.v1',\n name: 'Chain Insights Investigations',\n workspace_root: workspaceRoot,\n default_network: 'bittensor',\n graph_mcp_endpoint: LOCAL_GRAPH_MCP_ENDPOINT,\n cases_dir: 'cases',\n imports_dir: 'imports',\n reports_dir: 'reports',\n templates_dir: 'templates',\n created_at: todayIso(),\n }, null, 2) + '\\n'\n}\n\nconst README = `# Chain Insights Investigation Vault\n\nThis is a Chain Insights AML investigation workspace and Obsidian-compatible vault.\nOpen this directory in Obsidian to review cases, entities, evidence, graphs, and\npublished investigation bundles alongside the Chain Insights runtime metadata.\n\n## Start\n\n\\`\\`\\`bash\nchain-insights mcp tools --refresh\nchain-insights wallet ready --check-only\n\\`\\`\\`\n\n## Layout\n\n\\`\\`\\`text\n.chain-insights/ Workspace metadata\ncases/ Case exports and notes\nimports/ External reports, CSVs, screenshots, raw notes\nreports/ Final or interim analyst reports\nreports/graphs/ Graph JSON for visualization\nreports/tables/ Compact tabular extracts\ntemplates/ Reusable case/report templates\npublished/ Obsidian-ready case exports and published bundles\n.chain-insights/schema/ Runtime graph schema captures\n.chain-insights/runtime/ Workspace-local runtime process state and debug logs\n.chain-insights/runtime-skill/ Workspace-specific agent schema notes\n.obsidian/ Obsidian vault configuration\nCanvases/ Obsidian canvases for graph review\nEntities/ Entity notes and indexes\nEvidence/ Evidence notes and indexes\n\\`\\`\\`\n`\n\nconst AGENTS = `# Agent Instructions\n\nYou are operating inside a Chain Insights investigation workspace.\n\n- Read README.md first.\n- If this directory is not initialized, run \\`cia init .\\` before investigation-producing commands.\n- Do not rerun init in an existing workspace unless replacing scaffolding with \\`--force\\`.\n- Read .chain-insights/runtime-skill/SKILL.md before graph queries.\n- Preserve full blockchain addresses exactly.\n- Do not guess the network for graph queries.\n- Capture or refresh graph schema before the first case query.\n- Save compact evidence with original graph field names.\n- Put canonical graph JSON in reports/graphs/ and analyst tables in reports/tables/.\n- Evidence files should summarize and point to graph/table outputs; do not paste large raw JSON blobs into evidence Markdown.\n- Investigation output must stay in this initialized workspace.\n- Never write cases, evidence, reports, graph JSON, HTML, schema captures, or logs to ~/.chain-insights.\n- Keep theories lightweight until evidence supports them.\n`\n\nconst CLAUDE = AGENTS\n\nconst CASE_BRIEF = `# Case Brief\n\n## Summary\n\nStatus:\nNetwork:\nCurrent Assessment:\n\n## Known Addresses\n\n## Claims To Validate\n\n## Evidence\n\n## Next Steps\n`\n\nconst IMPORTS_README = `# External Investigation Inputs\n\nPut user-provided or third-party investigation material here before turning it\ninto case evidence.\n\nExamples:\n\n- Exchange support exports\n- CSV extracts\n- Screenshots\n- Raw notes\n- Partner reports\n\nFiles in this directory are inputs, not verified evidence. When an import\nsupports a claim, summarize it into the case evidence manifest and reference\nthe original file path.\n`\n\nconst TEMPLATES_README = `# Reusable Workspace Templates\n\nStore local report, case, prompt, and evidence templates here.\n\nTemplates are optional workspace helpers. They are not evidence and should not\nbe treated as case state until copied into a case, evidence file, dossier, or\nreport.\n`\n\nconst RUNTIME_SKILL = `---\nname: chain-insights-runtime-schema\ndescription: Workspace-local Chain Insights runtime schema notes. Refresh this after connecting to a graph MCP endpoint.\n---\n\n# Runtime Graph Schema\n\nBefore the first investigation query, capture the live graph schema into:\n\n\\`\\`\\`text\n.chain-insights/schema/<network>.graph-schema.json\n\\`\\`\\`\n\nUse \\`graph_query_batch\\` for schema capture. Prefix current topology reads\nwith \\`USE live_topology\\`, historical topology reads with\n\\`USE archive_topology\\`, and fact reads with \\`USE facts\\`, for example:\n\n\\`\\`\\`bash\ncia mcp call graph_query_batch network=<network> 'queries=[{\"id\":\"node_labels\",\"query\":\"USE live_topology MATCH (n:Address) RETURN \\\"Address\\\" AS node_label, count(n) AS sample_count LIMIT 1\"},{\"id\":\"archive_flow_sample\",\"query\":\"USE archive_topology MATCH (:Address)-[f:FLOWS_TO]->(:Address) RETURN f.period_granularity AS granularity, f.amount_sum AS amount_sum LIMIT 20\"}]'\n\\`\\`\\`\n\nThen update this file with observed labels, relationship types, and allowed\nproperty names for the active network.\n\nRules:\n\n- Prefer \\`graph_query\\` and \\`graph_query_batch\\` for graph-language reads.\n- Use \\`USE live_topology\\` for recent topology, \\`USE archive_topology\\`\n for historical topology, and \\`USE facts\\` for labels, features,\n risk scores, assets, and enrichment. Address facts can be reached through\n relationships such as \\`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\\`.\n Archived money-flow topology is exposed as\n \\`(:Address)-[:FLOWS_TO]->(:Address)\\` with \\`period_granularity\\`,\n \\`period_start_date\\`, and \\`period_end_date\\` on the relationship.\n- Preserve source schema field names in evidence and generated data files.\n- Do not rename, reinterpret, or add unit labels to graph fields unless the\n schema or query result explicitly supports that interpretation.\n- Keep evidence compact: select only the fields needed to support the claim.\n Avoid storing whole node or relationship property blobs in evidence unless\n the purpose of the query is schema discovery or debugging.\n- When using BFS, fixed-depth traversal fallbacks, or any manual \\`FLOWS_TO\\`\n traversal, treat exchange hot wallets as terminal endpoints only. Do not\n expand from, through, or classify exchange nodes as deposit, suspect, or\n intermediate candidates. In Cypher, require every non-terminal traversal node\n to satisfy \\`is_exchange IS NULL\\`; only the final exchange endpoint should\n satisfy \\`is_exchange IS NOT NULL\\`.\n- Keep analysis products separate from evidence: graph JSON belongs under\n \\`reports/graphs/\\`, tabular extracts under \\`reports/tables/\\`, and analyst\n narrative under \\`reports/\\`.\n- Evidence Markdown should be a short provenance record with key facts and\n pointers. Large JSON belongs in \\`reports/tables/\\`, not inline in evidence.\n\nTrace tool chaining:\n\n1. Use \\`trace_victim_funds\\` when the user gives victim/source addresses.\n2. Pass returned \\`continuation.candidate_deposit_addresses\\` to\n \\`trace_deposit_sources\\`; do not make victim tracing run deposit traceback\n internally.\n3. Pass high-confidence \\`continuation.candidate_suspect_addresses\\` from\n deposit traceback to \\`trace_suspect_funds\\`.\n4. Use \\`trace_suspect_funds\\` when the user gives suspected scammer, mule,\n operator, or laundering-ring addresses. \\`incident_timestamp_ms\\` is\n optional.\n5. Use \\`address_risk\\` for single-address enrichment, and\n \\`graph_query_batch\\` only when the role-specific tools do not answer the\n exact question.\n\nAll trace tools return \\`chain-insights.trace.v1\\`. Preserve full addresses in\n\\`input.addresses\\`, \\`addresses[].address\\`, \\`edges[].from_address\\`,\n\\`edges[].to_address\\`, \\`paths[].addresses\\`, \\`candidate_labels[].address\\`,\nand \\`continuation\\` address lists.\n`\n\nconst SCHEMA_README = `# Runtime Schema Captures\n\nStore graph schema captures here, for example:\n\n\\`\\`\\`text\nbittensor.graph-schema.json\n\\`\\`\\`\n\nSchema captures should be generated before the first case query in a fresh\nworkspace, then referenced by evidence, reports, and runtime skill notes.\n`\n\nfunction workspaceFiles(workspaceRoot: string): Array<[string, string]> {\n return [\n ['.chain-insights/workspace.json', workspaceJson(workspaceRoot)],\n ['README.md', README],\n ['AGENTS.md', AGENTS],\n ['CLAUDE.md', CLAUDE],\n ['imports/README.md', IMPORTS_README],\n ['templates/README.md', TEMPLATES_README],\n ['templates/case-brief.md', CASE_BRIEF],\n ['.chain-insights/runtime-skill/SKILL.md', RUNTIME_SKILL],\n ['.chain-insights/schema/README.md', SCHEMA_README],\n ['.chain-insights/runtime/.keep', ''],\n ['.chain-insights/runtime/logs/.keep', ''],\n ]\n}\n\nasync function assertNoFileCollisions(workspaceRoot: string): Promise<void> {\n for (const [relativePath] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await access(filePath)\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n continue\n }\n throw err\n }\n }\n}\n\nexport async function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult> {\n const workspaceRoot = path.resolve(options.targetDir)\n if (!options.force) {\n await assertNoFileCollisions(workspaceRoot)\n await assertNoVaultFileCollisions(workspaceRoot)\n }\n\n for (const dir of WORKSPACE_DIRS) {\n await mkdir(path.join(workspaceRoot, dir), { recursive: true })\n }\n\n const filesWritten: string[] = []\n const flag = options.force ? 'w' : 'wx'\n for (const [relativePath, content] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await writeFile(filePath, content, { mode: 0o600, flag })\n filesWritten.push(relativePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n }\n throw err\n }\n }\n\n const vault = await scaffoldVault({ workspaceRoot, force: options.force })\n filesWritten.push(...vault.filesWritten)\n\n return { workspaceRoot, filesWritten }\n}\n"],"mappings":";;;;;AAeA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,WAAmB;CAC1B,wBAAO,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAEA,SAAS,cAAc,eAA+B;CACpD,OAAO,KAAK,UAAU;EACpB,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,aAAa;EACb,aAAa;EACb,eAAe;EACf,YAAY,SAAS;CACvB,GAAG,MAAM,CAAC,IAAI;AAChB;AAEA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCf,MAAM,SAAS;;;;;;;;;;;;;;;;;;AAmBf,MAAM,SAAS;AAEf,MAAM,aAAa;;;;;;;;;;;;;;;;AAiBnB,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEtB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAe,eAAgD;CACtE,OAAO;EACL,CAAC,kCAAkC,cAAc,aAAa,CAAC;EAC/D,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,qBAAqB,cAAc;EACpC,CAAC,uBAAuB,gBAAgB;EACxC,CAAC,2BAA2B,UAAU;EACtC,CAAC,0CAA0C,aAAa;EACxD,CAAC,oCAAoC,aAAa;EAClD,CAAC,iCAAiC,EAAE;EACpC,CAAC,sCAAsC,EAAE;CAC3C;AACF;AAEA,eAAe,uBAAuB,eAAsC;CAC1E,KAAK,MAAM,CAAC,iBAAiB,eAAe,aAAa,GAAG;EAC1D,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,OAAO,QAAQ;GACrB,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;EACtG,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C;GAEF,MAAM;EACR;CACF;AACF;AAEA,eAAsB,cAAc,SAA6D;CAC/F,MAAM,gBAAgB,KAAK,QAAQ,QAAQ,SAAS;CACpD,IAAI,CAAC,QAAQ,OAAO;EAClB,MAAM,uBAAuB,aAAa;EAC1C,MAAM,4BAA4B,aAAa;CACjD;CAEA,KAAK,MAAM,OAAO,gBAChB,MAAM,MAAM,KAAK,KAAK,eAAe,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CAGhE,MAAM,eAAyB,CAAC;CAChC,MAAM,OAAO,QAAQ,QAAQ,MAAM;CACnC,KAAK,MAAM,CAAC,cAAc,YAAY,eAAe,aAAa,GAAG;EACnE,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,UAAU,UAAU,SAAS;IAAE,MAAM;IAAO;GAAK,CAAC;GACxD,aAAa,KAAK,YAAY;EAChC,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;GAEtG,MAAM;EACR;CACF;CAEA,MAAM,QAAQ,MAAM,cAAc;EAAE;EAAe,OAAO,QAAQ;CAAM,CAAC;CACzE,aAAa,KAAK,GAAG,MAAM,YAAY;CAEvC,OAAO;EAAE;EAAe;CAAa;AACvC"}
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_mcp_endpoint = require("./mcp-endpoint-BaV8h_lq.cjs");
2
+ const require_mcp_endpoint = require("./mcp-endpoint-cQIZSjkK.cjs");
3
3
  const require_vault = require("./vault-B2y78Ypu.cjs");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_chunk.__toESM(node_path, 1);
@@ -14,6 +14,9 @@ function isLoopbackHost(hostname) {
14
14
  if (isIP(normalized) !== 4) return false;
15
15
  return normalized.split(".")[0] === "127";
16
16
  }
17
+ function isKubernetesServiceHost(hostname) {
18
+ return hostname.toLowerCase().endsWith(".svc.cluster.local");
19
+ }
17
20
  function graphMcpEndpointEnvOverride() {
18
21
  const envEndpoint = process.env.CHAIN_INSIGHTS_GRAPH_MCP_ENDPOINT?.trim() || process.env.GRAPH_MCP_ENDPOINT?.trim();
19
22
  return envEndpoint ? validateMcpEndpoint(envEndpoint, "graphMcpEndpoint") : void 0;
@@ -30,10 +33,10 @@ function validateMcpEndpoint(value, key) {
30
33
  if (parsed.username || parsed.password) throw new Error(`${key} must not include URL credentials.`);
31
34
  if (parsed.search || parsed.hash) throw new Error(`${key} must not include query parameters or URL fragments.`);
32
35
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error(`${key} must use either https:// or http://.`);
33
- if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname)) throw new Error(`${key} must use https:// for remote hosts. http:// is allowed only for localhost or loopback addresses.`);
36
+ if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname) && !isKubernetesServiceHost(parsed.hostname)) throw new Error(`${key} must use https:// for remote hosts. http:// is allowed only for localhost, loopback addresses, or Kubernetes *.svc.cluster.local service DNS.`);
34
37
  return trimmed;
35
38
  }
36
39
  //#endregion
37
40
  export { validateMcpEndpoint as i, LOCAL_LEGACY_MCP_ENDPOINT as n, graphMcpEndpointEnvOverride as r, LOCAL_GRAPH_MCP_ENDPOINT as t };
38
41
 
39
- //# sourceMappingURL=mcp-endpoint-DHs1cRFH.mjs.map
42
+ //# sourceMappingURL=mcp-endpoint-QQ5Lbqc2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-endpoint-QQ5Lbqc2.mjs","names":[],"sources":["../src/config/mcp-endpoint.ts"],"sourcesContent":["import { isIP } from 'node:net'\n\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]'])\n\nexport const LOCAL_GRAPH_MCP_ENDPOINT = 'http://127.0.0.1:8012/mcp'\nexport const LOCAL_LEGACY_MCP_ENDPOINT = 'http://127.0.0.1:4000'\n\nfunction isLoopbackHost(hostname: string): boolean {\n const normalized = hostname.toLowerCase().replace(/^\\[(.*)\\]$/, '$1')\n if (LOOPBACK_HOSTS.has(normalized)) return true\n if (isIP(normalized) !== 4) return false\n return normalized.split('.')[0] === '127'\n}\n\nfunction isKubernetesServiceHost(hostname: string): boolean {\n return hostname.toLowerCase().endsWith('.svc.cluster.local')\n}\n\nexport function graphMcpEndpointEnvOverride(): string | undefined {\n const envEndpoint = process.env.CHAIN_INSIGHTS_GRAPH_MCP_ENDPOINT?.trim()\n || process.env.GRAPH_MCP_ENDPOINT?.trim()\n return envEndpoint ? validateMcpEndpoint(envEndpoint, 'graphMcpEndpoint') : undefined\n}\n\nexport function validateMcpEndpoint(value: string, key: string): string {\n const trimmed = value.trim()\n if (!trimmed) {\n throw new Error(`${key} must be a non-empty absolute URL.`)\n }\n\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n throw new Error(\n `${key} must be an absolute URL (example: https://mcp.example.com/mcp or http://127.0.0.1:8012/mcp).`,\n )\n }\n\n if (parsed.username || parsed.password) {\n throw new Error(`${key} must not include URL credentials.`)\n }\n if (parsed.search || parsed.hash) {\n throw new Error(`${key} must not include query parameters or URL fragments.`)\n }\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error(`${key} must use either https:// or http://.`)\n }\n if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname) && !isKubernetesServiceHost(parsed.hostname)) {\n throw new Error(\n `${key} must use https:// for remote hosts. http:// is allowed only for localhost, loopback addresses, or Kubernetes *.svc.cluster.local service DNS.`,\n )\n }\n\n return trimmed\n}\n"],"mappings":";;AAEA,MAAM,iBAAiB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;AAAO,CAAC;AAEzE,MAAa,2BAA2B;AACxC,MAAa,4BAA4B;AAEzC,SAAS,eAAe,UAA2B;CACjD,MAAM,aAAa,SAAS,YAAY,EAAE,QAAQ,cAAc,IAAI;CACpE,IAAI,eAAe,IAAI,UAAU,GAAG,OAAO;CAC3C,IAAI,KAAK,UAAU,MAAM,GAAG,OAAO;CACnC,OAAO,WAAW,MAAM,GAAG,EAAE,OAAO;AACtC;AAEA,SAAS,wBAAwB,UAA2B;CAC1D,OAAO,SAAS,YAAY,EAAE,SAAS,oBAAoB;AAC7D;AAEA,SAAgB,8BAAkD;CAChE,MAAM,cAAc,QAAQ,IAAI,mCAAmC,KAAK,KACnE,QAAQ,IAAI,oBAAoB,KAAK;CAC1C,OAAO,cAAc,oBAAoB,aAAa,kBAAkB,IAAI,KAAA;AAC9E;AAEA,SAAgB,oBAAoB,OAAe,KAAqB;CACtE,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,GAAG,IAAI,mCAAmC;CAG5D,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,OAAO;CAC1B,QAAQ;EACN,MAAM,IAAI,MACR,GAAG,IAAI,8FACT;CACF;CAEA,IAAI,OAAO,YAAY,OAAO,UAC5B,MAAM,IAAI,MAAM,GAAG,IAAI,mCAAmC;CAE5D,IAAI,OAAO,UAAU,OAAO,MAC1B,MAAM,IAAI,MAAM,GAAG,IAAI,qDAAqD;CAE9E,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,SACtD,MAAM,IAAI,MAAM,GAAG,IAAI,sCAAsC;CAE/D,IAAI,OAAO,aAAa,WAAW,CAAC,eAAe,OAAO,QAAQ,KAAK,CAAC,wBAAwB,OAAO,QAAQ,GAC7G,MAAM,IAAI,MACR,GAAG,IAAI,+IACT;CAGF,OAAO;AACT"}
@@ -14,6 +14,9 @@ function isLoopbackHost(hostname) {
14
14
  if ((0, node_net.isIP)(normalized) !== 4) return false;
15
15
  return normalized.split(".")[0] === "127";
16
16
  }
17
+ function isKubernetesServiceHost(hostname) {
18
+ return hostname.toLowerCase().endsWith(".svc.cluster.local");
19
+ }
17
20
  function graphMcpEndpointEnvOverride() {
18
21
  const envEndpoint = process.env.CHAIN_INSIGHTS_GRAPH_MCP_ENDPOINT?.trim() || process.env.GRAPH_MCP_ENDPOINT?.trim();
19
22
  return envEndpoint ? validateMcpEndpoint(envEndpoint, "graphMcpEndpoint") : void 0;
@@ -30,7 +33,7 @@ function validateMcpEndpoint(value, key) {
30
33
  if (parsed.username || parsed.password) throw new Error(`${key} must not include URL credentials.`);
31
34
  if (parsed.search || parsed.hash) throw new Error(`${key} must not include query parameters or URL fragments.`);
32
35
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error(`${key} must use either https:// or http://.`);
33
- if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname)) throw new Error(`${key} must use https:// for remote hosts. http:// is allowed only for localhost or loopback addresses.`);
36
+ if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname) && !isKubernetesServiceHost(parsed.hostname)) throw new Error(`${key} must use https:// for remote hosts. http:// is allowed only for localhost, loopback addresses, or Kubernetes *.svc.cluster.local service DNS.`);
34
37
  return trimmed;
35
38
  }
36
39
  //#endregion
@@ -588,7 +588,7 @@ async function normalizeRemoteToolResult(result, config, toolName = "remote-grap
588
588
  const meta = { ...result._meta ?? {} };
589
589
  if (graphPayload && includeAttachments) {
590
590
  const { writeGraphReport } = await Promise.resolve().then(() => require("./graph-reports-B3mkLP8Z.cjs"));
591
- const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-XbN16DwU.cjs"));
591
+ const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-C6_gtIql.cjs"));
592
592
  const report = await writeGraphReport(graphPayload, {
593
593
  serverPort: config.serverPort,
594
594
  slug: toolName || "remote-graph"
@@ -615,7 +615,7 @@ function shouldIncludeAttachments(args, workspaceArtifactsEnabled) {
615
615
  async function writeLocalGraphMeta(graphData, config, slug, includeAttachments) {
616
616
  if (!includeAttachments) return void 0;
617
617
  const { writeGraphReport } = await Promise.resolve().then(() => require("./graph-reports-B3mkLP8Z.cjs"));
618
- const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-XbN16DwU.cjs"));
618
+ const { ensureArtifactServer } = await Promise.resolve().then(() => require("./artifact-server-C6_gtIql.cjs"));
619
619
  const report = await writeGraphReport(graphData, {
620
620
  serverPort: config.serverPort,
621
621
  slug
@@ -637,7 +637,7 @@ function graphMetaResult(graph) {
637
637
  * All diagnostic output goes to console.error() or process.stderr.write().
638
638
  */
639
639
  async function createProxy() {
640
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
640
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
641
641
  const { activeDataDir, findActiveWorkspace } = await Promise.resolve().then(() => require("./active-BVr55kvW.cjs")).then((n) => n.active_exports);
642
642
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Y_zqKqJT.cjs")).then((n) => n.client_exports);
643
643
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-CJk1EL3L.cjs"));
@@ -584,7 +584,7 @@ async function normalizeRemoteToolResult(result, config, toolName = "remote-grap
584
584
  const meta = { ...result._meta ?? {} };
585
585
  if (graphPayload && includeAttachments) {
586
586
  const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
587
- const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
587
+ const { ensureArtifactServer } = await import("./artifact-server-DHPM0lxS.mjs");
588
588
  const report = await writeGraphReport(graphPayload, {
589
589
  serverPort: config.serverPort,
590
590
  slug: toolName || "remote-graph"
@@ -611,7 +611,7 @@ function shouldIncludeAttachments(args, workspaceArtifactsEnabled) {
611
611
  async function writeLocalGraphMeta(graphData, config, slug, includeAttachments) {
612
612
  if (!includeAttachments) return void 0;
613
613
  const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
614
- const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
614
+ const { ensureArtifactServer } = await import("./artifact-server-DHPM0lxS.mjs");
615
615
  const report = await writeGraphReport(graphData, {
616
616
  serverPort: config.serverPort,
617
617
  slug
@@ -633,7 +633,7 @@ function graphMetaResult(graph) {
633
633
  * All diagnostic output goes to console.error() or process.stderr.write().
634
634
  */
635
635
  async function createProxy() {
636
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
636
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
637
637
  const { activeDataDir, findActiveWorkspace } = await import("./active-ByNgjuAg.mjs").then((n) => n.n);
638
638
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-BgmHjBHQ.mjs").then((n) => n.r);
639
639
  const { loadSchema, saveSchema } = await import("./schema-cache-DwDvPy4e.mjs");
@@ -1,5 +1,5 @@
1
1
  import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
2
- import { n as loadConfig } from "./config-Drgc2HuF.mjs";
2
+ import { n as loadConfig } from "./config-C6zM8Xir.mjs";
3
3
  import { i as createConfiguredMcpFetch } from "./client-BgmHjBHQ.mjs";
4
4
  import { t as generateVisualization } from "./viz-DB5XFG1z.mjs";
5
5
  import { t as CaseStore } from "./store-C2B_AssI.mjs";
@@ -146,4 +146,4 @@ async run(playbook, opts) {
146
146
  //#endregion
147
147
  export { PlaybookRunner };
148
148
 
149
- //# sourceMappingURL=runner-CVnjpqc-.mjs.map
149
+ //# sourceMappingURL=runner-B9fXAP0t.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner-CVnjpqc-.mjs","names":[],"sources":["../src/playbooks/runner.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { CaseStore } from '../cases/store.js'\nimport { EvidenceStore } from '../cases/evidence.js'\nimport { loadConfig } from '../config/index.js'\nimport { createConfiguredMcpFetch } from '../mcp/client.js'\nimport { generateVisualization } from '../viz/index.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { PlaybookDefinition } from './schema.js'\n\nexport interface RunnerOptions {\n caseId?: string // attach to existing case; omit for quick-case auto-creation\n from?: number // 1-based step to resume from (default: 1)\n dryRun?: boolean // print steps, no MCP calls\n params?: Record<string, string>\n}\n\n/** Sleep for ms milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** Check if an error is a timeout/abort error. */\nfunction isTimeoutError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ECONNRESET'\n}\n\n/** Check if an error is a payment failure. */\nfunction isPaymentError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n const msg = err.message.toLowerCase()\n // Match HTTP 402 status more precisely, or x402-specific error signals\n return msg.includes('http 402') ||\n msg.includes('status 402') ||\n msg.includes('payment required') ||\n msg.includes('x402')\n}\n\n/**\n * Call an MCP tool with retry logic on timeout (up to 3 total attempts).\n * Returns the text result or throws on non-retryable error.\n */\nasync function callWithRetry(\n client: Client,\n toolName: string,\n params: Record<string, string>\n): Promise<string> {\n const MAX_ATTEMPTS = 3\n let lastErr: unknown\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const result = await client.callTool({ name: toolName, arguments: params })\n const content = result.content as Array<{ type: string; text?: string }>\n return content.filter(c => c.type === 'text').map(c => c.text ?? '').join('\\n')\n } catch (err) {\n if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {\n lastErr = err\n await sleep(1000)\n continue\n }\n throw err\n }\n }\n\n throw lastErr\n}\n\nasync function validateStepTools(client: Client, steps: PlaybookDefinition['steps']): Promise<void> {\n const result = await client.listTools()\n const available = new Set(result.tools.map(tool => tool.name))\n const missing = [...new Set(steps.map(step => step.tool).filter(tool => !available.has(tool)))]\n if (missing.length === 0) return\n\n const availableList = [...available].sort().join(', ') || 'none'\n throw new Error(\n `Unknown MCP tool(s) in playbook: ${missing.join(', ')}. ` +\n `Available tools: ${availableList}. Run \\`chain-insights mcp tools --refresh\\` to inspect the live MCP schema.`\n )\n}\n\nexport const PlaybookRunner = {\n /**\n * Execute a playbook definition step-by-step against the live MCP.\n *\n * @param playbook - Parsed and validated PlaybookDefinition\n * @param opts - Runner options (caseId, from, dryRun, params)\n */\n async run(playbook: PlaybookDefinition, opts: RunnerOptions): Promise<void> {\n const startIndex = (opts.from ?? 1) - 1 // convert 1-based to 0-based\n const stepsToRun = playbook.steps.slice(startIndex)\n const totalSteps = playbook.steps.length\n\n // --- DRY RUN ---\n if (opts.dryRun) {\n console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`)\n console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`)\n console.log('')\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`)\n }\n console.log('')\n console.log('Cost: unknown (MCP pricing not available without live connection)')\n return\n }\n\n // --- MCP AUTH CHECK (before case creation to avoid orphan cases) ---\n const config = await loadConfig()\n const mcpFetch = await createConfiguredMcpFetch(config)\n\n // --- CASE RESOLUTION ---\n let caseId: string\n if (opts.caseId) {\n const existingCase = await CaseStore.get(opts.caseId)\n caseId = existingCase.id\n } else {\n const newCase = await CaseStore.create({\n name: `quick-${playbook.name}-${Date.now()}`,\n tags: ['quick', 'playbook', playbook.name],\n description: `Auto-created for one-off playbook run: ${playbook.name}`,\n })\n caseId = newCase.id\n console.log(`Created quick case: ${caseId}`)\n }\n\n // --- MCP CONNECTION ---\n const client = new Client({ name: 'chain-insights-playbook', version: PACKAGE_VERSION })\n await client.connect(\n new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch })\n )\n\n let evidenceCount = 0\n\n try {\n await validateStepTools(client, stepsToRun)\n\n // --- STEP LOOP ---\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`)\n\n let result: string\n try {\n result = await callWithRetry(client, step.tool, step.params)\n } catch (err) {\n if (isPaymentError(err)) {\n if (process.stdin.isTTY) {\n // Interactive: prompt user\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const answer = await new Promise<string>(resolve => {\n rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve)\n })\n rl.close()\n\n if (answer.trim().toLowerCase() === 'retry') {\n result = await callWithRetry(client, step.tool, step.params)\n } else if (answer.trim().toLowerCase() === 'skip') {\n console.log(`Step ${step.index} skipped.`)\n continue\n } else {\n throw new Error(`Aborted at step ${step.index} due to payment failure.`)\n }\n } else {\n // Non-TTY: abort\n throw new Error(\n `Payment required for step ${step.index} but no interactive terminal available. ` +\n `Configure wallet with \\`chain-insights wallet import <private-key>\\`, then run \\`chain-insights wallet ready\\`. Aborting.`\n )\n }\n } else {\n // Non-payment, non-timeout MCP error — stop and report\n const completedSteps = step.index - 1 - startIndex\n const completedMsg = completedSteps > 0\n ? `Completed: steps ${startIndex + 1}..${step.index - 1}.`\n : 'No steps completed before failure.'\n console.error(\n `Step ${step.index} failed: ${(err as Error).message}. ` +\n `${completedMsg} Run with --from ${step.index} to resume.`\n )\n throw err\n }\n }\n\n // --- STORE EVIDENCE ---\n await EvidenceStore.append(caseId, {\n source: step.tool,\n content: result,\n queryParams: JSON.stringify(step.params),\n })\n evidenceCount++\n console.log(` (${result.length} chars stored)`)\n }\n\n // --- AUTO-VIZ for trace-funds ---\n if (playbook.name === 'trace-funds') {\n try {\n const viz = await generateVisualization({ caseId })\n console.log(`Visualization generated: ${viz.htmlPath}`)\n } catch {\n console.log('No transaction data to visualize.')\n }\n }\n\n // --- FINAL SUMMARY ---\n console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`)\n } finally {\n await client.close()\n }\n },\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runner-B9fXAP0t.mjs","names":[],"sources":["../src/playbooks/runner.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { CaseStore } from '../cases/store.js'\nimport { EvidenceStore } from '../cases/evidence.js'\nimport { loadConfig } from '../config/index.js'\nimport { createConfiguredMcpFetch } from '../mcp/client.js'\nimport { generateVisualization } from '../viz/index.js'\nimport { PACKAGE_VERSION } from '../version.js'\nimport type { PlaybookDefinition } from './schema.js'\n\nexport interface RunnerOptions {\n caseId?: string // attach to existing case; omit for quick-case auto-creation\n from?: number // 1-based step to resume from (default: 1)\n dryRun?: boolean // print steps, no MCP calls\n params?: Record<string, string>\n}\n\n/** Sleep for ms milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** Check if an error is a timeout/abort error. */\nfunction isTimeoutError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ECONNRESET'\n}\n\n/** Check if an error is a payment failure. */\nfunction isPaymentError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n const msg = err.message.toLowerCase()\n // Match HTTP 402 status more precisely, or x402-specific error signals\n return msg.includes('http 402') ||\n msg.includes('status 402') ||\n msg.includes('payment required') ||\n msg.includes('x402')\n}\n\n/**\n * Call an MCP tool with retry logic on timeout (up to 3 total attempts).\n * Returns the text result or throws on non-retryable error.\n */\nasync function callWithRetry(\n client: Client,\n toolName: string,\n params: Record<string, string>\n): Promise<string> {\n const MAX_ATTEMPTS = 3\n let lastErr: unknown\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const result = await client.callTool({ name: toolName, arguments: params })\n const content = result.content as Array<{ type: string; text?: string }>\n return content.filter(c => c.type === 'text').map(c => c.text ?? '').join('\\n')\n } catch (err) {\n if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {\n lastErr = err\n await sleep(1000)\n continue\n }\n throw err\n }\n }\n\n throw lastErr\n}\n\nasync function validateStepTools(client: Client, steps: PlaybookDefinition['steps']): Promise<void> {\n const result = await client.listTools()\n const available = new Set(result.tools.map(tool => tool.name))\n const missing = [...new Set(steps.map(step => step.tool).filter(tool => !available.has(tool)))]\n if (missing.length === 0) return\n\n const availableList = [...available].sort().join(', ') || 'none'\n throw new Error(\n `Unknown MCP tool(s) in playbook: ${missing.join(', ')}. ` +\n `Available tools: ${availableList}. Run \\`chain-insights mcp tools --refresh\\` to inspect the live MCP schema.`\n )\n}\n\nexport const PlaybookRunner = {\n /**\n * Execute a playbook definition step-by-step against the live MCP.\n *\n * @param playbook - Parsed and validated PlaybookDefinition\n * @param opts - Runner options (caseId, from, dryRun, params)\n */\n async run(playbook: PlaybookDefinition, opts: RunnerOptions): Promise<void> {\n const startIndex = (opts.from ?? 1) - 1 // convert 1-based to 0-based\n const stepsToRun = playbook.steps.slice(startIndex)\n const totalSteps = playbook.steps.length\n\n // --- DRY RUN ---\n if (opts.dryRun) {\n console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`)\n console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`)\n console.log('')\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`)\n }\n console.log('')\n console.log('Cost: unknown (MCP pricing not available without live connection)')\n return\n }\n\n // --- MCP AUTH CHECK (before case creation to avoid orphan cases) ---\n const config = await loadConfig()\n const mcpFetch = await createConfiguredMcpFetch(config)\n\n // --- CASE RESOLUTION ---\n let caseId: string\n if (opts.caseId) {\n const existingCase = await CaseStore.get(opts.caseId)\n caseId = existingCase.id\n } else {\n const newCase = await CaseStore.create({\n name: `quick-${playbook.name}-${Date.now()}`,\n tags: ['quick', 'playbook', playbook.name],\n description: `Auto-created for one-off playbook run: ${playbook.name}`,\n })\n caseId = newCase.id\n console.log(`Created quick case: ${caseId}`)\n }\n\n // --- MCP CONNECTION ---\n const client = new Client({ name: 'chain-insights-playbook', version: PACKAGE_VERSION })\n await client.connect(\n new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch })\n )\n\n let evidenceCount = 0\n\n try {\n await validateStepTools(client, stepsToRun)\n\n // --- STEP LOOP ---\n for (const step of stepsToRun) {\n console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`)\n\n let result: string\n try {\n result = await callWithRetry(client, step.tool, step.params)\n } catch (err) {\n if (isPaymentError(err)) {\n if (process.stdin.isTTY) {\n // Interactive: prompt user\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n const answer = await new Promise<string>(resolve => {\n rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve)\n })\n rl.close()\n\n if (answer.trim().toLowerCase() === 'retry') {\n result = await callWithRetry(client, step.tool, step.params)\n } else if (answer.trim().toLowerCase() === 'skip') {\n console.log(`Step ${step.index} skipped.`)\n continue\n } else {\n throw new Error(`Aborted at step ${step.index} due to payment failure.`)\n }\n } else {\n // Non-TTY: abort\n throw new Error(\n `Payment required for step ${step.index} but no interactive terminal available. ` +\n `Configure wallet with \\`chain-insights wallet import <private-key>\\`, then run \\`chain-insights wallet ready\\`. Aborting.`\n )\n }\n } else {\n // Non-payment, non-timeout MCP error — stop and report\n const completedSteps = step.index - 1 - startIndex\n const completedMsg = completedSteps > 0\n ? `Completed: steps ${startIndex + 1}..${step.index - 1}.`\n : 'No steps completed before failure.'\n console.error(\n `Step ${step.index} failed: ${(err as Error).message}. ` +\n `${completedMsg} Run with --from ${step.index} to resume.`\n )\n throw err\n }\n }\n\n // --- STORE EVIDENCE ---\n await EvidenceStore.append(caseId, {\n source: step.tool,\n content: result,\n queryParams: JSON.stringify(step.params),\n })\n evidenceCount++\n console.log(` (${result.length} chars stored)`)\n }\n\n // --- AUTO-VIZ for trace-funds ---\n if (playbook.name === 'trace-funds') {\n try {\n const viz = await generateVisualization({ caseId })\n console.log(`Visualization generated: ${viz.htmlPath}`)\n } catch {\n console.log('No transaction data to visualize.')\n }\n }\n\n // --- FINAL SUMMARY ---\n console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`)\n } finally {\n await client.close()\n }\n },\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  const require_version = require("./version-CO9Or_YV.cjs");
2
- const require_config = require("./config-BwVx19Og.cjs");
2
+ const require_config = require("./config-CkW404Cs.cjs");
3
3
  const require_client = require("./client-Y_zqKqJT.cjs");
4
4
  const require_viz = require("./viz-D1620cBX.cjs");
5
5
  const require_store = require("./store-CQhU8dz8.cjs");
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { i as validateMcpEndpoint, n as LOCAL_LEGACY_MCP_ENDPOINT, t as LOCAL_GRAPH_MCP_ENDPOINT } from "./mcp-endpoint-DHs1cRFH.mjs";
2
+ import { i as validateMcpEndpoint, n as LOCAL_LEGACY_MCP_ENDPOINT, t as LOCAL_GRAPH_MCP_ENDPOINT } from "./mcp-endpoint-QQ5Lbqc2.mjs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
5
  import * as z from "zod";
@@ -57,4 +57,4 @@ const CONFIG_KEYS = [
57
57
  //#endregion
58
58
  export { parseInvestigatorConfig as n, schema_exports as r, DEFAULT_CONFIG as t };
59
59
 
60
- //# sourceMappingURL=schema-BFEWhzg7.mjs.map
60
+ //# sourceMappingURL=schema-D_qwaQA5.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-BFEWhzg7.mjs","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":["import * as z from 'zod'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT, LOCAL_LEGACY_MCP_ENDPOINT, validateMcpEndpoint } from './mcp-endpoint.js'\n\nfunction endpointSchema(key: 'mcpEndpoint' | 'graphMcpEndpoint') {\n return z.string().transform((value, ctx) => {\n try {\n return validateMcpEndpoint(value, key)\n } catch (err) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: (err as Error).message,\n })\n return z.NEVER\n }\n })\n}\n\nexport const ConfigSchema = z.object({\n mcpEndpoint: endpointSchema('mcpEndpoint').default(LOCAL_LEGACY_MCP_ENDPOINT),\n mcpAuthToken: z.string().optional(),\n graphMcpEndpoint: endpointSchema('graphMcpEndpoint').default(LOCAL_GRAPH_MCP_ENDPOINT),\n graphMcpAuthToken: z.string().optional(),\n graphMcpMode: z.enum(['paid', 'debug']).default('paid'),\n walletAddress: z.string().optional(),\n serverPort: z.number().int().min(1024).max(65535).default(4321),\n dataDir: z.string().default(path.join(os.homedir(), '.chain-insights')),\n version: z.string().default('1'),\n})\n\nexport type InvestigatorConfig = z.infer<typeof ConfigSchema>\n\nfunction formatConfigValidationError(error: z.ZodError): string {\n return error.issues.map((issue) => issue.message).join('\\n')\n}\n\nexport function parseInvestigatorConfig(input: unknown): InvestigatorConfig {\n const parsed = ConfigSchema.safeParse(input)\n if (parsed.success) return parsed.data\n throw new Error(formatConfigValidationError(parsed.error))\n}\n\nexport const DEFAULT_CONFIG: InvestigatorConfig = parseInvestigatorConfig({})\n\nexport const CONFIG_KEYS = [\n 'mcpEndpoint',\n 'mcpAuthToken',\n 'graphMcpEndpoint',\n 'graphMcpAuthToken',\n 'graphMcpMode',\n 'walletAddress',\n 'serverPort',\n 'dataDir',\n 'version',\n] as const\n\nexport type ConfigKey = typeof CONFIG_KEYS[number]\n"],"mappings":";;;;;;;;;;;;AAKA,SAAS,eAAe,KAAyC;CAC/D,OAAO,EAAE,OAAO,EAAE,WAAW,OAAO,QAAQ;EAC1C,IAAI;GACF,OAAO,oBAAoB,OAAO,GAAG;EACvC,SAAS,KAAK;GACZ,IAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB,SAAU,IAAc;GAC1B,CAAC;GACD,OAAO,EAAE;EACX;CACF,CAAC;AACH;AAEA,MAAa,eAAe,EAAE,OAAO;CACnC,aAAmB,eAAe,aAAa,EAAE,QAAQ,yBAAyB;CAClF,cAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,kBAAmB,eAAe,kBAAkB,EAAE,QAAQ,wBAAwB;CACtF,mBAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,cAAmB,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC,EAAE,QAAQ,MAAM;CAC3D,eAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,YAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,KAAK,EAAE,QAAQ,IAAI;CACrE,SAAmB,EAAE,OAAO,EAAE,QAAQ,KAAK,KAAK,GAAG,QAAQ,GAAG,iBAAiB,CAAC;CAChF,SAAmB,EAAE,OAAO,EAAE,QAAQ,GAAG;AAC3C,CAAC;AAID,SAAS,4BAA4B,OAA2B;CAC9D,OAAO,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,EAAE,KAAK,IAAI;AAC7D;AAEA,SAAgB,wBAAwB,OAAoC;CAC1E,MAAM,SAAS,aAAa,UAAU,KAAK;CAC3C,IAAI,OAAO,SAAS,OAAO,OAAO;CAClC,MAAM,IAAI,MAAM,4BAA4B,OAAO,KAAK,CAAC;AAC3D;AAEA,MAAa,iBAAqC,wBAAwB,CAAC,CAAC;AAE5E,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
1
+ {"version":3,"file":"schema-D_qwaQA5.mjs","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":["import * as z from 'zod'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT, LOCAL_LEGACY_MCP_ENDPOINT, validateMcpEndpoint } from './mcp-endpoint.js'\n\nfunction endpointSchema(key: 'mcpEndpoint' | 'graphMcpEndpoint') {\n return z.string().transform((value, ctx) => {\n try {\n return validateMcpEndpoint(value, key)\n } catch (err) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: (err as Error).message,\n })\n return z.NEVER\n }\n })\n}\n\nexport const ConfigSchema = z.object({\n mcpEndpoint: endpointSchema('mcpEndpoint').default(LOCAL_LEGACY_MCP_ENDPOINT),\n mcpAuthToken: z.string().optional(),\n graphMcpEndpoint: endpointSchema('graphMcpEndpoint').default(LOCAL_GRAPH_MCP_ENDPOINT),\n graphMcpAuthToken: z.string().optional(),\n graphMcpMode: z.enum(['paid', 'debug']).default('paid'),\n walletAddress: z.string().optional(),\n serverPort: z.number().int().min(1024).max(65535).default(4321),\n dataDir: z.string().default(path.join(os.homedir(), '.chain-insights')),\n version: z.string().default('1'),\n})\n\nexport type InvestigatorConfig = z.infer<typeof ConfigSchema>\n\nfunction formatConfigValidationError(error: z.ZodError): string {\n return error.issues.map((issue) => issue.message).join('\\n')\n}\n\nexport function parseInvestigatorConfig(input: unknown): InvestigatorConfig {\n const parsed = ConfigSchema.safeParse(input)\n if (parsed.success) return parsed.data\n throw new Error(formatConfigValidationError(parsed.error))\n}\n\nexport const DEFAULT_CONFIG: InvestigatorConfig = parseInvestigatorConfig({})\n\nexport const CONFIG_KEYS = [\n 'mcpEndpoint',\n 'mcpAuthToken',\n 'graphMcpEndpoint',\n 'graphMcpAuthToken',\n 'graphMcpMode',\n 'walletAddress',\n 'serverPort',\n 'dataDir',\n 'version',\n] as const\n\nexport type ConfigKey = typeof CONFIG_KEYS[number]\n"],"mappings":";;;;;;;;;;;;AAKA,SAAS,eAAe,KAAyC;CAC/D,OAAO,EAAE,OAAO,EAAE,WAAW,OAAO,QAAQ;EAC1C,IAAI;GACF,OAAO,oBAAoB,OAAO,GAAG;EACvC,SAAS,KAAK;GACZ,IAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB,SAAU,IAAc;GAC1B,CAAC;GACD,OAAO,EAAE;EACX;CACF,CAAC;AACH;AAEA,MAAa,eAAe,EAAE,OAAO;CACnC,aAAmB,eAAe,aAAa,EAAE,QAAQ,yBAAyB;CAClF,cAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,kBAAmB,eAAe,kBAAkB,EAAE,QAAQ,wBAAwB;CACtF,mBAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,cAAmB,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC,EAAE,QAAQ,MAAM;CAC3D,eAAmB,EAAE,OAAO,EAAE,SAAS;CACvC,YAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,KAAK,EAAE,QAAQ,IAAI;CACrE,SAAmB,EAAE,OAAO,EAAE,QAAQ,KAAK,KAAK,GAAG,QAAQ,GAAG,iBAAiB,CAAC;CAChF,SAAmB,EAAE,OAAO,EAAE,QAAQ,GAAG;AAC3C,CAAC;AAID,SAAS,4BAA4B,OAA2B;CAC9D,OAAO,MAAM,OAAO,KAAK,UAAU,MAAM,OAAO,EAAE,KAAK,IAAI;AAC7D;AAEA,SAAgB,wBAAwB,OAAoC;CAC1E,MAAM,SAAS,aAAa,UAAU,KAAK;CAC3C,IAAI,OAAO,SAAS,OAAO,OAAO;CAClC,MAAM,IAAI,MAAM,4BAA4B,OAAO,KAAK,CAAC;AAC3D;AAEA,MAAa,iBAAqC,wBAAwB,CAAC,CAAC;AAE5E,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_mcp_endpoint = require("./mcp-endpoint-BaV8h_lq.cjs");
2
+ const require_mcp_endpoint = require("./mcp-endpoint-cQIZSjkK.cjs");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_chunk.__toESM(node_path, 1);
5
5
  let node_os = require("node:os");
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { t as createApp } from "./app-CRd39JJ8.mjs";
2
+ import { t as createApp } from "./app-norpwdou.mjs";
3
3
  import { serve } from "@hono/node-server";
4
4
  //#region src/server/index.ts
5
5
  var server_exports = /* @__PURE__ */ __exportAll({ startServer: () => startServer });
@@ -42,4 +42,4 @@ function startServer(port = 4321) {
42
42
  //#endregion
43
43
  export { startServer as n, server_exports as t };
44
44
 
45
- //# sourceMappingURL=server-BXLX2j_A.mjs.map
45
+ //# sourceMappingURL=server-86dyCsJO.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-BXLX2j_A.mjs","names":[],"sources":["../src/server/index.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { createApp } from './app.js'\n\nexport function startServer(port = 4321): () => void {\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1', // localhost-only — REQUIRED (default 0.0.0.0 is insecure)\n port,\n })\n\n server.on('listening', () => {\n console.log(`Chain Insights server running on http://127.0.0.1:${port}`)\n })\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n process.stderr.write(`Port already in use: 127.0.0.1:${port}\\n`)\n } else {\n process.stderr.write(`Chain Insights server failed: ${err.message}\\n`)\n }\n process.exitCode = 1\n })\n\n let stopped = false\n const stop = (callback?: () => void): void => {\n if (stopped) {\n callback?.()\n return\n }\n stopped = true\n process.off('SIGINT', onSigint)\n process.off('SIGTERM', onSigterm)\n server.close(callback)\n }\n const onSigint = () => { stop(); process.exit(0) }\n const onSigterm = () => { stop(() => process.exit(0)) }\n\n process.on('SIGINT', onSigint)\n process.on('SIGTERM', onSigterm)\n\n return stop\n}\n"],"mappings":";;;;;AAGA,SAAgB,YAAY,OAAO,MAAkB;CAEnD,MAAM,SAAS,MAAM;EACnB,OAFa,UAED,EAAE;EACd,UAAU;EACV;CACF,CAAC;CAED,OAAO,GAAG,mBAAmB;EAC3B,QAAQ,IAAI,qDAAqD,MAAM;CACzE,CAAC;CACD,OAAO,GAAG,UAAU,QAA+B;EACjD,IAAI,IAAI,SAAS,cACf,QAAQ,OAAO,MAAM,kCAAkC,KAAK,GAAG;OAE/D,QAAQ,OAAO,MAAM,iCAAiC,IAAI,QAAQ,GAAG;EAEvE,QAAQ,WAAW;CACrB,CAAC;CAED,IAAI,UAAU;CACd,MAAM,QAAQ,aAAgC;EAC5C,IAAI,SAAS;GACX,WAAW;GACX;EACF;EACA,UAAU;EACV,QAAQ,IAAI,UAAU,QAAQ;EAC9B,QAAQ,IAAI,WAAW,SAAS;EAChC,OAAO,MAAM,QAAQ;CACvB;CACA,MAAM,iBAAiB;EAAE,KAAK;EAAG,QAAQ,KAAK,CAAC;CAAE;CACjD,MAAM,kBAAkB;EAAE,WAAW,QAAQ,KAAK,CAAC,CAAC;CAAE;CAEtD,QAAQ,GAAG,UAAU,QAAQ;CAC7B,QAAQ,GAAG,WAAW,SAAS;CAE/B,OAAO;AACT"}
1
+ {"version":3,"file":"server-86dyCsJO.mjs","names":[],"sources":["../src/server/index.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { createApp } from './app.js'\n\nexport function startServer(port = 4321): () => void {\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1', // localhost-only — REQUIRED (default 0.0.0.0 is insecure)\n port,\n })\n\n server.on('listening', () => {\n console.log(`Chain Insights server running on http://127.0.0.1:${port}`)\n })\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n process.stderr.write(`Port already in use: 127.0.0.1:${port}\\n`)\n } else {\n process.stderr.write(`Chain Insights server failed: ${err.message}\\n`)\n }\n process.exitCode = 1\n })\n\n let stopped = false\n const stop = (callback?: () => void): void => {\n if (stopped) {\n callback?.()\n return\n }\n stopped = true\n process.off('SIGINT', onSigint)\n process.off('SIGTERM', onSigterm)\n server.close(callback)\n }\n const onSigint = () => { stop(); process.exit(0) }\n const onSigterm = () => { stop(() => process.exit(0)) }\n\n process.on('SIGINT', onSigint)\n process.on('SIGTERM', onSigterm)\n\n return stop\n}\n"],"mappings":";;;;;AAGA,SAAgB,YAAY,OAAO,MAAkB;CAEnD,MAAM,SAAS,MAAM;EACnB,OAFa,UAED,EAAE;EACd,UAAU;EACV;CACF,CAAC;CAED,OAAO,GAAG,mBAAmB;EAC3B,QAAQ,IAAI,qDAAqD,MAAM;CACzE,CAAC;CACD,OAAO,GAAG,UAAU,QAA+B;EACjD,IAAI,IAAI,SAAS,cACf,QAAQ,OAAO,MAAM,kCAAkC,KAAK,GAAG;OAE/D,QAAQ,OAAO,MAAM,iCAAiC,IAAI,QAAQ,GAAG;EAEvE,QAAQ,WAAW;CACrB,CAAC;CAED,IAAI,UAAU;CACd,MAAM,QAAQ,aAAgC;EAC5C,IAAI,SAAS;GACX,WAAW;GACX;EACF;EACA,UAAU;EACV,QAAQ,IAAI,UAAU,QAAQ;EAC9B,QAAQ,IAAI,WAAW,SAAS;EAChC,OAAO,MAAM,QAAQ;CACvB;CACA,MAAM,iBAAiB;EAAE,KAAK;EAAG,QAAQ,KAAK,CAAC;CAAE;CACjD,MAAM,kBAAkB;EAAE,WAAW,QAAQ,KAAK,CAAC,CAAC;CAAE;CAEtD,QAAQ,GAAG,UAAU,QAAQ;CAC7B,QAAQ,GAAG,WAAW,SAAS;CAE/B,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_app = require("./app-BxojXjtB.cjs");
2
+ const require_app = require("./app-DjJn3irw.cjs");
3
3
  let _hono_node_server = require("@hono/node-server");
4
4
  //#region src/server/index.ts
5
5
  var server_exports = /* @__PURE__ */ require_chunk.__exportAll({ startServer: () => startServer });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "AML investigation CLI and MCP proxy for blockchain risk, fund-flow tracing, case reports, and GraphRAG MCP access",
5
5
  "homepage": "https://chain-insights.ai",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-endpoint-DHs1cRFH.mjs","names":[],"sources":["../src/config/mcp-endpoint.ts"],"sourcesContent":["import { isIP } from 'node:net'\n\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]'])\n\nexport const LOCAL_GRAPH_MCP_ENDPOINT = 'http://127.0.0.1:8012/mcp'\nexport const LOCAL_LEGACY_MCP_ENDPOINT = 'http://127.0.0.1:4000'\n\nfunction isLoopbackHost(hostname: string): boolean {\n const normalized = hostname.toLowerCase().replace(/^\\[(.*)\\]$/, '$1')\n if (LOOPBACK_HOSTS.has(normalized)) return true\n if (isIP(normalized) !== 4) return false\n return normalized.split('.')[0] === '127'\n}\n\nexport function graphMcpEndpointEnvOverride(): string | undefined {\n const envEndpoint = process.env.CHAIN_INSIGHTS_GRAPH_MCP_ENDPOINT?.trim()\n || process.env.GRAPH_MCP_ENDPOINT?.trim()\n return envEndpoint ? validateMcpEndpoint(envEndpoint, 'graphMcpEndpoint') : undefined\n}\n\nexport function validateMcpEndpoint(value: string, key: string): string {\n const trimmed = value.trim()\n if (!trimmed) {\n throw new Error(`${key} must be a non-empty absolute URL.`)\n }\n\n let parsed: URL\n try {\n parsed = new URL(trimmed)\n } catch {\n throw new Error(\n `${key} must be an absolute URL (example: https://mcp.example.com/mcp or http://127.0.0.1:8012/mcp).`,\n )\n }\n\n if (parsed.username || parsed.password) {\n throw new Error(`${key} must not include URL credentials.`)\n }\n if (parsed.search || parsed.hash) {\n throw new Error(`${key} must not include query parameters or URL fragments.`)\n }\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error(`${key} must use either https:// or http://.`)\n }\n if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {\n throw new Error(\n `${key} must use https:// for remote hosts. http:// is allowed only for localhost or loopback addresses.`,\n )\n }\n\n return trimmed\n}\n"],"mappings":";;AAEA,MAAM,iBAAiB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;AAAO,CAAC;AAEzE,MAAa,2BAA2B;AACxC,MAAa,4BAA4B;AAEzC,SAAS,eAAe,UAA2B;CACjD,MAAM,aAAa,SAAS,YAAY,EAAE,QAAQ,cAAc,IAAI;CACpE,IAAI,eAAe,IAAI,UAAU,GAAG,OAAO;CAC3C,IAAI,KAAK,UAAU,MAAM,GAAG,OAAO;CACnC,OAAO,WAAW,MAAM,GAAG,EAAE,OAAO;AACtC;AAEA,SAAgB,8BAAkD;CAChE,MAAM,cAAc,QAAQ,IAAI,mCAAmC,KAAK,KACnE,QAAQ,IAAI,oBAAoB,KAAK;CAC1C,OAAO,cAAc,oBAAoB,aAAa,kBAAkB,IAAI,KAAA;AAC9E;AAEA,SAAgB,oBAAoB,OAAe,KAAqB;CACtE,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,GAAG,IAAI,mCAAmC;CAG5D,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,OAAO;CAC1B,QAAQ;EACN,MAAM,IAAI,MACR,GAAG,IAAI,8FACT;CACF;CAEA,IAAI,OAAO,YAAY,OAAO,UAC5B,MAAM,IAAI,MAAM,GAAG,IAAI,mCAAmC;CAE5D,IAAI,OAAO,UAAU,OAAO,MAC1B,MAAM,IAAI,MAAM,GAAG,IAAI,qDAAqD;CAE9E,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,SACtD,MAAM,IAAI,MAAM,GAAG,IAAI,sCAAsC;CAE/D,IAAI,OAAO,aAAa,WAAW,CAAC,eAAe,OAAO,QAAQ,GAChE,MAAM,IAAI,MACR,GAAG,IAAI,kGACT;CAGF,OAAO;AACT"}