chain-insights 0.3.9 → 0.3.18

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 (137) hide show
  1. package/README.md +49 -52
  2. package/dist/{active-ByNgjuAg.mjs → active-BQopLul8.mjs} +6 -8
  3. package/dist/active-BQopLul8.mjs.map +1 -0
  4. package/dist/{active-BVr55kvW.cjs → active-XWv72R1X.cjs} +4 -12
  5. package/dist/{app-BxojXjtB.cjs → app-DBrqk_iP.cjs} +12 -28
  6. package/dist/{app-CRd39JJ8.mjs → app-DXwILI_a.mjs} +13 -28
  7. package/dist/app-DXwILI_a.mjs.map +1 -0
  8. package/dist/{artifact-server-CP6LXQ9d.mjs → artifact-server-CcmLBv1j.mjs} +2 -2
  9. package/dist/{artifact-server-CP6LXQ9d.mjs.map → artifact-server-CcmLBv1j.mjs.map} +1 -1
  10. package/dist/{artifact-server-XbN16DwU.cjs → artifact-server-v0WgTPFT.cjs} +1 -1
  11. package/dist/{capabilities-BCvkTkIu.mjs → capabilities-CM72SErE.mjs} +2 -2
  12. package/dist/{capabilities-BCvkTkIu.mjs.map → capabilities-CM72SErE.mjs.map} +1 -1
  13. package/dist/{capabilities-DOa6EFO-.cjs → capabilities-DGeF-oHc.cjs} +1 -1
  14. package/dist/cli.cjs +149 -405
  15. package/dist/cli.mjs +149 -405
  16. package/dist/cli.mjs.map +1 -1
  17. package/dist/{client-Y_zqKqJT.cjs → client-BY-56ojr.cjs} +0 -17
  18. package/dist/{client-BgmHjBHQ.mjs → client-ytTO0mcZ.mjs} +2 -13
  19. package/dist/{client-BgmHjBHQ.mjs.map → client-ytTO0mcZ.mjs.map} +1 -1
  20. package/dist/{config-Drgc2HuF.mjs → config-C6zM8Xir.mjs} +3 -3
  21. package/dist/{config-Drgc2HuF.mjs.map → config-C6zM8Xir.mjs.map} +1 -1
  22. package/dist/{config-BwVx19Og.cjs → config-CkW404Cs.cjs} +2 -2
  23. package/dist/{graph-reports-BDELxmpi.mjs → graph-reports-CEq-Mvx0.mjs} +2 -2
  24. package/dist/{graph-reports-BDELxmpi.mjs.map → graph-reports-CEq-Mvx0.mjs.map} +1 -1
  25. package/dist/{graph-reports-B3mkLP8Z.cjs → graph-reports-CkglRtg4.cjs} +1 -1
  26. package/dist/{html-generator-Bx3UcLTB.cjs → html-generator-BFKafL8y.cjs} +5 -6
  27. package/dist/{html-generator-AowOmzyi.mjs → html-generator-D4fX71hI.mjs} +6 -6
  28. package/dist/html-generator-D4fX71hI.mjs.map +1 -0
  29. package/dist/index.cjs +5 -5
  30. package/dist/index.d.cts +1 -2
  31. package/dist/index.d.cts.map +1 -1
  32. package/dist/index.d.mts +1 -2
  33. package/dist/index.d.mts.map +1 -1
  34. package/dist/index.mjs +5 -5
  35. package/dist/{init-CKQ6F07J.mjs → init-BGDvGreX.mjs} +52 -55
  36. package/dist/init-BGDvGreX.mjs.map +1 -0
  37. package/dist/{init-Dhw8F23z.cjs → init-Cuw9TznI.cjs} +51 -54
  38. package/dist/{mcp-endpoint-DHs1cRFH.mjs → mcp-endpoint-QQ5Lbqc2.mjs} +5 -2
  39. package/dist/mcp-endpoint-QQ5Lbqc2.mjs.map +1 -0
  40. package/dist/{mcp-endpoint-BaV8h_lq.cjs → mcp-endpoint-cQIZSjkK.cjs} +4 -1
  41. package/dist/mcp-proxy.cjs +650 -771
  42. package/dist/mcp-proxy.d.cts.map +1 -1
  43. package/dist/mcp-proxy.d.mts.map +1 -1
  44. package/dist/mcp-proxy.mjs +651 -772
  45. package/dist/mcp-proxy.mjs.map +1 -1
  46. package/dist/{output-root-BRhzhhXZ.mjs → output-root-BK4pdjyz.mjs} +6 -3
  47. package/dist/output-root-BK4pdjyz.mjs.map +1 -0
  48. package/dist/{output-root-YIbl6PwF.cjs → output-root-DI0tzA0X.cjs} +5 -2
  49. package/dist/{public-tools-BY3PTw6x.cjs → public-tools-BREojpU7.cjs} +1244 -426
  50. package/dist/{public-tools-CvlZcysd.mjs → public-tools-brHmHGYm.mjs} +1240 -428
  51. package/dist/public-tools-brHmHGYm.mjs.map +1 -0
  52. package/dist/{schema-BFEWhzg7.mjs → schema-D_qwaQA5.mjs} +2 -2
  53. package/dist/{schema-BFEWhzg7.mjs.map → schema-D_qwaQA5.mjs.map} +1 -1
  54. package/dist/{schema-Vl9yuOFO.cjs → schema-Dr6JXSOF.cjs} +1 -1
  55. package/dist/{server-BXLX2j_A.mjs → server-BK4bfOiv.mjs} +2 -2
  56. package/dist/{server-BXLX2j_A.mjs.map → server-BK4bfOiv.mjs.map} +1 -1
  57. package/dist/{server-BqVdWath.cjs → server-ColyTG1t.cjs} +1 -1
  58. package/dist/templates/graph.html +1 -1
  59. package/dist/{tool-visibility-Buq7YdUZ.cjs → tool-visibility--QPgrRE5.cjs} +5 -1
  60. package/dist/{tool-visibility-BpyZHRBi.mjs → tool-visibility-nr6XqO1F.mjs} +6 -2
  61. package/dist/tool-visibility-nr6XqO1F.mjs.map +1 -0
  62. package/dist/viz-BBvY-wXz.cjs +210 -0
  63. package/dist/viz-D8umSF-t.mjs +199 -0
  64. package/dist/viz-D8umSF-t.mjs.map +1 -0
  65. package/docs/architecture.md +4 -3
  66. package/docs/contributing.md +12 -6
  67. package/docs/graph-tools.md +93 -68
  68. package/docs/investigation-workspaces.md +38 -124
  69. package/docs/mcp-proxy.md +23 -34
  70. package/package.json +2 -2
  71. package/skills/chain-insights-address-risk/SKILL.md +92 -0
  72. package/skills/chain-insights-bittensor-cypher/SKILL.md +2 -22
  73. package/skills/chain-insights-developer-experience/SKILL.md +8 -28
  74. package/skills/chain-insights-exposure-analysis/SKILL.md +83 -0
  75. package/skills/chain-insights-investigation/SKILL.md +59 -211
  76. package/skills/chain-insights-investigation/agents/openai.yaml +1 -1
  77. package/skills/chain-insights-investigation/scripts/run-target-uat.sh +37 -55
  78. package/skills/chain-insights-trace-funds/SKILL.md +14 -14
  79. package/skills/ci-status/SKILL.md +9 -15
  80. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +5 -4
  81. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +272 -18
  82. package/dist/active-ByNgjuAg.mjs.map +0 -1
  83. package/dist/app-CRd39JJ8.mjs.map +0 -1
  84. package/dist/canvas-Cn-maEIh.mjs +0 -203
  85. package/dist/canvas-Cn-maEIh.mjs.map +0 -1
  86. package/dist/canvas-p-oKCMjc.cjs +0 -251
  87. package/dist/cases-Bz_9XKEw.cjs +0 -19
  88. package/dist/cases-TVcAifxu.mjs +0 -16
  89. package/dist/cases-TVcAifxu.mjs.map +0 -1
  90. package/dist/data-extractor-B4nHw1wZ.mjs +0 -336
  91. package/dist/data-extractor-B4nHw1wZ.mjs.map +0 -1
  92. package/dist/data-extractor-DS4rzy3M.cjs +0 -353
  93. package/dist/dossier-BXy57V4-.cjs +0 -88
  94. package/dist/dossier-Bjpcbcxa.mjs +0 -78
  95. package/dist/dossier-Bjpcbcxa.mjs.map +0 -1
  96. package/dist/evidence-CvEesemA.cjs +0 -200
  97. package/dist/evidence-D96PTzOQ.mjs +0 -195
  98. package/dist/evidence-D96PTzOQ.mjs.map +0 -1
  99. package/dist/export-CBhcJuZ6.mjs +0 -394
  100. package/dist/export-CBhcJuZ6.mjs.map +0 -1
  101. package/dist/export-D4v4-6F4.cjs +0 -394
  102. package/dist/frontmatter-D0ccQnUM.mjs +0 -26
  103. package/dist/frontmatter-D0ccQnUM.mjs.map +0 -1
  104. package/dist/frontmatter-Dvqa5HX6.cjs +0 -35
  105. package/dist/html-generator-AowOmzyi.mjs.map +0 -1
  106. package/dist/init-CKQ6F07J.mjs.map +0 -1
  107. package/dist/mcp-endpoint-DHs1cRFH.mjs.map +0 -1
  108. package/dist/output-root-BRhzhhXZ.mjs.map +0 -1
  109. package/dist/parser-BXLAHYnZ.cjs +0 -182
  110. package/dist/parser-CJfMsOl6.mjs +0 -182
  111. package/dist/parser-CJfMsOl6.mjs.map +0 -1
  112. package/dist/public-tools-CvlZcysd.mjs.map +0 -1
  113. package/dist/resolver-2jXNtWQO.mjs +0 -184
  114. package/dist/resolver-2jXNtWQO.mjs.map +0 -1
  115. package/dist/resolver-CZdQwKvh.cjs +0 -186
  116. package/dist/runner-CVnjpqc-.mjs +0 -149
  117. package/dist/runner-CVnjpqc-.mjs.map +0 -1
  118. package/dist/runner-bLy0pTr_.cjs +0 -147
  119. package/dist/selector-BvXM9jbe.mjs +0 -12
  120. package/dist/selector-BvXM9jbe.mjs.map +0 -1
  121. package/dist/selector-Dps_ZFxq.cjs +0 -10
  122. package/dist/session-BT7VpbAd.cjs +0 -127
  123. package/dist/session-DROyhebe.mjs +0 -117
  124. package/dist/session-DROyhebe.mjs.map +0 -1
  125. package/dist/store-C2B_AssI.mjs +0 -231
  126. package/dist/store-C2B_AssI.mjs.map +0 -1
  127. package/dist/store-CQhU8dz8.cjs +0 -242
  128. package/dist/tool-visibility-BpyZHRBi.mjs.map +0 -1
  129. package/dist/vault-B2y78Ypu.cjs +0 -560
  130. package/dist/vault-z35Dohdq.mjs +0 -560
  131. package/dist/vault-z35Dohdq.mjs.map +0 -1
  132. package/dist/viz-D1620cBX.cjs +0 -44
  133. package/dist/viz-DB5XFG1z.mjs +0 -35
  134. package/dist/viz-DB5XFG1z.mjs.map +0 -1
  135. package/docs/knowledge-exports.md +0 -204
  136. package/docs/obsidian-vault.md +0 -130
  137. package/skills/ci-case/SKILL.md +0 -43
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Chain Insights is an open-source AML investigation toolkit for AI agents and
6
6
  analysts. Install it from npm to screen blockchain addresses, trace role-specific
