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