chain-insights 0.2.18 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +54 -12
  2. package/bin/cli.js +2 -3
  3. package/bin/install.cjs +0 -1
  4. package/dist/{app-DxlQE_P5.cjs → app-BxojXjtB.cjs} +1 -1
  5. package/dist/{app-DdWQF_zb.mjs → app-CRd39JJ8.mjs} +2 -2
  6. package/dist/{app-DdWQF_zb.mjs.map → app-CRd39JJ8.mjs.map} +1 -1
  7. package/dist/{artifact-server-4DiMvwhC.mjs → artifact-server-CP6LXQ9d.mjs} +2 -2
  8. package/dist/{artifact-server-4DiMvwhC.mjs.map → artifact-server-CP6LXQ9d.mjs.map} +1 -1
  9. package/dist/{artifact-server-B-3ho4bk.cjs → artifact-server-XbN16DwU.cjs} +1 -1
  10. package/dist/cli.cjs +66 -25
  11. package/dist/cli.mjs +66 -25
  12. package/dist/cli.mjs.map +1 -1
  13. package/dist/{config-BhYbhLDI.cjs → config-BwVx19Og.cjs} +48 -15
  14. package/dist/config-Drgc2HuF.mjs +77 -0
  15. package/dist/config-Drgc2HuF.mjs.map +1 -0
  16. package/dist/frontmatter-D0ccQnUM.mjs.map +1 -1
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.cts.map +1 -1
  20. package/dist/index.d.mts +3 -3
  21. package/dist/index.d.mts.map +1 -1
  22. package/dist/index.mjs +4 -4
  23. package/dist/{init-CZbZegIW.mjs → init-4tn7jfhN.mjs} +3 -2
  24. package/dist/init-4tn7jfhN.mjs.map +1 -0
  25. package/dist/{init-BvpZtFiT.cjs → init-TCQY5RDJ.cjs} +2 -1
  26. package/dist/mcp-endpoint-BaV8h_lq.cjs +60 -0
  27. package/dist/mcp-endpoint-DHs1cRFH.mjs +39 -0
  28. package/dist/mcp-endpoint-DHs1cRFH.mjs.map +1 -0
  29. package/dist/mcp-proxy.cjs +108 -9
  30. package/dist/mcp-proxy.d.cts.map +1 -1
  31. package/dist/mcp-proxy.d.mts.map +1 -1
  32. package/dist/mcp-proxy.mjs +108 -9
  33. package/dist/mcp-proxy.mjs.map +1 -1
  34. package/dist/{public-tools-D6Q5MTcO.mjs → public-tools-B13J0MJZ.mjs} +465 -70
  35. package/dist/public-tools-B13J0MJZ.mjs.map +1 -0
  36. package/dist/{public-tools-V7ON7goq.cjs → public-tools-BC1fi0DV.cjs} +464 -68
  37. package/dist/resolver-D7VBb0uB.mjs.map +1 -1
  38. package/dist/{runner-BatyCxv7.mjs → runner-DIs04IhN.mjs} +2 -2
  39. package/dist/{runner-BatyCxv7.mjs.map → runner-DIs04IhN.mjs.map} +1 -1
  40. package/dist/{runner-CCA7SJ7X.cjs → runner-ZYowxCVl.cjs} +1 -1
  41. package/dist/schema-BFEWhzg7.mjs +60 -0
  42. package/dist/schema-BFEWhzg7.mjs.map +1 -0
  43. package/dist/{schema-DN-KLkYN.cjs → schema-Vl9yuOFO.cjs} +31 -8
  44. package/dist/{server-BDlbmGbL.mjs → server-BXLX2j_A.mjs} +2 -2
  45. package/dist/{server-BDlbmGbL.mjs.map → server-BXLX2j_A.mjs.map} +1 -1
  46. package/dist/{server-C3y1gQmZ.cjs → server-BqVdWath.cjs} +1 -1
  47. package/dist/{topup-server-6MH7q73X.mjs → topup-server-BJgVw6Jt.mjs} +100 -42
  48. package/dist/topup-server-BJgVw6Jt.mjs.map +1 -0
  49. package/dist/{topup-server-DjUjhNjv.cjs → topup-server-yAaXYkJP.cjs} +98 -40
  50. package/docs/architecture.md +4 -0
  51. package/docs/contributing.md +1 -0
  52. package/docs/debugging.md +10 -14
  53. package/docs/graph-tools.md +60 -2
  54. package/docs/mcp-proxy.md +44 -0
  55. package/package.json +2 -2
  56. package/skills/chain-insights-developer-experience/SKILL.md +4 -2
  57. package/skills/chain-insights-investigation/SKILL.md +1 -1
  58. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +4 -5
  59. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +5 -24
  60. package/dist/config-9KYXaAv-.mjs +0 -44
  61. package/dist/config-9KYXaAv-.mjs.map +0 -1
  62. package/dist/init-CZbZegIW.mjs.map +0 -1
  63. package/dist/public-tools-D6Q5MTcO.mjs.map +0 -1
  64. package/dist/schema-BbQVXp36.mjs +0 -37
  65. package/dist/schema-BbQVXp36.mjs.map +0 -1
  66. package/dist/topup-server-6MH7q73X.mjs.map +0 -1
