chain-insights 0.24.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/cli.cjs +121 -19
- package/dist/cli.mjs +121 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/mcp-proxy.cjs +14 -11
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +14 -11
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-CpOLkME5.mjs → public-tools-CMI_0y87.mjs} +28 -2
- package/dist/public-tools-CMI_0y87.mjs.map +1 -0
- package/dist/{public-tools-Bou25cki.cjs → public-tools-DDGHRazh.cjs} +28 -1
- package/dist/{tool-visibility-DA4pOuJt.cjs → tool-visibility-BBEbpKt4.cjs} +2 -1
- package/dist/{tool-visibility-wDMzmHU-.mjs → tool-visibility-CJqjmDF5.mjs} +3 -2
- package/dist/tool-visibility-CJqjmDF5.mjs.map +1 -0
- package/docs/graph-tools.md +10 -0
- package/docs/mcp-proxy.md +30 -0
- package/docs/stability.md +4 -0
- package/package.json +1 -1
- package/skills/chain-insights-address-risk/SKILL.md +6 -0
- package/dist/public-tools-CpOLkME5.mjs.map +0 -1
- package/dist/tool-visibility-wDMzmHU-.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ as-is on macOS; on Windows use WSL.
|
|
|
22
22
|
```bash
|
|
23
23
|
npx chain-insights@latest --help # run without installing
|
|
24
24
|
npm install -g chain-insights # or install the cia CLI globally
|
|
25
|
-
cia
|
|
25
|
+
cia aml-address-risk --address 0xYourAddressHere --network robinhood
|
|
26
26
|
cia networks # network status + dataset overview
|
|
27
27
|
cia network robinhood # details for one network
|
|
28
28
|
```
|
|
@@ -31,6 +31,20 @@ Sixty seconds gets you the CLI, a network overview, and the remote tool catalog.
|
|
|
31
31
|
To call the same tools from an agent, register the MCP proxy:
|
|
32
32
|
`cia setup claude-code` (or `codex` / `hermes`).
|
|
33
33
|
|
|
34
|
+
CLI results are human-readable by default. Add `--json` when another tool or
|
|
35
|
+
script needs indented JSON:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cia mcp call --json graph_query \
|
|
39
|
+
network=robinhood \
|
|
40
|
+
"query=USE topology MATCH (a:Address) RETURN a.address AS address LIMIT 10"
|
|
41
|
+
cia aml-address-risk --json --address 0xYourAddressHere --network robinhood
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The AML address-risk contract uses the latest version when `version` is
|
|
45
|
+
omitted. Pin the current contract with `--version v1` on the AML command, or
|
|
46
|
+
with `version=v1` when using `cia mcp call aml_address_risk`.
|
|
47
|
+
|
|
34
48
|
## Purpose And Ownership
|
|
35
49
|
|
|
36
50
|
One public npm package (`chain-insights`) providing the `cia` CLI and a
|
|
@@ -210,6 +224,13 @@ cia update --check
|
|
|
210
224
|
|
|
211
225
|
Run a first screen from any directory:
|
|
212
226
|
|
|
227
|
+
```bash
|
|
228
|
+
cia aml-address-risk \
|
|
229
|
+
--address 0xYourAddressHere --network robinhood
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The same screen is available through the generic MCP caller:
|
|
233
|
+
|
|
213
234
|
```bash
|
|
214
235
|
cia mcp call aml_address_risk \
|
|
215
236
|
network=robinhood address=0xYourAddressHere
|
package/dist/cli.cjs
CHANGED
|
@@ -6,6 +6,85 @@ let node_url = require("node:url");
|
|
|
6
6
|
let node_path = require("node:path");
|
|
7
7
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
8
8
|
//#region src/mcp/print-result.ts
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function displayValue(value) {
|
|
13
|
+
if (value === null || value === void 0) return "";
|
|
14
|
+
if (typeof value === "string") return value;
|
|
15
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
} catch {
|
|
19
|
+
return String(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function formatTable(rows) {
|
|
23
|
+
if (rows.length === 0) return [];
|
|
24
|
+
const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
25
|
+
const values = rows.map((row) => columns.map((column) => displayValue(row[column])));
|
|
26
|
+
const widths = columns.map((column, index) => Math.max(column.length, ...values.map((row) => row[index]?.length ?? 0)));
|
|
27
|
+
const line = (row) => row.map((value, index) => index === row.length - 1 ? value : value.padEnd(widths[index] ?? 0)).join(" | ");
|
|
28
|
+
return [
|
|
29
|
+
line(columns),
|
|
30
|
+
widths.map((width) => "-".repeat(width)).join("-+-"),
|
|
31
|
+
...values.map(line)
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
function formatGraphResult(value, fallbackTool) {
|
|
35
|
+
const facts = isRecord(value.facts) ? value.facts : void 0;
|
|
36
|
+
const query = facts && isRecord(facts.query) ? facts.query : void 0;
|
|
37
|
+
const queries = facts && Array.isArray(facts.queries) ? facts.queries : void 0;
|
|
38
|
+
if (!query && !queries) return null;
|
|
39
|
+
const lines = [`Tool: ${displayValue(value.tool) || fallbackTool || "MCP result"}`];
|
|
40
|
+
const subject = isRecord(value.subject) ? value.subject : facts && isRecord(facts.subject) ? facts.subject : void 0;
|
|
41
|
+
if (subject?.network !== void 0) lines.push(`Network: ${displayValue(subject.network)}`);
|
|
42
|
+
if (query) {
|
|
43
|
+
const results = Array.isArray(query.results) ? query.results.filter(isRecord) : [];
|
|
44
|
+
if (query.count !== void 0) lines.push(`Rows: ${displayValue(query.count)}`);
|
|
45
|
+
else lines.push(`Rows: ${results.length}`);
|
|
46
|
+
if (query.billable_units !== void 0) lines.push(`Billed units: ${displayValue(query.billable_units)}`);
|
|
47
|
+
if (query.elapsed_ms !== void 0) lines.push(`Elapsed: ${displayValue(query.elapsed_ms)} ms`);
|
|
48
|
+
if (query.truncated !== void 0) lines.push(`Truncated: ${displayValue(query.truncated)}`);
|
|
49
|
+
if (query.error !== void 0) lines.push(`Error: ${displayValue(query.error)}`);
|
|
50
|
+
if (results.length > 0) lines.push("", "Results:", ...formatTable(results));
|
|
51
|
+
else lines.push("", "Results: none");
|
|
52
|
+
}
|
|
53
|
+
if (queries) {
|
|
54
|
+
lines.push(`Queries: ${queries.length}`);
|
|
55
|
+
for (const entry of queries) {
|
|
56
|
+
if (!isRecord(entry)) continue;
|
|
57
|
+
const id = displayValue(entry.id) || "query";
|
|
58
|
+
const results = Array.isArray(entry.results) ? entry.results.filter(isRecord) : [];
|
|
59
|
+
lines.push("", `[${id}]`);
|
|
60
|
+
if (entry.error !== void 0) lines.push(`Error: ${displayValue(entry.error)}`);
|
|
61
|
+
lines.push(`Rows: ${displayValue(entry.count ?? results.length)}`);
|
|
62
|
+
if (results.length > 0) lines.push(...formatTable(results));
|
|
63
|
+
else lines.push("Results: none");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Formats one MCP text block for terminal output. JSON is indented when the
|
|
70
|
+
* caller requests `--json`; graph query envelopes are rendered as a compact
|
|
71
|
+
* result summary and table by default.
|
|
72
|
+
*/
|
|
73
|
+
function formatMcpTextContent(text, tool, options = {}) {
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(text);
|
|
77
|
+
} catch {
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
if (options.json) try {
|
|
81
|
+
return JSON.stringify(parsed, null, 2);
|
|
82
|
+
} catch {
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
85
|
+
if (isRecord(parsed)) return formatGraphResult(parsed, tool) ?? JSON.stringify(parsed, null, 2);
|
|
86
|
+
return displayValue(parsed);
|
|
87
|
+
}
|
|
9
88
|
/**
|
|
10
89
|
* Prints the text blocks of an MCP tool result to stdout. When the result is
|
|
11
90
|
* flagged `isError`, throws with the tool's error text instead — MCP `callTool`
|
|
@@ -13,10 +92,10 @@ node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
|
13
92
|
* surface them as failures (non-zero exit) rather than printing to stdout and
|
|
14
93
|
* exiting 0.
|
|
15
94
|
*/
|
|
16
|
-
function printMcpTextContent(result) {
|
|
95
|
+
function printMcpTextContent(result, options = {}) {
|
|
17
96
|
const texts = (result.content ?? []).filter((item) => item.type === "text").map((item) => item.text ?? "");
|
|
18
97
|
if (result.isError) throw new Error(texts.join("\n").trim() || "MCP tool returned an error");
|
|
19
|
-
for (const text of texts) console.log(text);
|
|
98
|
+
for (const text of texts) console.log(formatMcpTextContent(text, options.tool, options));
|
|
20
99
|
}
|
|
21
100
|
//#endregion
|
|
22
101
|
//#region src/cli.ts
|
|
@@ -29,6 +108,16 @@ function createCliCommand(name) {
|
|
|
29
108
|
program.name("cia").description("AML investigation toolkit for blockchain analysis").version(require_version.PACKAGE_INFO.version).showHelpAfterError().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");
|
|
30
109
|
const rawArgs = process.argv.slice(2);
|
|
31
110
|
const installerFlags = rawArgs.filter((a) => a === "--claude" || a === "--codex" || a === "--hermes");
|
|
111
|
+
function normalizeAmlToolVersionArgv(argv) {
|
|
112
|
+
const commandIndex = rawArgs.findIndex((arg, index) => arg === "aml-address-risk" && (index === 0 || index === 1 && rawArgs[0] === "mcp"));
|
|
113
|
+
if (commandIndex === -1) return argv;
|
|
114
|
+
return argv.map((arg, index) => {
|
|
115
|
+
if (index - 2 <= commandIndex) return arg;
|
|
116
|
+
if (arg === "--version") return "--tool-version";
|
|
117
|
+
if (arg.startsWith("--version=")) return `--tool-version=${arg.slice(10)}`;
|
|
118
|
+
return arg;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
32
121
|
const wantsHelpOrVersion = rawArgs.some((a) => a === "--help" || a === "-h" || a === "--version" || a === "-V");
|
|
33
122
|
if (installerFlags.length > 0 && !wantsHelpOrVersion && !rawArgs.some((a) => !a.startsWith("-"))) {
|
|
34
123
|
try {
|
|
@@ -100,16 +189,19 @@ async function printNetworkCapability(name, opts) {
|
|
|
100
189
|
else console.log(formatNetworkCapability(network));
|
|
101
190
|
}
|
|
102
191
|
function addAmlAddressRiskCommand(parent, networksCommand) {
|
|
103
|
-
parent.addCommand(createCliCommand("aml-address-risk").description("Screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").action(async (opts) => {
|
|
192
|
+
parent.addCommand(createCliCommand("aml-address-risk").description("Screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new commander.Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
|
|
104
193
|
try {
|
|
194
|
+
const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-DDGHRazh.cjs"));
|
|
195
|
+
const requestedVersion = opts.toolVersion ?? opts.version;
|
|
196
|
+
resolveAmlAddressRiskVersion(requestedVersion);
|
|
105
197
|
await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
|
|
106
|
-
const {
|
|
107
|
-
const result = await
|
|
198
|
+
const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-DDGHRazh.cjs"));
|
|
199
|
+
const result = await runAmlAddressRisk(client, {
|
|
108
200
|
address: opts.address,
|
|
109
201
|
network: opts.network,
|
|
110
202
|
compareAddress: opts.compareAddress
|
|
111
|
-
});
|
|
112
|
-
console.log(result.summaryText);
|
|
203
|
+
}, requestedVersion);
|
|
204
|
+
console.log(opts.json ? JSON.stringify(result.structuredContent, null, 2) : result.summaryText);
|
|
113
205
|
});
|
|
114
206
|
} catch (err) {
|
|
115
207
|
console.error(err.message);
|
|
@@ -404,7 +496,7 @@ const mcpCommand = program.command("mcp").description("Interact with the Chain I
|
|
|
404
496
|
try {
|
|
405
497
|
const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-n7Gbhvsj.cjs"));
|
|
406
498
|
const { formatToolsTable } = await Promise.resolve().then(() => require("./format-BjZuYDr8.cjs"));
|
|
407
|
-
const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-
|
|
499
|
+
const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-BBEbpKt4.cjs")).then((n) => n.tool_visibility_exports);
|
|
408
500
|
const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
|
|
409
501
|
const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Ypw9BNbn.cjs")).then((n) => n.client_exports);
|
|
410
502
|
const config = await loadConfig();
|
|
@@ -433,20 +525,24 @@ const mcpCommand = program.command("mcp").description("Interact with the Chain I
|
|
|
433
525
|
}
|
|
434
526
|
}));
|
|
435
527
|
addAmlAddressRiskCommand(mcpCommand, "cia mcp networks");
|
|
436
|
-
mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool directly (debug)").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234... network=robinhood)").action(async (tool, rawArgs) => {
|
|
528
|
+
mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool directly (debug)").option("--json", "Print machine-readable JSON output").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234... network=robinhood)").action(async (tool, rawArgs, opts) => {
|
|
437
529
|
try {
|
|
438
530
|
const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-DAiQSDmD.cjs"));
|
|
439
|
-
const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-
|
|
531
|
+
const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-BBEbpKt4.cjs")).then((n) => n.tool_visibility_exports);
|
|
440
532
|
const args = parseMcpCallArgs(rawArgs);
|
|
441
533
|
assertPublicMcpToolName(tool);
|
|
442
534
|
validatePublicMcpToolArguments(tool, args);
|
|
535
|
+
if (tool === "aml_address_risk") {
|
|
536
|
+
const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-DDGHRazh.cjs"));
|
|
537
|
+
resolveAmlAddressRiskVersion(args["version"] === void 0 ? void 0 : String(args["version"]));
|
|
538
|
+
}
|
|
443
539
|
if (tool === "wallet_balance") {
|
|
444
540
|
const { getWalletBalanceText } = await Promise.resolve().then(() => require("./tools-BHfphgy9.cjs")).then((n) => n.tools_exports);
|
|
445
541
|
console.log(await getWalletBalanceText());
|
|
446
542
|
return;
|
|
447
543
|
}
|
|
448
544
|
if (tool === "meta_network_capabilities") {
|
|
449
|
-
await printNetworkCapabilities({ json:
|
|
545
|
+
await printNetworkCapabilities({ json: opts.json });
|
|
450
546
|
return;
|
|
451
547
|
}
|
|
452
548
|
if (tool === "meta_help") {
|
|
@@ -459,7 +555,10 @@ mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool dir
|
|
|
459
555
|
printMcpTextContent(await client.callTool({
|
|
460
556
|
name: "usage_status",
|
|
461
557
|
arguments: {}
|
|
462
|
-
})
|
|
558
|
+
}), {
|
|
559
|
+
tool,
|
|
560
|
+
json: opts.json
|
|
561
|
+
});
|
|
463
562
|
} catch (err) {
|
|
464
563
|
const { isMissingUsageStatusToolError, primitiveBackendUsageStatus, usageStatusText } = await Promise.resolve().then(() => require("./usage-status-BdwwgYsu.cjs")).then((n) => n.usage_status_exports);
|
|
465
564
|
if (!isMissingUsageStatusToolError(err)) throw err;
|
|
@@ -469,27 +568,30 @@ mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool dir
|
|
|
469
568
|
return;
|
|
470
569
|
}
|
|
471
570
|
if (tool === "aml_address_risk") {
|
|
472
|
-
const {
|
|
473
|
-
const result = await
|
|
571
|
+
const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-DDGHRazh.cjs"));
|
|
572
|
+
const result = await runAmlAddressRisk(client, {
|
|
474
573
|
address: String(args["address"] ?? ""),
|
|
475
574
|
network: String(args["network"] ?? ""),
|
|
476
575
|
compareAddress: args["compare_address"] === void 0 ? void 0 : String(args["compare_address"])
|
|
477
|
-
});
|
|
478
|
-
console.log(result.summaryText);
|
|
576
|
+
}, args["version"] === void 0 ? void 0 : String(args["version"]));
|
|
577
|
+
console.log(opts.json ? JSON.stringify(result.structuredContent, null, 2) : result.summaryText);
|
|
479
578
|
return;
|
|
480
579
|
}
|
|
481
580
|
printMcpTextContent(await client.callTool({
|
|
482
581
|
name: tool,
|
|
483
582
|
arguments: args
|
|
484
|
-
})
|
|
583
|
+
}), {
|
|
584
|
+
tool,
|
|
585
|
+
json: opts.json
|
|
586
|
+
});
|
|
485
587
|
});
|
|
486
588
|
} catch (err) {
|
|
487
|
-
const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-
|
|
589
|
+
const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-BBEbpKt4.cjs")).then((n) => n.tool_visibility_exports);
|
|
488
590
|
console.error(formatMcpCallError(tool, err));
|
|
489
591
|
process.exit(1);
|
|
490
592
|
}
|
|
491
593
|
}));
|
|
492
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
594
|
+
program.parseAsync(normalizeAmlToolVersionArgv(process.argv)).catch((err) => {
|
|
493
595
|
console.error(err.message);
|
|
494
596
|
process.exit(1);
|
|
495
597
|
});
|
package/dist/cli.mjs
CHANGED
|
@@ -4,6 +4,85 @@ import { execFileSync } from "node:child_process";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
//#region src/mcp/print-result.ts
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function displayValue(value) {
|
|
11
|
+
if (value === null || value === void 0) return "";
|
|
12
|
+
if (typeof value === "string") return value;
|
|
13
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
14
|
+
try {
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
} catch {
|
|
17
|
+
return String(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function formatTable(rows) {
|
|
21
|
+
if (rows.length === 0) return [];
|
|
22
|
+
const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
23
|
+
const values = rows.map((row) => columns.map((column) => displayValue(row[column])));
|
|
24
|
+
const widths = columns.map((column, index) => Math.max(column.length, ...values.map((row) => row[index]?.length ?? 0)));
|
|
25
|
+
const line = (row) => row.map((value, index) => index === row.length - 1 ? value : value.padEnd(widths[index] ?? 0)).join(" | ");
|
|
26
|
+
return [
|
|
27
|
+
line(columns),
|
|
28
|
+
widths.map((width) => "-".repeat(width)).join("-+-"),
|
|
29
|
+
...values.map(line)
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
function formatGraphResult(value, fallbackTool) {
|
|
33
|
+
const facts = isRecord(value.facts) ? value.facts : void 0;
|
|
34
|
+
const query = facts && isRecord(facts.query) ? facts.query : void 0;
|
|
35
|
+
const queries = facts && Array.isArray(facts.queries) ? facts.queries : void 0;
|
|
36
|
+
if (!query && !queries) return null;
|
|
37
|
+
const lines = [`Tool: ${displayValue(value.tool) || fallbackTool || "MCP result"}`];
|
|
38
|
+
const subject = isRecord(value.subject) ? value.subject : facts && isRecord(facts.subject) ? facts.subject : void 0;
|
|
39
|
+
if (subject?.network !== void 0) lines.push(`Network: ${displayValue(subject.network)}`);
|
|
40
|
+
if (query) {
|
|
41
|
+
const results = Array.isArray(query.results) ? query.results.filter(isRecord) : [];
|
|
42
|
+
if (query.count !== void 0) lines.push(`Rows: ${displayValue(query.count)}`);
|
|
43
|
+
else lines.push(`Rows: ${results.length}`);
|
|
44
|
+
if (query.billable_units !== void 0) lines.push(`Billed units: ${displayValue(query.billable_units)}`);
|
|
45
|
+
if (query.elapsed_ms !== void 0) lines.push(`Elapsed: ${displayValue(query.elapsed_ms)} ms`);
|
|
46
|
+
if (query.truncated !== void 0) lines.push(`Truncated: ${displayValue(query.truncated)}`);
|
|
47
|
+
if (query.error !== void 0) lines.push(`Error: ${displayValue(query.error)}`);
|
|
48
|
+
if (results.length > 0) lines.push("", "Results:", ...formatTable(results));
|
|
49
|
+
else lines.push("", "Results: none");
|
|
50
|
+
}
|
|
51
|
+
if (queries) {
|
|
52
|
+
lines.push(`Queries: ${queries.length}`);
|
|
53
|
+
for (const entry of queries) {
|
|
54
|
+
if (!isRecord(entry)) continue;
|
|
55
|
+
const id = displayValue(entry.id) || "query";
|
|
56
|
+
const results = Array.isArray(entry.results) ? entry.results.filter(isRecord) : [];
|
|
57
|
+
lines.push("", `[${id}]`);
|
|
58
|
+
if (entry.error !== void 0) lines.push(`Error: ${displayValue(entry.error)}`);
|
|
59
|
+
lines.push(`Rows: ${displayValue(entry.count ?? results.length)}`);
|
|
60
|
+
if (results.length > 0) lines.push(...formatTable(results));
|
|
61
|
+
else lines.push("Results: none");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Formats one MCP text block for terminal output. JSON is indented when the
|
|
68
|
+
* caller requests `--json`; graph query envelopes are rendered as a compact
|
|
69
|
+
* result summary and table by default.
|
|
70
|
+
*/
|
|
71
|
+
function formatMcpTextContent(text, tool, options = {}) {
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(text);
|
|
75
|
+
} catch {
|
|
76
|
+
return text;
|
|
77
|
+
}
|
|
78
|
+
if (options.json) try {
|
|
79
|
+
return JSON.stringify(parsed, null, 2);
|
|
80
|
+
} catch {
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
83
|
+
if (isRecord(parsed)) return formatGraphResult(parsed, tool) ?? JSON.stringify(parsed, null, 2);
|
|
84
|
+
return displayValue(parsed);
|
|
85
|
+
}
|
|
7
86
|
/**
|
|
8
87
|
* Prints the text blocks of an MCP tool result to stdout. When the result is
|
|
9
88
|
* flagged `isError`, throws with the tool's error text instead — MCP `callTool`
|
|
@@ -11,10 +90,10 @@ import path from "node:path";
|
|
|
11
90
|
* surface them as failures (non-zero exit) rather than printing to stdout and
|
|
12
91
|
* exiting 0.
|
|
13
92
|
*/
|
|
14
|
-
function printMcpTextContent(result) {
|
|
93
|
+
function printMcpTextContent(result, options = {}) {
|
|
15
94
|
const texts = (result.content ?? []).filter((item) => item.type === "text").map((item) => item.text ?? "");
|
|
16
95
|
if (result.isError) throw new Error(texts.join("\n").trim() || "MCP tool returned an error");
|
|
17
|
-
for (const text of texts) console.log(text);
|
|
96
|
+
for (const text of texts) console.log(formatMcpTextContent(text, options.tool, options));
|
|
18
97
|
}
|
|
19
98
|
//#endregion
|
|
20
99
|
//#region src/cli.ts
|
|
@@ -27,6 +106,16 @@ function createCliCommand(name) {
|
|
|
27
106
|
program.name("cia").description("AML investigation toolkit for blockchain analysis").version(PACKAGE_INFO.version).showHelpAfterError().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");
|
|
28
107
|
const rawArgs = process.argv.slice(2);
|
|
29
108
|
const installerFlags = rawArgs.filter((a) => a === "--claude" || a === "--codex" || a === "--hermes");
|
|
109
|
+
function normalizeAmlToolVersionArgv(argv) {
|
|
110
|
+
const commandIndex = rawArgs.findIndex((arg, index) => arg === "aml-address-risk" && (index === 0 || index === 1 && rawArgs[0] === "mcp"));
|
|
111
|
+
if (commandIndex === -1) return argv;
|
|
112
|
+
return argv.map((arg, index) => {
|
|
113
|
+
if (index - 2 <= commandIndex) return arg;
|
|
114
|
+
if (arg === "--version") return "--tool-version";
|
|
115
|
+
if (arg.startsWith("--version=")) return `--tool-version=${arg.slice(10)}`;
|
|
116
|
+
return arg;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
30
119
|
const wantsHelpOrVersion = rawArgs.some((a) => a === "--help" || a === "-h" || a === "--version" || a === "-V");
|
|
31
120
|
if (installerFlags.length > 0 && !wantsHelpOrVersion && !rawArgs.some((a) => !a.startsWith("-"))) {
|
|
32
121
|
try {
|
|
@@ -98,16 +187,19 @@ async function printNetworkCapability(name, opts) {
|
|
|
98
187
|
else console.log(formatNetworkCapability(network));
|
|
99
188
|
}
|
|
100
189
|
function addAmlAddressRiskCommand(parent, networksCommand) {
|
|
101
|
-
parent.addCommand(createCliCommand("aml-address-risk").description("Screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").action(async (opts) => {
|
|
190
|
+
parent.addCommand(createCliCommand("aml-address-risk").description("Screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
|
|
102
191
|
try {
|
|
192
|
+
const { resolveAmlAddressRiskVersion } = await import("./public-tools-CMI_0y87.mjs");
|
|
193
|
+
const requestedVersion = opts.toolVersion ?? opts.version;
|
|
194
|
+
resolveAmlAddressRiskVersion(requestedVersion);
|
|
103
195
|
await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
|
|
104
|
-
const {
|
|
105
|
-
const result = await
|
|
196
|
+
const { runAmlAddressRisk } = await import("./public-tools-CMI_0y87.mjs");
|
|
197
|
+
const result = await runAmlAddressRisk(client, {
|
|
106
198
|
address: opts.address,
|
|
107
199
|
network: opts.network,
|
|
108
200
|
compareAddress: opts.compareAddress
|
|
109
|
-
});
|
|
110
|
-
console.log(result.summaryText);
|
|
201
|
+
}, requestedVersion);
|
|
202
|
+
console.log(opts.json ? JSON.stringify(result.structuredContent, null, 2) : result.summaryText);
|
|
111
203
|
});
|
|
112
204
|
} catch (err) {
|
|
113
205
|
console.error(err.message);
|
|
@@ -402,7 +494,7 @@ const mcpCommand = program.command("mcp").description("Interact with the Chain I
|
|
|
402
494
|
try {
|
|
403
495
|
const { loadSchema, saveSchema } = await import("./schema-cache-DJdU4MIP.mjs");
|
|
404
496
|
const { formatToolsTable } = await import("./format-DB2IH2U5.mjs");
|
|
405
|
-
const { visibleRemoteTools } = await import("./tool-visibility-
|
|
497
|
+
const { visibleRemoteTools } = await import("./tool-visibility-CJqjmDF5.mjs").then((n) => n.i);
|
|
406
498
|
const { loadConfig } = await import("./config-DbbPiGk0.mjs").then((n) => n.t);
|
|
407
499
|
const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-CHYskPnG.mjs").then((n) => n.r);
|
|
408
500
|
const config = await loadConfig();
|
|
@@ -431,20 +523,24 @@ const mcpCommand = program.command("mcp").description("Interact with the Chain I
|
|
|
431
523
|
}
|
|
432
524
|
}));
|
|
433
525
|
addAmlAddressRiskCommand(mcpCommand, "cia mcp networks");
|
|
434
|
-
mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool directly (debug)").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234... network=robinhood)").action(async (tool, rawArgs) => {
|
|
526
|
+
mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool directly (debug)").option("--json", "Print machine-readable JSON output").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234... network=robinhood)").action(async (tool, rawArgs, opts) => {
|
|
435
527
|
try {
|
|
436
528
|
const { parseMcpCallArgs } = await import("./call-args-ysbB_xdA.mjs");
|
|
437
|
-
const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-
|
|
529
|
+
const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-CJqjmDF5.mjs").then((n) => n.i);
|
|
438
530
|
const args = parseMcpCallArgs(rawArgs);
|
|
439
531
|
assertPublicMcpToolName(tool);
|
|
440
532
|
validatePublicMcpToolArguments(tool, args);
|
|
533
|
+
if (tool === "aml_address_risk") {
|
|
534
|
+
const { resolveAmlAddressRiskVersion } = await import("./public-tools-CMI_0y87.mjs");
|
|
535
|
+
resolveAmlAddressRiskVersion(args["version"] === void 0 ? void 0 : String(args["version"]));
|
|
536
|
+
}
|
|
441
537
|
if (tool === "wallet_balance") {
|
|
442
538
|
const { getWalletBalanceText } = await import("./tools-BRQAPF2R.mjs").then((n) => n.p);
|
|
443
539
|
console.log(await getWalletBalanceText());
|
|
444
540
|
return;
|
|
445
541
|
}
|
|
446
542
|
if (tool === "meta_network_capabilities") {
|
|
447
|
-
await printNetworkCapabilities({ json:
|
|
543
|
+
await printNetworkCapabilities({ json: opts.json });
|
|
448
544
|
return;
|
|
449
545
|
}
|
|
450
546
|
if (tool === "meta_help") {
|
|
@@ -457,7 +553,10 @@ mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool dir
|
|
|
457
553
|
printMcpTextContent(await client.callTool({
|
|
458
554
|
name: "usage_status",
|
|
459
555
|
arguments: {}
|
|
460
|
-
})
|
|
556
|
+
}), {
|
|
557
|
+
tool,
|
|
558
|
+
json: opts.json
|
|
559
|
+
});
|
|
461
560
|
} catch (err) {
|
|
462
561
|
const { isMissingUsageStatusToolError, primitiveBackendUsageStatus, usageStatusText } = await import("./usage-status-CHiOPA-2.mjs").then((n) => n.n);
|
|
463
562
|
if (!isMissingUsageStatusToolError(err)) throw err;
|
|
@@ -467,27 +566,30 @@ mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool dir
|
|
|
467
566
|
return;
|
|
468
567
|
}
|
|
469
568
|
if (tool === "aml_address_risk") {
|
|
470
|
-
const {
|
|
471
|
-
const result = await
|
|
569
|
+
const { runAmlAddressRisk } = await import("./public-tools-CMI_0y87.mjs");
|
|
570
|
+
const result = await runAmlAddressRisk(client, {
|
|
472
571
|
address: String(args["address"] ?? ""),
|
|
473
572
|
network: String(args["network"] ?? ""),
|
|
474
573
|
compareAddress: args["compare_address"] === void 0 ? void 0 : String(args["compare_address"])
|
|
475
|
-
});
|
|
476
|
-
console.log(result.summaryText);
|
|
574
|
+
}, args["version"] === void 0 ? void 0 : String(args["version"]));
|
|
575
|
+
console.log(opts.json ? JSON.stringify(result.structuredContent, null, 2) : result.summaryText);
|
|
477
576
|
return;
|
|
478
577
|
}
|
|
479
578
|
printMcpTextContent(await client.callTool({
|
|
480
579
|
name: tool,
|
|
481
580
|
arguments: args
|
|
482
|
-
})
|
|
581
|
+
}), {
|
|
582
|
+
tool,
|
|
583
|
+
json: opts.json
|
|
584
|
+
});
|
|
483
585
|
});
|
|
484
586
|
} catch (err) {
|
|
485
|
-
const { formatMcpCallError } = await import("./tool-visibility-
|
|
587
|
+
const { formatMcpCallError } = await import("./tool-visibility-CJqjmDF5.mjs").then((n) => n.i);
|
|
486
588
|
console.error(formatMcpCallError(tool, err));
|
|
487
589
|
process.exit(1);
|
|
488
590
|
}
|
|
489
591
|
}));
|
|
490
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
592
|
+
program.parseAsync(normalizeAmlToolVersionArgv(process.argv)).catch((err) => {
|
|
491
593
|
console.error(err.message);
|
|
492
594
|
process.exit(1);
|
|
493
595
|
});
|