7
- fund flows, manage case evidence, and generate graph reports.
7
+ fund flows, manage workspace evidence, and generate graph reports.
8
8
 
9
9
  Graph access is configuration-driven. The package defaults to a local GraphRAG
10
10
  MCP endpoint for development; hosted endpoints are set explicitly with
@@ -14,14 +14,20 @@ MCP endpoint for development; hosted endpoints are set explicitly with
14
14
 
15
15
  | Tool | Use it for |
16
16
  | --- | --- |
17
- | `address_risk` | Screen one address for risk, behavior, neighborhood context, and exchange exposure |
18
- | `stake_insights` | Explain Bittensor staking relationships, net stake movement, and counterparties |
19
- | `trace_victim_funds` | Trace victim/source funds forward to exchange deposit candidates |
20
- | `trace_deposit_sources` | Trace backward from suspected deposit/cashout addresses to upstream sources and convergence |
21
- | `trace_suspect_funds` | Trace suspected scammer, mule, operator, or laundering-ring funds forward to cashout topology |
22
- | `usage_status` | Check the caller's daily free-tier graph query allowance |
17
+ | `aml_address_risk` | Screen one address for risk, behavior, neighborhood context, and exchange exposure |
18
+ | `aml_trace_victim_funds` | Trace victim/source funds forward to exchange deposit candidates |
19
+ | `aml_trace_deposit_sources` | Trace backward from suspected deposit/cashout addresses to upstream sources and convergence |
20
+ | `aml_trace_suspect_funds` | Trace suspected scammer, mule, operator, or laundering-ring funds forward to cashout topology |
21
+ | `exposure_profile` | Explain staking or trading exposure around an account, owner, or counterparty |
22
+ | `exposure_quality` | Score whether exposure behavior looks disciplined, fragile, lucky, or noisy |
23
+ | `exposure_carry` | Explain carry earned or paid from staking, trading, funding, fees, emissions, or dividends |
24
+ | `exposure_crowding` | Measure side concentration for a market, subnet, hotkey, or strategy |
25
+ | `exposure_exit_pressure` | Explain liquidation, slippage, unstake, funding pain, or other exit pressure |
26
+ | `exposure_correlation` | Compare accounts for possible copy, overlap, or strategy-cluster behavior |
27
+ | `exposure_explain` | Explain a specific exposure lifecycle, trade, position, stake, rotation, or incident |
23
28
  | `graph_query` | Run one read-only GQL/Cypher query against a GraphRAG MCP graph layer |
24
29
  | `graph_query_batch` | Run related read-only graph queries as one MCP call |
30
+ | `usage_status` | Check the caller's daily free-tier graph query allowance |
25
31
 
26
32
  ## Quick Start
27
33
 
@@ -51,19 +57,17 @@ npm install -g .
51
57
  cia --version
52
58
  ```
53
59
 
54
- Create an investigation vault:
60
+ Create an investigation workspace:
55
61
 
56
62
  ```bash
57
63
  mkdir -p ./chain-insights-investigations
58
64
  cd ./chain-insights-investigations
59
65
  cia init .
60
- cia obsidian open .
61
66
  ```
62
67
 
63
- Chain Insights workspaces are Obsidian-compatible vaults and plain local
64
- folders. Obsidian is a first-class review UI, but it is not required to use the
65
- workspace files. See the
66
- [Obsidian vault workflow](docs/obsidian-vault.md).
68
+ Chain Insights workspaces are plain local folders. Use any editor or agent
69
+ tooling you want to inspect workspace files, graph reports, artifacts, and
70
+ published outputs.
67
71
 
68
72
  ## Configure GraphRAG MCP Endpoint
69
73
 
@@ -119,7 +123,7 @@ cia mcp tools --refresh
119
123
  ```
120
124
 
121
125
  If network or tool discovery fails, check the endpoint and access mode first.
122
- The CLI can still initialize workspaces and manage cases without a reachable
126
+ The CLI can still initialize workspaces and continue investigation workflow without a reachable
123
127
  GraphRAG MCP endpoint.
124
128
 
125
129
  Hosted GraphRAG MCP includes a small public free tier for `graph_query` before
@@ -131,46 +135,36 @@ If you do not have a prepared wallet yet, use bounded single `graph_query`
131
135
  calls within the free tier, then prepare a wallet or use an invited tester
132
136
  access key when the allowance is exhausted.
133
137
 
134
- Open a case and run a small investigation:
138
+ Run a focused investigation in the initialized workspace:
135
139
 
136
140
  ```bash
137
- cia case open "First Chain Insights investigation" \
138
- --tags aml,bittensor \
139
- --description "Screen and trace a known source address"
141
+ cia init .
140
142
 
141
143
  cia mcp trace-victim-funds \
142
144
  --network bittensor \
143
- --victim-addresses 5GTjfJaLpBNrgybhY24NqhDnKW9r94z72RSYLxeodxJfSkj5 \
144
- --case 1
145
+ --victim-addresses 5GTjfJaLpBNrgybhY24NqhDnKW9r94z72RSYLxeodxJfSkj5
145
146
  ```
146
147
 
147
148
  Then inspect:
148
149
 
