chain-insights 0.2.31 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -14
- package/dist/cases-Cp9DUbEV.mjs +6 -0
- package/dist/{cases-c0iV-XLI.cjs → cases-sTY5aXav.cjs} +3 -3
- package/dist/cli.cjs +122 -66
- package/dist/cli.mjs +122 -66
- package/dist/cli.mjs.map +1 -1
- package/dist/{viz-Da9YWN_I.cjs → data-extractor-Cavd7wHk.cjs} +11 -34
- package/dist/{viz-DkJyqlUu.mjs → data-extractor-DZUJu1Bz.mjs} +3 -32
- package/dist/data-extractor-DZUJu1Bz.mjs.map +1 -0
- package/dist/{dossier-Br62hCG7.cjs → dossier-BXy57V4-.cjs} +13 -1
- package/dist/{dossier-Bl0NkJKC.mjs → dossier-Bjpcbcxa.mjs} +4 -2
- package/dist/{dossier-Bl0NkJKC.mjs.map → dossier-Bjpcbcxa.mjs.map} +1 -1
- package/dist/export-BqTCO9lP.mjs +591 -0
- package/dist/export-BqTCO9lP.mjs.map +1 -0
- package/dist/export-DsXgtCwO.cjs +592 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{init-DBC9Ml33.mjs → init-DLBL_nVG.mjs} +27 -1
- package/dist/{init-DBC9Ml33.mjs.map → init-DLBL_nVG.mjs.map} +1 -1
- package/dist/{init-CFaUWgjK.cjs → init-zqbd7i-_.cjs} +26 -0
- package/dist/mcp-proxy.cjs +215 -77
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +215 -77
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-BwguvIsf.cjs → public-tools-BvMb3H2P.cjs} +701 -1479
- package/dist/{public-tools-DoRNhMn9.mjs → public-tools-wJoAFDFa.mjs} +700 -1479
- package/dist/public-tools-wJoAFDFa.mjs.map +1 -0
- package/dist/{resolver-D7VBb0uB.mjs → resolver-2jXNtWQO.mjs} +12 -29
- package/dist/resolver-2jXNtWQO.mjs.map +1 -0
- package/dist/{resolver-BUU7ZgW-.cjs → resolver-CZdQwKvh.cjs} +11 -28
- package/dist/{runner-BCDeBYsR.cjs → runner-BhZ4lnF1.cjs} +2 -2
- package/dist/{runner-CTFK0Qcg.mjs → runner-DIJSbkjc.mjs} +3 -3
- package/dist/{runner-CTFK0Qcg.mjs.map → runner-DIJSbkjc.mjs.map} +1 -1
- package/dist/{selector-CTUiQrzI.mjs → selector-CF2o5gxN.mjs} +2 -2
- package/dist/{selector-CTUiQrzI.mjs.map → selector-CF2o5gxN.mjs.map} +1 -1
- package/dist/{selector-DBS2jYH4.cjs → selector-DfAMZEC9.cjs} +1 -1
- package/dist/{session-DwyikazY.cjs → session-BT7VpbAd.cjs} +13 -1
- package/dist/{session-Bha3zFrx.mjs → session-DROyhebe.mjs} +4 -2
- package/dist/{session-Bha3zFrx.mjs.map → session-DROyhebe.mjs.map} +1 -1
- package/dist/{store-BT2SCcQr.mjs → store-CTtqQtaE.mjs} +10 -4
- package/dist/{store-BT2SCcQr.mjs.map → store-CTtqQtaE.mjs.map} +1 -1
- package/dist/{store-DogLawSj.cjs → store-CqPfs47P.cjs} +37 -7
- package/dist/{tool-visibility-BHRFLXuU.mjs → tool-visibility-BpyZHRBi.mjs} +4 -2
- package/dist/tool-visibility-BpyZHRBi.mjs.map +1 -0
- package/dist/{tool-visibility-iAVQV3t0.cjs → tool-visibility-Buq7YdUZ.cjs} +3 -1
- package/dist/viz-5y24S5X1.mjs +35 -0
- package/dist/viz-5y24S5X1.mjs.map +1 -0
- package/dist/viz-Dqp3C5kb.cjs +44 -0
- package/docs/contributing.md +3 -2
- package/docs/graph-tools.md +129 -118
- package/docs/investigation-workspaces.md +14 -0
- package/docs/mcp-proxy.md +20 -2
- package/package.json +1 -1
- package/skills/chain-insights-bittensor-cypher/SKILL.md +24 -0
- package/skills/chain-insights-cypher/SKILL.md +16 -1
- package/skills/chain-insights-cypher/references/memgraph-examples.md +193 -0
- package/skills/chain-insights-developer-experience/SKILL.md +26 -6
- package/skills/chain-insights-investigation/SKILL.md +64 -48
- package/skills/chain-insights-trace-funds/SKILL.md +80 -197
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +1 -1
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +4 -4
- package/dist/cases-qjPtbnUd.mjs +0 -6
- package/dist/public-tools-DoRNhMn9.mjs.map +0 -1
- package/dist/resolver-D7VBb0uB.mjs.map +0 -1
- package/dist/tool-visibility-BHRFLXuU.mjs.map +0 -1
- package/dist/viz-DkJyqlUu.mjs.map +0 -1
|
@@ -33,36 +33,29 @@ network: {{network}}
|
|
|
33
33
|
## Step 2: Trace Funds To Exchanges
|
|
34
34
|
|
|
35
35
|
\`\`\`tool
|
|
36
|
-
|
|
36
|
+
trace_victim_funds
|
|
37
37
|
\`\`\`
|
|
38
38
|
|
|
39
39
|
\`\`\`params
|
|
40
|
-
|
|
40
|
+
victim_addresses: {{address}}
|
|
41
41
|
network: {{network}}
|
|
42
42
|
\`\`\`
|
|
43
43
|
`,
|
|
44
|
-
"
|
|
45
|
-
name:
|
|
46
|
-
description:
|
|
44
|
+
"trace-suspect-funds": `---
|
|
45
|
+
name: trace-suspect-funds
|
|
46
|
+
description: Trace suspect-controlled funds from a suspected scammer, mule, operator, or laundering-ring address
|
|
47
47
|
version: 1.0.0
|
|
48
48
|
params:
|
|
49
49
|
- name: address
|
|
50
50
|
type: string
|
|
51
51
|
required: true
|
|
52
|
-
- name: incident_timestamp_ms
|
|
53
|
-
type: string
|
|
54
|
-
required: true
|
|
55
52
|
- name: network
|
|
56
53
|
type: string
|
|
57
54
|
required: false
|
|
58
55
|
default: bittensor
|
|
59
|
-
- name: activity_policy
|
|
60
|
-
type: string
|
|
61
|
-
required: false
|
|
62
|
-
default: node_relative_only
|
|
63
56
|
---
|
|
64
57
|
|
|
65
|
-
## Step 1: Screen
|
|
58
|
+
## Step 1: Screen Suspect Address
|
|
66
59
|
|
|
67
60
|
\`\`\`tool
|
|
68
61
|
address_risk
|
|
@@ -73,28 +66,18 @@ address: {{address}}
|
|
|
73
66
|
network: {{network}}
|
|
74
67
|
\`\`\`
|
|
75
68
|
|
|
76
|
-
## Step 2:
|
|
69
|
+
## Step 2: Trace Suspect Funds
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
once, repeated targets remain as non-expanding convergence context, and
|
|
81
|
-
downstream edges must be active at or after the current node's wave-arrival
|
|
82
|
-
timestamp. Set activity_policy to global_incident_only to filter every wave
|
|
83
|
-
against incident_timestamp_ms instead. Exchange terminal safety stops exchange
|
|
84
|
-
endpoints, and label candidates are reviewable, not automatic writes.
|
|
85
|
-
Contract summary: victim-only traversal is outward from victim/source funds;
|
|
86
|
-
node-relative novelty wave by default; global_incident_only is available;
|
|
87
|
-
exchange terminal safety; scam_labels are ML-ready flags.
|
|
71
|
+
Use this when the provided address is suspect-controlled. It traces forward to
|
|
72
|
+
cashout topology and returns reviewable candidate labels, not automatic writes.
|
|
88
73
|
|
|
89
74
|
\`\`\`tool
|
|
90
|
-
|
|
75
|
+
trace_suspect_funds
|
|
91
76
|
\`\`\`
|
|
92
77
|
|
|
93
78
|
\`\`\`params
|
|
94
|
-
|
|
95
|
-
incident_timestamp_ms: {{incident_timestamp_ms}}
|
|
79
|
+
suspect_addresses: {{address}}
|
|
96
80
|
network: {{network}}
|
|
97
|
-
activity_policy: {{activity_policy}}
|
|
98
81
|
\`\`\`
|
|
99
82
|
`,
|
|
100
83
|
"risk-check": `---
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_version = require("./version-CO9Or_YV.cjs");
|
|
2
2
|
const require_config = require("./config-BwVx19Og.cjs");
|
|
3
3
|
const require_client = require("./client-Db6IV1tv.cjs");
|
|
4
|
-
const require_viz = require("./viz-
|
|
5
|
-
const require_store = require("./store-
|
|
4
|
+
const require_viz = require("./viz-Dqp3C5kb.cjs");
|
|
5
|
+
const require_store = require("./store-CqPfs47P.cjs");
|
|
6
6
|
const require_evidence = require("./evidence-CvEesemA.cjs");
|
|
7
7
|
let _modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
|
|
8
8
|
let _modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
|
|
2
2
|
import { n as loadConfig } from "./config-Drgc2HuF.mjs";
|
|
3
3
|
import { r as createConfiguredMcpFetch } from "./client-D4JE7fFF.mjs";
|
|
4
|
-
import { t as generateVisualization } from "./viz-
|
|
5
|
-
import { CaseStore } from "./store-
|
|
4
|
+
import { t as generateVisualization } from "./viz-5y24S5X1.mjs";
|
|
5
|
+
import { t as CaseStore } from "./store-CTtqQtaE.mjs";
|
|
6
6
|
import { t as EvidenceStore } from "./evidence-D96PTzOQ.mjs";
|
|
7
7
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
8
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -146,4 +146,4 @@ async run(playbook, opts) {
|
|
|
146
146
|
//#endregion
|
|
147
147
|
export { PlaybookRunner };
|
|
148
148
|
|
|
149
|
-
//# sourceMappingURL=runner-
|
|
149
|
+
//# sourceMappingURL=runner-DIJSbkjc.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-CTFK0Qcg.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-DIJSbkjc.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,4 +1,4 @@
|
|
|
1
|
-
import { CaseStore } from "./store-
|
|
1
|
+
import { t as CaseStore } from "./store-CTtqQtaE.mjs";
|
|
2
2
|
//#region src/cases/selector.ts
|
|
3
3
|
async function resolveCaseSelector(input) {
|
|
4
4
|
if (!/^[1-9]\d*$/.test(input)) return input;
|
|
@@ -9,4 +9,4 @@ async function resolveCaseSelector(input) {
|
|
|
9
9
|
//#endregion
|
|
10
10
|
export { resolveCaseSelector };
|
|
11
11
|
|
|
12
|
-
//# sourceMappingURL=selector-
|
|
12
|
+
//# sourceMappingURL=selector-CF2o5gxN.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-
|
|
1
|
+
{"version":3,"file":"selector-CF2o5gxN.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;CACxE,IAAI,CAAC,aAAa,KAAK,KAAK,GAAG,OAAO;CAItC,MAAM,YAAW,MAFG,UAAU,KAAK,GACrB,OAAO,KAAK,IAAI;CAE9B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,oBAAoB,MAAM,gDAAgD;CAE5F,OAAO,SAAS;AAClB"}
|
|
@@ -5,6 +5,7 @@ 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");
|
|
7
7
|
//#region src/cases/session.ts
|
|
8
|
+
var session_exports = /* @__PURE__ */ require_chunk.__exportAll({ SessionStore: () => SessionStore });
|
|
8
9
|
function caseDir(caseId) {
|
|
9
10
|
return node_path.default.join(require_output_root.workspaceOutputPaths().casesRoot, caseId);
|
|
10
11
|
}
|
|
@@ -112,4 +113,15 @@ const SessionStore = {
|
|
|
112
113
|
}
|
|
113
114
|
};
|
|
114
115
|
//#endregion
|
|
115
|
-
exports
|
|
116
|
+
Object.defineProperty(exports, "SessionStore", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
get: function() {
|
|
119
|
+
return SessionStore;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
Object.defineProperty(exports, "session_exports", {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
get: function() {
|
|
125
|
+
return session_exports;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
1
2
|
import { n as serializeFrontmatter, t as parseFrontmatter } from "./frontmatter-D0ccQnUM.mjs";
|
|
2
3
|
import { n as workspaceOutputPaths } from "./output-root-BRhzhhXZ.mjs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
6
|
//#region src/cases/session.ts
|
|
7
|
+
var session_exports = /* @__PURE__ */ __exportAll({ SessionStore: () => SessionStore });
|
|
6
8
|
function caseDir(caseId) {
|
|
7
9
|
return path.join(workspaceOutputPaths().casesRoot, caseId);
|
|
8
10
|
}
|
|
@@ -110,6 +112,6 @@ const SessionStore = {
|
|
|
110
112
|
}
|
|
111
113
|
};
|
|
112
114
|
//#endregion
|
|
113
|
-
export { SessionStore };
|
|
115
|
+
export { session_exports as n, SessionStore as t };
|
|
114
116
|
|
|
115
|
-
//# sourceMappingURL=session-
|
|
117
|
+
//# sourceMappingURL=session-DROyhebe.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-
|
|
1
|
+
{"version":3,"file":"session-DROyhebe.mjs","names":[],"sources":["../src/cases/session.ts"],"sourcesContent":["import { readFile, writeFile, readdir, rm } from 'node:fs/promises'\nimport path from 'node:path'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js'\nimport { type Session } from './schema.js'\n\nfunction caseDir(caseId: string): string {\n return path.join(workspaceOutputPaths().casesRoot, caseId)\n}\n\nconst MAX_SESSIONS = 5\n\nfunction sessionNumber(filename: string): number {\n return parseInt(filename.replace('session_', '').replace('.md', ''), 10)\n}\n\nfunction sessionFromFrontmatter(frontmatter: Record<string, string>): Session {\n return {\n sessionId: frontmatter['sessionId'] ?? '',\n caseId: frontmatter['caseId'] ?? '',\n startTime: frontmatter['startTime'] ?? new Date().toISOString(),\n endTime: frontmatter['endTime'] || undefined,\n status: frontmatter['status'] === 'ended' ? 'ended' : 'active',\n }\n}\n\nasync function listSessionFiles(dir: string): Promise<string[]> {\n const files = await readdir(dir)\n return files\n .filter(f => f.match(/^session_\\d+\\.md$/))\n .sort((a, b) => sessionNumber(b) - sessionNumber(a))\n}\n\nexport const SessionStore = {\n async start(caseId: string, input: { title?: string } = {}): Promise<Session> {\n const dir = caseDir(caseId)\n let sessionFiles: string[] = []\n try {\n sessionFiles = await listSessionFiles(dir)\n } catch {\n sessionFiles = []\n }\n\n for (const filename of sessionFiles) {\n const raw = await readFile(path.join(dir, filename), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n if (frontmatter['status'] !== 'ended') {\n return sessionFromFrontmatter(frontmatter)\n }\n }\n\n const seq = sessionFiles.length + 1\n const seqStr = String(seq).padStart(3, '0')\n const filename = `session_${seqStr}.md`\n const sessionId = `${caseId}_s${seqStr}`\n const now = new Date().toISOString()\n const title = input.title?.trim()\n\n const fm: Record<string, string> = {\n sessionId,\n caseId,\n startTime: now,\n endTime: '',\n status: 'active',\n }\n if (title) fm['title'] = title\n const heading = title || now.slice(0, 10)\n const body = `# Session ${seq}: ${heading}\\n\\n## Investigation Log\\n\\n## Key Findings\\n\\n## Next Steps\\n\\n`\n await writeFile(path.join(dir, filename), serializeFrontmatter(fm, body), { mode: 0o600 })\n\n return { sessionId, caseId, startTime: now, status: 'active' }\n },\n\n async end(\n caseId: string,\n input: { findings: string; nextSteps: string }\n ): Promise<void> {\n const dir = caseDir(caseId)\n const sessionFiles = await listSessionFiles(dir)\n if (sessionFiles.length === 0) throw new Error(`No active session for case ${caseId}`)\n let activeFile: string | null = null\n let activeFrontmatter: Record<string, string> | null = null\n let activeBody = ''\n for (const filename of sessionFiles) {\n const raw = await readFile(path.join(dir, filename), 'utf8')\n const { frontmatter, body } = parseFrontmatter(raw)\n if (frontmatter['status'] !== 'ended') {\n activeFile = filename\n activeFrontmatter = frontmatter\n activeBody = body\n break\n }\n }\n if (!activeFile || !activeFrontmatter) throw new Error(`No active session for case ${caseId}`)\n const now = new Date().toISOString()\n activeFrontmatter['endTime'] = now\n activeFrontmatter['status'] = 'ended'\n\n // Append findings and next steps to body\n const updatedBody = activeBody\n .replace('## Key Findings\\n', `## Key Findings\\n\\n${input.findings}\\n`)\n .replace('## Next Steps\\n', `## Next Steps\\n\\n${input.nextSteps}\\n`)\n\n await writeFile(path.join(dir, activeFile), serializeFrontmatter(activeFrontmatter, updatedBody), { mode: 0o600 })\n },\n\n async getLatest(caseId: string): Promise<{ frontmatter: Record<string, string>; body: string } | null> {\n const dir = caseDir(caseId)\n try {\n const sessionFiles = await listSessionFiles(dir)\n if (sessionFiles.length === 0) return null\n const raw = await readFile(path.join(dir, sessionFiles[0]!), 'utf8')\n return parseFrontmatter(raw)\n } catch {\n return null\n }\n },\n\n async archiveOldSessions(caseId: string): Promise<void> {\n const dir = caseDir(caseId)\n const sessionFiles = (await listSessionFiles(dir)).reverse()\n\n if (sessionFiles.length <= MAX_SESSIONS) return\n\n // Files to archive = everything beyond the 5 most recent (which are at the end)\n const toArchive = sessionFiles.slice(0, sessionFiles.length - MAX_SESSIONS)\n const historyPath = path.join(dir, 'history.md')\n\n // Pitfall 5 mitigation: ENOENT-safe read of history.md\n const existingHistory = await readFile(historyPath, 'utf8').catch(() => '')\n\n const summaries: string[] = []\n for (const filename of toArchive) {\n const raw = await readFile(path.join(dir, filename), 'utf8')\n const { frontmatter, body } = parseFrontmatter(raw)\n // Extract a one-paragraph summary: sessionId + key findings section\n const findingsMatch = body.match(/## Key Findings\\n+([\\s\\S]*?)(?:\\n## |$)/)\n const findings = findingsMatch ? findingsMatch[1]!.trim() : '(no findings recorded)'\n summaries.push(`### ${frontmatter['sessionId'] ?? filename} (${frontmatter['startTime'] ?? ''})\\n\\n${findings}\\n`)\n }\n\n const newHistory = existingHistory + '\\n' + summaries.join('\\n') + '\\n'\n await writeFile(historyPath, newHistory, { mode: 0o600 })\n\n // Delete archived session files\n for (const filename of toArchive) {\n await rm(path.join(dir, filename))\n }\n },\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
1
2
|
import { n as serializeFrontmatter, t as parseFrontmatter } from "./frontmatter-D0ccQnUM.mjs";
|
|
2
3
|
import { n as workspaceOutputPaths } from "./output-root-BRhzhhXZ.mjs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -50,6 +51,11 @@ z.object({
|
|
|
50
51
|
});
|
|
51
52
|
//#endregion
|
|
52
53
|
//#region src/cases/store.ts
|
|
54
|
+
var store_exports = /* @__PURE__ */ __exportAll({
|
|
55
|
+
CaseStore: () => CaseStore,
|
|
56
|
+
casesRoot: () => casesRoot,
|
|
57
|
+
generateCaseId: () => generateCaseId
|
|
58
|
+
});
|
|
53
59
|
const casesRoot = () => workspaceOutputPaths().casesRoot;
|
|
54
60
|
function caseDir(id) {
|
|
55
61
|
return path.join(casesRoot(), id);
|
|
@@ -190,8 +196,8 @@ const CaseStore = {
|
|
|
190
196
|
const dir = caseDir(id);
|
|
191
197
|
const { frontmatter } = parseFrontmatter(await readFile(path.join(dir, "case.md"), "utf8"));
|
|
192
198
|
const tags = (frontmatter["tags"] ?? "").split(",").filter(Boolean);
|
|
193
|
-
const { SessionStore } = await import("./session-
|
|
194
|
-
const { DossierStore } = await import("./dossier-
|
|
199
|
+
const { SessionStore } = await import("./session-DROyhebe.mjs").then((n) => n.n);
|
|
200
|
+
const { DossierStore } = await import("./dossier-Bjpcbcxa.mjs").then((n) => n.n);
|
|
195
201
|
const [latestSession, dossierSummaries, manifest] = await Promise.all([
|
|
196
202
|
SessionStore.getLatest(id),
|
|
197
203
|
DossierStore.listSummaries(id),
|
|
@@ -220,6 +226,6 @@ const CaseStore = {
|
|
|
220
226
|
}
|
|
221
227
|
};
|
|
222
228
|
//#endregion
|
|
223
|
-
export {
|
|
229
|
+
export { CaseStatusEnum as a, CaseSchema as i, generateCaseId as n, store_exports as r, CaseStore as t };
|
|
224
230
|
|
|
225
|
-
//# sourceMappingURL=store-
|
|
231
|
+
//# sourceMappingURL=store-CTtqQtaE.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-BT2SCcQr.mjs","names":["nodeErr"],"sources":["../src/cases/schema.ts","../src/cases/store.ts"],"sourcesContent":["import * as z from 'zod'\n\n// Case ID format: YYYYMMDD_NNN_slug (e.g. 20260511_001_tornado-mixer)\n// Regex rejects path traversal chars (../, shell chars) per T-03-01 threat model.\nconst caseIdRegex = /^\\d{8}_\\d{3}_[a-z0-9][a-z0-9-]*$/\n\nexport const CaseStatusEnum = z.enum(['open', 'active', 'suspended', 'closed'])\nexport type CaseStatus = z.infer<typeof CaseStatusEnum>\n\nexport const CaseSchema = z.object({\n id: z.string().regex(caseIdRegex, 'Invalid case ID format'),\n name: z.string().min(1).max(200),\n status: CaseStatusEnum.default('open'),\n created: z.string().datetime(),\n updated: z.string().datetime(),\n tags: z.array(z.string()).default([]),\n description: z.string().default(''),\n slug: z.string().optional(),\n})\nexport type Case = z.infer<typeof CaseSchema>\n\nexport const EvidenceSchema = z.object({\n id: z.string().min(1),\n caseId: z.string().regex(caseIdRegex),\n source: z.string().min(1),\n timestamp: z.string().datetime(),\n queryParams: z.string().default(''),\n})\nexport type Evidence = z.infer<typeof EvidenceSchema>\n\nexport const DossierSchema = z.object({\n address: z.string().min(1).max(100),\n type: z.enum(['eoa', 'contract', 'exchange', 'mixer', 'unknown']).default('unknown'),\n firstSeen: z.string().datetime(),\n lastSeen: z.string().datetime(),\n riskTags: z.string().default(''),\n})\nexport type Dossier = z.infer<typeof DossierSchema>\n\nexport const SessionSchema = z.object({\n sessionId: z.string().min(1),\n caseId: z.string().regex(caseIdRegex),\n startTime: z.string().datetime(),\n endTime: z.string().optional(),\n status: z.enum(['active', 'ended']).default('active'),\n})\nexport type Session = z.infer<typeof SessionSchema>\n","import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js'\nimport { CaseSchema, type Case, type CaseStatus } from './schema.js'\n\nexport const casesRoot = () => workspaceOutputPaths().casesRoot\n\nfunction caseDir(id: string): string {\n return path.join(casesRoot(), id)\n}\n\nexport function generateCaseId(name: string, existingIds: string[]): string {\n const date = new Date().toISOString().slice(0, 10).replace(/-/g, '')\n const slug = name.toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 40)\n const todayNums = existingIds\n .filter(id => id.startsWith(date + '_'))\n .map(id => parseInt(id.split('_')[1] ?? '0', 10))\n .filter(n => !isNaN(n))\n const next = todayNums.length > 0 ? Math.max(...todayNums) + 1 : 1\n return `${date}_${String(next).padStart(3, '0')}_${slug}`\n}\n\nexport const CaseStore = {\n async create(input: { name: string; tags: string[]; description: string }): Promise<Case> {\n const root = casesRoot()\n await mkdir(root, { recursive: true })\n const existingIds = await readdir(root).catch(() => [])\n const id = generateCaseId(input.name, existingIds)\n const slug = id.split('_').slice(2).join('_')\n const now = new Date().toISOString()\n const tags = input.tags\n\n const dir = caseDir(id)\n await mkdir(path.join(dir, 'evidence'), { recursive: true })\n await mkdir(path.join(dir, 'dossiers'), { recursive: true })\n\n const fm: Record<string, string> = {\n id,\n name: input.name,\n status: 'open',\n created: now,\n updated: now,\n tags: tags.join(','),\n description: input.description,\n slug,\n }\n const body = [\n `# ${input.name}`,\n '',\n `Opened: ${now}`,\n `Status: open`,\n '',\n '## Question',\n '',\n input.description || 'TBD',\n '',\n '## Current Assessment',\n '',\n 'TBD',\n '',\n '## Top Findings',\n '',\n '| Finding | Confidence | Evidence |',\n '|---|---:|---|',\n '',\n '## Next Actions',\n '',\n '- TBD',\n '',\n '## Reports',\n '',\n ].join('\\n')\n await writeFile(path.join(dir, 'case.md'), serializeFrontmatter(fm, body), { mode: 0o600 })\n\n const manifest = JSON.stringify({ caseId: id, entries: [] }, null, 2) + '\\n'\n await writeFile(path.join(dir, 'manifest.json'), manifest, { mode: 0o600 })\n\n return CaseSchema.parse({ id, name: input.name, status: 'open', created: now, updated: now, tags, description: input.description, slug })\n },\n\n async setStatus(id: string, status: CaseStatus): Promise<Case> {\n const dir = caseDir(id)\n const filePath = path.join(dir, 'case.md')\n const raw = await readFile(filePath, 'utf8')\n const { frontmatter, body } = parseFrontmatter(raw)\n const now = new Date().toISOString()\n frontmatter['status'] = status\n frontmatter['updated'] = now\n await writeFile(filePath, serializeFrontmatter(frontmatter, body), { mode: 0o600 })\n\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n return CaseSchema.parse({\n id,\n name: frontmatter['name'] ?? '',\n status,\n created: frontmatter['created'] ?? now,\n updated: now,\n tags,\n description: frontmatter['description'] ?? '',\n })\n },\n\n async list(): Promise<Array<{ id: string; name: string; status: string }>> {\n const root = casesRoot()\n try {\n const ids = await readdir(root)\n const cases: Array<{ id: string; name: string; status: string; created: string }> = []\n for (const id of ids) {\n try {\n const raw = await readFile(path.join(caseDir(id), 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n cases.push({\n id,\n name: frontmatter['name'] ?? id,\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? '',\n })\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code !== 'ENOENT' && nodeErr.code !== 'ENOTDIR') throw err\n }\n }\n return cases\n .sort((a, b) => b.created.localeCompare(a.created) || b.id.localeCompare(a.id))\n .map(({ id, name, status }) => ({ id, name, status }))\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code === 'ENOENT') return []\n throw err\n }\n },\n\n async get(id: string): Promise<Case> {\n const dir = caseDir(id)\n const raw = await readFile(path.join(dir, 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n return CaseSchema.parse({\n id,\n name: frontmatter['name'] ?? '',\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? new Date().toISOString(),\n updated: frontmatter['updated'] ?? new Date().toISOString(),\n tags,\n description: frontmatter['description'] ?? '',\n })\n },\n\n async loadContext(id: string): Promise<{\n case: { id: string; name: string; status: string; created: string; updated: string; tags: string[] };\n lastSession: { sessionId: string; startTime: string; endTime?: string; body: string } | null;\n dossierSummaries: Array<{ address: string; type: string; riskTags: string; firstSeen: string; lastSeen: string }>;\n evidenceCount: number;\n }> {\n const dir = caseDir(id)\n\n // Read case.md\n const raw = await readFile(path.join(dir, 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n\n // Lazy imports to avoid circular deps\n const { SessionStore } = await import('./session.js')\n const { DossierStore } = await import('./dossier.js')\n\n const [latestSession, dossierSummaries, manifest] = await Promise.all([\n SessionStore.getLatest(id),\n DossierStore.listSummaries(id),\n readFile(path.join(dir, 'manifest.json'), 'utf8').catch(() => '{\"entries\":[]}'),\n ])\n\n const manifestData = JSON.parse(manifest) as { entries: unknown[] }\n const evidenceCount = manifestData.entries.length\n\n const lastSession = latestSession\n ? {\n sessionId: latestSession.frontmatter['sessionId'] ?? '',\n startTime: latestSession.frontmatter['startTime'] ?? '',\n endTime: latestSession.frontmatter['endTime'] || undefined,\n body: latestSession.body,\n }\n : null\n\n return {\n case: {\n id,\n name: frontmatter['name'] ?? '',\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? '',\n updated: frontmatter['updated'] ?? '',\n tags,\n },\n lastSession,\n dossierSummaries,\n evidenceCount,\n }\n },\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store-CTtqQtaE.mjs","names":["nodeErr"],"sources":["../src/cases/schema.ts","../src/cases/store.ts"],"sourcesContent":["import * as z from 'zod'\n\n// Case ID format: YYYYMMDD_NNN_slug (e.g. 20260511_001_tornado-mixer)\n// Regex rejects path traversal chars (../, shell chars) per T-03-01 threat model.\nconst caseIdRegex = /^\\d{8}_\\d{3}_[a-z0-9][a-z0-9-]*$/\n\nexport const CaseStatusEnum = z.enum(['open', 'active', 'suspended', 'closed'])\nexport type CaseStatus = z.infer<typeof CaseStatusEnum>\n\nexport const CaseSchema = z.object({\n id: z.string().regex(caseIdRegex, 'Invalid case ID format'),\n name: z.string().min(1).max(200),\n status: CaseStatusEnum.default('open'),\n created: z.string().datetime(),\n updated: z.string().datetime(),\n tags: z.array(z.string()).default([]),\n description: z.string().default(''),\n slug: z.string().optional(),\n})\nexport type Case = z.infer<typeof CaseSchema>\n\nexport const EvidenceSchema = z.object({\n id: z.string().min(1),\n caseId: z.string().regex(caseIdRegex),\n source: z.string().min(1),\n timestamp: z.string().datetime(),\n queryParams: z.string().default(''),\n})\nexport type Evidence = z.infer<typeof EvidenceSchema>\n\nexport const DossierSchema = z.object({\n address: z.string().min(1).max(100),\n type: z.enum(['eoa', 'contract', 'exchange', 'mixer', 'unknown']).default('unknown'),\n firstSeen: z.string().datetime(),\n lastSeen: z.string().datetime(),\n riskTags: z.string().default(''),\n})\nexport type Dossier = z.infer<typeof DossierSchema>\n\nexport const SessionSchema = z.object({\n sessionId: z.string().min(1),\n caseId: z.string().regex(caseIdRegex),\n startTime: z.string().datetime(),\n endTime: z.string().optional(),\n status: z.enum(['active', 'ended']).default('active'),\n})\nexport type Session = z.infer<typeof SessionSchema>\n","import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js'\nimport { CaseSchema, type Case, type CaseStatus } from './schema.js'\n\nexport const casesRoot = () => workspaceOutputPaths().casesRoot\n\nfunction caseDir(id: string): string {\n return path.join(casesRoot(), id)\n}\n\nexport function generateCaseId(name: string, existingIds: string[]): string {\n const date = new Date().toISOString().slice(0, 10).replace(/-/g, '')\n const slug = name.toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n .slice(0, 40)\n const todayNums = existingIds\n .filter(id => id.startsWith(date + '_'))\n .map(id => parseInt(id.split('_')[1] ?? '0', 10))\n .filter(n => !isNaN(n))\n const next = todayNums.length > 0 ? Math.max(...todayNums) + 1 : 1\n return `${date}_${String(next).padStart(3, '0')}_${slug}`\n}\n\nexport const CaseStore = {\n async create(input: { name: string; tags: string[]; description: string }): Promise<Case> {\n const root = casesRoot()\n await mkdir(root, { recursive: true })\n const existingIds = await readdir(root).catch(() => [])\n const id = generateCaseId(input.name, existingIds)\n const slug = id.split('_').slice(2).join('_')\n const now = new Date().toISOString()\n const tags = input.tags\n\n const dir = caseDir(id)\n await mkdir(path.join(dir, 'evidence'), { recursive: true })\n await mkdir(path.join(dir, 'dossiers'), { recursive: true })\n\n const fm: Record<string, string> = {\n id,\n name: input.name,\n status: 'open',\n created: now,\n updated: now,\n tags: tags.join(','),\n description: input.description,\n slug,\n }\n const body = [\n `# ${input.name}`,\n '',\n `Opened: ${now}`,\n `Status: open`,\n '',\n '## Question',\n '',\n input.description || 'TBD',\n '',\n '## Current Assessment',\n '',\n 'TBD',\n '',\n '## Top Findings',\n '',\n '| Finding | Confidence | Evidence |',\n '|---|---:|---|',\n '',\n '## Next Actions',\n '',\n '- TBD',\n '',\n '## Reports',\n '',\n ].join('\\n')\n await writeFile(path.join(dir, 'case.md'), serializeFrontmatter(fm, body), { mode: 0o600 })\n\n const manifest = JSON.stringify({ caseId: id, entries: [] }, null, 2) + '\\n'\n await writeFile(path.join(dir, 'manifest.json'), manifest, { mode: 0o600 })\n\n return CaseSchema.parse({ id, name: input.name, status: 'open', created: now, updated: now, tags, description: input.description, slug })\n },\n\n async setStatus(id: string, status: CaseStatus): Promise<Case> {\n const dir = caseDir(id)\n const filePath = path.join(dir, 'case.md')\n const raw = await readFile(filePath, 'utf8')\n const { frontmatter, body } = parseFrontmatter(raw)\n const now = new Date().toISOString()\n frontmatter['status'] = status\n frontmatter['updated'] = now\n await writeFile(filePath, serializeFrontmatter(frontmatter, body), { mode: 0o600 })\n\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n return CaseSchema.parse({\n id,\n name: frontmatter['name'] ?? '',\n status,\n created: frontmatter['created'] ?? now,\n updated: now,\n tags,\n description: frontmatter['description'] ?? '',\n })\n },\n\n async list(): Promise<Array<{ id: string; name: string; status: string }>> {\n const root = casesRoot()\n try {\n const ids = await readdir(root)\n const cases: Array<{ id: string; name: string; status: string; created: string }> = []\n for (const id of ids) {\n try {\n const raw = await readFile(path.join(caseDir(id), 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n cases.push({\n id,\n name: frontmatter['name'] ?? id,\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? '',\n })\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code !== 'ENOENT' && nodeErr.code !== 'ENOTDIR') throw err\n }\n }\n return cases\n .sort((a, b) => b.created.localeCompare(a.created) || b.id.localeCompare(a.id))\n .map(({ id, name, status }) => ({ id, name, status }))\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code === 'ENOENT') return []\n throw err\n }\n },\n\n async get(id: string): Promise<Case> {\n const dir = caseDir(id)\n const raw = await readFile(path.join(dir, 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n return CaseSchema.parse({\n id,\n name: frontmatter['name'] ?? '',\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? new Date().toISOString(),\n updated: frontmatter['updated'] ?? new Date().toISOString(),\n tags,\n description: frontmatter['description'] ?? '',\n })\n },\n\n async loadContext(id: string): Promise<{\n case: { id: string; name: string; status: string; created: string; updated: string; tags: string[] };\n lastSession: { sessionId: string; startTime: string; endTime?: string; body: string } | null;\n dossierSummaries: Array<{ address: string; type: string; riskTags: string; firstSeen: string; lastSeen: string }>;\n evidenceCount: number;\n }> {\n const dir = caseDir(id)\n\n // Read case.md\n const raw = await readFile(path.join(dir, 'case.md'), 'utf8')\n const { frontmatter } = parseFrontmatter(raw)\n const tags = (frontmatter['tags'] ?? '').split(',').filter(Boolean)\n\n // Lazy imports to avoid circular deps\n const { SessionStore } = await import('./session.js')\n const { DossierStore } = await import('./dossier.js')\n\n const [latestSession, dossierSummaries, manifest] = await Promise.all([\n SessionStore.getLatest(id),\n DossierStore.listSummaries(id),\n readFile(path.join(dir, 'manifest.json'), 'utf8').catch(() => '{\"entries\":[]}'),\n ])\n\n const manifestData = JSON.parse(manifest) as { entries: unknown[] }\n const evidenceCount = manifestData.entries.length\n\n const lastSession = latestSession\n ? {\n sessionId: latestSession.frontmatter['sessionId'] ?? '',\n startTime: latestSession.frontmatter['startTime'] ?? '',\n endTime: latestSession.frontmatter['endTime'] || undefined,\n body: latestSession.body,\n }\n : null\n\n return {\n case: {\n id,\n name: frontmatter['name'] ?? '',\n status: frontmatter['status'] ?? 'open',\n created: frontmatter['created'] ?? '',\n updated: frontmatter['updated'] ?? '',\n tags,\n },\n lastSession,\n dossierSummaries,\n evidenceCount,\n }\n },\n}\n"],"mappings":"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"}
|
|
@@ -53,6 +53,11 @@ zod.object({
|
|
|
53
53
|
});
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/cases/store.ts
|
|
56
|
+
var store_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
57
|
+
CaseStore: () => CaseStore,
|
|
58
|
+
casesRoot: () => casesRoot,
|
|
59
|
+
generateCaseId: () => generateCaseId
|
|
60
|
+
});
|
|
56
61
|
const casesRoot = () => require_output_root.workspaceOutputPaths().casesRoot;
|
|
57
62
|
function caseDir(id) {
|
|
58
63
|
return node_path.default.join(casesRoot(), id);
|
|
@@ -193,8 +198,8 @@ const CaseStore = {
|
|
|
193
198
|
const dir = caseDir(id);
|
|
194
199
|
const { frontmatter } = require_frontmatter.parseFrontmatter(await (0, node_fs_promises.readFile)(node_path.default.join(dir, "case.md"), "utf8"));
|
|
195
200
|
const tags = (frontmatter["tags"] ?? "").split(",").filter(Boolean);
|
|
196
|
-
const { SessionStore } = await Promise.resolve().then(() => require("./session-
|
|
197
|
-
const { DossierStore } = await Promise.resolve().then(() => require("./dossier-
|
|
201
|
+
const { SessionStore } = await Promise.resolve().then(() => require("./session-BT7VpbAd.cjs")).then((n) => n.session_exports);
|
|
202
|
+
const { DossierStore } = await Promise.resolve().then(() => require("./dossier-BXy57V4-.cjs")).then((n) => n.dossier_exports);
|
|
198
203
|
const [latestSession, dossierSummaries, manifest] = await Promise.all([
|
|
199
204
|
SessionStore.getLatest(id),
|
|
200
205
|
DossierStore.listSummaries(id),
|
|
@@ -223,8 +228,33 @@ const CaseStore = {
|
|
|
223
228
|
}
|
|
224
229
|
};
|
|
225
230
|
//#endregion
|
|
226
|
-
exports
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
Object.defineProperty(exports, "CaseSchema", {
|
|
232
|
+
enumerable: true,
|
|
233
|
+
get: function() {
|
|
234
|
+
return CaseSchema;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
Object.defineProperty(exports, "CaseStatusEnum", {
|
|
238
|
+
enumerable: true,
|
|
239
|
+
get: function() {
|
|
240
|
+
return CaseStatusEnum;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
Object.defineProperty(exports, "CaseStore", {
|
|
244
|
+
enumerable: true,
|
|
245
|
+
get: function() {
|
|
246
|
+
return CaseStore;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
Object.defineProperty(exports, "generateCaseId", {
|
|
250
|
+
enumerable: true,
|
|
251
|
+
get: function() {
|
|
252
|
+
return generateCaseId;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
Object.defineProperty(exports, "store_exports", {
|
|
256
|
+
enumerable: true,
|
|
257
|
+
get: function() {
|
|
258
|
+
return store_exports;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
@@ -9,6 +9,8 @@ var tool_visibility_exports = /* @__PURE__ */ __exportAll({
|
|
|
9
9
|
const HIDDEN_REMOTE_TOOL_NAMES = new Set([
|
|
10
10
|
"topup",
|
|
11
11
|
"trace_funds",
|
|
12
|
+
"track_funds",
|
|
13
|
+
"scam_topology",
|
|
12
14
|
"money_flows_between_exchanges",
|
|
13
15
|
"address_connection_risk"
|
|
14
16
|
]);
|
|
@@ -20,9 +22,9 @@ function visibleRemoteTools(tools) {
|
|
|
20
22
|
}
|
|
21
23
|
function assertPublicMcpToolName(name) {
|
|
22
24
|
if (!isHiddenRemoteToolName(name)) return;
|
|
23
|
-
throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${name === "trace_funds" ? " Use track_funds instead." : ""}`);
|
|
25
|
+
throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${name === "trace_funds" ? " Use trace_victim_funds, trace_suspect_funds, or trace_deposit_sources instead." : name === "track_funds" ? " Use trace_victim_funds instead." : name === "scam_topology" ? " Use trace_suspect_funds instead." : ""}`);
|
|
24
26
|
}
|
|
25
27
|
//#endregion
|
|
26
28
|
export { tool_visibility_exports as n, HIDDEN_REMOTE_TOOL_NAMES as t };
|
|
27
29
|
|
|
28
|
-
//# sourceMappingURL=tool-visibility-
|
|
30
|
+
//# sourceMappingURL=tool-visibility-BpyZHRBi.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-visibility-BpyZHRBi.mjs","names":[],"sources":["../src/mcp/tool-visibility.ts"],"sourcesContent":["export const HIDDEN_REMOTE_TOOL_NAMES = new Set([\n 'topup',\n 'trace_funds',\n 'track_funds',\n 'scam_topology',\n 'money_flows_between_exchanges',\n 'address_connection_risk',\n])\n\nexport function isHiddenRemoteToolName(name: string): boolean {\n return HIDDEN_REMOTE_TOOL_NAMES.has(name)\n}\n\nexport function visibleRemoteTools<T extends { name: string }>(tools: T[]): T[] {\n return tools.filter((tool) => !isHiddenRemoteToolName(tool.name))\n}\n\nexport function assertPublicMcpToolName(name: string): void {\n if (!isHiddenRemoteToolName(name)) return\n const replacement = name === 'trace_funds'\n ? ' Use trace_victim_funds, trace_suspect_funds, or trace_deposit_sources instead.'\n : name === 'track_funds'\n ? ' Use trace_victim_funds instead.'\n : name === 'scam_topology'\n ? ' Use trace_suspect_funds instead.'\n : ''\n throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${replacement}`)\n}\n"],"mappings":";;;;;;;;AAAA,MAAa,2BAA2B,IAAI,IAAI;CAC9C;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAgB,uBAAuB,MAAuB;CAC5D,OAAO,yBAAyB,IAAI,IAAI;AAC1C;AAEA,SAAgB,mBAA+C,OAAiB;CAC9E,OAAO,MAAM,QAAQ,SAAS,CAAC,uBAAuB,KAAK,IAAI,CAAC;AAClE;AAEA,SAAgB,wBAAwB,MAAoB;CAC1D,IAAI,CAAC,uBAAuB,IAAI,GAAG;CAQnC,MAAM,IAAI,MAAM,aAAa,KAAK,qCAPd,SAAS,gBACzB,oFACA,SAAS,gBACP,qCACA,SAAS,kBACP,sCACA,IAC4E;AACtF"}
|
|
@@ -8,6 +8,8 @@ var tool_visibility_exports = /* @__PURE__ */ require("./chunk-DakpK96I.cjs").__
|
|
|
8
8
|
const HIDDEN_REMOTE_TOOL_NAMES = new Set([
|
|
9
9
|
"topup",
|
|
10
10
|
"trace_funds",
|
|
11
|
+
"track_funds",
|
|
12
|
+
"scam_topology",
|
|
11
13
|
"money_flows_between_exchanges",
|
|
12
14
|
"address_connection_risk"
|
|
13
15
|
]);
|
|
@@ -19,7 +21,7 @@ function visibleRemoteTools(tools) {
|
|
|
19
21
|
}
|
|
20
22
|
function assertPublicMcpToolName(name) {
|
|
21
23
|
if (!isHiddenRemoteToolName(name)) return;
|
|
22
|
-
throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${name === "trace_funds" ? " Use track_funds instead." : ""}`);
|
|
24
|
+
throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${name === "trace_funds" ? " Use trace_victim_funds, trace_suspect_funds, or trace_deposit_sources instead." : name === "track_funds" ? " Use trace_victim_funds instead." : name === "scam_topology" ? " Use trace_suspect_funds instead." : ""}`);
|
|
23
25
|
}
|
|
24
26
|
//#endregion
|
|
25
27
|
Object.defineProperty(exports, "HIDDEN_REMOTE_TOOL_NAMES", {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { r as truncateGraph } from "./data-extractor-DZUJu1Bz.mjs";
|
|
3
|
+
import { r as writeVizHtml, t as generateHtml } from "./html-generator-AowOmzyi.mjs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
//#region src/viz/index.ts
|
|
6
|
+
var viz_exports = /* @__PURE__ */ __exportAll({ generateVisualization: () => generateVisualization });
|
|
7
|
+
async function generateVisualization(opts) {
|
|
8
|
+
let rawData;
|
|
9
|
+
if (opts.dataFile) {
|
|
10
|
+
const content = await readFile(opts.dataFile, "utf-8");
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(content);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.");
|
|
16
|
+
}
|
|
17
|
+
const { extractGraphFromJson } = await import("./data-extractor-DZUJu1Bz.mjs").then((n) => n.t);
|
|
18
|
+
rawData = extractGraphFromJson(parsed);
|
|
19
|
+
} else if (opts.caseId) {
|
|
20
|
+
const { extractGraphFromCase } = await import("./data-extractor-DZUJu1Bz.mjs").then((n) => n.t);
|
|
21
|
+
const extracted = await extractGraphFromCase(opts.caseId);
|
|
22
|
+
if (extracted.nodes.length === 0) throw new Error("No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.");
|
|
23
|
+
rawData = extracted;
|
|
24
|
+
} else throw new Error("Provide either a case ID or --data <file.json>");
|
|
25
|
+
const data = truncateGraph(rawData);
|
|
26
|
+
const vizId = opts.caseId ? `${opts.caseId}_${Date.now()}` : `adhoc_${Date.now()}`;
|
|
27
|
+
return {
|
|
28
|
+
vizId,
|
|
29
|
+
htmlPath: await writeVizHtml(vizId, generateHtml(data, data.metadata.caseId ? `${data.metadata.caseId} - Money Flow` : "Ad-hoc Visualization"), opts.caseId)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { viz_exports as n, generateVisualization as t };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=viz-5y24S5X1.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viz-5y24S5X1.mjs","names":[],"sources":["../src/viz/index.ts"],"sourcesContent":["export { GraphData, GraphNode, GraphEdge, EntityType, RiskLevel, truncateGraph } from './graph-model.js'\nexport type { GraphData as GraphDataType, GraphNode as GraphNodeType, GraphEdge as GraphEdgeType } from './graph-model.js'\nexport { generateHtml, writeVizHtml, transformToGraphHtml } from './html-generator.js'\nexport { DataExtractor, extractGraphFromCase, extractGraphFromJson } from './data-extractor.js'\n\nimport { readFile } from 'node:fs/promises'\nimport { truncateGraph } from './graph-model.js'\nimport { generateHtml, writeVizHtml } from './html-generator.js'\n\nexport async function generateVisualization(opts: {\n caseId?: string\n dataFile?: string\n}): Promise<{ vizId: string; htmlPath: string }> {\n let rawData: unknown\n\n if (opts.dataFile) {\n const content = await readFile(opts.dataFile, 'utf-8')\n let parsed: unknown\n try {\n parsed = JSON.parse(content)\n } catch {\n throw new Error('Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.')\n }\n const { extractGraphFromJson } = await import('./data-extractor.js')\n rawData = extractGraphFromJson(parsed)\n } else if (opts.caseId) {\n const { extractGraphFromCase } = await import('./data-extractor.js')\n const extracted = await extractGraphFromCase(opts.caseId)\n if (extracted.nodes.length === 0) {\n throw new Error('No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.')\n }\n rawData = extracted\n } else {\n throw new Error('Provide either a case ID or --data <file.json>')\n }\n\n const data = truncateGraph(rawData as Parameters<typeof truncateGraph>[0])\n\n const vizId = opts.caseId\n ? `${opts.caseId}_${Date.now()}`\n : `adhoc_${Date.now()}`\n\n const title = data.metadata.caseId\n ? `${data.metadata.caseId} - Money Flow`\n : 'Ad-hoc Visualization'\n\n const html = generateHtml(data, title)\n const htmlPath = await writeVizHtml(vizId, html, opts.caseId)\n\n return { vizId, htmlPath }\n}\n"],"mappings":";;;;;;AASA,eAAsB,sBAAsB,MAGK;CAC/C,IAAI;CAEJ,IAAI,KAAK,UAAU;EACjB,MAAM,UAAU,MAAM,SAAS,KAAK,UAAU,OAAO;EACrD,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,OAAO;EAC7B,QAAQ;GACN,MAAM,IAAI,MAAM,kIAAkI;EACpJ;EACA,MAAM,EAAE,yBAAyB,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,CAAA;EAC9C,UAAU,qBAAqB,MAAM;CACvC,OAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,EAAE,yBAAyB,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,CAAA;EAC9C,MAAM,YAAY,MAAM,qBAAqB,KAAK,MAAM;EACxD,IAAI,UAAU,MAAM,WAAW,GAC7B,MAAM,IAAI,MAAM,6LAA6L;EAE/M,UAAU;CACZ,OACE,MAAM,IAAI,MAAM,gDAAgD;CAGlE,MAAM,OAAO,cAAc,OAA8C;CAEzE,MAAM,QAAQ,KAAK,SACf,GAAG,KAAK,OAAO,GAAG,KAAK,IAAI,MAC3B,SAAS,KAAK,IAAI;CAStB,OAAO;EAAE;EAAO,UAAA,MAFO,aAAa,OADvB,aAAa,MAJZ,KAAK,SAAS,SACxB,GAAG,KAAK,SAAS,OAAO,iBACxB,sBAG0C,GAAG,KAAK,MAAM;CAEnC;AAC3B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-DakpK96I.cjs");
|
|
2
|
+
const require_data_extractor = require("./data-extractor-Cavd7wHk.cjs");
|
|
3
|
+
const require_html_generator = require("./html-generator-Bx3UcLTB.cjs");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
//#region src/viz/index.ts
|
|
6
|
+
var viz_exports = /* @__PURE__ */ require_chunk.__exportAll({ generateVisualization: () => generateVisualization });
|
|
7
|
+
async function generateVisualization(opts) {
|
|
8
|
+
let rawData;
|
|
9
|
+
if (opts.dataFile) {
|
|
10
|
+
const content = await (0, node_fs_promises.readFile)(opts.dataFile, "utf-8");
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(content);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.");
|
|
16
|
+
}
|
|
17
|
+
const { extractGraphFromJson } = await Promise.resolve().then(() => require("./data-extractor-Cavd7wHk.cjs")).then((n) => n.data_extractor_exports);
|
|
18
|
+
rawData = extractGraphFromJson(parsed);
|
|
19
|
+
} else if (opts.caseId) {
|
|
20
|
+
const { extractGraphFromCase } = await Promise.resolve().then(() => require("./data-extractor-Cavd7wHk.cjs")).then((n) => n.data_extractor_exports);
|
|
21
|
+
const extracted = await extractGraphFromCase(opts.caseId);
|
|
22
|
+
if (extracted.nodes.length === 0) throw new Error("No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.");
|
|
23
|
+
rawData = extracted;
|
|
24
|
+
} else throw new Error("Provide either a case ID or --data <file.json>");
|
|
25
|
+
const data = require_data_extractor.truncateGraph(rawData);
|
|
26
|
+
const vizId = opts.caseId ? `${opts.caseId}_${Date.now()}` : `adhoc_${Date.now()}`;
|
|
27
|
+
return {
|
|
28
|
+
vizId,
|
|
29
|
+
htmlPath: await require_html_generator.writeVizHtml(vizId, require_html_generator.generateHtml(data, data.metadata.caseId ? `${data.metadata.caseId} - Money Flow` : "Ad-hoc Visualization"), opts.caseId)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
Object.defineProperty(exports, "generateVisualization", {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
get: function() {
|
|
36
|
+
return generateVisualization;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(exports, "viz_exports", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function() {
|
|
42
|
+
return viz_exports;
|
|
43
|
+
}
|
|
44
|
+
});
|
package/docs/contributing.md
CHANGED
|
@@ -18,8 +18,9 @@ Current AML tools live in the Chain Insights layer:
|
|
|
18
18
|
|
|
19
19
|
- `address_risk`
|
|
20
20
|
- `stake_insights`
|
|
21
|
-
- `
|
|
22
|
-
- `
|
|
21
|
+
- `trace_victim_funds`
|
|
22
|
+
- `trace_deposit_sources`
|
|
23
|
+
- `trace_suspect_funds`
|
|
23
24
|
|
|
24
25
|
When adding a tool, document:
|
|
25
26
|
|