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.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/bin/cli.js +10 -0
  4. package/bin/install.cjs +252 -0
  5. package/bin/mcp-proxy.cjs +10 -0
  6. package/dist/active-BSrxLKwn.mjs +50 -0
  7. package/dist/active-BSrxLKwn.mjs.map +1 -0
  8. package/dist/active-Dv7Tu-O4.cjs +68 -0
  9. package/dist/app-BjjuQM0B.mjs +155 -0
  10. package/dist/app-BjjuQM0B.mjs.map +1 -0
  11. package/dist/app-Dq1TdB6p.cjs +161 -0
  12. package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
  13. package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
  14. package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
  15. package/dist/assets/bg-pattern.png +0 -0
  16. package/dist/assets/logo.png +0 -0
  17. package/dist/call-args-DQA2QcRA.cjs +27 -0
  18. package/dist/call-args-Lk_wOJxd.mjs +29 -0
  19. package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
  20. package/dist/capabilities-CB97WMA5.cjs +83 -0
  21. package/dist/capabilities-DliMBim-.mjs +84 -0
  22. package/dist/capabilities-DliMBim-.mjs.map +1 -0
  23. package/dist/cases-By7INiOa.mjs +6 -0
  24. package/dist/cases-CDcNU91B.cjs +9 -0
  25. package/dist/chunk-CZWwpsFl.cjs +43 -0
  26. package/dist/cli.cjs +752 -0
  27. package/dist/cli.d.cts +1 -0
  28. package/dist/cli.d.mts +1 -0
  29. package/dist/cli.mjs +753 -0
  30. package/dist/cli.mjs.map +1 -0
  31. package/dist/client-D4Bq0rp9.mjs +111 -0
  32. package/dist/client-D4Bq0rp9.mjs.map +1 -0
  33. package/dist/client-D4fZgIaO.cjs +132 -0
  34. package/dist/config-Bmdl5hdk.cjs +67 -0
  35. package/dist/config-BwrBYmiC.mjs +44 -0
  36. package/dist/config-BwrBYmiC.mjs.map +1 -0
  37. package/dist/data-extractor-BNGj7ECT.cjs +347 -0
  38. package/dist/data-extractor-DFzsa5CS.mjs +336 -0
  39. package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
  40. package/dist/dossier-BsroDgD3.mjs +76 -0
  41. package/dist/dossier-BsroDgD3.mjs.map +1 -0
  42. package/dist/dossier-DtxREpPm.cjs +76 -0
  43. package/dist/evidence-BGcdKxuV.cjs +200 -0
  44. package/dist/evidence-BhvFW-y_.mjs +195 -0
  45. package/dist/evidence-BhvFW-y_.mjs.map +1 -0
  46. package/dist/format-Ce1RObVl.mjs +22 -0
  47. package/dist/format-Ce1RObVl.mjs.map +1 -0
  48. package/dist/format-DOrPvXEr.cjs +20 -0
  49. package/dist/frontmatter-D8wWCeOa.mjs +26 -0
  50. package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
  51. package/dist/frontmatter-DgAuai7E.cjs +35 -0
  52. package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
  53. package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
  54. package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
  55. package/dist/graph-reports-C4TBjCkM.mjs +63 -0
  56. package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
  57. package/dist/graph-reports-DU05YCei.cjs +64 -0
  58. package/dist/html-generator-CAv81IWH.cjs +85 -0
  59. package/dist/html-generator-V6Bp0uRb.mjs +68 -0
  60. package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
  61. package/dist/index.cjs +31 -0
  62. package/dist/index.d.cts +187 -0
  63. package/dist/index.d.cts.map +1 -0
  64. package/dist/index.d.mts +187 -0
  65. package/dist/index.d.mts.map +1 -0
  66. package/dist/index.mjs +9 -0
  67. package/dist/init-BjuFt54X.cjs +232 -0
  68. package/dist/init-CaOsHTIo.mjs +232 -0
  69. package/dist/init-CaOsHTIo.mjs.map +1 -0
  70. package/dist/mcp-proxy.cjs +1257 -0
  71. package/dist/mcp-proxy.d.cts +12 -0
  72. package/dist/mcp-proxy.d.cts.map +1 -0
  73. package/dist/mcp-proxy.d.mts +12 -0
  74. package/dist/mcp-proxy.d.mts.map +1 -0
  75. package/dist/mcp-proxy.mjs +1255 -0
  76. package/dist/mcp-proxy.mjs.map +1 -0
  77. package/dist/output-root-CFYms3ad.cjs +43 -0
  78. package/dist/output-root-CmWM7aV2.mjs +33 -0
  79. package/dist/output-root-CmWM7aV2.mjs.map +1 -0
  80. package/dist/parser-BUIWW1OH.cjs +182 -0
  81. package/dist/parser-DO0_SssG.mjs +182 -0
  82. package/dist/parser-DO0_SssG.mjs.map +1 -0
  83. package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
  84. package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
  85. package/dist/public-tools-XSpkz2ky.cjs +2556 -0
  86. package/dist/resolver-C2ZS7oC8.mjs +201 -0
  87. package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
  88. package/dist/resolver-zYbu4wDV.cjs +203 -0
  89. package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
  90. package/dist/runner-1Eq55OYb.cjs +148 -0
  91. package/dist/runner-BhUHbiHG.mjs +149 -0
  92. package/dist/runner-BhUHbiHG.mjs.map +1 -0
  93. package/dist/schema-4XpzDFQM.cjs +55 -0
  94. package/dist/schema-8d0rVIdZ.mjs +37 -0
  95. package/dist/schema-8d0rVIdZ.mjs.map +1 -0
  96. package/dist/schema-cache-9CksD7tX.mjs +34 -0
  97. package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
  98. package/dist/schema-cache-CgWRCN2N.cjs +36 -0
  99. package/dist/selector-CkFcTXzz.cjs +10 -0
  100. package/dist/selector-xjm6NTHI.mjs +12 -0
  101. package/dist/selector-xjm6NTHI.mjs.map +1 -0
  102. package/dist/server-BkM5xrXb.mjs +45 -0
  103. package/dist/server-BkM5xrXb.mjs.map +1 -0
  104. package/dist/server-DXowbpfi.cjs +54 -0
  105. package/dist/session-BpNylyuJ.cjs +115 -0
  106. package/dist/session-CcTgYxsj.mjs +115 -0
  107. package/dist/session-CcTgYxsj.mjs.map +1 -0
  108. package/dist/setup-DOpKPrlx.cjs +81 -0
  109. package/dist/setup-DyrWHuwQ.mjs +80 -0
  110. package/dist/setup-DyrWHuwQ.mjs.map +1 -0
  111. package/dist/store-BiUhQOIf.cjs +230 -0
  112. package/dist/store-BoWE-Gtl.mjs +225 -0
  113. package/dist/store-BoWE-Gtl.mjs.map +1 -0
  114. package/dist/templates/graph.html +1406 -0
  115. package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
  116. package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
  117. package/dist/tool-visibility-CwgY205r.cjs +36 -0
  118. package/dist/tools-Cp2jAAAb.mjs +100 -0
  119. package/dist/tools-Cp2jAAAb.mjs.map +1 -0
  120. package/dist/tools-f_vJUZAF.cjs +139 -0
  121. package/dist/topup-server-BZuQifvh.cjs +940 -0
  122. package/dist/topup-server-DUjyFftI.mjs +919 -0
  123. package/dist/topup-server-DUjyFftI.mjs.map +1 -0
  124. package/dist/version-1gP19Lhi.mjs +8 -0
  125. package/dist/version-1gP19Lhi.mjs.map +1 -0
  126. package/dist/version-BNGtdpmH.cjs +18 -0
  127. package/dist/viz-BlCJe6Tk.mjs +35 -0
  128. package/dist/viz-BlCJe6Tk.mjs.map +1 -0
  129. package/dist/viz-ClezVXrJ.cjs +44 -0
  130. package/dist/wallet-BMelXBYP.mjs +104 -0
  131. package/dist/wallet-BMelXBYP.mjs.map +1 -0
  132. package/dist/wallet-RnvvSpV2.cjs +146 -0
  133. package/docs/architecture.md +145 -0
  134. package/docs/contributing.md +68 -0
  135. package/docs/debugging.md +68 -0
  136. package/docs/development.md +44 -0
  137. package/docs/graph-tools.md +251 -0
  138. package/docs/images/graph-mcp-iframe.png +0 -0
  139. package/docs/images/graph-visualization.png +0 -0
  140. package/docs/images/topup-page.png +0 -0
  141. package/docs/investigation-workspaces.md +151 -0
  142. package/docs/mcp-proxy.md +180 -0
  143. package/package.json +59 -0
  144. package/skills/chain-insights-developer-experience/SKILL.md +101 -0
  145. package/skills/chain-insights-investigation/SKILL.md +285 -0
  146. package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
  147. package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
  148. package/skills/chain-insights-trace-funds/SKILL.md +249 -0
  149. package/skills/ci-case/SKILL.md +43 -0
  150. package/skills/ci-status/SKILL.md +45 -0
  151. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
  152. package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
  153. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