package/dist/cli.mjs CHANGED
@@ -69,7 +69,7 @@ function optionalScamTopologyActivityPolicy(value) {
69
69
  throw new Error("activity_policy must be one of: node_relative_only, global_incident_only");
70
70
  }
71
71
  async function withGraphMcpClient(name, fn) {
72
- const { loadConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
72
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
73
73
  const config = await loadConfig();
74
74
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-D4_hd4AP.mjs").then((n) => n.n);
75
75
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
@@ -90,7 +90,7 @@ function printMcpTextContent(result) {
90
90
  for (const item of result.content ?? []) if (item.type === "text") console.log(item.text);
91
91
  }
92
92
  async function printNetworkCapabilities(opts) {
93
- const { loadConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
93
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
94
94
  const { fetchNetworkCapabilities, formatNetworkCapabilities } = await import("./capabilities-BShqspb-.mjs");
95
95
  const document = await fetchNetworkCapabilities(await loadConfig());
96
96
  if (opts.json) console.log(JSON.stringify(document, null, 2));
@@ -108,7 +108,7 @@ program.command("serve").description("Start local visualization server").option(
108
108
  try {
109
109
  const { requireWorkspaceRoot } = await import("./output-root-BRhzhhXZ.mjs").then((n) => n.t);
110
110
  const workspaceRoot = requireWorkspaceRoot();
111
- const { startServer } = await import("./server-BDlbmGbL.mjs").then((n) => n.t);
111
+ const { startServer } = await import("./server-BXLX2j_A.mjs").then((n) => n.t);
112
112
  console.log(`Workspace: ${workspaceRoot}`);
113
113
  startServer(parseInt(opts.port, 10));
114
114
  } catch (err) {
@@ -117,7 +117,7 @@ program.command("serve").description("Start local visualization server").option(
117
117
  }
118
118
  });
119
119
  program.command("status").description("Show toolkit status and configuration").action(async () => {
120
- const { loadConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
120
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
121
121
  const { findActiveWorkspace, activeDataDir } = await import("./active-ByNgjuAg.mjs").then((n) => n.n);
122
122
  const config = await loadConfig();
123
123
  const workspace = findActiveWorkspace();
@@ -130,7 +130,7 @@ program.command("status").description("Show toolkit status and configuration").a
130
130
  });
131
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
132
  try {
133
- const { saveConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
133
+ const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
134
134
  await saveConfig({
135
135
  graphMcpMode: "debug",
136
136
  graphMcpAuthToken: opts.token,
@@ -145,7 +145,7 @@ program.command("debug").description("Configure Graph MCP debug mode").addComman
145
145
  }
146
146
  })).addCommand(new Command("off").description("Disable Graph MCP debug mode and use paid x402 calls").action(async () => {
147
147
  try {
148
- const { saveConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
148
+ const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
149
149
  await saveConfig({
150
150
  graphMcpMode: "paid",
151
151
  graphMcpAuthToken: ""
@@ -158,7 +158,7 @@ program.command("debug").description("Configure Graph MCP debug mode").addComman
158
158
  }
159
159
  })).addCommand(new Command("status").description("Show Graph MCP payment/debug mode").action(async () => {
160
160
  try {
161
- const { loadConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
161
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
162
162
  const config = await loadConfig();
163
163
  console.log(`Graph MCP mode: ${config.graphMcpMode}`);
164
164
  console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
@@ -173,7 +173,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
173
173
  try {
174
174
  const normalizedKey = key.trim();
175
175
  if (!normalizedKey) throw new Error("Test access key is required");
176
- const { saveConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
176
+ const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
177
177
  await saveConfig({
178
178
  graphMcpMode: "debug",
179
179
  graphMcpAuthToken: normalizedKey,
@@ -188,7 +188,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
188
188
  }
189
189
  })).addCommand(new Command("clear").description("Remove the Graph MCP test access key and use paid x402 calls").action(async () => {
190
190
  try {
191
- const { saveConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
191
+ const { saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
192
192
  await saveConfig({
193
193
  graphMcpMode: "paid",
194
194
  graphMcpAuthToken: ""
@@ -201,7 +201,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
201
201
  }
202
202
  })).addCommand(new Command("status").description("Show Graph MCP test access key status").action(async () => {
203
203
  try {
204
- const { loadConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
204
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
205
205
  const config = await loadConfig();
206
206
  console.log(`Graph endpoint: ${config.graphMcpEndpoint}`);
207
207
  console.log(`Access key: ${config.graphMcpAuthToken?.trim() ? "configured" : "not configured"}`);
@@ -213,7 +213,7 @@ program.command("access-key").description("Configure Graph MCP test access key m
213
213
  }));
214
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
215
  try {
216
- const { initWorkspace } = await import("./init-CZbZegIW.mjs");
216
+ const { initWorkspace } = await import("./init-4tn7jfhN.mjs");
217
217
  const result = await initWorkspace({
218
218
  targetDir: dir,
219
219
  force: opts.force
@@ -248,8 +248,8 @@ program.command("setup").description("Configure external MCP clients").addComman
248
248
  }
249
249
  }));
250
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-9KYXaAv-.mjs").then((n) => n.t);
252
- const { CONFIG_KEYS } = await import("./schema-BbQVXp36.mjs").then((n) => n.r);
251
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
252
+ const { CONFIG_KEYS } = await import("./schema-BFEWhzg7.mjs").then((n) => n.r);
253
253
  if (!CONFIG_KEYS.includes(key)) {
254
254
  console.error(`Unknown config key: ${key}`);
255
255
  process.exit(1);
@@ -269,8 +269,8 @@ program.command("config").description("Read or write configuration values").addC
269
269
  }
270
270
  return;
271
271
  }
272
- const { loadConfig, saveConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
273
- const { CONFIG_KEYS, DEFAULT_CONFIG } = await import("./schema-BbQVXp36.mjs").then((n) => n.r);
272
+ const { loadConfig, saveConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
273
+ const { CONFIG_KEYS, DEFAULT_CONFIG } = await import("./schema-BFEWhzg7.mjs").then((n) => n.r);
274
274
  const current = await loadConfig();
275
275
  if (!CONFIG_KEYS.includes(key)) {
276
276
  console.error(`Unknown config key: ${key}`);
@@ -303,7 +303,7 @@ program.command("wallet").description("Manage the local Base USDC payment wallet
303
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
304
  try {
305
305
  const { buildTopupInfo, getWalletAccount } = await import("./tools-Py6SXg6J.mjs").then((n) => n.s);
306
- const { startTopupServer } = await import("./topup-server-6MH7q73X.mjs").then((n) => n.r);
306
+ const { startTopupServer } = await import("./topup-server-BJgVw6Jt.mjs").then((n) => n.r);
307
307
  const account = await getWalletAccount();
308
308
  const url = await startTopupServer(account);
309
309
  const info = buildTopupInfo(account.address, url);
@@ -336,7 +336,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
336
336
  const { loadSchema, saveSchema } = await import("./schema-cache-DwDvPy4e.mjs");
337
337
  const { formatToolsTable } = await import("./format-Bq94jSyw.mjs");
338
338
  const { visibleRemoteTools } = await import("./tool-visibility-BHRFLXuU.mjs").then((n) => n.n);
339
- const { loadConfig } = await import("./config-9KYXaAv-.mjs").then((n) => n.t);
339
+ const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
340
340
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-D4_hd4AP.mjs").then((n) => n.n);
341
341
  const config = await loadConfig();
342
342
  const graphMcpEndpoint = resolveGraphMcpEndpoint(config);
@@ -376,7 +376,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
376
376
  }));
377
377
  return;
378
378
  }
379
- const { addressRisk } = await import("./public-tools-D6Q5MTcO.mjs");
379
+ const { addressRisk } = await import("./public-tools-B13J0MJZ.mjs");
380
380
  const result = await addressRisk(client, {
381
381
  address: opts.address,
382
382
  network: opts.network,
@@ -404,7 +404,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
404
404
  }));
405
405
  return;
406
406
  }
407
- const { trackFunds } = await import("./public-tools-D6Q5MTcO.mjs");
407
+ const { trackFunds } = await import("./public-tools-B13J0MJZ.mjs");
408
408
  const caseId = opts.case ? await resolveCaseSelector(opts.case) : void 0;
409
409
  const result = await trackFunds(client, config, {
410
410
  trustedAddresses: opts.trustedAddresses,
@@ -427,7 +427,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
427
427
  const { requireWorkspaceRoot } = await import("./output-root-BRhzhhXZ.mjs").then((n) => n.t);
428
428
  requireWorkspaceRoot();
429
429
  await withGraphMcpClient("chain-insights-cli-scam-topology", async (client, config) => {
430
- const { scamTopology } = await import("./public-tools-D6Q5MTcO.mjs");
430
+ const { scamTopology } = await import("./public-tools-B13J0MJZ.mjs");
431
431
  const incidentTimestampMs = optionalNumber(opts.incidentTimestampMs);
432
432
  if (incidentTimestampMs === void 0) throw new Error("incident-timestamp-ms is required");
433
433
  const caseId = opts.case ? await resolveCaseSelector(opts.case) : void 0;
@@ -446,6 +446,29 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
446
446
  console.error(err.message);
447
447
  process.exit(1);
448
448
  }
449
+ })).addCommand(new Command("stake-insights").description("Explain Bittensor staking behavior around an address, coldkey, or hotkey").requiredOption("--network <network>", "Network to query. Run `cia mcp networks` for supported networks.").option("--address <address>", "Full Bittensor address to inspect as either coldkey or hotkey").option("--coldkey <address>", "Full Bittensor coldkey address to inspect").option("--hotkey <address>", "Full Bittensor hotkey address to inspect").option("--netuid <number>", "Optional subnet netuid filter").option("--start-timestamp-ms <milliseconds>", "Optional inclusive lower activity timestamp bound").option("--end-timestamp-ms <milliseconds>", "Optional inclusive upper activity timestamp bound").option("--start-block <number>", "Optional start block. Current stake graph parity may require timestamp windows instead.").option("--end-block <number>", "Optional end block. Current stake graph parity may require timestamp windows instead.").option("--depth <number>", "Optional expansion depth limit, default 1, max 3").action(async (opts) => {
450
+ try {
451
+ await withGraphMcpClient("chain-insights-cli-stake-insights", async (client) => {
452
+ const { stakeInsights } = await import("./public-tools-B13J0MJZ.mjs");
453
+ const result = await stakeInsights(client, {
454
+ network: opts.network,
455
+ address: opts.address,
456
+ coldkey: opts.coldkey,
457
+ hotkey: opts.hotkey,
458
+ netuid: optionalNumber(opts.netuid),
459
+ startTimestampMs: optionalNumber(opts.startTimestampMs),
460
+ endTimestampMs: optionalNumber(opts.endTimestampMs),
461
+ startBlock: optionalNumber(opts.startBlock),
462
+ endBlock: optionalNumber(opts.endBlock),
463
+ depth: optionalNumber(opts.depth)
464
+ });
465
+ console.log(result.summaryText);
466
+ console.log(JSON.stringify(result.structuredContent, null, 2));
467
+ });
468
+ } catch (err) {
469
+ console.error(err.message);
470
+ process.exit(1);
471
+ }
449
472
  })).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
473
  try {
451
474
  const { parseMcpCallArgs } = await import("./call-args-DPXdX3_D.mjs");
@@ -454,7 +477,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
454
477
  assertPublicMcpToolName(tool);
455
478
  await withGraphMcpClient("chain-insights-cli-call", async (client, config) => {
456
479
  if (tool === "address_risk") {
457
- const { addressRisk } = await import("./public-tools-D6Q5MTcO.mjs");
480
+ const { addressRisk } = await import("./public-tools-B13J0MJZ.mjs");
458
481
  const result = await addressRisk(client, {
459
482
  address: String(args["address"] ?? ""),
460
483
  network: String(args["network"] ?? ""),
@@ -464,7 +487,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
464
487
  return;
465
488
  }
466
489
  if (tool === "track_funds") {
467
- const { trackFunds } = await import("./public-tools-D6Q5MTcO.mjs");
490
+ const { trackFunds } = await import("./public-tools-B13J0MJZ.mjs");
468
491
  const result = await trackFunds(client, config, {
469
492
  trustedAddresses: args["trusted_addresses"] ?? "",
470
493
  untrustedAddresses: args["untrusted_addresses"],
@@ -479,7 +502,7 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
479
502
  return;
480
503
  }
481
504
  if (tool === "scam_topology") {
482
- const { scamTopology } = await import("./public-tools-D6Q5MTcO.mjs");
505
+ const { scamTopology } = await import("./public-tools-B13J0MJZ.mjs");
483
506
  const victimAddress = String(args["victim_address"] ?? "").trim();
484
507
  if (!victimAddress) throw new Error("victim_address is required");
485
508
  const incidentTimestampMs = optionalNumberArg(args["incident_timestamp_ms"], "incident_timestamp_ms");
@@ -496,6 +519,24 @@ program.command("mcp").description("Interact with the Chain Insights MCP endpoin
496
519
  console.log(JSON.stringify(result.structuredContent, null, 2));
497
520
  return;
498
521
  }
522
+ if (tool === "stake_insights") {
523
+ const { stakeInsights } = await import("./public-tools-B13J0MJZ.mjs");
524
+ const result = await stakeInsights(client, {
525
+ network: String(args["network"] ?? ""),
526
+ address: args["address"] === void 0 ? void 0 : String(args["address"]),
527
+ coldkey: args["coldkey"] === void 0 ? void 0 : String(args["coldkey"]),
528
+ hotkey: args["hotkey"] === void 0 ? void 0 : String(args["hotkey"]),
529
+ netuid: optionalNumberArg(args["netuid"], "netuid"),
530
+ startTimestampMs: optionalNumberArg(args["start_timestamp_ms"], "start_timestamp_ms"),
531
+ endTimestampMs: optionalNumberArg(args["end_timestamp_ms"], "end_timestamp_ms"),
532
+ startBlock: optionalNumberArg(args["start_block"], "start_block"),
533
+ endBlock: optionalNumberArg(args["end_block"], "end_block"),
534
+ depth: optionalNumberArg(args["depth"] ?? args["max_hops"], "depth")
535
+ });
536
+ console.log(result.summaryText);
537
+ console.log(JSON.stringify(result.structuredContent, null, 2));
538
+ return;
539
+ }
499
540
  printMcpTextContent(await client.callTool({
500
541
  name: tool,
501
542
  arguments: args
@@ -679,7 +720,7 @@ program.command("playbook").description("Run and manage investigation playbooks"
679
720
  console.error(`Invalid --from value: "${opts.from}". Must be a positive integer.`);
680
721
  process.exit(1);
681
722
  }
682
- const { PlaybookRunner } = await import("./runner-BatyCxv7.mjs");
723
+ const { PlaybookRunner } = await import("./runner-DIs04IhN.mjs");
683
724
  await PlaybookRunner.run(definition, {
684
725
  caseId: opts.case,
685
726
  from: fromN,
@@ -734,7 +775,7 @@ program.command("viz").description("Generate money flow visualization").argument
734
775
  caseId,
735
776
  dataFile: opts.data
736
777
  });
737
- const { startServer } = await import("./server-BDlbmGbL.mjs").then((n) => n.t);
778
+ const { startServer } = await import("./server-BXLX2j_A.mjs").then((n) => n.t);
738
779
  const port = parseInt(opts.port, 10);
739
780
  startServer(port);
740
781
  const url = `http://127.0.0.1:${port}/viz/${result.vizId}`;