chain-insights 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/bin/cli.js +10 -0
- package/bin/install.cjs +252 -0
- package/bin/mcp-proxy.cjs +10 -0
- package/dist/active-BSrxLKwn.mjs +50 -0
- package/dist/active-BSrxLKwn.mjs.map +1 -0
- package/dist/active-Dv7Tu-O4.cjs +68 -0
- package/dist/app-BjjuQM0B.mjs +155 -0
- package/dist/app-BjjuQM0B.mjs.map +1 -0
- package/dist/app-Dq1TdB6p.cjs +161 -0
- package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
- package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
- package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
- package/dist/assets/bg-pattern.png +0 -0
- package/dist/assets/logo.png +0 -0
- package/dist/call-args-DQA2QcRA.cjs +27 -0
- package/dist/call-args-Lk_wOJxd.mjs +29 -0
- package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
- package/dist/capabilities-CB97WMA5.cjs +83 -0
- package/dist/capabilities-DliMBim-.mjs +84 -0
- package/dist/capabilities-DliMBim-.mjs.map +1 -0
- package/dist/cases-By7INiOa.mjs +6 -0
- package/dist/cases-CDcNU91B.cjs +9 -0
- package/dist/chunk-CZWwpsFl.cjs +43 -0
- package/dist/cli.cjs +752 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +753 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-D4Bq0rp9.mjs +111 -0
- package/dist/client-D4Bq0rp9.mjs.map +1 -0
- package/dist/client-D4fZgIaO.cjs +132 -0
- package/dist/config-Bmdl5hdk.cjs +67 -0
- package/dist/config-BwrBYmiC.mjs +44 -0
- package/dist/config-BwrBYmiC.mjs.map +1 -0
- package/dist/data-extractor-BNGj7ECT.cjs +347 -0
- package/dist/data-extractor-DFzsa5CS.mjs +336 -0
- package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
- package/dist/dossier-BsroDgD3.mjs +76 -0
- package/dist/dossier-BsroDgD3.mjs.map +1 -0
- package/dist/dossier-DtxREpPm.cjs +76 -0
- package/dist/evidence-BGcdKxuV.cjs +200 -0
- package/dist/evidence-BhvFW-y_.mjs +195 -0
- package/dist/evidence-BhvFW-y_.mjs.map +1 -0
- package/dist/format-Ce1RObVl.mjs +22 -0
- package/dist/format-Ce1RObVl.mjs.map +1 -0
- package/dist/format-DOrPvXEr.cjs +20 -0
- package/dist/frontmatter-D8wWCeOa.mjs +26 -0
- package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
- package/dist/frontmatter-DgAuai7E.cjs +35 -0
- package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
- package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
- package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
- package/dist/graph-reports-C4TBjCkM.mjs +63 -0
- package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
- package/dist/graph-reports-DU05YCei.cjs +64 -0
- package/dist/html-generator-CAv81IWH.cjs +85 -0
- package/dist/html-generator-V6Bp0uRb.mjs +68 -0
- package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.d.cts +187 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +187 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9 -0
- package/dist/init-BjuFt54X.cjs +232 -0
- package/dist/init-CaOsHTIo.mjs +232 -0
- package/dist/init-CaOsHTIo.mjs.map +1 -0
- package/dist/mcp-proxy.cjs +1257 -0
- package/dist/mcp-proxy.d.cts +12 -0
- package/dist/mcp-proxy.d.cts.map +1 -0
- package/dist/mcp-proxy.d.mts +12 -0
- package/dist/mcp-proxy.d.mts.map +1 -0
- package/dist/mcp-proxy.mjs +1255 -0
- package/dist/mcp-proxy.mjs.map +1 -0
- package/dist/output-root-CFYms3ad.cjs +43 -0
- package/dist/output-root-CmWM7aV2.mjs +33 -0
- package/dist/output-root-CmWM7aV2.mjs.map +1 -0
- package/dist/parser-BUIWW1OH.cjs +182 -0
- package/dist/parser-DO0_SssG.mjs +182 -0
- package/dist/parser-DO0_SssG.mjs.map +1 -0
- package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
- package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
- package/dist/public-tools-XSpkz2ky.cjs +2556 -0
- package/dist/resolver-C2ZS7oC8.mjs +201 -0
- package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
- package/dist/resolver-zYbu4wDV.cjs +203 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/runner-1Eq55OYb.cjs +148 -0
- package/dist/runner-BhUHbiHG.mjs +149 -0
- package/dist/runner-BhUHbiHG.mjs.map +1 -0
- package/dist/schema-4XpzDFQM.cjs +55 -0
- package/dist/schema-8d0rVIdZ.mjs +37 -0
- package/dist/schema-8d0rVIdZ.mjs.map +1 -0
- package/dist/schema-cache-9CksD7tX.mjs +34 -0
- package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
- package/dist/schema-cache-CgWRCN2N.cjs +36 -0
- package/dist/selector-CkFcTXzz.cjs +10 -0
- package/dist/selector-xjm6NTHI.mjs +12 -0
- package/dist/selector-xjm6NTHI.mjs.map +1 -0
- package/dist/server-BkM5xrXb.mjs +45 -0
- package/dist/server-BkM5xrXb.mjs.map +1 -0
- package/dist/server-DXowbpfi.cjs +54 -0
- package/dist/session-BpNylyuJ.cjs +115 -0
- package/dist/session-CcTgYxsj.mjs +115 -0
- package/dist/session-CcTgYxsj.mjs.map +1 -0
- package/dist/setup-DOpKPrlx.cjs +81 -0
- package/dist/setup-DyrWHuwQ.mjs +80 -0
- package/dist/setup-DyrWHuwQ.mjs.map +1 -0
- package/dist/store-BiUhQOIf.cjs +230 -0
- package/dist/store-BoWE-Gtl.mjs +225 -0
- package/dist/store-BoWE-Gtl.mjs.map +1 -0
- package/dist/templates/graph.html +1406 -0
- package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
- package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
- package/dist/tool-visibility-CwgY205r.cjs +36 -0
- package/dist/tools-Cp2jAAAb.mjs +100 -0
- package/dist/tools-Cp2jAAAb.mjs.map +1 -0
- package/dist/tools-f_vJUZAF.cjs +139 -0
- package/dist/topup-server-BZuQifvh.cjs +940 -0
- package/dist/topup-server-DUjyFftI.mjs +919 -0
- package/dist/topup-server-DUjyFftI.mjs.map +1 -0
- package/dist/version-1gP19Lhi.mjs +8 -0
- package/dist/version-1gP19Lhi.mjs.map +1 -0
- package/dist/version-BNGtdpmH.cjs +18 -0
- package/dist/viz-BlCJe6Tk.mjs +35 -0
- package/dist/viz-BlCJe6Tk.mjs.map +1 -0
- package/dist/viz-ClezVXrJ.cjs +44 -0
- package/dist/wallet-BMelXBYP.mjs +104 -0
- package/dist/wallet-BMelXBYP.mjs.map +1 -0
- package/dist/wallet-RnvvSpV2.cjs +146 -0
- package/docs/architecture.md +145 -0
- package/docs/contributing.md +68 -0
- package/docs/debugging.md +68 -0
- package/docs/development.md +44 -0
- package/docs/graph-tools.md +251 -0
- package/docs/images/graph-mcp-iframe.png +0 -0
- package/docs/images/graph-visualization.png +0 -0
- package/docs/images/topup-page.png +0 -0
- package/docs/investigation-workspaces.md +151 -0
- package/docs/mcp-proxy.md +180 -0
- package/package.json +59 -0
- package/skills/chain-insights-developer-experience/SKILL.md +101 -0
- package/skills/chain-insights-investigation/SKILL.md +285 -0
- package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
- package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
- package/skills/chain-insights-trace-funds/SKILL.md +249 -0
- package/skills/ci-case/SKILL.md +43 -0
- package/skills/ci-status/SKILL.md +45 -0
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
- package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { n as PACKAGE_VERSION } from "./version-1gP19Lhi.mjs";
|
|
2
|
+
import { n as loadConfig } from "./config-BwrBYmiC.mjs";
|
|
3
|
+
import { n as createConfiguredMcpFetch } from "./client-D4Bq0rp9.mjs";
|
|
4
|
+
import { t as generateVisualization } from "./viz-BlCJe6Tk.mjs";
|
|
5
|
+
import { CaseStore } from "./store-BoWE-Gtl.mjs";
|
|
6
|
+
import { t as EvidenceStore } from "./evidence-BhvFW-y_.mjs";
|
|
7
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
|
+
//#region src/playbooks/runner.ts
|
|
10
|
+
/** Sleep for ms milliseconds. */
|
|
11
|
+
function sleep(ms) {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
}
|
|
14
|
+
/** Check if an error is a timeout/abort error. */
|
|
15
|
+
function isTimeoutError(err) {
|
|
16
|
+
if (!(err instanceof Error)) return false;
|
|
17
|
+
return err.name === "AbortError" || err.code === "ECONNRESET";
|
|
18
|
+
}
|
|
19
|
+
/** Check if an error is a payment failure. */
|
|
20
|
+
function isPaymentError(err) {
|
|
21
|
+
if (!(err instanceof Error)) return false;
|
|
22
|
+
const msg = err.message.toLowerCase();
|
|
23
|
+
return msg.includes("http 402") || msg.includes("status 402") || msg.includes("payment required") || msg.includes("x402");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Call an MCP tool with retry logic on timeout (up to 3 total attempts).
|
|
27
|
+
* Returns the text result or throws on non-retryable error.
|
|
28
|
+
*/
|
|
29
|
+
async function callWithRetry(client, toolName, params) {
|
|
30
|
+
const MAX_ATTEMPTS = 3;
|
|
31
|
+
let lastErr;
|
|
32
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) try {
|
|
33
|
+
return (await client.callTool({
|
|
34
|
+
name: toolName,
|
|
35
|
+
arguments: params
|
|
36
|
+
})).content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n");
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {
|
|
39
|
+
lastErr = err;
|
|
40
|
+
await sleep(1e3);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
throw lastErr;
|
|
46
|
+
}
|
|
47
|
+
async function validateStepTools(client, steps) {
|
|
48
|
+
const result = await client.listTools();
|
|
49
|
+
const available = new Set(result.tools.map((tool) => tool.name));
|
|
50
|
+
const missing = [...new Set(steps.map((step) => step.tool).filter((tool) => !available.has(tool)))];
|
|
51
|
+
if (missing.length === 0) return;
|
|
52
|
+
const availableList = [...available].sort().join(", ") || "none";
|
|
53
|
+
throw new Error(`Unknown MCP tool(s) in playbook: ${missing.join(", ")}. Available tools: ${availableList}. Run \`chain-insights mcp tools --refresh\` to inspect the live MCP schema.`);
|
|
54
|
+
}
|
|
55
|
+
const PlaybookRunner = {
|
|
56
|
+
/**
|
|
57
|
+
* Execute a playbook definition step-by-step against the live MCP.
|
|
58
|
+
*
|
|
59
|
+
* @param playbook - Parsed and validated PlaybookDefinition
|
|
60
|
+
* @param opts - Runner options (caseId, from, dryRun, params)
|
|
61
|
+
*/
|
|
62
|
+
async run(playbook, opts) {
|
|
63
|
+
const startIndex = (opts.from ?? 1) - 1;
|
|
64
|
+
const stepsToRun = playbook.steps.slice(startIndex);
|
|
65
|
+
const totalSteps = playbook.steps.length;
|
|
66
|
+
if (opts.dryRun) {
|
|
67
|
+
console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`);
|
|
68
|
+
console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`);
|
|
69
|
+
console.log("");
|
|
70
|
+
for (const step of stepsToRun) console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`);
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log("Cost: unknown (MCP pricing not available without live connection)");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const config = await loadConfig();
|
|
76
|
+
const mcpFetch = await createConfiguredMcpFetch(config);
|
|
77
|
+
let caseId;
|
|
78
|
+
if (opts.caseId) caseId = (await CaseStore.get(opts.caseId)).id;
|
|
79
|
+
else {
|
|
80
|
+
caseId = (await CaseStore.create({
|
|
81
|
+
name: `quick-${playbook.name}-${Date.now()}`,
|
|
82
|
+
tags: [
|
|
83
|
+
"quick",
|
|
84
|
+
"playbook",
|
|
85
|
+
playbook.name
|
|
86
|
+
],
|
|
87
|
+
description: `Auto-created for one-off playbook run: ${playbook.name}`
|
|
88
|
+
})).id;
|
|
89
|
+
console.log(`Created quick case: ${caseId}`);
|
|
90
|
+
}
|
|
91
|
+
const client = new Client({
|
|
92
|
+
name: "chain-insights-playbook",
|
|
93
|
+
version: PACKAGE_VERSION
|
|
94
|
+
});
|
|
95
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch }));
|
|
96
|
+
let evidenceCount = 0;
|
|
97
|
+
try {
|
|
98
|
+
await validateStepTools(client, stepsToRun);
|
|
99
|
+
for (const step of stepsToRun) {
|
|
100
|
+
console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`);
|
|
101
|
+
let result;
|
|
102
|
+
try {
|
|
103
|
+
result = await callWithRetry(client, step.tool, step.params);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (isPaymentError(err)) if (process.stdin.isTTY) {
|
|
106
|
+
const { createInterface } = await import("node:readline");
|
|
107
|
+
const rl = createInterface({
|
|
108
|
+
input: process.stdin,
|
|
109
|
+
output: process.stdout
|
|
110
|
+
});
|
|
111
|
+
const answer = await new Promise((resolve) => {
|
|
112
|
+
rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve);
|
|
113
|
+
});
|
|
114
|
+
rl.close();
|
|
115
|
+
if (answer.trim().toLowerCase() === "retry") result = await callWithRetry(client, step.tool, step.params);
|
|
116
|
+
else if (answer.trim().toLowerCase() === "skip") {
|
|
117
|
+
console.log(`Step ${step.index} skipped.`);
|
|
118
|
+
continue;
|
|
119
|
+
} else throw new Error(`Aborted at step ${step.index} due to payment failure.`);
|
|
120
|
+
} else throw new Error(`Payment required for step ${step.index} but no interactive terminal available. Configure wallet with \`chain-insights config set walletPrivateKey <key>\`. Aborting.`);
|
|
121
|
+
else {
|
|
122
|
+
const completedMsg = step.index - 1 - startIndex > 0 ? `Completed: steps ${startIndex + 1}..${step.index - 1}.` : "No steps completed before failure.";
|
|
123
|
+
console.error(`Step ${step.index} failed: ${err.message}. ${completedMsg} Run with --from ${step.index} to resume.`);
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await EvidenceStore.append(caseId, {
|
|
128
|
+
source: step.tool,
|
|
129
|
+
content: result,
|
|
130
|
+
queryParams: JSON.stringify(step.params)
|
|
131
|
+
});
|
|
132
|
+
evidenceCount++;
|
|
133
|
+
console.log(` (${result.length} chars stored)`);
|
|
134
|
+
}
|
|
135
|
+
if (playbook.name === "trace-funds") try {
|
|
136
|
+
const viz = await generateVisualization({ caseId });
|
|
137
|
+
console.log(`Visualization generated: ${viz.htmlPath}`);
|
|
138
|
+
} catch {
|
|
139
|
+
console.log("No transaction data to visualize.");
|
|
140
|
+
}
|
|
141
|
+
console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`);
|
|
142
|
+
} finally {
|
|
143
|
+
await client.close();
|
|
144
|
+
}
|
|
145
|
+
} };
|
|
146
|
+
//#endregion
|
|
147
|
+
export { PlaybookRunner };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=runner-BhUHbiHG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-BhUHbiHG.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 config set walletPrivateKey <key>\\`. 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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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
4
|
+
let node_os = require("node:os");
|
|
5
|
+
node_os = require_chunk.__toESM(node_os, 1);
|
|
6
|
+
let zod = require("zod");
|
|
7
|
+
zod = require_chunk.__toESM(zod, 1);
|
|
8
|
+
//#region src/config/schema.ts
|
|
9
|
+
var schema_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
10
|
+
CONFIG_KEYS: () => CONFIG_KEYS,
|
|
11
|
+
ConfigSchema: () => ConfigSchema,
|
|
12
|
+
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
13
|
+
});
|
|
14
|
+
const ConfigSchema = zod.object({
|
|
15
|
+
mcpEndpoint: zod.string().url().default("http://localhost:4000"),
|
|
16
|
+
mcpAuthToken: zod.string().optional(),
|
|
17
|
+
graphMcpEndpoint: zod.string().default("https://staging-mcp.chain-insights.ai/mcp"),
|
|
18
|
+
graphMcpAuthToken: zod.string().optional(),
|
|
19
|
+
graphMcpMode: zod.enum(["paid", "debug"]).default("paid"),
|
|
20
|
+
walletAddress: zod.string().optional(),
|
|
21
|
+
serverPort: zod.number().int().min(1024).max(65535).default(4321),
|
|
22
|
+
dataDir: zod.string().default(node_path.default.join(node_os.default.homedir(), ".chain-insights")),
|
|
23
|
+
version: zod.string().default("1")
|
|
24
|
+
});
|
|
25
|
+
const DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
26
|
+
const CONFIG_KEYS = [
|
|
27
|
+
"mcpEndpoint",
|
|
28
|
+
"mcpAuthToken",
|
|
29
|
+
"graphMcpEndpoint",
|
|
30
|
+
"graphMcpAuthToken",
|
|
31
|
+
"graphMcpMode",
|
|
32
|
+
"walletAddress",
|
|
33
|
+
"serverPort",
|
|
34
|
+
"dataDir",
|
|
35
|
+
"version"
|
|
36
|
+
];
|
|
37
|
+
//#endregion
|
|
38
|
+
Object.defineProperty(exports, "ConfigSchema", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function() {
|
|
41
|
+
return ConfigSchema;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(exports, "DEFAULT_CONFIG", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function() {
|
|
47
|
+
return DEFAULT_CONFIG;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(exports, "schema_exports", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function() {
|
|
53
|
+
return schema_exports;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
//#region src/config/schema.ts
|
|
6
|
+
var schema_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
CONFIG_KEYS: () => CONFIG_KEYS,
|
|
8
|
+
ConfigSchema: () => ConfigSchema,
|
|
9
|
+
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
10
|
+
});
|
|
11
|
+
const ConfigSchema = z.object({
|
|
12
|
+
mcpEndpoint: z.string().url().default("http://localhost:4000"),
|
|
13
|
+
mcpAuthToken: z.string().optional(),
|
|
14
|
+
graphMcpEndpoint: z.string().default("https://staging-mcp.chain-insights.ai/mcp"),
|
|
15
|
+
graphMcpAuthToken: z.string().optional(),
|
|
16
|
+
graphMcpMode: z.enum(["paid", "debug"]).default("paid"),
|
|
17
|
+
walletAddress: z.string().optional(),
|
|
18
|
+
serverPort: z.number().int().min(1024).max(65535).default(4321),
|
|
19
|
+
dataDir: z.string().default(path.join(os.homedir(), ".chain-insights")),
|
|
20
|
+
version: z.string().default("1")
|
|
21
|
+
});
|
|
22
|
+
const DEFAULT_CONFIG = ConfigSchema.parse({});
|
|
23
|
+
const CONFIG_KEYS = [
|
|
24
|
+
"mcpEndpoint",
|
|
25
|
+
"mcpAuthToken",
|
|
26
|
+
"graphMcpEndpoint",
|
|
27
|
+
"graphMcpAuthToken",
|
|
28
|
+
"graphMcpMode",
|
|
29
|
+
"walletAddress",
|
|
30
|
+
"serverPort",
|
|
31
|
+
"dataDir",
|
|
32
|
+
"version"
|
|
33
|
+
];
|
|
34
|
+
//#endregion
|
|
35
|
+
export { DEFAULT_CONFIG as n, schema_exports as r, ConfigSchema as t };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=schema-8d0rVIdZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-8d0rVIdZ.mjs","names":[],"sources":["../src/config/schema.ts"],"sourcesContent":["import * as z from 'zod'\nimport os from 'node:os'\nimport path from 'node:path'\n\nexport const ConfigSchema = z.object({\n mcpEndpoint: z.string().url().default('http://localhost:4000'),\n mcpAuthToken: z.string().optional(),\n graphMcpEndpoint: z.string().default('https://staging-mcp.chain-insights.ai/mcp'),\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>\nexport const DEFAULT_CONFIG: InvestigatorConfig = ConfigSchema.parse({})\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":";;;;;;;;;;AAIA,MAAa,eAAe,EAAE,OAAO;CACnC,aAAmB,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,wBAAwB;CACpE,cAAmB,EAAE,QAAQ,CAAC,UAAU;CACxC,kBAAmB,EAAE,QAAQ,CAAC,QAAQ,4CAA4C;CAClF,mBAAmB,EAAE,QAAQ,CAAC,UAAU;CACxC,cAAmB,EAAE,KAAK,CAAC,QAAQ,QAAQ,CAAC,CAAC,QAAQ,OAAO;CAC5D,eAAmB,EAAE,QAAQ,CAAC,UAAU;CACxC,YAAmB,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,KAAK;CACtE,SAAmB,EAAE,QAAQ,CAAC,QAAQ,KAAK,KAAK,GAAG,SAAS,EAAE,kBAAkB,CAAC;CACjF,SAAmB,EAAE,QAAQ,CAAC,QAAQ,IAAI;CAC3C,CAAC;AAGF,MAAa,iBAAqC,aAAa,MAAM,EAAE,CAAC;AAExE,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
//#region src/mcp/schema-cache.ts
|
|
5
|
+
const TTL_MS = 1440 * 60 * 1e3;
|
|
6
|
+
function schemaPath() {
|
|
7
|
+
return path.join(os.homedir(), ".chain-insights", "mcp-schema.json");
|
|
8
|
+
}
|
|
9
|
+
async function loadSchema(endpoint) {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await readFile(schemaPath(), "utf8");
|
|
12
|
+
const cache = JSON.parse(raw);
|
|
13
|
+
if (Date.now() - cache.cachedAt > TTL_MS) return null;
|
|
14
|
+
if (endpoint && cache.endpoint !== endpoint) return null;
|
|
15
|
+
return cache.tools;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (err.code === "ENOENT") return null;
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function saveSchema(tools, endpoint) {
|
|
22
|
+
const p = schemaPath();
|
|
23
|
+
await mkdir(path.dirname(p), { recursive: true });
|
|
24
|
+
const cache = {
|
|
25
|
+
tools,
|
|
26
|
+
cachedAt: Date.now(),
|
|
27
|
+
...endpoint ? { endpoint } : {}
|
|
28
|
+
};
|
|
29
|
+
await writeFile(p, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { loadSchema, saveSchema };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=schema-cache-9CksD7tX.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-cache-9CksD7tX.mjs","names":[],"sources":["../src/mcp/schema-cache.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\n\nconst TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\n\nexport interface McpTool {\n name: string\n title?: string\n description?: string\n inputSchema?: Record<string, unknown>\n outputSchema?: Record<string, unknown>\n _meta?: Record<string, unknown>\n}\n\ninterface SchemaCache {\n tools: McpTool[]\n cachedAt: number\n endpoint?: string\n}\n\n// Derived at call time so tests can override HOME via process.env['HOME']\nfunction schemaPath(): string {\n return path.join(os.homedir(), '.chain-insights', 'mcp-schema.json')\n}\n\nexport async function loadSchema(endpoint?: string): Promise<McpTool[] | null> {\n try {\n const raw = await readFile(schemaPath(), 'utf8')\n const cache = JSON.parse(raw) as SchemaCache // JSON parse errors propagate\n if (Date.now() - cache.cachedAt > TTL_MS) return null // TTL expired\n if (endpoint && cache.endpoint !== endpoint) return null\n return cache.tools\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw err // re-throw JSON parse or other errors\n }\n}\n\nexport async function saveSchema(tools: McpTool[], endpoint?: string): Promise<void> {\n const p = schemaPath()\n await mkdir(path.dirname(p), { recursive: true })\n const cache: SchemaCache = { tools, cachedAt: Date.now(), ...(endpoint ? { endpoint } : {}) }\n await writeFile(p, JSON.stringify(cache, null, 2) + '\\n', { mode: 0o600 })\n}\n"],"mappings":";;;;AAIA,MAAM,SAAS,OAAU,KAAK;AAkB9B,SAAS,aAAqB;AAC5B,QAAO,KAAK,KAAK,GAAG,SAAS,EAAE,mBAAmB,kBAAkB;;AAGtE,eAAsB,WAAW,UAA8C;AAC7E,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,YAAY,EAAE,OAAO;EAChD,MAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,KAAK,KAAK,GAAG,MAAM,WAAW,OAAQ,QAAO;AACjD,MAAI,YAAY,MAAM,aAAa,SAAU,QAAO;AACpD,SAAO,MAAM;UACN,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,QAAM;;;AAIV,eAAsB,WAAW,OAAkB,UAAkC;CACnF,MAAM,IAAI,YAAY;AACtB,OAAM,MAAM,KAAK,QAAQ,EAAE,EAAE,EAAE,WAAW,MAAM,CAAC;CACjD,MAAM,QAAqB;EAAE;EAAO,UAAU,KAAK,KAAK;EAAE,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;EAAG;AAC7F,OAAM,UAAU,GAAG,KAAK,UAAU,OAAO,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,KAAO,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_os = require("node:os");
|
|
6
|
+
node_os = require_chunk.__toESM(node_os, 1);
|
|
7
|
+
//#region src/mcp/schema-cache.ts
|
|
8
|
+
const TTL_MS = 1440 * 60 * 1e3;
|
|
9
|
+
function schemaPath() {
|
|
10
|
+
return node_path.default.join(node_os.default.homedir(), ".chain-insights", "mcp-schema.json");
|
|
11
|
+
}
|
|
12
|
+
async function loadSchema(endpoint) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await (0, node_fs_promises.readFile)(schemaPath(), "utf8");
|
|
15
|
+
const cache = JSON.parse(raw);
|
|
16
|
+
if (Date.now() - cache.cachedAt > TTL_MS) return null;
|
|
17
|
+
if (endpoint && cache.endpoint !== endpoint) return null;
|
|
18
|
+
return cache.tools;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err.code === "ENOENT") return null;
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function saveSchema(tools, endpoint) {
|
|
25
|
+
const p = schemaPath();
|
|
26
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(p), { recursive: true });
|
|
27
|
+
const cache = {
|
|
28
|
+
tools,
|
|
29
|
+
cachedAt: Date.now(),
|
|
30
|
+
...endpoint ? { endpoint } : {}
|
|
31
|
+
};
|
|
32
|
+
await (0, node_fs_promises.writeFile)(p, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
exports.loadSchema = loadSchema;
|
|
36
|
+
exports.saveSchema = saveSchema;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const require_store = require("./store-BiUhQOIf.cjs");
|
|
2
|
+
//#region src/cases/selector.ts
|
|
3
|
+
async function resolveCaseSelector(input) {
|
|
4
|
+
if (!/^[1-9]\d*$/.test(input)) return input;
|
|
5
|
+
const selected = (await require_store.CaseStore.list())[Number(input) - 1];
|
|
6
|
+
if (!selected) throw new Error(`No case numbered ${input}. Run \`cia case list\` to see available cases.`);
|
|
7
|
+
return selected.id;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
exports.resolveCaseSelector = resolveCaseSelector;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CaseStore } from "./store-BoWE-Gtl.mjs";
|
|
2
|
+
//#region src/cases/selector.ts
|
|
3
|
+
async function resolveCaseSelector(input) {
|
|
4
|
+
if (!/^[1-9]\d*$/.test(input)) return input;
|
|
5
|
+
const selected = (await CaseStore.list())[Number(input) - 1];
|
|
6
|
+
if (!selected) throw new Error(`No case numbered ${input}. Run \`cia case list\` to see available cases.`);
|
|
7
|
+
return selected.id;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { resolveCaseSelector };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=selector-xjm6NTHI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector-xjm6NTHI.mjs","names":[],"sources":["../src/cases/selector.ts"],"sourcesContent":["import { CaseStore } from './store.js'\n\nexport async function resolveCaseSelector(input: string): Promise<string> {\n if (!/^[1-9]\\d*$/.test(input)) return input\n\n const cases = await CaseStore.list()\n const index = Number(input) - 1\n const selected = cases[index]\n if (!selected) {\n throw new Error(`No case numbered ${input}. Run \\`cia case list\\` to see available cases.`)\n }\n return selected.id\n}\n"],"mappings":";;AAEA,eAAsB,oBAAoB,OAAgC;AACxE,KAAI,CAAC,aAAa,KAAK,MAAM,CAAE,QAAO;CAItC,MAAM,YAAW,MAFG,UAAU,MAAM,EACtB,OAAO,MAAM,GAAG;AAE9B,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,oBAAoB,MAAM,iDAAiD;AAE7F,QAAO,SAAS"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import { t as createApp } from "./app-BjjuQM0B.mjs";
|
|
3
|
+
import { serve } from "@hono/node-server";
|
|
4
|
+
//#region src/server/index.ts
|
|
5
|
+
var server_exports = /* @__PURE__ */ __exportAll({ startServer: () => startServer });
|
|
6
|
+
function startServer(port = 4321) {
|
|
7
|
+
const server = serve({
|
|
8
|
+
fetch: createApp().fetch,
|
|
9
|
+
hostname: "127.0.0.1",
|
|
10
|
+
port
|
|
11
|
+
});
|
|
12
|
+
server.on("listening", () => {
|
|
13
|
+
console.log(`Chain Insights server running on http://127.0.0.1:${port}`);
|
|
14
|
+
});
|
|
15
|
+
server.on("error", (err) => {
|
|
16
|
+
if (err.code === "EADDRINUSE") process.stderr.write(`Port already in use: 127.0.0.1:${port}\n`);
|
|
17
|
+
else process.stderr.write(`Chain Insights server failed: ${err.message}\n`);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
});
|
|
20
|
+
let stopped = false;
|
|
21
|
+
const stop = (callback) => {
|
|
22
|
+
if (stopped) {
|
|
23
|
+
callback?.();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
stopped = true;
|
|
27
|
+
process.off("SIGINT", onSigint);
|
|
28
|
+
process.off("SIGTERM", onSigterm);
|
|
29
|
+
server.close(callback);
|
|
30
|
+
};
|
|
31
|
+
const onSigint = () => {
|
|
32
|
+
stop();
|
|
33
|
+
process.exit(0);
|
|
34
|
+
};
|
|
35
|
+
const onSigterm = () => {
|
|
36
|
+
stop(() => process.exit(0));
|
|
37
|
+
};
|
|
38
|
+
process.on("SIGINT", onSigint);
|
|
39
|
+
process.on("SIGTERM", onSigterm);
|
|
40
|
+
return stop;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { startServer as n, server_exports as t };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=server-BkM5xrXb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-BkM5xrXb.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,WAEA,CAAC;EACd,UAAU;EACV;EACD,CAAC;AAEF,QAAO,GAAG,mBAAmB;AAC3B,UAAQ,IAAI,qDAAqD,OAAO;GACxE;AACF,QAAO,GAAG,UAAU,QAA+B;AACjD,MAAI,IAAI,SAAS,aACf,SAAQ,OAAO,MAAM,kCAAkC,KAAK,IAAI;MAEhE,SAAQ,OAAO,MAAM,iCAAiC,IAAI,QAAQ,IAAI;AAExE,UAAQ,WAAW;GACnB;CAEF,IAAI,UAAU;CACd,MAAM,QAAQ,aAAgC;AAC5C,MAAI,SAAS;AACX,eAAY;AACZ;;AAEF,YAAU;AACV,UAAQ,IAAI,UAAU,SAAS;AAC/B,UAAQ,IAAI,WAAW,UAAU;AACjC,SAAO,MAAM,SAAS;;CAExB,MAAM,iBAAiB;AAAE,QAAM;AAAE,UAAQ,KAAK,EAAE;;CAChD,MAAM,kBAAkB;AAAE,aAAW,QAAQ,KAAK,EAAE,CAAC;;AAErD,SAAQ,GAAG,UAAU,SAAS;AAC9B,SAAQ,GAAG,WAAW,UAAU;AAEhC,QAAO"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
const require_app = require("./app-Dq1TdB6p.cjs");
|
|
3
|
+
let _hono_node_server = require("@hono/node-server");
|
|
4
|
+
//#region src/server/index.ts
|
|
5
|
+
var server_exports = /* @__PURE__ */ require_chunk.__exportAll({ startServer: () => startServer });
|
|
6
|
+
function startServer(port = 4321) {
|
|
7
|
+
const server = (0, _hono_node_server.serve)({
|
|
8
|
+
fetch: require_app.createApp().fetch,
|
|
9
|
+
hostname: "127.0.0.1",
|
|
10
|
+
port
|
|
11
|
+
});
|
|
12
|
+
server.on("listening", () => {
|
|
13
|
+
console.log(`Chain Insights server running on http://127.0.0.1:${port}`);
|
|
14
|
+
});
|
|
15
|
+
server.on("error", (err) => {
|
|
16
|
+
if (err.code === "EADDRINUSE") process.stderr.write(`Port already in use: 127.0.0.1:${port}\n`);
|
|
17
|
+
else process.stderr.write(`Chain Insights server failed: ${err.message}\n`);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
});
|
|
20
|
+
let stopped = false;
|
|
21
|
+
const stop = (callback) => {
|
|
22
|
+
if (stopped) {
|
|
23
|
+
callback?.();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
stopped = true;
|
|
27
|
+
process.off("SIGINT", onSigint);
|
|
28
|
+
process.off("SIGTERM", onSigterm);
|
|
29
|
+
server.close(callback);
|
|
30
|
+
};
|
|
31
|
+
const onSigint = () => {
|
|
32
|
+
stop();
|
|
33
|
+
process.exit(0);
|
|
34
|
+
};
|
|
35
|
+
const onSigterm = () => {
|
|
36
|
+
stop(() => process.exit(0));
|
|
37
|
+
};
|
|
38
|
+
process.on("SIGINT", onSigint);
|
|
39
|
+
process.on("SIGTERM", onSigterm);
|
|
40
|
+
return stop;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
Object.defineProperty(exports, "server_exports", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
get: function() {
|
|
46
|
+
return server_exports;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(exports, "startServer", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
get: function() {
|
|
52
|
+
return startServer;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
const require_frontmatter = require("./frontmatter-DgAuai7E.cjs");
|
|
3
|
+
const require_output_root = require("./output-root-CFYms3ad.cjs");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
6
|
+
let node_fs_promises = require("node:fs/promises");
|
|
7
|
+
//#region src/cases/session.ts
|
|
8
|
+
function caseDir(caseId) {
|
|
9
|
+
return node_path.default.join(require_output_root.workspaceOutputPaths().casesRoot, caseId);
|
|
10
|
+
}
|
|
11
|
+
const MAX_SESSIONS = 5;
|
|
12
|
+
function sessionNumber(filename) {
|
|
13
|
+
return parseInt(filename.replace("session_", "").replace(".md", ""), 10);
|
|
14
|
+
}
|
|
15
|
+
function sessionFromFrontmatter(frontmatter) {
|
|
16
|
+
return {
|
|
17
|
+
sessionId: frontmatter["sessionId"] ?? "",
|
|
18
|
+
caseId: frontmatter["caseId"] ?? "",
|
|
19
|
+
startTime: frontmatter["startTime"] ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
20
|
+
endTime: frontmatter["endTime"] || void 0,
|
|
21
|
+
status: frontmatter["status"] === "ended" ? "ended" : "active"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function listSessionFiles(dir) {
|
|
25
|
+
return (await (0, node_fs_promises.readdir)(dir)).filter((f) => f.match(/^session_\d+\.md$/)).sort((a, b) => sessionNumber(b) - sessionNumber(a));
|
|
26
|
+
}
|
|
27
|
+
const SessionStore = {
|
|
28
|
+
async start(caseId, input = {}) {
|
|
29
|
+
const dir = caseDir(caseId);
|
|
30
|
+
let sessionFiles = [];
|
|
31
|
+
try {
|
|
32
|
+
sessionFiles = await listSessionFiles(dir);
|
|
33
|
+
} catch {
|
|
34
|
+
sessionFiles = [];
|
|
35
|
+
}
|
|
36
|
+
for (const filename of sessionFiles) {
|
|
37
|
+
const { frontmatter } = require_frontmatter.parseFrontmatter(await (0, node_fs_promises.readFile)(node_path.default.join(dir, filename), "utf8"));
|
|
38
|
+
if (frontmatter["status"] !== "ended") return sessionFromFrontmatter(frontmatter);
|
|
39
|
+
}
|
|
40
|
+
const seq = sessionFiles.length + 1;
|
|
41
|
+
const seqStr = String(seq).padStart(3, "0");
|
|
42
|
+
const filename = `session_${seqStr}.md`;
|
|
43
|
+
const sessionId = `${caseId}_s${seqStr}`;
|
|
44
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
45
|
+
const title = input.title?.trim();
|
|
46
|
+
const fm = {
|
|
47
|
+
sessionId,
|
|
48
|
+
caseId,
|
|
49
|
+
startTime: now,
|
|
50
|
+
endTime: "",
|
|
51
|
+
status: "active"
|
|
52
|
+
};
|
|
53
|
+
if (title) fm["title"] = title;
|
|
54
|
+
const body = `# Session ${seq}: ${title || now.slice(0, 10)}\n\n## Investigation Log\n\n## Key Findings\n\n## Next Steps\n\n`;
|
|
55
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(dir, filename), require_frontmatter.serializeFrontmatter(fm, body), { mode: 384 });
|
|
56
|
+
return {
|
|
57
|
+
sessionId,
|
|
58
|
+
caseId,
|
|
59
|
+
startTime: now,
|
|
60
|
+
status: "active"
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
async end(caseId, input) {
|
|
64
|
+
const dir = caseDir(caseId);
|
|
65
|
+
const sessionFiles = await listSessionFiles(dir);
|
|
66
|
+
if (sessionFiles.length === 0) throw new Error(`No active session for case ${caseId}`);
|
|
67
|
+
let activeFile = null;
|
|
68
|
+
let activeFrontmatter = null;
|
|
69
|
+
let activeBody = "";
|
|
70
|
+
for (const filename of sessionFiles) {
|
|
71
|
+
const { frontmatter, body } = require_frontmatter.parseFrontmatter(await (0, node_fs_promises.readFile)(node_path.default.join(dir, filename), "utf8"));
|
|
72
|
+
if (frontmatter["status"] !== "ended") {
|
|
73
|
+
activeFile = filename;
|
|
74
|
+
activeFrontmatter = frontmatter;
|
|
75
|
+
activeBody = body;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!activeFile || !activeFrontmatter) throw new Error(`No active session for case ${caseId}`);
|
|
80
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
|
+
activeFrontmatter["endTime"] = now;
|
|
82
|
+
activeFrontmatter["status"] = "ended";
|
|
83
|
+
const updatedBody = activeBody.replace("## Key Findings\n", `## Key Findings\n\n${input.findings}\n`).replace("## Next Steps\n", `## Next Steps\n\n${input.nextSteps}\n`);
|
|
84
|
+
await (0, node_fs_promises.writeFile)(node_path.default.join(dir, activeFile), require_frontmatter.serializeFrontmatter(activeFrontmatter, updatedBody), { mode: 384 });
|
|
85
|
+
},
|
|
86
|
+
async getLatest(caseId) {
|
|
87
|
+
const dir = caseDir(caseId);
|
|
88
|
+
try {
|
|
89
|
+
const sessionFiles = await listSessionFiles(dir);
|
|
90
|
+
if (sessionFiles.length === 0) return null;
|
|
91
|
+
return require_frontmatter.parseFrontmatter(await (0, node_fs_promises.readFile)(node_path.default.join(dir, sessionFiles[0]), "utf8"));
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
async archiveOldSessions(caseId) {
|
|
97
|
+
const dir = caseDir(caseId);
|
|
98
|
+
const sessionFiles = (await listSessionFiles(dir)).reverse();
|
|
99
|
+
if (sessionFiles.length <= MAX_SESSIONS) return;
|
|
100
|
+
const toArchive = sessionFiles.slice(0, sessionFiles.length - MAX_SESSIONS);
|
|
101
|
+
const historyPath = node_path.default.join(dir, "history.md");
|
|
102
|
+
const existingHistory = await (0, node_fs_promises.readFile)(historyPath, "utf8").catch(() => "");
|
|
103
|
+
const summaries = [];
|
|
104
|
+
for (const filename of toArchive) {
|
|
105
|
+
const { frontmatter, body } = require_frontmatter.parseFrontmatter(await (0, node_fs_promises.readFile)(node_path.default.join(dir, filename), "utf8"));
|
|
106
|
+
const findingsMatch = body.match(/## Key Findings\n+([\s\S]*?)(?:\n## |$)/);
|
|
107
|
+
const findings = findingsMatch ? findingsMatch[1].trim() : "(no findings recorded)";
|
|
108
|
+
summaries.push(`### ${frontmatter["sessionId"] ?? filename} (${frontmatter["startTime"] ?? ""})\n\n${findings}\n`);
|
|
109
|
+
}
|
|
110
|
+
await (0, node_fs_promises.writeFile)(historyPath, existingHistory + "\n" + summaries.join("\n") + "\n", { mode: 384 });
|
|
111
|
+
for (const filename of toArchive) await (0, node_fs_promises.rm)(node_path.default.join(dir, filename));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
exports.SessionStore = SessionStore;
|