package/dist/cli.cjs ADDED
@@ -0,0 +1,752 @@
1
+ const require_chunk = require("./chunk-CZWwpsFl.cjs");
2
+ const require_version = require("./version-BNGtdpmH.cjs");
3
+ let commander = require("commander");
4
+ let node_child_process = require("node:child_process");
5
+ let node_url = require("node:url");
6
+ let node_path = require("node:path");
7
+ node_path = require_chunk.__toESM(node_path, 1);
8
+ //#region src/cli.ts
9
+ const __dirname$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
10
+ const installerPath = node_path.default.resolve(__dirname$1, "..", "bin", "install.cjs");
11
+ const program = new commander.Command();
12
+ program.name("chain-insights").description("AML investigation toolkit for blockchain analysis").version(require_version.PACKAGE_INFO.version).option("--claude", "Install Claude Code skills globally to ~/.claude/skills/").option("--codex", "Install Codex skills globally to ~/.codex/skills/ and register MCP").option("--hermes", "Install Hermes skills globally to ~/.hermes/skills/chain-insights/ and register MCP");
13
+ const rawArgs = process.argv.slice(2);
14
+ const installerFlags = rawArgs.filter((a) => a === "--claude" || a === "--codex" || a === "--hermes");
15
+ if (installerFlags.length > 0 && !rawArgs.some((a) => !a.startsWith("-"))) {
16
+ try {
17
+ (0, node_child_process.execFileSync)(process.execPath, [installerPath, ...installerFlags], { stdio: "inherit" });
18
+ } catch (err) {
19
+ console.error("Installation failed:", err.message);
20
+ process.exit(1);
21
+ }
22
+ process.exit(0);
23
+ }
24
+ if (rawArgs[0] === "mcp" && rawArgs[1] === "trace-funds") {
25
+ console.error("error: unknown command 'trace-funds'");
26
+ process.exit(1);
27
+ }
28
+ async function resolveCaseSelector(input) {
29
+ const { resolveCaseSelector } = await Promise.resolve().then(() => require("./selector-CkFcTXzz.cjs"));
30
+ return resolveCaseSelector(input);
31
+ }
32
+ async function scopeCasesToInvocationDir() {
33
+ if (process.env["CHAIN_INSIGHTS_CASES_ROOT"]?.trim()) return;
34
+ const { activeCasesRoot } = await Promise.resolve().then(() => require("./active-Dv7Tu-O4.cjs")).then((n) => n.active_exports);
35
+ process.env["CHAIN_INSIGHTS_CASES_ROOT"] = activeCasesRoot();
36
+ }
37
+ async function showCaseContext(caseSelector) {
38
+ const { CaseStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
39
+ const caseId = await resolveCaseSelector(caseSelector);
40
+ const ctx = await CaseStore.loadContext(caseId);
41
+ console.log(`\n=== Case: ${ctx.case.id} ===`);
42
+ console.log(`Name: ${ctx.case.name}`);
43
+ console.log(`Status: ${ctx.case.status}`);
44
+ console.log(`Tags: ${ctx.case.tags.join(", ") || "none"}`);
45
+ console.log(`Evidence files: ${ctx.evidenceCount}`);
46
+ console.log(`Dossiers: ${ctx.dossierSummaries.length}`);
47
+ if (ctx.lastSession) {
48
+ console.log(`\n--- Last Session (${ctx.lastSession.sessionId}) ---`);
49
+ console.log(ctx.lastSession.body.slice(0, 500));
50
+ } else console.log("\nNo previous sessions.");
51
+ if (ctx.dossierSummaries.length > 0) {
52
+ console.log("\n--- Entity Dossiers ---");
53
+ for (const d of ctx.dossierSummaries) console.log(` ${d.address} [${d.type}] tags: ${d.riskTags || "none"}`);
54
+ }
55
+ }
56
+ function optionalNumber(value) {
57
+ if (value === void 0) return void 0;
58
+ const parsed = Number(value);
59
+ if (!Number.isFinite(parsed)) throw new Error(`Invalid number: ${value}`);
60
+ return parsed;
61
+ }
62
+ function optionalNumberArg(value, name) {
63
+ if (value === void 0) return void 0;
64
+ if (typeof value === "number" && Number.isFinite(value)) return value;
65
+ if (typeof value === "string") return optionalNumber(value);
66
+ throw new Error(`Invalid number for ${name}: ${String(value)}`);
67
+ }
68
+ function optionalScamTopologyActivityPolicy(value) {
69
+ if (value === void 0 || value === null || value === "") return void 0;
70
+ if (value === "node_relative_only" || value === "global_incident_only") return value;
71
+ throw new Error("activity_policy must be one of: node_relative_only, global_incident_only");
72
+ }
73
+ async function withGraphMcpClient(name, fn) {
74
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
75
+ const config = await loadConfig();
76
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-D4fZgIaO.cjs")).then((n) => n.client_exports);
77
+ const paymentFetch = await createConfiguredGraphMcpFetch(config);
78
+ const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
79
+ const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
80
+ const client = new Client({
81
+ name,
82
+ version: require_version.PACKAGE_VERSION
83
+ });
84
+ await client.connect(new StreamableHTTPClientTransport(new URL(resolveGraphMcpEndpoint(config)), { fetch: paymentFetch }));
85
+ try {
86
+ return await fn(client, config);
87
+ } finally {
88
+ await client.close();
89
+ }
90
+ }
91
+ function printMcpTextContent(result) {
92
+ for (const item of result.content ?? []) if (item.type === "text") console.log(item.text);
93
+ }
94
+ async function printNetworkCapabilities(opts) {
95
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
96
+ const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-CB97WMA5.cjs"));
97
+ const document = await fetchNetworkCapabilities(await loadConfig());
98
+ if (opts.json) console.log(JSON.stringify(document, null, 2));
99
+ else console.log(formatNetworkCapabilities(document));
100
+ }
101
+ program.command("networks").alias("network").description("List supported graph networks, capability layers, retention, and freshness").option("--json", "Print raw capability JSON").action(async (opts) => {
102
+ try {
103
+ await printNetworkCapabilities(opts);
104
+ } catch (err) {
105
+ console.error(err.message);
106
+ process.exit(1);
107
+ }
108
+ });
109
+ program.command("serve").description("Start local visualization server").option("-p, --port <number>", "Port to bind (default: 4321)", "4321").action(async (opts) => {
110
+ try {
111
+ const { requireWorkspaceRoot } = await Promise.resolve().then(() => require("./output-root-CFYms3ad.cjs")).then((n) => n.output_root_exports);
112
+ const workspaceRoot = requireWorkspaceRoot();
113
+ const { startServer } = await Promise.resolve().then(() => require("./server-DXowbpfi.cjs")).then((n) => n.server_exports);
114
+ console.log(`Workspace: ${workspaceRoot}`);
115
+ startServer(parseInt(opts.port, 10));
116
+ } catch (err) {
117
+ console.error(err.message);
118
+ process.exit(1);
119
+ }
120
+ });
121
+ program.command("status").description("Show toolkit status and configuration").action(async () => {
122
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
123
+ const { findActiveWorkspace, activeDataDir } = await Promise.resolve().then(() => require("./active-Dv7Tu-O4.cjs")).then((n) => n.active_exports);
124
+ const config = await loadConfig();
125
+ const workspace = findActiveWorkspace();
126
+ const graphMcpStatus = config.graphMcpMode === "debug" && config.graphMcpAuthToken?.trim() ? "bearer access mode" : `${config.graphMcpMode} mode`;
127
+ console.log("Config: ", activeDataDir(config.dataDir));
128
+ if (workspace) console.log("Workspace:", workspace.root);
129
+ console.log("Server: ", `http://127.0.0.1:${config.serverPort}`);
130
+ console.log("Graph MCP:", graphMcpStatus);
131
+ console.log("Graph endpoint:", config.graphMcpEndpoint);
132
+ });
133
+ program.command("debug").description("Configure Graph MCP debug mode").addCommand(new commander.Command("on").description("Enable Graph MCP debug mode without x402 payments").requiredOption("--token <token>", "Debug bearer token").option("--endpoint <url>", "Graph MCP endpoint").action(async (opts) => {
134
+ try {
135
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
136
+ await saveConfig({
137
+ graphMcpMode: "debug",
138
+ graphMcpAuthToken: opts.token,
139
+ ...opts.endpoint ? { graphMcpEndpoint: opts.endpoint } : {}
140
+ });
141
+ console.log("Graph MCP debug mode enabled");
142
+ if (opts.endpoint) console.log(`Graph endpoint: ${opts.endpoint}`);
143
+ console.log("Payments: disabled for Graph MCP calls");
144
+ } catch (err) {
145
+ console.error(err.message);
146
+ process.exit(1);
147
+ }
148
+ })).addCommand(new commander.Command("off").description("Disable Graph MCP debug mode and use paid x402 calls").action(async () => {
149
+ try {
150
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
151
+ await saveConfig({
152
+ graphMcpMode: "paid",
153
+ graphMcpAuthToken: ""
154
+ });
155
+ console.log("Graph MCP debug mode disabled");
156
+ console.log("Payments: enabled for Graph MCP calls");
157
+ } catch (err) {
158
+ console.error(err.message);
159
+ process.exit(1);
160
+ }
161
+ })).addCommand(new commander.Command("status").description("Show Graph MCP payment/debug mode").action(async () => {
162
+ try {
163
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
164
+ const config = await loadConfig();
165
+ console.log(`Graph MCP mode: ${config.graphMcpMode}`);
166
+ console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
167
+ console.log(`Debug token: ${config.graphMcpAuthToken?.trim() ? "configured" : "not configured"}`);
168
+ console.log(`Payments: ${config.graphMcpMode === "debug" ? "disabled" : "enabled"}`);
169
+ } catch (err) {
170
+ console.error(err.message);
171
+ process.exit(1);
172
+ }
173
+ }));
174
+ program.command("access-key").description("Configure Graph MCP test access key mode").addCommand(new commander.Command("set").description("Use a Graph MCP test access key without x402 payments").argument("<key>", "Test access key").option("--endpoint <url>", "Graph MCP endpoint").action(async (key, opts) => {
175
+ try {
176
+ const normalizedKey = key.trim();
177
+ if (!normalizedKey) throw new Error("Test access key is required");
178
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
179
+ await saveConfig({
180
+ graphMcpMode: "debug",
181
+ graphMcpAuthToken: normalizedKey,
182
+ ...opts.endpoint ? { graphMcpEndpoint: opts.endpoint } : {}
183
+ });
184
+ console.log("Graph MCP test access key configured");
185
+ if (opts.endpoint) console.log(`Graph endpoint: ${opts.endpoint}`);
186
+ console.log("Payments: disabled when the server accepts this key");
187
+ } catch (err) {
188
+ console.error(err.message);
189
+ process.exit(1);
190
+ }
191
+ })).addCommand(new commander.Command("clear").description("Remove the Graph MCP test access key and use paid x402 calls").action(async () => {
192
+ try {
193
+ const { saveConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
194
+ await saveConfig({
195
+ graphMcpMode: "paid",
196
+ graphMcpAuthToken: ""
197
+ });
198
+ console.log("Graph MCP test access key cleared");
199
+ console.log("Payments: enabled for Graph MCP calls");
200
+ } catch (err) {
201
+ console.error(err.message);
202
+ process.exit(1);
203
+ }
204
+ })).addCommand(new commander.Command("status").description("Show Graph MCP test access key status").action(async () => {
205
+ try {
206
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
207
+ const config = await loadConfig();
208
+ console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
209
+ console.log(`Access key: ${config.graphMcpAuthToken?.trim() ? "configured" : "not configured"}`);
210
+ console.log(`Payments: ${config.graphMcpAuthToken?.trim() ? "disabled when accepted by server" : "enabled"}`);
211
+ } catch (err) {
212
+ console.error(err.message);
213
+ process.exit(1);
214
+ }
215
+ }));
216
+ program.command("init").description("Initialize an investigation workspace").argument("[dir]", "Workspace directory to initialize", ".").option("--force", "Overwrite existing workspace files").action(async (dir, opts) => {
217
+ try {
218
+ const { initWorkspace } = await Promise.resolve().then(() => require("./init-BjuFt54X.cjs"));
219
+ const result = await initWorkspace({
220
+ targetDir: dir,
221
+ force: opts.force
222
+ });
223
+ console.log(`Workspace initialized: ${result.workspaceRoot}`);
224
+ console.log(`Files written: ${result.filesWritten.length}`);
225
+ } catch (err) {
226
+ console.error(err.message);
227
+ process.exit(1);
228
+ }
229
+ });
230
+ program.command("setup").description("Configure external MCP clients").addCommand(new commander.Command("claude-desktop").alias("claude").description("Install or update the Claude Desktop MCP server entry").option("--config <path>", "Path to claude_desktop_config.json").option("--dry-run", "Print the intended change without writing files").action(async (opts) => {
231
+ try {
232
+ const { setupClaudeDesktop } = await Promise.resolve().then(() => require("./setup-DOpKPrlx.cjs"));
233
+ const result = await setupClaudeDesktop({
234
+ configPath: opts.config,
235
+ dryRun: opts.dryRun
236
+ });
237
+ console.log(`Claude Desktop config: ${result.configPath}`);
238
+ console.log("MCP server: chain-insights");
239
+ console.log(`Command: ${result.command}`);
240
+ console.log(`Args: ${result.args.join(" ")}`);
241
+ if (result.dryRun) console.log(`Dry run: ${result.changed ? "would update config" : "already up to date"}`);
242
+ else if (result.changed) {
243
+ console.log(`Updated: yes`);
244
+ if (result.backupPath) console.log(`Backup: ${result.backupPath}`);
245
+ } else console.log("Updated: already up to date");
246
+ console.log("Reload required: quit and reopen Claude Desktop; it does not hot-reload MCP config.");
247
+ } catch (err) {
248
+ console.error(err.message);
249
+ process.exit(1);
250
+ }
251
+ }));
252
+ program.command("config").description("Read or write configuration values").addCommand(new commander.Command("get").argument("<key>", "Config key to read").action(async (key) => {
253
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
254
+ const { CONFIG_KEYS } = await Promise.resolve().then(() => require("./schema-4XpzDFQM.cjs")).then((n) => n.schema_exports);
255
+ if (!CONFIG_KEYS.includes(key)) {
256
+ console.error(`Unknown config key: ${key}`);
257
+ process.exit(1);
258
+ }
259
+ const value = (await loadConfig())[key];
260
+ console.log(value ?? "");
261
+ })).addCommand(new commander.Command("set").argument("<key>", "Config key to write").argument("<value>", "Value to set").action(async (key, value) => {
262
+ if (key === "walletPrivateKey") {
263
+ try {
264
+ const { setWalletPrivateKey } = await Promise.resolve().then(() => require("./wallet-RnvvSpV2.cjs")).then((n) => n.wallet_exports);
265
+ const address = await setWalletPrivateKey(value);
266
+ console.log("Wallet private key encrypted and stored in ~/.chain-insights/wallet.json");
267
+ console.log(`Wallet address: ${address}`);
268
+ } catch (err) {
269
+ console.error(err.message);
270
+ process.exit(1);
271
+ }
272
+ return;
273
+ }
274
+ const { loadConfig, saveConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
275
+ const { CONFIG_KEYS, DEFAULT_CONFIG } = await Promise.resolve().then(() => require("./schema-4XpzDFQM.cjs")).then((n) => n.schema_exports);
276
+ const current = await loadConfig();
277
+ if (!CONFIG_KEYS.includes(key)) {
278
+ console.error(`Unknown config key: ${key}`);
279
+ process.exit(1);
280
+ }
281
+ const existing = current[key];
282
+ const defaultValue = DEFAULT_CONFIG[key];
283
+ const coerced = typeof existing === "number" || typeof defaultValue === "number" ? Number(value) : value;
284
+ await saveConfig({ [key]: coerced });
285
+ const displayed = key.toLowerCase().includes("token") ? "[redacted]" : coerced;
286
+ console.log(`Set ${key} = ${displayed}`);
287
+ }));
288
+ program.command("wallet").description("Manage the local Base USDC payment wallet").addCommand(new commander.Command("address").description("Print the local payment wallet address").action(async () => {
289
+ try {
290
+ const { getWalletAccount } = await Promise.resolve().then(() => require("./tools-f_vJUZAF.cjs")).then((n) => n.tools_exports);
291
+ const account = await getWalletAccount();
292
+ console.log(account.address);
293
+ } catch (err) {
294
+ console.error(err.message);
295
+ process.exit(1);
296
+ }
297
+ })).addCommand(new commander.Command("balance").description("Show the local payment wallet Base USDC balance").action(async () => {
298
+ try {
299
+ const { getWalletBalanceText } = await Promise.resolve().then(() => require("./tools-f_vJUZAF.cjs")).then((n) => n.tools_exports);
300
+ console.log(await getWalletBalanceText());
301
+ } catch (err) {
302
+ console.error(err.message);
303
+ process.exit(1);
304
+ }
305
+ })).addCommand(new commander.Command("topup").description("Open a local browser page to top up the payment wallet").option("--no-open", "Print the top-up URL without opening a browser").option("--json", "Print machine-readable top-up metadata").action(async (opts) => {
306
+ try {
307
+ const { buildTopupInfo, getWalletAccount } = await Promise.resolve().then(() => require("./tools-f_vJUZAF.cjs")).then((n) => n.tools_exports);
308
+ const { startTopupServer } = await Promise.resolve().then(() => require("./topup-server-BZuQifvh.cjs")).then((n) => n.topup_server_exports);
309
+ const account = await getWalletAccount();
310
+ const url = await startTopupServer(account);
311
+ const info = buildTopupInfo(account.address, url);
312
+ if (opts.json) console.log(JSON.stringify(info, null, 2));
313
+ else {
314
+ console.log(`Top-up URL: ${url}`);
315
+ console.log(`Wallet: ${account.address}`);
316
+ console.log("Network: Base");
317
+ console.log("Token: USDC");
318
+ console.log("Press Ctrl+C to stop the top-up server.");
319
+ }
320
+ if (opts.open !== false) {
321
+ const open = (await import("open")).default;
322
+ await open(url);
323
+ }
324
+ } catch (err) {
325
+ console.error(err.message);
326
+ process.exit(1);
327
+ }
328
+ }));
329
+ program.command("mcp").description("Interact with the Chain Insights MCP endpoint").allowExcessArguments(false).addCommand(new commander.Command("networks").description("List supported graph networks, capability layers, retention, and freshness").option("--json", "Print raw capability JSON").action(async (opts) => {
330
+ try {
331
+ await printNetworkCapabilities(opts);
332
+ } catch (err) {
333
+ console.error(err.message);
334
+ process.exit(1);
335
+ }
336
+ })).addCommand(new commander.Command("tools").description("List available MCP tools (cached 24h)").option("--refresh", "Force refresh schema cache").action(async (opts) => {
337
+ try {
338
+ const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-CgWRCN2N.cjs"));
339
+ const { formatToolsTable } = await Promise.resolve().then(() => require("./format-DOrPvXEr.cjs"));
340
+ const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-CwgY205r.cjs")).then((n) => n.tool_visibility_exports);
341
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-Bmdl5hdk.cjs")).then((n) => n.config_exports);
342
+ const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-D4fZgIaO.cjs")).then((n) => n.client_exports);
343
+ const config = await loadConfig();
344
+ const graphMcpEndpoint = resolveGraphMcpEndpoint(config);
345
+ let tools = opts.refresh ? null : await loadSchema(graphMcpEndpoint);
346
+ if (!tools) {
347
+ const paymentFetch = await createConfiguredGraphMcpFetch(config);
348
+ const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
349
+ const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
350
+ const client = new Client({
351
+ name: "chain-insights-cli",
352
+ version: require_version.PACKAGE_VERSION
353
+ });
354
+ await client.connect(new StreamableHTTPClientTransport(new URL(graphMcpEndpoint), { fetch: paymentFetch }));
355
+ try {
356
+ tools = (await client.listTools()).tools;
357
+ await saveSchema(tools, graphMcpEndpoint);
358
+ } finally {
359
+ await client.close();
360
+ }
361
+ }
362
+ console.log(formatToolsTable(visibleRemoteTools(tools)));
363
+ } catch (err) {
364
+ console.error(err.message);
365
+ process.exit(1);
366
+ }
367
+ })).addCommand(new commander.Command("address-risk").description("Screen an address for risk, exchange behavior, and optional compare_address connection risk").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", "Network to query. Run `cia mcp networks` for supported networks.").option("--compare-address <address>", "Optional second address for connection-risk compare mode").option("--remote", "Force remote MCP tool call instead of local Chain Insights recipe").action(async (opts) => {
368
+ try {
369
+ await withGraphMcpClient("chain-insights-cli-address-risk", async (client) => {
370
+ if (opts.remote) {
371
+ printMcpTextContent(await client.callTool({
372
+ name: "address_risk",
373
+ arguments: {
374
+ address: opts.address,
375
+ network: opts.network,
376
+ ...opts.compareAddress ? { compare_address: opts.compareAddress } : {}
377
+ }
378
+ }));
379
+ return;
380
+ }
381
+ const { addressRisk } = await Promise.resolve().then(() => require("./public-tools-XSpkz2ky.cjs"));
382
+ const result = await addressRisk(client, {
383
+ address: opts.address,
384
+ network: opts.network,
385
+ compareAddress: opts.compareAddress
386
+ });
387
+ console.log(result.summaryText);
388
+ });
389
+ } catch (err) {
390
+ console.error(err.message);
391
+ process.exit(1);
392
+ }
393
+ })).addCommand(new commander.Command("track-funds").description("Trace trusted/victim addresses and optional known untrusted/scammer addresses").requiredOption("--trusted-addresses <addresses>", "Comma-separated full trusted/victim addresses, max 5").requiredOption("--network <network>", "Network to query. Run `cia mcp networks` for supported networks.").option("--untrusted-addresses <addresses>", "Comma-separated full known untrusted/scammer addresses, max 5").option("--case <id>", "Case ID to attach compact evidence pointers").option("--max-hops <number>", "Maximum trace hops, 1-5").option("--per-address-limit <number>", "Maximum exchange paths/results per address, 1-10").option("--min-amount-sum <number>", "Minimum r.amount_sum for traced edges").option("--remote", "Force remote MCP tool call instead of local Chain Insights recipe").action(async (opts) => {
394
+ try {
395
+ const { requireWorkspaceRoot } = await Promise.resolve().then(() => require("./output-root-CFYms3ad.cjs")).then((n) => n.output_root_exports);
396
+ requireWorkspaceRoot();
397
+ await withGraphMcpClient("chain-insights-cli-track-funds", async (client, config) => {
398
+ if (opts.remote) {
399
+ printMcpTextContent(await client.callTool({
400
+ name: "track_funds",
401
+ arguments: {
402
+ trusted_addresses: opts.trustedAddresses,
403
+ network: opts.network,
404
+ ...opts.untrustedAddresses ? { untrusted_addresses: opts.untrustedAddresses } : {}
405
+ }
406
+ }));
407
+ return;
408
+ }
409
+ const { trackFunds } = await Promise.resolve().then(() => require("./public-tools-XSpkz2ky.cjs"));
410
+ const caseId = opts.case ? await resolveCaseSelector(opts.case) : void 0;
411
+ const result = await trackFunds(client, config, {
412
+ trustedAddresses: opts.trustedAddresses,
413
+ untrustedAddresses: opts.untrustedAddresses,
414
+ network: opts.network,
415
+ caseId,
416
+ maxHops: optionalNumber(opts.maxHops),
417
+ perAddressLimit: optionalNumber(opts.perAddressLimit),
418
+ minAmountSum: optionalNumber(opts.minAmountSum)
419
+ });
420
+ console.log(result.summaryText);
421
+ console.log(JSON.stringify(result.structuredContent, null, 2));
422
+ });
423
+ } catch (err) {
424
+ console.error(err.message);
425
+ process.exit(1);
426
+ }
427
+ })).addCommand(new commander.Command("scam-topology").description("Build victim-incident scam topology and ML-ready scam labels").requiredOption("--network <network>", "Network to query. Run `cia mcp networks` for supported networks.").requiredOption("--victim-address <address>", "Full victim/source address that anchors the incident").requiredOption("--incident-timestamp-ms <milliseconds>", "Earliest known incident transfer timestamp in milliseconds").option("--max-hops <number>", "Maximum trace hops, default 16, max 64").option("--activity-policy <mode>", "Traversal activity policy: node_relative_only or global_incident_only", "node_relative_only").option("--case <id>", "Case ID to attach compact evidence pointers").action(async (opts) => {
428
+ try {
429
+ const { requireWorkspaceRoot } = await Promise.resolve().then(() => require("./output-root-CFYms3ad.cjs")).then((n) => n.output_root_exports);
430
+ requireWorkspaceRoot();
431
+ await withGraphMcpClient("chain-insights-cli-scam-topology", async (client, config) => {
432
+ const { scamTopology } = await Promise.resolve().then(() => require("./public-tools-XSpkz2ky.cjs"));
433
+ const incidentTimestampMs = optionalNumber(opts.incidentTimestampMs);
434
+ if (incidentTimestampMs === void 0) throw new Error("incident-timestamp-ms is required");
435
+ const caseId = opts.case ? await resolveCaseSelector(opts.case) : void 0;
436
+ const result = await scamTopology(client, config, {
437
+ victimAddress: opts.victimAddress,
438
+ network: opts.network,
439
+ maxHops: optionalNumber(opts.maxHops),
440
+ incidentTimestampMs,
441
+ activityPolicyMode: optionalScamTopologyActivityPolicy(opts.activityPolicy),
442
+ caseId
443
+ });
444
+ console.log(result.summaryText);
445
+ console.log(JSON.stringify(result.structuredContent, null, 2));
446
+ });
447
+ } catch (err) {
448
+ console.error(err.message);
449
+ process.exit(1);
450
+ }
451
+ })).addCommand(new commander.Command("call").description("Call an MCP tool directly (debug)").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234 chain=ethereum)").action(async (tool, rawArgs) => {
452
+ try {
453
+ const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-DQA2QcRA.cjs"));
454
+ const { assertPublicMcpToolName } = await Promise.resolve().then(() => require("./tool-visibility-CwgY205r.cjs")).then((n) => n.tool_visibility_exports);
455
+ const args = parseMcpCallArgs(rawArgs);
456
+ assertPublicMcpToolName(tool);
457
+ await withGraphMcpClient("chain-insights-cli-call", async (client, config) => {
458
+ if (tool === "address_risk") {
459
+ const { addressRisk } = await Promise.resolve().then(() => require("./public-tools-XSpkz2ky.cjs"));
460
+ const result = await addressRisk(client, {
461
+ address: String(args["address"] ?? ""),
462
+ network: String(args["network"] ?? ""),
463
+ compareAddress: args["compare_address"] === void 0 ? void 0 : String(args["compare_address"])
464
+ });
465
+ console.log(result.summaryText);
466
+ return;
467
+ }
468
+ if (tool === "track_funds") {
469
+ const { trackFunds } = await Promise.resolve().then(() => require("./public-tools-XSpkz2ky.cjs"));
470
+ const result = await trackFunds(client, config, {
471
+ trustedAddresses: args["trusted_addresses"] ?? "",
472
+ untrustedAddresses: args["untrusted_addresses"],
473
+ network: String(args["network"] ?? ""),
474
+ caseId: args["case_id"] === void 0 ? void 0 : String(args["case_id"]),
475
+ maxHops: typeof args["max_hops"] === "number" ? args["max_hops"] : void 0,
476
+ perAddressLimit: typeof args["per_address_limit"] === "number" ? args["per_address_limit"] : void 0,
477
+ minAmountSum: typeof args["min_amount_sum"] === "number" ? args["min_amount_sum"] : void 0
478
+ });
479
+ console.log(result.summaryText);
480
+ console.log(JSON.stringify(result.structuredContent, null, 2));
481
+ return;
482
+ }
483
+ if (tool === "scam_topology") {
484
+ const { scamTopology } = await Promise.resolve().then(() => require("./public-tools-XSpkz2ky.cjs"));
485
+ const victimAddress = String(args["victim_address"] ?? "").trim();
486
+ if (!victimAddress) throw new Error("victim_address is required");
487
+ const incidentTimestampMs = optionalNumberArg(args["incident_timestamp_ms"], "incident_timestamp_ms");
488
+ if (incidentTimestampMs === void 0) throw new Error("incident_timestamp_ms is required");
489
+ const result = await scamTopology(client, config, {
490
+ victimAddress,
491
+ network: String(args["network"] ?? ""),
492
+ caseId: args["case_id"] === void 0 ? void 0 : String(args["case_id"]),
493
+ maxHops: typeof args["max_hops"] === "number" ? args["max_hops"] : void 0,
494
+ incidentTimestampMs,
495
+ activityPolicyMode: optionalScamTopologyActivityPolicy(args["activity_policy"])
496
+ });
497
+ console.log(result.summaryText);
498
+ console.log(JSON.stringify(result.structuredContent, null, 2));
499
+ return;
500
+ }
501
+ printMcpTextContent(await client.callTool({
502
+ name: tool,
503
+ arguments: args
504
+ }));
505
+ });
506
+ } catch (err) {
507
+ console.error(err.message);
508
+ process.exit(1);
509
+ }
510
+ }));
511
+ const caseCommand = new commander.Command("case").description("Manage investigation cases").hook("preAction", async () => {
512
+ await scopeCasesToInvocationDir();
513
+ }).addCommand(new commander.Command("open").description("Open a new investigation case").argument("<name>", "Case name (e.g. \"Tornado Mixer Investigation\")").option("--tags <tags>", "Comma-separated tags (e.g. aml,mixer,defi)", "").option("--description <desc>", "Brief description of the investigation", "").action(async (name, opts) => {
514
+ try {
515
+ if (/^[1-9]\d*$/.test(name.trim())) throw new Error("Numeric case names look like list selectors. Use a descriptive case name, e.g. `cia case open \"Tracking stolen funds from <address>\"`.");
516
+ const { CaseStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
517
+ const tags = opts.tags ? opts.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
518
+ const c = await CaseStore.create({
519
+ name,
520
+ tags,
521
+ description: opts.description
522
+ });
523
+ const { casesRoot } = await Promise.resolve().then(() => require("./store-BiUhQOIf.cjs"));
524
+ console.log(`Case opened: ${c.id}`);
525
+ console.log(`Directory: ${node_path.default.join(casesRoot(), c.id)}/`);
526
+ console.log(`Status: ${c.status}`);
527
+ } catch (err) {
528
+ console.error(err.message);
529
+ process.exit(1);
530
+ }
531
+ })).addCommand(new commander.Command("activate").description("Activate a case (set status to active)").argument("<case-id>", "Case ID to activate").action(async (caseSelector) => {
532
+ try {
533
+ const { CaseStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
534
+ const caseId = await resolveCaseSelector(caseSelector);
535
+ const c = await CaseStore.setStatus(caseId, "active");
536
+ console.log(`Case ${c.id} is now: active`);
537
+ } catch (err) {
538
+ console.error(err.message);
539
+ process.exit(1);
540
+ }
541
+ })).addCommand(new commander.Command("suspend").description("Suspend a case (set status to suspended)").argument("<case-id>", "Case ID to suspend").action(async (caseSelector) => {
542
+ try {
543
+ const { CaseStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
544
+ const caseId = await resolveCaseSelector(caseSelector);
545
+ const c = await CaseStore.setStatus(caseId, "suspended");
546
+ console.log(`Case ${c.id} is now: suspended`);
547
+ } catch (err) {
548
+ console.error(err.message);
549
+ process.exit(1);
550
+ }
551
+ })).addCommand(new commander.Command("close").description("Close a case permanently").argument("<case-id>", "Case ID to close").action(async (caseSelector) => {
552
+ try {
553
+ const { CaseStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
554
+ const caseId = await resolveCaseSelector(caseSelector);
555
+ const c = await CaseStore.setStatus(caseId, "closed");
556
+ console.log(`Case ${c.id} is now: closed`);
557
+ } catch (err) {
558
+ console.error(err.message);
559
+ process.exit(1);
560
+ }
561
+ })).addCommand(new commander.Command("list").description("List all investigation cases").option("--status <status>", "Filter by status (open|active|suspended|closed)").action(async (opts) => {
562
+ try {
563
+ const { CaseStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
564
+ const cases = await CaseStore.list();
565
+ const filtered = opts.status ? cases.filter((c) => c.status === opts.status) : cases;
566
+ if (filtered.length === 0) {
567
+ console.log("No cases found.");
568
+ return;
569
+ }
570
+ for (const [index, c] of filtered.entries()) console.log(`${index + 1}. ${c.id} [${c.status}] ${c.name}`);
571
+ } catch (err) {
572
+ console.error(err.message);
573
+ process.exit(1);
574
+ }
575
+ })).addCommand(new commander.Command("evidence").description("Manage case evidence").addCommand(new commander.Command("add").description("Add evidence to a case from an MCP query result").argument("<case-id>", "Case ID to add evidence to").option("--source <tool>", "MCP tool name that produced this evidence", "manual").option("--content <text>", "Evidence content (MCP response or notes)", "").option("--query-params <params>", "Query parameters used (e.g. address=0x1234)", "").action(async (caseSelector, opts) => {
576
+ try {
577
+ const { EvidenceStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
578
+ const caseId = await resolveCaseSelector(caseSelector);
579
+ const result = await EvidenceStore.append(caseId, {
580
+ source: opts.source,
581
+ content: opts.content,
582
+ queryParams: opts.queryParams
583
+ });
584
+ console.log(`Evidence saved: ${result.filename}`);
585
+ console.log(`SHA-256: ${result.sha256}`);
586
+ } catch (err) {
587
+ console.error(err.message);
588
+ process.exit(1);
589
+ }
590
+ })).addCommand(new commander.Command("verify").description("Verify evidence manifest integrity for a case").argument("<case-id>", "Case ID to verify").action(async (caseSelector) => {
591
+ try {
592
+ const { EvidenceStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
593
+ const caseId = await resolveCaseSelector(caseSelector);
594
+ const result = await EvidenceStore.verifyManifest(caseId);
595
+ if (result.ok) console.log(`Manifest OK — ${result.count} evidence file(s) verified`);
596
+ else {
597
+ console.error(`Manifest FAILED — tampered files: ${(result.tampered ?? []).join(", ")}`);
598
+ process.exit(1);
599
+ }
600
+ } catch (err) {
601
+ console.error(err.message);
602
+ process.exit(1);
603
+ }
604
+ }))).addCommand(new commander.Command("dossier").description("Manage entity dossiers for a case").addCommand(new commander.Command("update").description("Append a finding to an entity dossier").argument("<case-id>", "Case ID").argument("<address>", "Entity address or identifier").option("--finding <text>", "Finding to append to the dossier", "").option("--type <type>", "Entity type (eoa|contract|exchange|mixer|unknown)", "unknown").action(async (caseSelector, address, opts) => {
605
+ try {
606
+ const { DossierStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
607
+ const caseId = await resolveCaseSelector(caseSelector);
608
+ const entityType = [
609
+ "eoa",
610
+ "contract",
611
+ "exchange",
612
+ "mixer",
613
+ "unknown"
614
+ ].includes(opts.type) ? opts.type : "unknown";
615
+ await DossierStore.appendFinding(caseId, address, opts.finding, entityType);
616
+ console.log(`Dossier updated for ${address}`);
617
+ } catch (err) {
618
+ console.error(err.message);
619
+ process.exit(1);
620
+ }
621
+ }))).addCommand(new commander.Command("session").description("Manage investigation sessions").addCommand(new commander.Command("start").description("Start a new investigation session for a case").argument("<case-id>", "Case ID").argument("[title...]", "Optional session title").action(async (caseSelector, titleParts) => {
622
+ try {
623
+ const { SessionStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
624
+ const caseId = await resolveCaseSelector(caseSelector);
625
+ const title = titleParts.join(" ").trim();
626
+ const s = await SessionStore.start(caseId, title ? { title } : {});
627
+ console.log(`Session started: ${s.sessionId}`);
628
+ } catch (err) {
629
+ console.error(err.message);
630
+ process.exit(1);
631
+ }
632
+ })).addCommand(new commander.Command("end").description("End the current session with findings and next steps").argument("<case-id>", "Case ID").option("--findings <text>", "Key findings from this session", "").option("--next-steps <text>", "Next steps for the investigation", "").action(async (caseSelector, opts) => {
633
+ try {
634
+ const { SessionStore } = await Promise.resolve().then(() => require("./cases-CDcNU91B.cjs"));
635
+ const caseId = await resolveCaseSelector(caseSelector);
636
+ await SessionStore.end(caseId, {
637
+ findings: opts.findings,
638
+ nextSteps: opts.nextSteps
639
+ });
640
+ await SessionStore.archiveOldSessions(caseId);
641
+ console.log(`Session ended for case ${caseId}`);
642
+ } catch (err) {
643
+ console.error(err.message);
644
+ process.exit(1);
645
+ }
646
+ }))).addCommand(new commander.Command("show").description("Show saved case context").argument("<case-id>", "Case ID or case list number to show").action(async (caseSelector) => {
647
+ try {
648
+ await showCaseContext(caseSelector);
649
+ } catch (err) {
650
+ console.error(err.message);
651
+ process.exit(1);
652
+ }
653
+ }));
654
+ program.addCommand(caseCommand);
655
+ program.command("playbook").description("Run and manage investigation playbooks").addCommand(new commander.Command("run").description("Execute a playbook by name").argument("<name>", "Playbook name (e.g. trace-funds, risk-check, entity-profile)").option("--case <id>", "Case ID to attach evidence to (auto-created if omitted)").option("--from <n>", "Resume from step N (1-based)", "1").option("--dry-run", "Show steps without executing").option("-p, --param <kv...>", "Parameters as key=value pairs (repeatable, e.g. -p address=0x1 -p hops=3)").action(async (name, opts) => {
656
+ try {
657
+ const resolvedParams = {};
658
+ for (const kv of opts.param ?? []) {
659
+ const eq = kv.indexOf("=");
660
+ if (eq === -1) {
661
+ console.error(`Invalid param format: "${kv}". Use key=value`);
662
+ process.exit(1);
663
+ }
664
+ const key = kv.slice(0, eq);
665
+ if (!key) {
666
+ console.error(`Invalid param format: "${kv}". Key must be non-empty`);
667
+ process.exit(1);
668
+ }
669
+ resolvedParams[key] = kv.slice(eq + 1);
670
+ }
671
+ const { resolvePlaybookContent } = await Promise.resolve().then(() => require("./resolver-zYbu4wDV.cjs"));
672
+ const markdown = await resolvePlaybookContent(name);
673
+ const { PlaybookParser } = await Promise.resolve().then(() => require("./parser-BUIWW1OH.cjs"));
674
+ const definition = PlaybookParser.parse(markdown, resolvedParams);
675
+ for (const spec of definition.params) if (spec.required && !resolvedParams[spec.name] && !spec.default) {
676
+ console.error(`Missing required param: ${spec.name}. Pass with: -p ${spec.name}=<value>`);
677
+ process.exit(1);
678
+ }
679
+ const fromN = parseInt(opts.from, 10);
680
+ if (isNaN(fromN) || fromN < 1) {
681
+ console.error(`Invalid --from value: "${opts.from}". Must be a positive integer.`);
682
+ process.exit(1);
683
+ }
684
+ const { PlaybookRunner } = await Promise.resolve().then(() => require("./runner-1Eq55OYb.cjs"));
685
+ await PlaybookRunner.run(definition, {
686
+ caseId: opts.case,
687
+ from: fromN,
688
+ dryRun: opts.dryRun,
689
+ params: resolvedParams
690
+ });
691
+ } catch (err) {
692
+ console.error(err.message);
693
+ process.exit(1);
694
+ }
695
+ })).addCommand(new commander.Command("list").description("List available playbooks (built-in and user-defined)").action(async () => {
696
+ try {
697
+ const { listPlaybooks } = await Promise.resolve().then(() => require("./resolver-zYbu4wDV.cjs"));
698
+ const playbooks = await listPlaybooks();
699
+ if (playbooks.length === 0) {
700
+ console.log("No playbooks found.");
701
+ return;
702
+ }
703
+ for (const p of playbooks) console.log(` ${p.name.padEnd(20)} [${p.source}]`);
704
+ } catch (err) {
705
+ console.error(err.message);
706
+ process.exit(1);
707
+ }
708
+ })).addCommand(new commander.Command("show").description("Show steps for a playbook without executing").argument("<name>", "Playbook name").action(async (name) => {
709
+ try {
710
+ const { resolvePlaybookContent } = await Promise.resolve().then(() => require("./resolver-zYbu4wDV.cjs"));
711
+ const { PlaybookParser } = await Promise.resolve().then(() => require("./parser-BUIWW1OH.cjs"));
712
+ const markdown = await resolvePlaybookContent(name);
713
+ const definition = PlaybookParser.parse(markdown, {});
714
+ console.log(`Playbook: ${definition.name} v${definition.version}`);
715
+ console.log(`${definition.description}\n`);
716
+ console.log(`Parameters:`);
717
+ for (const p of definition.params) {
718
+ const req = p.required ? "(required)" : `(optional, default: ${p.default ?? "none"})`;
719
+ console.log(` ${p.name}: ${p.type} ${req}`);
720
+ }
721
+ console.log(`\nSteps:`);
722
+ for (const step of definition.steps) console.log(` ${step.index}. ${step.label} → tool: ${step.tool}`);
723
+ } catch (err) {
724
+ console.error(err.message);
725
+ process.exit(1);
726
+ }
727
+ }));
728
+ program.command("viz").description("Generate money flow visualization").argument("[case-id]", "Case ID to visualize").option("--data <file>", "Raw transaction JSON file for ad-hoc visualization").option("-p, --port <number>", "Server port", "4321").action(async (caseId, opts) => {
729
+ try {
730
+ if (!caseId && !opts.data) {
731
+ console.error("Provide either a case ID or --data <file.json>");
732
+ process.exit(1);
733
+ }
734
+ const { generateVisualization } = await Promise.resolve().then(() => require("./viz-ClezVXrJ.cjs")).then((n) => n.viz_exports);
735
+ const result = await generateVisualization({
736
+ caseId,
737
+ dataFile: opts.data
738
+ });
739
+ const { startServer } = await Promise.resolve().then(() => require("./server-DXowbpfi.cjs")).then((n) => n.server_exports);
740
+ const port = parseInt(opts.port, 10);
741
+ startServer(port);
742
+ const url = `http://127.0.0.1:${port}/viz/${result.vizId}`;
743
+ console.log(`Visualization: ${url}`);
744
+ const open = (await import("open")).default;
745
+ await open(url);
746
+ } catch (err) {
747
+ console.error(err.message);
748
+ process.exit(1);
749
+ }
750
+ });
751
+ program.parse(process.argv);
752
+ //#endregion