chain-insights 0.24.2 → 0.26.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 CHANGED
@@ -22,15 +22,30 @@ 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 mcp call aml_address_risk network=robinhood address=0xYourAddressHere
25
+ cia mcp 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
  ```
29
29
 
30
- Sixty seconds gets you the CLI, a network overview, and the remote tool catalog.
30
+ Sixty seconds gets you the CLI, a network overview, and the available tools for
31
+ Robinhood.
31
32
  To call the same tools from an agent, register the MCP proxy:
32
33
  `cia setup claude-code` (or `codex` / `hermes`).
33
34
 
35
+ CLI results are human-readable by default. Add `--json` when another tool or
36
+ script needs indented JSON:
37
+
38
+ ```bash
39
+ cia mcp call --json graph_query \
40
+ network=robinhood \
41
+ "query=USE topology MATCH (a:Address) RETURN a.address AS address LIMIT 10"
42
+ cia mcp aml-address-risk --json --address 0xYourAddressHere --network robinhood
43
+ ```
44
+
45
+ The AML address-risk contract uses the latest version when `version` is
46
+ omitted. Pin the current contract with `--version v1` on the AML command, or
47
+ with `version=v1` when using `cia mcp call aml_address_risk`.
48
+
34
49
  ## Purpose And Ownership
35
50
 
36
51
  One public npm package (`chain-insights`) providing the `cia` CLI and a
@@ -129,7 +144,7 @@ Graph queries choose the read graph explicitly:
129
144
  | Graph | Use it for |
130
145
  | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
131
146
  | `topology` | The unified address / FLOWS_TO / LINKED graph — recent and full historical fund-flow traversal, plus the node `risk_score`/`risk_level` verdict |
132
- | `facts` | Labels, features, assets, and enrichment |
147
+ | `facts` | Bounded individual `TRANSFER` rows with amount, `amount_usd`, asset, transaction, and block facts |
133
148
 
134
149
  One rule is worth reading before writing a query by hand: the `network`
135
150
  argument selects the graph, not the addresses inside it. The address-space
@@ -210,6 +225,13 @@ cia update --check
210
225
 
211
226
  Run a first screen from any directory:
212
227
 
228
+ ```bash
229
+ cia mcp aml-address-risk \
230
+ --address 0xYourAddressHere --network robinhood
231
+ ```
232
+
233
+ The same screen is available through the generic MCP caller:
234
+
213
235
  ```bash
214
236
  cia mcp call aml_address_risk \
215
237
  network=robinhood address=0xYourAddressHere
@@ -2,7 +2,6 @@ const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
2
  const require_client = require("./client-Ypw9BNbn.cjs");
3
3
  //#region src/mcp/capabilities.ts
4
4
  var capabilities_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
5
- PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS: () => PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS,
6
5
  fetchNetworkCapabilities: () => fetchNetworkCapabilities,
7
6
  findNetworkCapability: () => findNetworkCapability,
8
7
  formatNetworkCapabilities: () => formatNetworkCapabilities,
@@ -10,19 +9,16 @@ var capabilities_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll(
10
9
  formatNetworkOverview: () => formatNetworkOverview,
11
10
  mirrorGraphNetworkCapabilities: () => mirrorGraphNetworkCapabilities
12
11
  });
13
- const PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS = {
14
- aml_address_risk: "available",
15
- graph_query: "available",
16
- graph_query_batch: "available",
17
- meta_network_capabilities: "available",
18
- meta_usage_status: "available",
19
- meta_help: "available",
20
- wallet_balance: "available"
21
- };
22
12
  const AVAILABLE_TOOLS_PER_LINE = 3;
23
13
  function isRecord(value) {
24
14
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
15
  }