149
150
  ```bash
150
- cia case show 1
151
- find reports cases -maxdepth 3 -type f | sort
151
+ find reports -maxdepth 3 -type f | sort
152
152
  ```
153
153
 
154
154
  ## Export Only When Sharing
155
155
 
156
- Normal local work happens in the investigation vault. Export only when you need
157
- to share a case, hand it off to a partner, ingest it into LLM Wiki, or archive a
156
+ Normal local work happens in the workspace. Export only when you need
157
+ to share, hand it off to a partner, ingest into LLM Wiki, or archive a
158
158
  review checkpoint.
159
159
 
160
160
  ```bash
161
- cia case evidence verify 1
162
- cia case export 1 --target obsidian-llmwiki --mode private
161
+ # Use your configured workspace export flow to produce the handoff package.
162
+ published/<workspace-slug>/
163
163
  ```
164
164
 
165
- The export writes Markdown notes, `manifest.chain-insights.json`,
166
- `graph.chain-insights.json`, `Graph.canvas`, LLM Wiki entrypoints, and prompts
167
- for Codex, Claude Code, and ChatGPT under `published/<case-slug>/`.
168
-
169
- Private exports may include full addresses. Use `--mode partner` for controlled
170
- handoff after review. Use `--mode public` only for shareable examples; public mode
171
- aliases addresses and removes secrets by default. Vault workflow guidance lives
172
- in [Obsidian vault workflow](docs/obsidian-vault.md); export bundle details
173
- live in [Knowledge exports](docs/knowledge-exports.md).
165
+ Workspace-generated reports, graph JSON, graph HTML, and published bundles live
166
+ under the initialized workspace. Treat those files as the durable handoff
167
+ surface.
174
168
 
175
169
  ## Examples
176
170
 
@@ -200,8 +194,7 @@ Run suspect topology without requiring an incident timestamp:
200
194
  cia mcp trace-suspect-funds \
201
195
  --network bittensor \
202
196
  --suspect-addresses 5... \
203
- --max-hops 16 \
204
- --case 1
197
+ --max-hops 16
205
198
  ```
206
199
 
207
200
  ## How It Fits Together
@@ -209,7 +202,7 @@ cia mcp trace-suspect-funds \
209
202
  ```text
210
203
  Agent or CLI user
211
204
  -> Chain Insights CLI / MCP proxy
212
- -> local config, wallet, workspace, cases, evidence, reports
205
+ -> local config, wallet, workspace, artifacts, reports
213
206
  -> GraphRAG MCP
214
207
  -> graph intelligence for AML workflows
