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.
- package/README.md +49 -52
- package/dist/{active-ByNgjuAg.mjs → active-BQopLul8.mjs} +6 -8
- package/dist/active-BQopLul8.mjs.map +1 -0
- package/dist/{active-BVr55kvW.cjs → active-XWv72R1X.cjs} +4 -12
- package/dist/{app-BxojXjtB.cjs → app-DBrqk_iP.cjs} +12 -28
- package/dist/{app-CRd39JJ8.mjs → app-DXwILI_a.mjs} +13 -28
- package/dist/app-DXwILI_a.mjs.map +1 -0
- package/dist/{artifact-server-CP6LXQ9d.mjs → artifact-server-CcmLBv1j.mjs} +2 -2
- package/dist/{artifact-server-CP6LXQ9d.mjs.map → artifact-server-CcmLBv1j.mjs.map} +1 -1
- package/dist/{artifact-server-XbN16DwU.cjs → artifact-server-v0WgTPFT.cjs} +1 -1
- package/dist/{capabilities-BCvkTkIu.mjs → capabilities-CM72SErE.mjs} +2 -2
- package/dist/{capabilities-BCvkTkIu.mjs.map → capabilities-CM72SErE.mjs.map} +1 -1
- package/dist/{capabilities-DOa6EFO-.cjs → capabilities-DGeF-oHc.cjs} +1 -1
- package/dist/cli.cjs +149 -405
- package/dist/cli.mjs +149 -405
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-Y_zqKqJT.cjs → client-BY-56ojr.cjs} +0 -17
- package/dist/{client-BgmHjBHQ.mjs → client-ytTO0mcZ.mjs} +2 -13
- package/dist/{client-BgmHjBHQ.mjs.map → client-ytTO0mcZ.mjs.map} +1 -1
- package/dist/{config-Drgc2HuF.mjs → config-C6zM8Xir.mjs} +3 -3
- package/dist/{config-Drgc2HuF.mjs.map → config-C6zM8Xir.mjs.map} +1 -1
- package/dist/{config-BwVx19Og.cjs → config-CkW404Cs.cjs} +2 -2
- package/dist/{graph-reports-BDELxmpi.mjs → graph-reports-CEq-Mvx0.mjs} +2 -2
- package/dist/{graph-reports-BDELxmpi.mjs.map → graph-reports-CEq-Mvx0.mjs.map} +1 -1
- package/dist/{graph-reports-B3mkLP8Z.cjs → graph-reports-CkglRtg4.cjs} +1 -1
- package/dist/{html-generator-Bx3UcLTB.cjs → html-generator-BFKafL8y.cjs} +5 -6
- package/dist/{html-generator-AowOmzyi.mjs → html-generator-D4fX71hI.mjs} +6 -6
- package/dist/html-generator-D4fX71hI.mjs.map +1 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +1 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{init-CKQ6F07J.mjs → init-BGDvGreX.mjs} +52 -55
- package/dist/init-BGDvGreX.mjs.map +1 -0
- package/dist/{init-Dhw8F23z.cjs → init-Cuw9TznI.cjs} +51 -54
- package/dist/{mcp-endpoint-DHs1cRFH.mjs → mcp-endpoint-QQ5Lbqc2.mjs} +5 -2
- package/dist/mcp-endpoint-QQ5Lbqc2.mjs.map +1 -0
- package/dist/{mcp-endpoint-BaV8h_lq.cjs → mcp-endpoint-cQIZSjkK.cjs} +4 -1
- package/dist/mcp-proxy.cjs +650 -771
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +651 -772
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{output-root-BRhzhhXZ.mjs → output-root-BK4pdjyz.mjs} +6 -3
- package/dist/output-root-BK4pdjyz.mjs.map +1 -0
- package/dist/{output-root-YIbl6PwF.cjs → output-root-DI0tzA0X.cjs} +5 -2
- package/dist/{public-tools-BY3PTw6x.cjs → public-tools-BREojpU7.cjs} +1244 -426
- package/dist/{public-tools-CvlZcysd.mjs → public-tools-brHmHGYm.mjs} +1240 -428
- package/dist/public-tools-brHmHGYm.mjs.map +1 -0
- package/dist/{schema-BFEWhzg7.mjs → schema-D_qwaQA5.mjs} +2 -2
- package/dist/{schema-BFEWhzg7.mjs.map → schema-D_qwaQA5.mjs.map} +1 -1
- package/dist/{schema-Vl9yuOFO.cjs → schema-Dr6JXSOF.cjs} +1 -1
- package/dist/{server-BXLX2j_A.mjs → server-BK4bfOiv.mjs} +2 -2
- package/dist/{server-BXLX2j_A.mjs.map → server-BK4bfOiv.mjs.map} +1 -1
- package/dist/{server-BqVdWath.cjs → server-ColyTG1t.cjs} +1 -1
- package/dist/templates/graph.html +1 -1
- package/dist/{tool-visibility-Buq7YdUZ.cjs → tool-visibility--QPgrRE5.cjs} +5 -1
- package/dist/{tool-visibility-BpyZHRBi.mjs → tool-visibility-nr6XqO1F.mjs} +6 -2
- package/dist/tool-visibility-nr6XqO1F.mjs.map +1 -0
- package/dist/viz-BBvY-wXz.cjs +210 -0
- package/dist/viz-D8umSF-t.mjs +199 -0
- package/dist/viz-D8umSF-t.mjs.map +1 -0
- package/docs/architecture.md +4 -3
- package/docs/contributing.md +12 -6
- package/docs/graph-tools.md +93 -68
- package/docs/investigation-workspaces.md +38 -124
- package/docs/mcp-proxy.md +23 -34
- package/package.json +2 -2
- package/skills/chain-insights-address-risk/SKILL.md +92 -0
- package/skills/chain-insights-bittensor-cypher/SKILL.md +2 -22
- package/skills/chain-insights-developer-experience/SKILL.md +8 -28
- package/skills/chain-insights-exposure-analysis/SKILL.md +83 -0
- package/skills/chain-insights-investigation/SKILL.md +59 -211
- package/skills/chain-insights-investigation/agents/openai.yaml +1 -1
- package/skills/chain-insights-investigation/scripts/run-target-uat.sh +37 -55
- package/skills/chain-insights-trace-funds/SKILL.md +14 -14
- package/skills/ci-status/SKILL.md +9 -15
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +5 -4
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +272 -18
- package/dist/active-ByNgjuAg.mjs.map +0 -1
- package/dist/app-CRd39JJ8.mjs.map +0 -1
- package/dist/canvas-Cn-maEIh.mjs +0 -203
- package/dist/canvas-Cn-maEIh.mjs.map +0 -1
- package/dist/canvas-p-oKCMjc.cjs +0 -251
- package/dist/cases-Bz_9XKEw.cjs +0 -19
- package/dist/cases-TVcAifxu.mjs +0 -16
- package/dist/cases-TVcAifxu.mjs.map +0 -1
- package/dist/data-extractor-B4nHw1wZ.mjs +0 -336
- package/dist/data-extractor-B4nHw1wZ.mjs.map +0 -1
- package/dist/data-extractor-DS4rzy3M.cjs +0 -353
- package/dist/dossier-BXy57V4-.cjs +0 -88
- package/dist/dossier-Bjpcbcxa.mjs +0 -78
- package/dist/dossier-Bjpcbcxa.mjs.map +0 -1
- package/dist/evidence-CvEesemA.cjs +0 -200
- package/dist/evidence-D96PTzOQ.mjs +0 -195
- package/dist/evidence-D96PTzOQ.mjs.map +0 -1
- package/dist/export-CBhcJuZ6.mjs +0 -394
- package/dist/export-CBhcJuZ6.mjs.map +0 -1
- package/dist/export-D4v4-6F4.cjs +0 -394
- package/dist/frontmatter-D0ccQnUM.mjs +0 -26
- package/dist/frontmatter-D0ccQnUM.mjs.map +0 -1
- package/dist/frontmatter-Dvqa5HX6.cjs +0 -35
- package/dist/html-generator-AowOmzyi.mjs.map +0 -1
- package/dist/init-CKQ6F07J.mjs.map +0 -1
- package/dist/mcp-endpoint-DHs1cRFH.mjs.map +0 -1
- package/dist/output-root-BRhzhhXZ.mjs.map +0 -1
- package/dist/parser-BXLAHYnZ.cjs +0 -182
- package/dist/parser-CJfMsOl6.mjs +0 -182
- package/dist/parser-CJfMsOl6.mjs.map +0 -1
- package/dist/public-tools-CvlZcysd.mjs.map +0 -1
- package/dist/resolver-2jXNtWQO.mjs +0 -184
- package/dist/resolver-2jXNtWQO.mjs.map +0 -1
- package/dist/resolver-CZdQwKvh.cjs +0 -186
- package/dist/runner-CVnjpqc-.mjs +0 -149
- package/dist/runner-CVnjpqc-.mjs.map +0 -1
- package/dist/runner-bLy0pTr_.cjs +0 -147
- package/dist/selector-BvXM9jbe.mjs +0 -12
- package/dist/selector-BvXM9jbe.mjs.map +0 -1
- package/dist/selector-Dps_ZFxq.cjs +0 -10
- package/dist/session-BT7VpbAd.cjs +0 -127
- package/dist/session-DROyhebe.mjs +0 -117
- package/dist/session-DROyhebe.mjs.map +0 -1
- package/dist/store-C2B_AssI.mjs +0 -231
- package/dist/store-C2B_AssI.mjs.map +0 -1
- package/dist/store-CQhU8dz8.cjs +0 -242
- package/dist/tool-visibility-BpyZHRBi.mjs.map +0 -1
- package/dist/vault-B2y78Ypu.cjs +0 -560
- package/dist/vault-z35Dohdq.mjs +0 -560
- package/dist/vault-z35Dohdq.mjs.map +0 -1
- package/dist/viz-D1620cBX.cjs +0 -44
- package/dist/viz-DB5XFG1z.mjs +0 -35
- package/dist/viz-DB5XFG1z.mjs.map +0 -1
- package/docs/knowledge-exports.md +0 -204
- package/docs/obsidian-vault.md +0 -130
- 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
|
|
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
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
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
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
-
|
|
138
|
+
Run a focused investigation in the initialized workspace:
|
|
135
139
|
|
|
136
140
|
```bash
|
|
137
|
-
cia
|
|
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
|
-
|
|
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
|
|
157
|
-
to share
|
|
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
|
-
|
|
162
|
-
|
|
161
|
+
# Use your configured workspace export flow to produce the handoff package.
|
|
162
|
+
published/<workspace-slug>/
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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,
|
|
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
|
|
236
|
+
and local workspace state:
|
|
244
237
|
|
|
245
|
-
- `
|
|
238
|
+
- `aml_address_risk` starts a single-address screen with risk, behavior,
|
|
246
239
|
neighborhood context, and exchange exposure.
|
|
247
|
-
- `
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
- `
|
|
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
|
-
- `
|
|
250
|
+
- `aml_trace_deposit_sources` traces backward from suspected deposit/cashout
|
|
253
251
|
addresses to upstream sources and shared-source convergence.
|
|
254
|
-
- `
|
|
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
|
|
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
|
|
266
|
-
reports under the workspace instead of embedding large payloads in
|
|
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
|
-
| [
|
|
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
|
|
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
|
-
|
|
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 {
|
|
46
|
+
export { findActiveWorkspace as n, active_exports as t };
|
|
49
47
|
|
|
50
|
-
//# sourceMappingURL=active-
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
|
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)(
|
|
72
|
-
} catch {
|
|
73
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
"
|
|
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
|
|
67
|
+
const paths = workspaceOutputPaths();
|
|
68
|
+
const vizPath = path.join(paths.publishedRoot, "viz", filename);
|
|
67
69
|
try {
|
|
68
|
-
return await readFile(
|
|
69
|
-
} catch {
|
|
70
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
48
|
+
//# sourceMappingURL=artifact-server-CcmLBv1j.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact-server-
|
|
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
|
-
import {
|
|
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-
|
|
81
|
+
//# sourceMappingURL=capabilities-CM72SErE.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities-
|
|
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"}
|