16
+ function advertisedTools(raw) {
17
+ if (!isRecord(raw)) return {};
18
+ const tools = {};
19
+ for (const [name, status] of Object.entries(raw)) if (name.trim() !== "" && typeof status === "string") tools[name] = status;
20
+ return tools;
21
+ }
26
22
  function advertisedNetwork(raw) {
27
23
  if (!isRecord(raw) || typeof raw.network !== "string" || raw.network.trim() === "") return null;
28
24
  const network = raw.network.trim();
@@ -31,7 +27,7 @@ function advertisedNetwork(raw) {
31
27
  display_name: typeof raw.display_name === "string" && raw.display_name.trim() !== "" ? raw.display_name : network,
32
28
  status: typeof raw.status === "string" && raw.status.trim() !== "" ? raw.status : "live",
33
29
  layers: {},
34
- tools: { ...PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS }
30
+ tools: advertisedTools(raw.tools)
35
31
  };
36
32
  if (raw.default === true) capability.default = true;
37
33
  if (raw.default === false) capability.default = false;
@@ -73,11 +69,7 @@ async function fetchNetworkCapabilities(config) {
73
69
  return publicNetworkCapabilities(parsed);
74
70
  }
75
71
  function availableTools(network) {
76
- const effectiveTools = {
77
- ...network.tools ?? {},
78
- ...PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS
79
- };
80
- return Object.entries(effectiveTools).filter(([, status]) => status === "available").map(([name]) => name).sort();
72
+ return Object.entries(network.tools ?? {}).filter(([, status]) => status === "available").map(([name]) => name).sort();
81
73
  }
82
74
  function availableToolLines(network) {
83
75
  const tools = availableTools(network);
@@ -2,7 +2,6 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { a as resolveGraphMcpEndpoint, n as applyMcpAuthHeaders } from "./client-CHYskPnG.mjs";
3
3
  //#region src/mcp/capabilities.ts
4
4
  var capabilities_exports = /* @__PURE__ */ __exportAll({
5
- PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS: () => PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS,
6
5
  fetchNetworkCapabilities: () => fetchNetworkCapabilities,
7
6
  findNetworkCapability: () => findNetworkCapability,
8
7
  formatNetworkCapabilities: () => formatNetworkCapabilities,
@@ -10,19 +9,16 @@ var capabilities_exports = /* @__PURE__ */ __exportAll({
10
9
  formatNetworkOverview: () => formatNetworkOverview,
11
10
  mirrorGraphNetworkCapabilities: () => mirrorGraphNetworkCapabilities
12
11
  });
13
- const PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS = {
14
- aml_address_risk: "available",
15
- graph_query: "available",
16
- graph_query_batch: "available",
17
- meta_network_capabilities: "available",
18
- meta_usage_status: "available",
19
- meta_help: "available",
20
- wallet_balance: "available"
21
- };
22
12
  const AVAILABLE_TOOLS_PER_LINE = 3;
23
13
  function isRecord(value) {
24
14
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
15
  }
16
+ function advertisedTools(raw) {
17
+ if (!isRecord(raw)) return {};
18
+ const tools = {};
19
+ for (const [name, status] of Object.entries(raw)) if (name.trim() !== "" && typeof status === "string") tools[name] = status;
20
+ return tools;
21
+ }
26
22
  function advertisedNetwork(raw) {
27
23
  if (!isRecord(raw) || typeof raw.network !== "string" || raw.network.trim() === "") return null;
28
24
  const network = raw.network.trim();
@@ -31,7 +27,7 @@ function advertisedNetwork(raw) {
31
27
  display_name: typeof raw.display_name === "string" && raw.display_name.trim() !== "" ? raw.display_name : network,
32
28
  status: typeof raw.status === "string" && raw.status.trim() !== "" ? raw.status : "live",
33
29
  layers: {},
34
- tools: { ...PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS }
30
+ tools: advertisedTools(raw.tools)
35
31
  };
36
32
  if (raw.default === true) capability.default = true;
37
33
  if (raw.default === false) capability.default = false;
@@ -73,11 +69,7 @@ async function fetchNetworkCapabilities(config) {
73
69
  return publicNetworkCapabilities(parsed);
74
70
  }
75
71
  function availableTools(network) {
76
- const effectiveTools = {
77
- ...network.tools ?? {},
78
- ...PUBLIC_CHAIN_INSIGHTS_TOOL_STATUS
79
- };
80
- return Object.entries(effectiveTools).filter(([, status]) => status === "available").map(([name]) => name).sort();
72
+ return Object.entries(network.tools ?? {}).filter(([, status]) => status === "available").map(([name]) => name).sort();
81
73
  }
82
74
  function availableToolLines(network) {
83
75
  const tools = availableTools(network);
@@ -168,4 +160,4 @@ function formatNetworkCapability(network) {
168
160
  //#endregion
169
161
  export { mirrorGraphNetworkCapabilities as n, capabilities_exports as t };
170
162
 
171
- //# sourceMappingURL=capabilities-CY3Ok5ls.mjs.map
163
+ //# sourceMappingURL=capabilities-ByKP5l0G.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities-ByKP5l0G.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { applyMcpAuthHeaders, resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkTopologyLayer {\n enabled: boolean\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n }\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n live?: NetworkTopologyLayer\n archive?: NetworkTopologyLayer\n}\n\nexport interface NetworkCapability {\n network: string\n display_name?: string\n status: string\n default?: boolean\n layers: Record<string, NetworkLayerCapability>\n tools: Record<string, string>\n coverage?: {\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n chain_tip_block?: number\n blocks_behind_tip?: number\n }\n freshness?: {\n last_processed_at?: string\n last_successful_sync_at?: string\n max_data_age_seconds?: number\n last_processing_duration_seconds?: number\n }\n}\n\nexport interface NetworkCapabilitiesDocument {\n schema: 'chain-insights.network-capabilities.v1'\n networks: NetworkCapability[]\n}\n\nconst AVAILABLE_TOOLS_PER_LINE = 3\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction advertisedTools(raw: unknown): Record<string, string> {\n if (!isRecord(raw)) return {}\n const tools: Record<string, string> = {}\n for (const [name, status] of Object.entries(raw)) {\n if (name.trim() !== '' && typeof status === 'string') tools[name] = status\n }\n return tools\n}\n\nfunction advertisedNetwork(raw: unknown): NetworkCapability | null {\n if (!isRecord(raw) || typeof raw.network !== 'string' || raw.network.trim() === '') {\n return null\n }\n const network = raw.network.trim()\n const capability: NetworkCapability = {\n network,\n display_name:\n typeof raw.display_name === 'string' && raw.display_name.trim() !== ''\n ? raw.display_name\n : network,\n status: typeof raw.status === 'string' && raw.status.trim() !== '' ? raw.status : 'live',\n layers: {},\n tools: advertisedTools(raw.tools),\n }\n if (raw.default === true) capability.default = true\n if (raw.default === false) capability.default = false\n if (isRecord(raw.coverage)) capability.coverage = raw.coverage as NetworkCapability['coverage']\n if (isRecord(raw.freshness))\n capability.freshness = raw.freshness as NetworkCapability['freshness']\n return capability\n}\n\n/** Repeat GraphRAG's network list. CIA does not add, drop, or invent names. */\nexport function mirrorGraphNetworkCapabilities(document: {\n networks: unknown[]\n}): NetworkCapabilitiesDocument {\n return {\n schema: 'chain-insights.network-capabilities.v1',\n networks: document.networks\n .map((network) => advertisedNetwork(network))\n .filter((network): network is NetworkCapability => network !== null),\n }\n}\n\nfunction publicNetworkCapabilities(\n document: NetworkCapabilitiesDocument\n): NetworkCapabilitiesDocument {\n return mirrorGraphNetworkCapabilities(document)\n}\n\nfunction metadataNetworksUrl(endpoint: string): URL {\n const url = new URL(endpoint)\n url.pathname = '/metadata/networks'\n url.search = ''\n url.hash = ''\n return url\n}\n\nexport async function fetchNetworkCapabilities(\n config: Pick<InvestigatorConfig, 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint'>\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim()\n if (token) {\n applyMcpAuthHeaders(headers, token)\n }\n let response: Response\n try {\n response = await fetch(request, { headers })\n } catch (err) {\n throw new Error(`network capabilities unavailable at ${request}: ${(err as Error).message}`)\n }\n if (!response.ok) {\n throw new Error(`network capabilities unavailable at ${request}: HTTP ${response.status}`)\n }\n const parsed = (await response.json()) as NetworkCapabilitiesDocument\n if (\n parsed.schema !== 'chain-insights.network-capabilities.v1' ||\n !Array.isArray(parsed.networks)\n ) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return publicNetworkCapabilities(parsed)\n}\n\nfunction availableTools(network: NetworkCapability): string[] {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.sort()\n}\n\nfunction availableToolLines(network: NetworkCapability): string[] {\n const tools = availableTools(network)\n if (tools.length === 0) return ['none']\n const lines: string[] = []\n for (let index = 0; index < tools.length; index += AVAILABLE_TOOLS_PER_LINE) {\n lines.push(tools.slice(index, index + AVAILABLE_TOOLS_PER_LINE).join(', '))\n }\n return lines\n}\n\nfunction shortDate(value?: string): string {\n if (!value) return ''\n return value.slice(0, 10)\n}\n\nfunction datasetLabel(network: NetworkCapability): string {\n const coverage = network.coverage\n if (!coverage) return 'unknown'\n const blockRange =\n coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange =\n coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : ''\n if (blockRange === 'blocks unknown' && dateRange === '') return 'unknown'\n if (blockRange === 'blocks unknown') return dateRange\n if (dateRange === '') return blockRange\n return `${blockRange} / ${dateRange}`\n}\n\nfunction displayName(network: NetworkCapability): string {\n return network.display_name || network.network\n}\n\nfunction statusLabel(network: NetworkCapability): string {\n return network.default ? `${network.status} (default)` : network.status\n}\n\nfunction formatTable(headers: string[], rows: string[][], minimumWidths: number[]): string {\n const widths = headers.map((header, index) =>\n Math.max(\n minimumWidths[index] ?? 0,\n header.length,\n ...rows.map((row) => row[index]?.length ?? 0)\n )\n )\n const row = (values: string[]) =>\n values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [row(headers), widths.map((width) => '-'.repeat(width)).join(' '), ...rows.map(row)].join(\n '\\n'\n )\n}\n\nexport function formatNetworkOverview(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const rows = document.networks.map((network) => [\n displayName(network),\n statusLabel(network),\n datasetLabel(network),\n ])\n return formatTable(['Network', 'Status', 'Dataset'], rows, [14, 10, 38])\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const networkRows = document.networks.flatMap((network) => {\n const toolLines = availableToolLines(network)\n return toolLines.map((toolLine, index) => [\n index === 0 ? displayName(network) : '',\n index === 0 ? datasetLabel(network) : '',\n toolLine,\n ])\n })\n return formatTable(['Network', 'Dataset', 'Chain Insights tools'], networkRows, [14, 38, 64])\n}\n\nexport function findNetworkCapability(\n document: NetworkCapabilitiesDocument,\n name: string\n): NetworkCapability | undefined {\n const normalizedName = name.trim().toLowerCase()\n if (!normalizedName) return undefined\n return document.networks.find((network) =>\n [network.network, network.display_name].some(\n (candidate) => candidate?.trim().toLowerCase() === normalizedName\n )\n )\n}\n\nexport function formatNetworkCapability(network: NetworkCapability): string {\n const rows = [\n ['Network', network.display_name || network.network],\n ['Identifier', network.network],\n ['Status', network.default ? `${network.status} (default)` : network.status],\n ['Dataset', datasetLabel(network)],\n ['Available tools', availableTools(network).join(', ') || 'none'],\n ]\n const labelWidth = Math.max(...rows.map(([label]) => label.length))\n return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join('\\n')\n}\n"],"mappings":"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"}
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 === 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 {
@@ -74,21 +163,21 @@ async function withGraphMcpClient(name, fn) {
74
163
  }
75
164
  async function printNetworkCapabilities(opts) {
76
165
  const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
77
- const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-CjpGnrVg.cjs")).then((n) => n.capabilities_exports);
166
+ const { fetchNetworkCapabilities, formatNetworkCapabilities } = await Promise.resolve().then(() => require("./capabilities-B-M8vAYp.cjs")).then((n) => n.capabilities_exports);
78
167
  const document = await fetchNetworkCapabilities(await loadConfig());
79
168
  if (opts.json) console.log(JSON.stringify(document, null, 2));
80
169
  else console.log(formatNetworkCapabilities(document));
81
170
  }
82
171
  async function printNetworkOverview(opts) {
83
172
  const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
84
- const { fetchNetworkCapabilities, formatNetworkOverview } = await Promise.resolve().then(() => require("./capabilities-CjpGnrVg.cjs")).then((n) => n.capabilities_exports);
173
+ const { fetchNetworkCapabilities, formatNetworkOverview } = await Promise.resolve().then(() => require("./capabilities-B-M8vAYp.cjs")).then((n) => n.capabilities_exports);
85
174
  const document = await fetchNetworkCapabilities(await loadConfig());
86
175
  if (opts.json) console.log(JSON.stringify(document, null, 2));
87
176
  else console.log(formatNetworkOverview(document));
88
177
  }
89
178
  async function printNetworkCapability(name, opts) {
90
179
  const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
91
- const { fetchNetworkCapabilities, findNetworkCapability, formatNetworkCapability } = await Promise.resolve().then(() => require("./capabilities-CjpGnrVg.cjs")).then((n) => n.capabilities_exports);
180
+ const { fetchNetworkCapabilities, findNetworkCapability, formatNetworkCapability } = await Promise.resolve().then(() => require("./capabilities-B-M8vAYp.cjs")).then((n) => n.capabilities_exports);
92
181
  const document = await fetchNetworkCapabilities(await loadConfig());
93
182
  const network = findNetworkCapability(document, name);
94
183
  if (!network) {
@@ -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 { addressRisk } = await Promise.resolve().then(() => require("./public-tools-Bou25cki.cjs"));
107
- const result = await addressRisk(client, {
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);
@@ -392,7 +484,6 @@ program.command("wallet").description("Manage the local Base USDC payment wallet
392
484
  process.exit(1);
393
485
  }
394
486
  }));
395
- addAmlAddressRiskCommand(program, "cia networks");
396
487
  const mcpCommand = program.command("mcp").description("Interact with the Chain Insights MCP endpoint").allowExcessArguments(false).addCommand(createCliCommand("networks").description("List the detailed Chain Insights capability matrix and tool support").option("--json", "Print raw capability JSON").action(async (opts) => {
397
488
  try {
398
489
  await printNetworkCapabilities(opts);
@@ -404,7 +495,7 @@ const mcpCommand = program.command("mcp").description("Interact with the Chain I
404
495
  try {
405
496
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-n7Gbhvsj.cjs"));
406
497
  const { formatToolsTable } = await Promise.resolve().then(() => require("./format-BjZuYDr8.cjs"));
407
- const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-DA4pOuJt.cjs")).then((n) => n.tool_visibility_exports);
498
+ const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-BBEbpKt4.cjs")).then((n) => n.tool_visibility_exports);
408
499
  const { loadConfig } = await Promise.resolve().then(() => require("./config-B542AM4w.cjs")).then((n) => n.config_exports);
409
500
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Ypw9BNbn.cjs")).then((n) => n.client_exports);
410
501
  const config = await loadConfig();
@@ -433,20 +524,24 @@ const mcpCommand = program.command("mcp").description("Interact with the Chain I
433
524
  }
434
525
  }));
435
526
  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) => {
527
+ 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
528
  try {
438
529
  const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-DAiQSDmD.cjs"));
439
- const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-DA4pOuJt.cjs")).then((n) => n.tool_visibility_exports);
530
+ const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-BBEbpKt4.cjs")).then((n) => n.tool_visibility_exports);
440
531
  const args = parseMcpCallArgs(rawArgs);
441
532
  assertPublicMcpToolName(tool);
442
533
  validatePublicMcpToolArguments(tool, args);
534
+ if (tool === "aml_address_risk") {
535
+ const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-DDGHRazh.cjs"));
536
+ resolveAmlAddressRiskVersion(args["version"] === void 0 ? void 0 : String(args["version"]));
537
+ }
443
538
  if (tool === "wallet_balance") {
444
539
  const { getWalletBalanceText } = await Promise.resolve().then(() => require("./tools-BHfphgy9.cjs")).then((n) => n.tools_exports);
445
540
  console.log(await getWalletBalanceText());
446
541
  return;
447
542
  }
448
543
  if (tool === "meta_network_capabilities") {
449
- await printNetworkCapabilities({ json: true });
544
+ await printNetworkCapabilities({ json: opts.json });
450
545
  return;
451
546
  }
452
547
  if (tool === "meta_help") {
@@ -459,7 +554,10 @@ mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool dir
459
554
  printMcpTextContent(await client.callTool({
460
555
  name: "usage_status",
461
556
  arguments: {}
462
- }));
557
+ }), {
558
+ tool,
559
+ json: opts.json
560
+ });
463
561
  } catch (err) {
464
562
  const { isMissingUsageStatusToolError, primitiveBackendUsageStatus, usageStatusText } = await Promise.resolve().then(() => require("./usage-status-BdwwgYsu.cjs")).then((n) => n.usage_status_exports);
465
563
  if (!isMissingUsageStatusToolError(err)) throw err;
@@ -469,27 +567,30 @@ mcpCommand.addCommand(createCliCommand("call").description("Call an MCP tool dir
469
567
  return;
470
568
  }
471
569
  if (tool === "aml_address_risk") {
472
- const { addressRisk } = await Promise.resolve().then(() => require("./public-tools-Bou25cki.cjs"));
473
- const result = await addressRisk(client, {
570
+ const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-DDGHRazh.cjs"));
571
+ const result = await runAmlAddressRisk(client, {
474
572
  address: String(args["address"] ?? ""),
475
573
  network: String(args["network"] ?? ""),
476
574
  compareAddress: args["compare_address"] === void 0 ? void 0 : String(args["compare_address"])
477
- });
478
- console.log(result.summaryText);
575
+ }, args["version"] === void 0 ? void 0 : String(args["version"]));
576
+ console.log(opts.json ? JSON.stringify(result.structuredContent, null, 2) : result.summaryText);
479
577
  return;
480
578
  }
481
579
  printMcpTextContent(await client.callTool({
482
580
  name: tool,
483
581
  arguments: args
484
- }));
582
+ }), {
583
+ tool,
584
+ json: opts.json
585
+ });
485
586
  });
486
587
  } catch (err) {
487
- const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-DA4pOuJt.cjs")).then((n) => n.tool_visibility_exports);
588
+ const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-BBEbpKt4.cjs")).then((n) => n.tool_visibility_exports);
488
589
  console.error(formatMcpCallError(tool, err));
489
590
  process.exit(1);
490
591
  }
491
592
  }));
492
- program.parseAsync(process.argv).catch((err) => {
593
+ program.parseAsync(normalizeAmlToolVersionArgv(process.argv)).catch((err) => {
493
594
  console.error(err.message);
494
595
  process.exit(1);
495
596
  });