215
208
  ```
@@ -240,39 +233,43 @@ schema notes and examples.
240
233
  ## AML Tools
241
234
 
242
235
  The high-level AML tools are Chain Insights workflows built around graph access
243
- and local case state:
236
+ and local workspace state:
244
237
 
245
- - `address_risk` starts a single-address screen with risk, behavior,
238
+ - `aml_address_risk` starts a single-address screen with risk, behavior,
246
239
  neighborhood context, and exchange exposure.
247
- - `stake_insights` explains Bittensor coldkey-hotkey-netuid staking
248
- relationships, aggregate stake movement amounts, top counterparties, first
249
- and last activity, and source backend evidence.
250
- - `trace_victim_funds` traces victim/source funds forward through
240
+ - `exposure_profile` explains staking exposure and trading exposure
241
+ with venues, instruments, position changes, public support events, and
242
+ caveats when pricing or unit coverage is incomplete.
243
+ - `exposure_quality`, `exposure_carry`, `exposure_crowding`,
244
+ `exposure_exit_pressure`, `exposure_correlation`, and `exposure_explain`
245
+ add deterministic exposure analytics over the same generic model. They work
246
+ for Bittensor staking rows now and are shaped for Hyperliquid trading rows as
247
+ soon as the Hyperliquid indexer writes the shared exposure contract.
248
+ - `aml_trace_victim_funds` traces victim/source funds forward through
251
249
  intermediaries to exchange deposit candidates.
252
- - `trace_deposit_sources` traces backward from suspected deposit/cashout
250
+ - `aml_trace_deposit_sources` traces backward from suspected deposit/cashout
253
251
  addresses to upstream sources and shared-source convergence.
254
- - `trace_suspect_funds` traces suspected scammer, mule, operator, or
252
+ - `aml_trace_suspect_funds` traces suspected scammer, mule, operator, or
255
253
  laundering-ring funds forward to cashout topology.
256
254
 
257
255
  The three trace tools share `chain-insights.trace.v1` and return compact,
258
256
  chainable results. Full graph/table/report artifacts remain on disk under the
259
- workspace, with pointers in the tool result and case evidence.
257
+ workspace, with pointers in the tool result and workspace evidence.
260
258
 
261
259
  Trace traversal treats exchange hot wallets as terminal endpoints only. Tools do
262
260
  not expand through exchange nodes or classify them as deposit, suspect, or
263
261
  intermediate candidates.
264
262
 
265
- When a case is provided, tools can save compact evidence pointers and graph
266
- reports under the workspace instead of embedding large payloads in case notes.
263
+ When investigation output is large, tools can save compact evidence pointers and
264
+ graph reports under the workspace instead of embedding large payloads in human
265
+ notes.
267
266
 
268
267
  ## Docs Map
269
268
 
270
269
  | Doc | Use it for |
271
270
  | --- | --- |
272
271
  | [Graph tools](docs/graph-tools.md) | GraphRAG MCP layers, `graph_query`, `graph_query_batch`, AML tool contracts, graph reports, evidence pointers |
273
- | [Obsidian vault workflow](docs/obsidian-vault.md) | Create an investigation vault, open Obsidian, refresh live notes, and use VS Code, Codex, Claude Code, and LLM Wiki overlays |
274
- | [Investigation workspaces](docs/investigation-workspaces.md) | `cia init`, Obsidian-compatible vault layout, live note refresh, evidence, dossiers, imports, templates, sessions, reports |
275
- | [Knowledge exports](docs/knowledge-exports.md) | Portable and redacted bundles for sharing, partner handoff, LLM Wiki ingestion, and archive |
272
+ | [Investigation workspaces](docs/investigation-workspaces.md) | `cia init`, workspace layout, artifacts, imports, templates, sessions, reports, and visualization outputs |
276
273
  | [MCP proxy](docs/mcp-proxy.md) | Stdio proxy behavior, endpoint configuration, agent installers, local tools, auth modes, Inspector validation |
277
274
  | [Architecture](docs/architecture.md) | Product layers, data flow, local storage, security model, config keys |
278
275
  | [Development](docs/development.md) | Build, test, and local install commands |
@@ -4,7 +4,6 @@ import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  //#region src/workspace/active.ts
6
6
  var active_exports = /* @__PURE__ */ __exportAll({
7
- activeCasesRoot: () => activeCasesRoot,
8
7
  activeDataDir: () => activeDataDir,
9
8
  findActiveWorkspace: () => findActiveWorkspace
10
9
  });
@@ -16,11 +15,13 @@ function workspaceFromRoot(rootCandidate) {
16
15
  const parsed = JSON.parse(fs.readFileSync(markerPath, "utf8"));
17
16
  if (parsed.schema !== "chain-insights.workspace.v1") return null;
18
17
  const workspaceRoot = path.resolve(parsed.workspace_root ?? root);
19
- const casesDir = parsed.cases_dir ?? "cases";
18
+ const artifactsDir = parsed.artifacts_dir ?? "artifacts";
19
+ const domainHints = Array.isArray(parsed.domain_hints) ? parsed.domain_hints.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
20
20
  return {
21
21
  root: workspaceRoot,
22
22
  metadataDir: path.join(workspaceRoot, ".chain-insights"),
23
- casesRoot: path.resolve(workspaceRoot, casesDir)
23
+ artifactsRoot: path.resolve(workspaceRoot, artifactsDir),
24
+ domainHints
24
25
  };
25
26
  }
26
27
  function findActiveWorkspace(startDir = process.cwd()) {
@@ -38,13 +39,10 @@ function findActiveWorkspace(startDir = process.cwd()) {
38
39
  current = parent;
39
40
  }
40
41
  }
41
- function activeCasesRoot() {
42
- return findActiveWorkspace()?.casesRoot ?? path.join(os.homedir(), ".chain-insights", "cases");
43
- }
44
42
  function activeDataDir(fallbackDataDir) {
45
43
  return findActiveWorkspace()?.root ?? fallbackDataDir ?? path.join(os.homedir(), ".chain-insights");
46
44
  }
47
45
  //#endregion
48
- export { active_exports as n, findActiveWorkspace as r, activeCasesRoot as t };
46
+ export { findActiveWorkspace as n, active_exports as t };
49
47
 
50
- //# sourceMappingURL=active-ByNgjuAg.mjs.map
48
+ //# sourceMappingURL=active-BQopLul8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-BQopLul8.mjs","names":[],"sources":["../src/workspace/active.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\ninterface WorkspaceConfig {\n schema?: string\n workspace_root?: string\n artifacts_dir?: string\n domain_hints?: unknown\n}\n\nexport interface ActiveWorkspace {\n root: string\n metadataDir: string\n artifactsRoot: string\n domainHints: string[]\n}\n\nfunction workspaceFromRoot(rootCandidate: string): ActiveWorkspace | null {\n const root = path.resolve(rootCandidate)\n const metadataDir = path.join(root, '.chain-insights')\n const markerPath = path.join(metadataDir, 'workspace.json')\n if (!fs.existsSync(markerPath)) return null\n\n const parsed = JSON.parse(fs.readFileSync(markerPath, 'utf8')) as WorkspaceConfig\n if (parsed.schema !== 'chain-insights.workspace.v1') return null\n\n const workspaceRoot = path.resolve(parsed.workspace_root ?? root)\n const artifactsDir = parsed.artifacts_dir ?? 'artifacts'\n const domainHints = Array.isArray(parsed.domain_hints)\n ? parsed.domain_hints.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n : []\n return {\n root: workspaceRoot,\n metadataDir: path.join(workspaceRoot, '.chain-insights'),\n artifactsRoot: path.resolve(workspaceRoot, artifactsDir),\n domainHints,\n }\n}\n\nexport function findActiveWorkspace(startDir = process.cwd()): ActiveWorkspace | null {\n const envWorkspace = process.env['CHAIN_INSIGHTS_WORKSPACE']?.trim()\n if (envWorkspace) {\n const active = workspaceFromRoot(envWorkspace)\n if (active) return active\n }\n\n let current = path.resolve(startDir)\n while (true) {\n const active = workspaceFromRoot(current)\n if (active) return active\n\n const parent = path.dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport function activeMetadataDir(): string {\n return findActiveWorkspace()?.metadataDir ?? path.join(os.homedir(), '.chain-insights')\n}\n\nexport function activeArtifactsRoot(): string {\n return findActiveWorkspace()?.artifactsRoot ?? path.join(os.homedir(), '.chain-insights', 'artifacts')\n}\n\nexport function activeDataDir(fallbackDataDir?: string): string {\n return findActiveWorkspace()?.root ?? fallbackDataDir ?? path.join(os.homedir(), '.chain-insights')\n}\n"],"mappings":";;;;;;;;;AAkBA,SAAS,kBAAkB,eAA+C;CACxE,MAAM,OAAO,KAAK,QAAQ,aAAa;CACvC,MAAM,cAAc,KAAK,KAAK,MAAM,iBAAiB;CACrD,MAAM,aAAa,KAAK,KAAK,aAAa,gBAAgB;CAC1D,IAAI,CAAC,GAAG,WAAW,UAAU,GAAG,OAAO;CAEvC,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,YAAY,MAAM,CAAC;CAC7D,IAAI,OAAO,WAAW,+BAA+B,OAAO;CAE5D,MAAM,gBAAgB,KAAK,QAAQ,OAAO,kBAAkB,IAAI;CAChE,MAAM,eAAe,OAAO,iBAAiB;CAC7C,MAAM,cAAc,MAAM,QAAQ,OAAO,YAAY,IACjD,OAAO,aAAa,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,CAAC,IAC3G,CAAC;CACL,OAAO;EACL,MAAM;EACN,aAAa,KAAK,KAAK,eAAe,iBAAiB;EACvD,eAAe,KAAK,QAAQ,eAAe,YAAY;EACvD;CACF;AACF;AAEA,SAAgB,oBAAoB,WAAW,QAAQ,IAAI,GAA2B;CACpF,MAAM,eAAe,QAAQ,IAAI,6BAA6B,KAAK;CACnE,IAAI,cAAc;EAChB,MAAM,SAAS,kBAAkB,YAAY;EAC7C,IAAI,QAAQ,OAAO;CACrB;CAEA,IAAI,UAAU,KAAK,QAAQ,QAAQ;CACnC,OAAO,MAAM;EACX,MAAM,SAAS,kBAAkB,OAAO;EACxC,IAAI,QAAQ,OAAO;EAEnB,MAAM,SAAS,KAAK,QAAQ,OAAO;EACnC,IAAI,WAAW,SAAS,OAAO;EAC/B,UAAU;CACZ;AACF;AAUA,SAAgB,cAAc,iBAAkC;CAC9D,OAAO,oBAAoB,GAAG,QAAQ,mBAAmB,KAAK,KAAK,GAAG,QAAQ,GAAG,iBAAiB;AACpG"}
@@ -7,7 +7,6 @@ let node_os = require("node:os");
7
7
  node_os = require_chunk.__toESM(node_os, 1);
8
8
  //#region src/workspace/active.ts
9
9
  var active_exports = /* @__PURE__ */ require_chunk.__exportAll({
10
- activeCasesRoot: () => activeCasesRoot,
11
10
  activeDataDir: () => activeDataDir,
12
11
  findActiveWorkspace: () => findActiveWorkspace
13
12
  });
@@ -19,11 +18,13 @@ function workspaceFromRoot(rootCandidate) {
19
18
  const parsed = JSON.parse(node_fs.default.readFileSync(markerPath, "utf8"));
20
19
  if (parsed.schema !== "chain-insights.workspace.v1") return null;
21
20
  const workspaceRoot = node_path.default.resolve(parsed.workspace_root ?? root);
22
- const casesDir = parsed.cases_dir ?? "cases";
21
+ const artifactsDir = parsed.artifacts_dir ?? "artifacts";
22
+ const domainHints = Array.isArray(parsed.domain_hints) ? parsed.domain_hints.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
23
23
  return {
24
24
  root: workspaceRoot,
25
25
  metadataDir: node_path.default.join(workspaceRoot, ".chain-insights"),
26
- casesRoot: node_path.default.resolve(workspaceRoot, casesDir)
26
+ artifactsRoot: node_path.default.resolve(workspaceRoot, artifactsDir),
27
+ domainHints
27
28
  };
28
29
  }
29
30
  function findActiveWorkspace(startDir = process.cwd()) {
@@ -41,19 +42,10 @@ function findActiveWorkspace(startDir = process.cwd()) {
41
42
  current = parent;
42
43
  }
43
44
  }
44
- function activeCasesRoot() {
45
- return findActiveWorkspace()?.casesRoot ?? node_path.default.join(node_os.default.homedir(), ".chain-insights", "cases");
46
- }
47
45
  function activeDataDir(fallbackDataDir) {
48
46
  return findActiveWorkspace()?.root ?? fallbackDataDir ?? node_path.default.join(node_os.default.homedir(), ".chain-insights");
49
47
  }
50
48
  //#endregion
51
- Object.defineProperty(exports, "activeCasesRoot", {
52
- enumerable: true,
53
- get: function() {
54
- return activeCasesRoot;
55
- }
56
- });
57
49
  Object.defineProperty(exports, "active_exports", {
58
50
  enumerable: true,
59
51
  get: function() {
@@ -1,14 +1,15 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
+ const require_output_root = require("./output-root-DI0tzA0X.cjs");
2
3
  let node_path = require("node:path");
3
4
  node_path = require_chunk.__toESM(node_path, 1);
4
5
  let node_fs_promises = require("node:fs/promises");
5
- let node_os = require("node:os");
6
- node_os = require_chunk.__toESM(node_os, 1);
7
6
  let hono = require("hono");
8
7
  //#region src/server/app.ts
9
8
  const WORKSPACE_TREE_ROOTS = [
10
- "cases",
9
+ "artifacts",
10
+ "entities",
11
11
  "reports",
12
+ "sessions",
12
13
  ".chain-insights/schema"
13
14
  ];
14
15
  const WORKSPACE_TREE_MAX_DEPTH = 4;
@@ -64,31 +65,14 @@ async function listWorkspaceEntries(workspaceRoot, roots = WORKSPACE_TREE_ROOTS,
64
65
  return entries;
65
66
  }
66
67
  async function findVizHtml(vizId) {
67
- const home = node_os.default.homedir();
68
68
  const filename = `${vizId}.html`;
69
- const centralPath = node_path.default.join(home, ".chain-insights", "viz", filename);
69
+ const paths = require_output_root.workspaceOutputPaths();
70
+ const vizPath = node_path.default.join(paths.publishedRoot, "viz", filename);
70
71
  try {
71
- return await (0, node_fs_promises.readFile)(centralPath, "utf-8");
72
- } catch {}
73
- const underscoreIdx = vizId.lastIndexOf("_");
74
- if (underscoreIdx > 0) {
75
- const possibleCaseId = vizId.substring(0, underscoreIdx);
76
- const casePath = node_path.default.join(home, ".chain-insights", "cases", possibleCaseId, "viz", filename);
77
- try {
78
- return await (0, node_fs_promises.readFile)(casePath, "utf-8");
79
- } catch {}
72
+ return await (0, node_fs_promises.readFile)(vizPath, "utf-8");
73
+ } catch {
74
+ return null;
80
75
  }
81
- const casesDir = node_path.default.join(home, ".chain-insights", "cases");
82
- try {
83
- const cases = await (0, node_fs_promises.readdir)(casesDir);
84
- for (const caseId of cases) {
85
- const casePath = node_path.default.join(casesDir, caseId, "viz", filename);
86
- try {
87
- return await (0, node_fs_promises.readFile)(casePath, "utf-8");
88
- } catch {}
89
- }
90
- } catch {}
91
- return null;
92
76
  }
93
77
  function isSafeGraphReportFilename(filename) {
94
78
  return filename.endsWith(".graph.json") && /^[A-Za-z0-9._-]+$/.test(filename) && !filename.includes("..") && !filename.includes("/") && !filename.includes("\\");
@@ -100,7 +84,7 @@ function createApp() {
100
84
  ts: Date.now()
101
85
  }));
102
86
  app.get("/status", async (c) => {
103
- const { loadConfig } = await Promise.resolve().then(() => require("./config-BwVx19Og.cjs")).then((n) => n.config_exports);
87
+ const { loadConfig } = await Promise.resolve().then(() => require("./config-CkW404Cs.cjs")).then((n) => n.config_exports);
104
88
  const config = await loadConfig();
105
89
  return c.json({
106
90
  dataDir: config.dataDir,
@@ -118,7 +102,7 @@ function createApp() {
118
102
  app.get("/graph-reports/:filename", async (c) => {
119
103
  const filename = c.req.param("filename");
120
104
  if (!isSafeGraphReportFilename(filename)) return c.json({ error: "Invalid graph report filename" }, 400);
121
- const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-YIbl6PwF.cjs")).then((n) => n.output_root_exports);
105
+ const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-DI0tzA0X.cjs")).then((n) => n.output_root_exports);
122
106
  const paths = workspaceOutputPaths();
123
107
  const graphPath = node_path.default.resolve(paths.reportGraphsRoot, filename);
124
108
  if (!withinRoot(paths.reportGraphsRoot, graphPath)) return c.json({ error: "Invalid graph report filename" }, 400);
@@ -137,7 +121,7 @@ function createApp() {
137
121
  return c.json({ error: "Invalid graph report filename" }, 400);
138
122
  });
139
123
  app.get("/workspace/tree", async (c) => {
140
- const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-YIbl6PwF.cjs")).then((n) => n.output_root_exports);
124
+ const { workspaceOutputPaths } = await Promise.resolve().then(() => require("./output-root-DI0tzA0X.cjs")).then((n) => n.output_root_exports);
141
125
  const paths = workspaceOutputPaths();
142
126
  const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS);
143
127
  return c.json({
@@ -1,11 +1,13 @@
1
+ import { n as workspaceOutputPaths } from "./output-root-BK4pdjyz.mjs";
1
2
  import path from "node:path";
2
3
  import { lstat, readFile, readdir, realpath } from "node:fs/promises";
3
- import os from "node:os";
4
4
  import { Hono } from "hono";
5
5
  //#region src/server/app.ts
6
6
  const WORKSPACE_TREE_ROOTS = [
7
- "cases",
7
+ "artifacts",
8
+ "entities",
8
9
  "reports",
10
+ "sessions",
9
11
  ".chain-insights/schema"
10
12
  ];
11
13
  const WORKSPACE_TREE_MAX_DEPTH = 4;
@@ -61,31 +63,14 @@ async function listWorkspaceEntries(workspaceRoot, roots = WORKSPACE_TREE_ROOTS,
61
63
  return entries;
62
64
  }
63
65
  async function findVizHtml(vizId) {
64
- const home = os.homedir();
65
66
  const filename = `${vizId}.html`;
66
- const centralPath = path.join(home, ".chain-insights", "viz", filename);
67
+ const paths = workspaceOutputPaths();
68
+ const vizPath = path.join(paths.publishedRoot, "viz", filename);
67
69
  try {
68
- return await readFile(centralPath, "utf-8");
69
- } catch {}
70
- const underscoreIdx = vizId.lastIndexOf("_");
71
- if (underscoreIdx > 0) {
72
- const possibleCaseId = vizId.substring(0, underscoreIdx);
73
- const casePath = path.join(home, ".chain-insights", "cases", possibleCaseId, "viz", filename);
74
- try {
75
- return await readFile(casePath, "utf-8");
76
- } catch {}
70
+ return await readFile(vizPath, "utf-8");
71
+ } catch {
72
+ return null;
77
73
  }
78
- const casesDir = path.join(home, ".chain-insights", "cases");
79
- try {
80
- const cases = await readdir(casesDir);
81
- for (const caseId of cases) {
82
- const casePath = path.join(casesDir, caseId, "viz", filename);
83
- try {
84
- return await readFile(casePath, "utf-8");
85
- } catch {}
86
- }
87
- } catch {}
88
- return null;
89
74
  }
90
75
  function isSafeGraphReportFilename(filename) {
91
76
  return filename.endsWith(".graph.json") && /^[A-Za-z0-9._-]+$/.test(filename) && !filename.includes("..") && !filename.includes("/") && !filename.includes("\\");
@@ -97,7 +82,7 @@ function createApp() {
97
82
  ts: Date.now()
98
83
  }));
99
84
  app.get("/status", async (c) => {
100
- const { loadConfig } = await import("./config-Drgc2HuF.mjs").then((n) => n.t);
85
+ const { loadConfig } = await import("./config-C6zM8Xir.mjs").then((n) => n.t);
101
86
  const config = await loadConfig();
102
87
  return c.json({
103
88
  dataDir: config.dataDir,
@@ -115,7 +100,7 @@ function createApp() {
115
100
  app.get("/graph-reports/:filename", async (c) => {
116
101
  const filename = c.req.param("filename");
117
102
  if (!isSafeGraphReportFilename(filename)) return c.json({ error: "Invalid graph report filename" }, 400);
118
- const { workspaceOutputPaths } = await import("./output-root-BRhzhhXZ.mjs").then((n) => n.t);
103
+ const { workspaceOutputPaths } = await import("./output-root-BK4pdjyz.mjs").then((n) => n.t);
119
104
  const paths = workspaceOutputPaths();
120
105
  const graphPath = path.resolve(paths.reportGraphsRoot, filename);
121
106
  if (!withinRoot(paths.reportGraphsRoot, graphPath)) return c.json({ error: "Invalid graph report filename" }, 400);
@@ -134,7 +119,7 @@ function createApp() {
134
119
  return c.json({ error: "Invalid graph report filename" }, 400);
135
120
  });
136
121
  app.get("/workspace/tree", async (c) => {
137
- const { workspaceOutputPaths } = await import("./output-root-BRhzhhXZ.mjs").then((n) => n.t);
122
+ const { workspaceOutputPaths } = await import("./output-root-BK4pdjyz.mjs").then((n) => n.t);
138
123
  const paths = workspaceOutputPaths();
139
124
  const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS);
140
125
  return c.json({
@@ -152,4 +137,4 @@ function createApp() {
152
137
  //#endregion
153
138
  export { createApp as t };
154
139
 
155
- //# sourceMappingURL=app-CRd39JJ8.mjs.map
140
+ //# sourceMappingURL=app-DXwILI_a.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-DXwILI_a.mjs","names":[],"sources":["../src/server/app.ts"],"sourcesContent":["import { Hono } from 'hono'\nimport { lstat, readFile, readdir, realpath } from 'node:fs/promises'\nimport path from 'node:path'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\n\nconst WORKSPACE_TREE_ROOTS = ['artifacts', 'entities', 'reports', 'sessions', '.chain-insights/schema']\nconst WORKSPACE_TREE_MAX_DEPTH = 4\n\ninterface WorkspaceTreeEntry {\n path: string\n type: 'file' | 'directory' | 'symlink'\n size?: number\n}\n\nfunction withinRoot(root: string, target: string): boolean {\n const relative = path.relative(path.resolve(root), path.resolve(target))\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))\n}\n\nasync function realPathWithinRoot(root: string, target: string): Promise<boolean> {\n try {\n const [realRoot, realTarget] = await Promise.all([realpath(root), realpath(target)])\n return withinRoot(realRoot, realTarget)\n } catch {\n return false\n }\n}\n\nfunction toWorkspaceRelative(root: string, target: string): string {\n return path.relative(root, target).split(path.sep).join('/')\n}\n\nasync function listWorkspaceEntries(\n workspaceRoot: string,\n roots = WORKSPACE_TREE_ROOTS,\n maxDepth = WORKSPACE_TREE_MAX_DEPTH\n): Promise<WorkspaceTreeEntry[]> {\n const entries: WorkspaceTreeEntry[] = []\n const root = path.resolve(workspaceRoot)\n\n async function visit(target: string, depth: number): Promise<void> {\n const resolved = path.resolve(target)\n if (!withinRoot(root, resolved)) return\n\n let info: Awaited<ReturnType<typeof lstat>>\n try {\n info = await lstat(resolved)\n } catch {\n return\n }\n\n const type = info.isSymbolicLink() ? 'symlink' : info.isDirectory() ? 'directory' : info.isFile() ? 'file' : null\n if (!type) return\n\n const entry: WorkspaceTreeEntry = {\n path: toWorkspaceRelative(root, resolved),\n type,\n }\n if (type === 'file') entry.size = info.size\n entries.push(entry)\n\n if (type !== 'directory' || depth >= maxDepth) return\n if (!await realPathWithinRoot(root, resolved)) return\n\n let children: string[]\n try {\n children = await readdir(resolved)\n } catch {\n return\n }\n\n for (const child of children.sort()) {\n await visit(path.join(resolved, child), depth + 1)\n }\n }\n\n for (const rootName of roots) {\n const target = path.resolve(root, rootName)\n if (withinRoot(root, target)) await visit(target, 0)\n }\n\n return entries\n}\n\nasync function findVizHtml(vizId: string): Promise<string | null> {\n const filename = `${vizId}.html`\n const paths = workspaceOutputPaths()\n const vizPath = path.join(paths.publishedRoot, 'viz', filename)\n try {\n return await readFile(vizPath, 'utf-8')\n } catch {\n return null\n }\n}\n\nfunction isSafeGraphReportFilename(filename: string): boolean {\n return (\n filename.endsWith('.graph.json') &&\n /^[A-Za-z0-9._-]+$/.test(filename) &&\n !filename.includes('..') &&\n !filename.includes('/') &&\n !filename.includes('\\\\')\n )\n}\n\nexport function createApp(): Hono {\n const app = new Hono()\n\n app.get('/health', (c) => c.json({ ok: true, ts: Date.now() }))\n\n app.get('/status', async (c) => {\n const { loadConfig } = await import('../config/index.js')\n const config = await loadConfig()\n return c.json({\n dataDir: config.dataDir,\n graphMcpMode: config.graphMcpMode,\n server: 'running',\n })\n })\n\n app.get('/viz/:id', async (c) => {\n const id = c.req.param('id')\n if (!/^[a-zA-Z0-9_-]+$/.test(id)) {\n return c.json({ error: 'Invalid visualization ID' }, 400)\n }\n const html = await findVizHtml(id)\n if (!html) {\n return c.json({ error: 'Visualization not found' }, 404)\n }\n return c.html(html)\n })\n\n app.get('/graph-reports/:filename', async (c) => {\n const filename = c.req.param('filename')\n if (!isSafeGraphReportFilename(filename)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const graphPath = path.resolve(paths.reportGraphsRoot, filename)\n if (!withinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n }\n if (!await realPathWithinRoot(paths.reportGraphsRoot, graphPath)) {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n\n try {\n const graph = await readFile(graphPath, 'utf-8')\n return c.body(graph, 200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n } catch {\n return c.json({ error: 'Graph report not found' }, 404)\n }\n })\n\n app.get('/graph-reports/*', (c) => {\n return c.json({ error: 'Invalid graph report filename' }, 400)\n })\n\n app.get('/workspace/tree', async (c) => {\n const { workspaceOutputPaths } = await import('../workspace/output-root.js')\n const paths = workspaceOutputPaths()\n const entries = await listWorkspaceEntries(paths.root, WORKSPACE_TREE_ROOTS)\n return c.json({\n schema: 'chain-insights.workspace-tree.v1',\n root: paths.root,\n entries,\n })\n })\n\n app.onError((err, c) => {\n console.error(err)\n return c.json({ error: 'Internal server error' }, 500)\n })\n\n return app\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as createApp } from "./app-CRd39JJ8.mjs";
1
+ import { t as createApp } from "./app-DXwILI_a.mjs";
2
2
  import { serve } from "@hono/node-server";
3
3
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
4
4
  //#region src/mcp/artifact-server.ts
@@ -45,4 +45,4 @@ async function ensureArtifactServer(port) {
45
45
  //#endregion
46
46
  export { ensureArtifactServer };
47
47
 
48
- //# sourceMappingURL=artifact-server-CP6LXQ9d.mjs.map
48
+ //# sourceMappingURL=artifact-server-CcmLBv1j.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-server-CP6LXQ9d.mjs","names":["delay"],"sources":["../src/mcp/artifact-server.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { createApp } from '../server/app.js'\n\ntype ArtifactServer = ReturnType<typeof serve>\n\nconst servers = new Map<number, ArtifactServer>()\n\nasync function isHealthy(port: number): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 500)\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n })\n return response.ok\n } catch {\n return false\n } finally {\n clearTimeout(timeout)\n }\n}\n\nasync function waitUntilHealthy(port: number): Promise<void> {\n for (let attempt = 0; attempt < 20; attempt += 1) {\n if (await isHealthy(port)) return\n await delay(50)\n }\n throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`)\n}\n\nexport async function ensureArtifactServer(port: number): Promise<void> {\n if (servers.has(port)) return\n if (await isHealthy(port)) return\n\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1',\n port,\n })\n servers.set(port, server)\n server.on('error', (err) => {\n servers.delete(port)\n process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${(err as Error).message}\\n`)\n })\n\n try {\n await waitUntilHealthy(port)\n } catch (err) {\n servers.delete(port)\n server.close()\n throw err\n }\n}\n\nexport function closeArtifactServers(): void {\n for (const [port, server] of servers.entries()) {\n server.close()\n servers.delete(port)\n }\n}\n"],"mappings":";;;;AAMA,MAAM,0BAAU,IAAI,IAA4B;AAEhD,eAAe,UAAU,MAAgC;CACvD,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,GAAG;CACxD,IAAI;EAIF,QAAO,MAHgB,MAAM,oBAAoB,KAAK,UAAU,EAC9D,QAAQ,WAAW,OACrB,CAAC,GACe;CAClB,QAAQ;EACN,OAAO;CACT,UAAU;EACR,aAAa,OAAO;CACtB;AACF;AAEA,eAAe,iBAAiB,MAA6B;CAC3D,KAAK,IAAI,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;EAChD,IAAI,MAAM,UAAU,IAAI,GAAG;EAC3B,MAAMA,aAAM,EAAE;CAChB;CACA,MAAM,IAAI,MAAM,2DAA2D,MAAM;AACnF;AAEA,eAAsB,qBAAqB,MAA6B;CACtE,IAAI,QAAQ,IAAI,IAAI,GAAG;CACvB,IAAI,MAAM,UAAU,IAAI,GAAG;CAG3B,MAAM,SAAS,MAAM;EACnB,OAFU,UAED,EAAE;EACX,UAAU;EACV;CACF,CAAC;CACD,QAAQ,IAAI,MAAM,MAAM;CACxB,OAAO,GAAG,UAAU,QAAQ;EAC1B,QAAQ,OAAO,IAAI;EACnB,QAAQ,OAAO,MAAM,0DAA0D,KAAK,IAAK,IAAc,QAAQ,GAAG;CACpH,CAAC;CAED,IAAI;EACF,MAAM,iBAAiB,IAAI;CAC7B,SAAS,KAAK;EACZ,QAAQ,OAAO,IAAI;EACnB,OAAO,MAAM;EACb,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"artifact-server-CcmLBv1j.mjs","names":["delay"],"sources":["../src/mcp/artifact-server.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { createApp } from '../server/app.js'\n\ntype ArtifactServer = ReturnType<typeof serve>\n\nconst servers = new Map<number, ArtifactServer>()\n\nasync function isHealthy(port: number): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 500)\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n })\n return response.ok\n } catch {\n return false\n } finally {\n clearTimeout(timeout)\n }\n}\n\nasync function waitUntilHealthy(port: number): Promise<void> {\n for (let attempt = 0; attempt < 20; attempt += 1) {\n if (await isHealthy(port)) return\n await delay(50)\n }\n throw new Error(`Graph report server did not become healthy on 127.0.0.1:${port}`)\n}\n\nexport async function ensureArtifactServer(port: number): Promise<void> {\n if (servers.has(port)) return\n if (await isHealthy(port)) return\n\n const app = createApp()\n const server = serve({\n fetch: app.fetch,\n hostname: '127.0.0.1',\n port,\n })\n servers.set(port, server)\n server.on('error', (err) => {\n servers.delete(port)\n process.stderr.write(`Chain Insights graph report server failed on 127.0.0.1:${port}: ${(err as Error).message}\\n`)\n })\n\n try {\n await waitUntilHealthy(port)\n } catch (err) {\n servers.delete(port)\n server.close()\n throw err\n }\n}\n\nexport function closeArtifactServers(): void {\n for (const [port, server] of servers.entries()) {\n server.close()\n servers.delete(port)\n }\n}\n"],"mappings":";;;;AAMA,MAAM,0BAAU,IAAI,IAA4B;AAEhD,eAAe,UAAU,MAAgC;CACvD,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,GAAG;CACxD,IAAI;EAIF,QAAO,MAHgB,MAAM,oBAAoB,KAAK,UAAU,EAC9D,QAAQ,WAAW,OACrB,CAAC,GACe;CAClB,QAAQ;EACN,OAAO;CACT,UAAU;EACR,aAAa,OAAO;CACtB;AACF;AAEA,eAAe,iBAAiB,MAA6B;CAC3D,KAAK,IAAI,UAAU,GAAG,UAAU,IAAI,WAAW,GAAG;EAChD,IAAI,MAAM,UAAU,IAAI,GAAG;EAC3B,MAAMA,aAAM,EAAE;CAChB;CACA,MAAM,IAAI,MAAM,2DAA2D,MAAM;AACnF;AAEA,eAAsB,qBAAqB,MAA6B;CACtE,IAAI,QAAQ,IAAI,IAAI,GAAG;CACvB,IAAI,MAAM,UAAU,IAAI,GAAG;CAG3B,MAAM,SAAS,MAAM;EACnB,OAFU,UAED,EAAE;EACX,UAAU;EACV;CACF,CAAC;CACD,QAAQ,IAAI,MAAM,MAAM;CACxB,OAAO,GAAG,UAAU,QAAQ;EAC1B,QAAQ,OAAO,IAAI;EACnB,QAAQ,OAAO,MAAM,0DAA0D,KAAK,IAAK,IAAc,QAAQ,GAAG;CACpH,CAAC;CAED,IAAI;EACF,MAAM,iBAAiB,IAAI;CAC7B,SAAS,KAAK;EACZ,QAAQ,OAAO,IAAI;EACnB,OAAO,MAAM;EACb,MAAM;CACR;AACF"}
@@ -1,4 +1,4 @@
1
- const require_app = require("./app-BxojXjtB.cjs");
1
+ const require_app = require("./app-DBrqk_iP.cjs");
2
2
  let _hono_node_server = require("@hono/node-server");
3
3
  let node_timers_promises = require("node:timers/promises");
4
4
  //#region src/mcp/artifact-server.ts
@@ -1,4 +1,4 @@
1
- import { n as applyMcpAuthHeaders, o as resolveGraphMcpEndpoint } from "./client-BgmHjBHQ.mjs";
1
+ import { a as resolveGraphMcpEndpoint, n as applyMcpAuthHeaders } from "./client-ytTO0mcZ.mjs";
2
2
  //#region src/mcp/capabilities.ts
3
3
  function metadataNetworksUrl(endpoint) {
4
4
  const url = new URL(endpoint);
@@ -78,4 +78,4 @@ function formatNetworkCapabilities(document) {
78
78
  //#endregion
79
79
  export { fetchNetworkCapabilities, formatNetworkCapabilities };
80
80
 
81
- //# sourceMappingURL=capabilities-BCvkTkIu.mjs.map
81
+ //# sourceMappingURL=capabilities-CM72SErE.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities-BCvkTkIu.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { applyMcpAuthHeaders, resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkRetention {\n mode: 'full_history' | 'rolling_window' | 'expanding_then_rolling' | 'bounded_range' | 'unknown' | string\n window_days?: number\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n started_at?: string\n rolls_after_at?: string\n current_window_seconds?: number\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n retention?: NetworkRetention | null\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\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, 'mcpAuthToken' | 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint' | 'mcpEndpoint'>,\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.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 (parsed.schema !== 'chain-insights.network-capabilities.v1' || !Array.isArray(parsed.networks)) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return parsed\n}\n\nfunction layerValue(network: NetworkCapability, layer: string): string {\n const capability = network.layers[layer]\n if (!capability?.enabled) return 'no'\n return 'yes'\n}\n\nfunction availableToolsLabel(network: NetworkCapability): string {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.length > 0 ? tools.join(', ') : 'none'\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 = coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange = coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : 'dates unknown'\n if (blockRange === 'blocks unknown' && dateRange === 'dates unknown') return 'unknown'\n return `${blockRange} / ${dateRange}`\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const headers = ['Network', 'Topology', 'Facts', 'Risk', 'Dataset', 'Available tools']\n const widths = [14, 10, 8, 8, 38, 64]\n const row = (values: string[]) => values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [\n row(headers),\n widths.map((width) => '-'.repeat(width)).join(' '),\n ...document.networks.map((network) => row([\n network.display_name || network.network,\n layerValue(network, 'topology'),\n layerValue(network, 'facts'),\n layerValue(network, 'risk'),\n datasetLabel(network),\n availableToolsLabel(network),\n ])),\n ].join('\\n')\n}\n"],"mappings":";;AAgDA,SAAS,oBAAoB,UAAuB;CAClD,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO;AACT;AAEA,eAAsB,yBACpB,QACsC;CAEtC,MAAM,UAAU,oBADC,wBAAwB,MACE,CAAC;CAC5C,MAAM,UAAU,IAAI,QAAQ;CAC5B,MAAM,QAAQ,OAAO,mBAAmB,KAAK,KAAK,OAAO,cAAc,KAAK;CAC5E,IAAI,OACF,oBAAoB,SAAS,KAAK;CAEpC,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,SAAS,EAAE,QAAQ,CAAC;CAC7C,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,IAAK,IAAc,SAAS;CAC7F;CACA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,SAAS,SAAS,QAAQ;CAE3F,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,WAAW,4CAA4C,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAC9F,MAAM,IAAI,MAAM,sDAAsD;CAExE,OAAO;AACT;AAEA,SAAS,WAAW,SAA4B,OAAuB;CAErE,IAAI,CADe,QAAQ,OAAO,QACjB,SAAS,OAAO;CACjC,OAAO;AACT;AAEA,SAAS,oBAAoB,SAAoC;CAC/D,MAAM,QAAQ,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,EAC7C,QAAQ,GAAG,YAAY,WAAW,WAAW,EAC7C,KAAK,CAAC,UAAU,IAAI;CACvB,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;AAEA,SAAS,UAAU,OAAwB;CACzC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,MAAM,GAAG,EAAE;AAC1B;AAEA,SAAS,aAAa,SAAoC;CACxD,MAAM,WAAW,QAAQ;CACzB,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,aAAa,SAAS,eAAe,KAAA,KAAa,SAAS,aAAa,KAAA,IAC1E,GAAG,SAAS,WAAW,IAAI,SAAS,aACpC;CACJ,MAAM,YAAY,SAAS,kBAAkB,SAAS,eAClD,GAAG,UAAU,SAAS,cAAc,EAAE,IAAI,UAAU,SAAS,YAAY,MACzE;CACJ,IAAI,eAAe,oBAAoB,cAAc,iBAAiB,OAAO;CAC7E,OAAO,GAAG,WAAW,KAAK;AAC5B;AAEA,SAAgB,0BAA0B,UAA+C;CACvF,IAAI,SAAS,SAAS,WAAW,GAAG,OAAO;CAC3C,MAAM,UAAU;EAAC;EAAW;EAAY;EAAS;EAAQ;EAAW;CAAiB;CACrF,MAAM,SAAS;EAAC;EAAI;EAAI;EAAG;EAAG;EAAI;CAAE;CACpC,MAAM,OAAO,WAAqB,OAAO,KAAK,OAAO,UAAU,MAAM,OAAO,OAAO,MAAO,CAAC,EAAE,KAAK,IAAI;CACtG,OAAO;EACL,IAAI,OAAO;EACX,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI;EAClD,GAAG,SAAS,SAAS,KAAK,YAAY,IAAI;GACxC,QAAQ,gBAAgB,QAAQ;GAChC,WAAW,SAAS,UAAU;GAC9B,WAAW,SAAS,OAAO;GAC3B,WAAW,SAAS,MAAM;GAC1B,aAAa,OAAO;GACpB,oBAAoB,OAAO;EAC7B,CAAC,CAAC;CACJ,EAAE,KAAK,IAAI;AACb"}
1
+ {"version":3,"file":"capabilities-CM72SErE.mjs","names":[],"sources":["../src/mcp/capabilities.ts"],"sourcesContent":["import type { InvestigatorConfig } from '../config/schema.js'\nimport { applyMcpAuthHeaders, resolveGraphMcpEndpoint } from './client.js'\n\nexport interface NetworkRetention {\n mode: 'full_history' | 'rolling_window' | 'expanding_then_rolling' | 'bounded_range' | 'unknown' | string\n window_days?: number\n from_block?: number\n to_block?: number\n from_timestamp?: string\n to_timestamp?: string\n started_at?: string\n rolls_after_at?: string\n current_window_seconds?: number\n}\n\nexport interface NetworkLayerCapability {\n enabled: boolean\n retention?: NetworkRetention | null\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\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, 'mcpAuthToken' | 'graphMcpAuthToken' | 'graphMcpMode' | 'graphMcpEndpoint' | 'mcpEndpoint'>,\n): Promise<NetworkCapabilitiesDocument> {\n const endpoint = resolveGraphMcpEndpoint(config)\n const request = metadataNetworksUrl(endpoint)\n const headers = new Headers()\n const token = config.graphMcpAuthToken?.trim() || config.mcpAuthToken?.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 (parsed.schema !== 'chain-insights.network-capabilities.v1' || !Array.isArray(parsed.networks)) {\n throw new Error('network capabilities response has unsupported schema')\n }\n return parsed\n}\n\nfunction layerValue(network: NetworkCapability, layer: string): string {\n const capability = network.layers[layer]\n if (!capability?.enabled) return 'no'\n return 'yes'\n}\n\nfunction availableToolsLabel(network: NetworkCapability): string {\n const tools = Object.entries(network.tools ?? {})\n .filter(([, status]) => status === 'available')\n .map(([name]) => name)\n return tools.length > 0 ? tools.join(', ') : 'none'\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 = coverage.from_block !== undefined && coverage.to_block !== undefined\n ? `${coverage.from_block}..${coverage.to_block}`\n : 'blocks unknown'\n const dateRange = coverage.from_timestamp && coverage.to_timestamp\n ? `${shortDate(coverage.from_timestamp)}..${shortDate(coverage.to_timestamp)}`\n : 'dates unknown'\n if (blockRange === 'blocks unknown' && dateRange === 'dates unknown') return 'unknown'\n return `${blockRange} / ${dateRange}`\n}\n\nexport function formatNetworkCapabilities(document: NetworkCapabilitiesDocument): string {\n if (document.networks.length === 0) return 'No supported networks advertised.'\n const headers = ['Network', 'Topology', 'Facts', 'Risk', 'Dataset', 'Available tools']\n const widths = [14, 10, 8, 8, 38, 64]\n const row = (values: string[]) => values.map((value, index) => value.padEnd(widths[index]!)).join(' ')\n return [\n row(headers),\n widths.map((width) => '-'.repeat(width)).join(' '),\n ...document.networks.map((network) => row([\n network.display_name || network.network,\n layerValue(network, 'topology'),\n layerValue(network, 'facts'),\n layerValue(network, 'risk'),\n datasetLabel(network),\n availableToolsLabel(network),\n ])),\n ].join('\\n')\n}\n"],"mappings":";;AAgDA,SAAS,oBAAoB,UAAuB;CAClD,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO;AACT;AAEA,eAAsB,yBACpB,QACsC;CAEtC,MAAM,UAAU,oBADC,wBAAwB,MACE,CAAC;CAC5C,MAAM,UAAU,IAAI,QAAQ;CAC5B,MAAM,QAAQ,OAAO,mBAAmB,KAAK,KAAK,OAAO,cAAc,KAAK;CAC5E,IAAI,OACF,oBAAoB,SAAS,KAAK;CAEpC,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,SAAS,EAAE,QAAQ,CAAC;CAC7C,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,IAAK,IAAc,SAAS;CAC7F;CACA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,uCAAuC,QAAQ,SAAS,SAAS,QAAQ;CAE3F,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,WAAW,4CAA4C,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAC9F,MAAM,IAAI,MAAM,sDAAsD;CAExE,OAAO;AACT;AAEA,SAAS,WAAW,SAA4B,OAAuB;CAErE,IAAI,CADe,QAAQ,OAAO,QACjB,SAAS,OAAO;CACjC,OAAO;AACT;AAEA,SAAS,oBAAoB,SAAoC;CAC/D,MAAM,QAAQ,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,EAC7C,QAAQ,GAAG,YAAY,WAAW,WAAW,EAC7C,KAAK,CAAC,UAAU,IAAI;CACvB,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;AAEA,SAAS,UAAU,OAAwB;CACzC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,MAAM,GAAG,EAAE;AAC1B;AAEA,SAAS,aAAa,SAAoC;CACxD,MAAM,WAAW,QAAQ;CACzB,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,aAAa,SAAS,eAAe,KAAA,KAAa,SAAS,aAAa,KAAA,IAC1E,GAAG,SAAS,WAAW,IAAI,SAAS,aACpC;CACJ,MAAM,YAAY,SAAS,kBAAkB,SAAS,eAClD,GAAG,UAAU,SAAS,cAAc,EAAE,IAAI,UAAU,SAAS,YAAY,MACzE;CACJ,IAAI,eAAe,oBAAoB,cAAc,iBAAiB,OAAO;CAC7E,OAAO,GAAG,WAAW,KAAK;AAC5B;AAEA,SAAgB,0BAA0B,UAA+C;CACvF,IAAI,SAAS,SAAS,WAAW,GAAG,OAAO;CAC3C,MAAM,UAAU;EAAC;EAAW;EAAY;EAAS;EAAQ;EAAW;CAAiB;CACrF,MAAM,SAAS;EAAC;EAAI;EAAI;EAAG;EAAG;EAAI;CAAE;CACpC,MAAM,OAAO,WAAqB,OAAO,KAAK,OAAO,UAAU,MAAM,OAAO,OAAO,MAAO,CAAC,EAAE,KAAK,IAAI;CACtG,OAAO;EACL,IAAI,OAAO;EACX,OAAO,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI;EAClD,GAAG,SAAS,SAAS,KAAK,YAAY,IAAI;GACxC,QAAQ,gBAAgB,QAAQ;GAChC,WAAW,SAAS,UAAU;GAC9B,WAAW,SAAS,OAAO;GAC3B,WAAW,SAAS,MAAM;GAC1B,aAAa,OAAO;GACpB,oBAAoB,OAAO;EAC7B,CAAC,CAAC;CACJ,EAAE,KAAK,IAAI;AACb"}
@@ -1,4 +1,4 @@
1
- const require_client = require("./client-Y_zqKqJT.cjs");
1
+ const require_client = require("./client-BY-56ojr.cjs");
2
2
  //#region src/mcp/capabilities.ts
3
3
  function metadataNetworksUrl(endpoint) {
4
4
  const url = new URL(endpoint);