chain-insights 0.3.4 → 0.3.6
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 +28 -11
- package/dist/canvas-Cn-maEIh.mjs +203 -0
- package/dist/canvas-Cn-maEIh.mjs.map +1 -0
- package/dist/canvas-p-oKCMjc.cjs +251 -0
- package/dist/cases-Bz_9XKEw.cjs +19 -0
- package/dist/cases-TVcAifxu.mjs +16 -0
- package/dist/cases-TVcAifxu.mjs.map +1 -0
- package/dist/cli.cjs +74 -28
- package/dist/cli.mjs +74 -28
- package/dist/cli.mjs.map +1 -1
- package/dist/{data-extractor-DZUJu1Bz.mjs → data-extractor-B4nHw1wZ.mjs} +2 -2
- package/dist/{data-extractor-DZUJu1Bz.mjs.map → data-extractor-B4nHw1wZ.mjs.map} +1 -1
- package/dist/{data-extractor-Cavd7wHk.cjs → data-extractor-DS4rzy3M.cjs} +1 -1
- package/dist/{export-BqTCO9lP.mjs → export-CBhcJuZ6.mjs} +8 -205
- package/dist/export-CBhcJuZ6.mjs.map +1 -0
- package/dist/{export-DsXgtCwO.cjs → export-D4v4-6F4.cjs} +16 -214
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{init-DLBL_nVG.mjs → init-CKQ6F07J.mjs} +22 -5
- package/dist/init-CKQ6F07J.mjs.map +1 -0
- package/dist/{init-zqbd7i-_.cjs → init-Dhw8F23z.cjs} +21 -4
- package/dist/mcp-proxy.cjs +20 -20
- package/dist/mcp-proxy.mjs +20 -20
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-wJoAFDFa.mjs → public-tools-CyUZEz9B.mjs} +3 -3
- package/dist/{public-tools-wJoAFDFa.mjs.map → public-tools-CyUZEz9B.mjs.map} +1 -1
- package/dist/{public-tools-BvMb3H2P.cjs → public-tools-xfVNz9NE.cjs} +2 -2
- package/dist/{runner-BhZ4lnF1.cjs → runner-CVo41fjz.cjs} +2 -2
- package/dist/{runner-DIJSbkjc.mjs → runner-DWuSy1Se.mjs} +3 -3
- package/dist/{runner-DIJSbkjc.mjs.map → runner-DWuSy1Se.mjs.map} +1 -1
- package/dist/{selector-CF2o5gxN.mjs → selector-BvXM9jbe.mjs} +2 -2
- package/dist/{selector-CF2o5gxN.mjs.map → selector-BvXM9jbe.mjs.map} +1 -1
- package/dist/{selector-DfAMZEC9.cjs → selector-Dps_ZFxq.cjs} +1 -1
- package/dist/{store-CTtqQtaE.mjs → store-C2B_AssI.mjs} +2 -2
- package/dist/{store-CTtqQtaE.mjs.map → store-C2B_AssI.mjs.map} +1 -1
- package/dist/{store-CqPfs47P.cjs → store-CQhU8dz8.cjs} +0 -18
- package/dist/vault-B2y78Ypu.cjs +560 -0
- package/dist/vault-z35Dohdq.mjs +560 -0
- package/dist/vault-z35Dohdq.mjs.map +1 -0
- package/dist/{viz-Dqp3C5kb.cjs → viz-D1620cBX.cjs} +3 -3
- package/dist/{viz-5y24S5X1.mjs → viz-DB5XFG1z.mjs} +4 -4
- package/dist/{viz-5y24S5X1.mjs.map → viz-DB5XFG1z.mjs.map} +1 -1
- package/docs/graph-tools.md +24 -8
- package/docs/investigation-workspaces.md +36 -10
- package/docs/knowledge-exports.md +6 -2
- package/docs/mcp-proxy.md +29 -7
- package/docs/obsidian-vault.md +130 -0
- package/package.json +1 -1
- package/skills/chain-insights-developer-experience/SKILL.md +2 -2
- package/skills/chain-insights-investigation/SKILL.md +1 -1
- package/dist/cases-Cp9DUbEV.mjs +0 -6
- package/dist/cases-sTY5aXav.cjs +0 -9
- package/dist/export-BqTCO9lP.mjs.map +0 -1
- package/dist/init-DLBL_nVG.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viz-
|
|
1
|
+
{"version":3,"file":"viz-DB5XFG1z.mjs","names":[],"sources":["../src/viz/index.ts"],"sourcesContent":["export { GraphData, GraphNode, GraphEdge, EntityType, RiskLevel, truncateGraph } from './graph-model.js'\nexport type { GraphData as GraphDataType, GraphNode as GraphNodeType, GraphEdge as GraphEdgeType } from './graph-model.js'\nexport { generateHtml, writeVizHtml, transformToGraphHtml } from './html-generator.js'\nexport { DataExtractor, extractGraphFromCase, extractGraphFromJson } from './data-extractor.js'\n\nimport { readFile } from 'node:fs/promises'\nimport { truncateGraph } from './graph-model.js'\nimport { generateHtml, writeVizHtml } from './html-generator.js'\n\nexport async function generateVisualization(opts: {\n caseId?: string\n dataFile?: string\n}): Promise<{ vizId: string; htmlPath: string }> {\n let rawData: unknown\n\n if (opts.dataFile) {\n const content = await readFile(opts.dataFile, 'utf-8')\n let parsed: unknown\n try {\n parsed = JSON.parse(content)\n } catch {\n throw new Error('Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.')\n }\n const { extractGraphFromJson } = await import('./data-extractor.js')\n rawData = extractGraphFromJson(parsed)\n } else if (opts.caseId) {\n const { extractGraphFromCase } = await import('./data-extractor.js')\n const extracted = await extractGraphFromCase(opts.caseId)\n if (extracted.nodes.length === 0) {\n throw new Error('No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.')\n }\n rawData = extracted\n } else {\n throw new Error('Provide either a case ID or --data <file.json>')\n }\n\n const data = truncateGraph(rawData as Parameters<typeof truncateGraph>[0])\n\n const vizId = opts.caseId\n ? `${opts.caseId}_${Date.now()}`\n : `adhoc_${Date.now()}`\n\n const title = data.metadata.caseId\n ? `${data.metadata.caseId} - Money Flow`\n : 'Ad-hoc Visualization'\n\n const html = generateHtml(data, title)\n const htmlPath = await writeVizHtml(vizId, html, opts.caseId)\n\n return { vizId, htmlPath }\n}\n"],"mappings":";;;;;;AASA,eAAsB,sBAAsB,MAGK;CAC/C,IAAI;CAEJ,IAAI,KAAK,UAAU;EACjB,MAAM,UAAU,MAAM,SAAS,KAAK,UAAU,OAAO;EACrD,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,OAAO;EAC7B,QAAQ;GACN,MAAM,IAAI,MAAM,kIAAkI;EACpJ;EACA,MAAM,EAAE,yBAAyB,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,CAAA;EAC9C,UAAU,qBAAqB,MAAM;CACvC,OAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,EAAE,yBAAyB,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,CAAA;EAC9C,MAAM,YAAY,MAAM,qBAAqB,KAAK,MAAM;EACxD,IAAI,UAAU,MAAM,WAAW,GAC7B,MAAM,IAAI,MAAM,6LAA6L;EAE/M,UAAU;CACZ,OACE,MAAM,IAAI,MAAM,gDAAgD;CAGlE,MAAM,OAAO,cAAc,OAA8C;CAEzE,MAAM,QAAQ,KAAK,SACf,GAAG,KAAK,OAAO,GAAG,KAAK,IAAI,MAC3B,SAAS,KAAK,IAAI;CAStB,OAAO;EAAE;EAAO,UAAA,MAFO,aAAa,OADvB,aAAa,MAJZ,KAAK,SAAS,SACxB,GAAG,KAAK,SAAS,OAAO,iBACxB,sBAG0C,GAAG,KAAK,MAAM;CAEnC;AAC3B"}
|
package/docs/graph-tools.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
This document covers the graph-facing tools and the result contracts that
|
|
4
4
|
agents should rely on during investigations.
|
|
5
5
|
|
|
6
|
+
Chain Insights workspaces are Obsidian-compatible vaults. Graph tools write
|
|
7
|
+
case evidence and report pointers into the workspace; use live vault refresh for
|
|
8
|
+
local review and exports only for sharing, partner handoff, LLM Wiki ingestion,
|
|
9
|
+
or archive.
|
|
10
|
+
|
|
6
11
|
## Graph MCP Surface
|
|
7
12
|
|
|
8
13
|
The GraphRAG MCP public graph surface is intentionally small:
|
|
@@ -29,11 +34,15 @@ GraphRAG MCP endpoint.
|
|
|
29
34
|
remaining free quota.
|
|
30
35
|
- Hosted endpoints can expose a public free graph_query quota. The default is
|
|
31
36
|
10 execution seconds per IP per UTC day.
|
|
37
|
+
- Prepared wallet users receive the daily public grant first; after it is used,
|
|
38
|
+
x402 payment continues automatically from the configured wallet.
|
|
32
39
|
- Use explicit LIMIT and pagination in your query when you want bounded result
|
|
33
40
|
sets.
|
|
34
41
|
- The GraphRAG MCP server does not append `LIMIT`; Chain Insights recipes own
|
|
35
42
|
their own limits and pagination.
|
|
36
|
-
- Use `
|
|
43
|
+
- Use single bounded `graph_query` calls for public no-wallet demos. Use
|
|
44
|
+
`graph_query_batch` for related reads that should share one paid call; public
|
|
45
|
+
free access does not include batches.
|
|
37
46
|
- `per_query_timeout_seconds` is optional and capped at `10` by default.
|
|
38
47
|
- Returned rows live in `structuredContent.facts`.
|
|
39
48
|
|
|
@@ -69,6 +78,10 @@ chain-insights mcp call graph_query_batch \
|
|
|
69
78
|
'queries=[{"id":"count","query":"USE live_topology MATCH (n) RETURN count(n) AS count LIMIT 1"},{"id":"archive_flows","query":"USE archive_topology MATCH (src:Address)-[f:FLOWS_TO]->(dst:Address) RETURN f.period_granularity AS granularity, src.address AS source, dst.address AS target LIMIT 3"}]'
|
|
70
79
|
```
|
|
71
80
|
|
|
81
|
+
Batch calls reserve worst-case execution time from their timeout settings. On
|
|
82
|
+
public hosted endpoints, they can ask for paid x402 access even when a small
|
|
83
|
+
free balance remains.
|
|
84
|
+
|
|
72
85
|
Batch result facts include:
|
|
73
86
|
|
|
74
87
|
```json
|
|
@@ -319,13 +332,16 @@ HTML, CSV or table files, and Markdown reports.
|
|
|
319
332
|
Evidence Markdown should be a provenance record with key facts and pointers.
|
|
320
333
|
Large JSON belongs under workspace report directories, not inline in evidence.
|
|
321
334
|
|
|
322
|
-
After a case has useful evidence, run
|
|
323
|
-
`cia case
|
|
324
|
-
|
|
325
|
-
`
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
335
|
+
After a case has useful evidence, run
|
|
336
|
+
`cia case vault refresh <case-id> --force` for live Obsidian-compatible notes in
|
|
337
|
+
the workspace. When you need to share, hand off to a partner, ingest into LLM
|
|
338
|
+
Wiki, or archive a checkpoint, run `cia case evidence verify <case-id>` and
|
|
339
|
+
`cia case export <case-id> --target obsidian-llmwiki --mode private`. The export
|
|
340
|
+
uses `manifest.chain-insights.json`, `graph.chain-insights.json`,
|
|
341
|
+
`Graph.canvas`, Markdown evidence/entity notes, and agent prompt files as views
|
|
342
|
+
over the canonical case evidence and report artifacts. Vault workflow guidance
|
|
343
|
+
lives in [Obsidian vault workflow](obsidian-vault.md); export and ingestion
|
|
344
|
+
steps live in [Knowledge exports](knowledge-exports.md).
|
|
329
345
|
|
|
330
346
|
## Runtime Schema Capture
|
|
331
347
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Investigation Workspaces
|
|
2
2
|
|
|
3
|
-
An investigation workspace is a normal directory
|
|
4
|
-
case state, evidence, reports, graph
|
|
5
|
-
schema notes. Agents and humans
|
|
6
|
-
editor.
|
|
3
|
+
An investigation workspace is a normal directory and Obsidian-compatible vault
|
|
4
|
+
that contains Chain Insights case state, live notes, evidence, reports, graph
|
|
5
|
+
payloads, imports, templates, and runtime schema notes. Agents and humans
|
|
6
|
+
should be able to inspect it with a normal editor.
|
|
7
7
|
|
|
8
8
|
## Initialize
|
|
9
9
|
|
|
@@ -24,12 +24,16 @@ workspace files before it creates directories or writes any files.
|
|
|
24
24
|
.chain-insights/schema/ Runtime graph schema captures
|
|
25
25
|
.chain-insights/runtime/ Workspace-local runtime state and debug logs
|
|
26
26
|
.chain-insights/runtime-skill/ Workspace-specific agent schema notes
|
|
27
|
+
.obsidian/ Obsidian vault settings
|
|
28
|
+
Canvases/ Obsidian Canvas graph review files
|
|
29
|
+
Entities/ Live entity notes and indexes
|
|
30
|
+
Evidence/ Live evidence notes and indexes
|
|
27
31
|
cases/ Case exports and notes
|
|
28
32
|
imports/ External investigation inputs
|
|
29
33
|
reports/ Final or interim analyst reports
|
|
30
34
|
reports/graphs/ Canonical graph JSON for visualization
|
|
31
35
|
reports/tables/ Compact tabular extracts
|
|
32
|
-
published/ Obsidian,
|
|
36
|
+
published/ Shareable Obsidian, LLM Wiki, and agent export bundles
|
|
33
37
|
templates/ Reusable case and report templates
|
|
34
38
|
```
|
|
35
39
|
|
|
@@ -78,6 +82,20 @@ cia case close <case-id>
|
|
|
78
82
|
Use `cia case show <case-id>` to show saved case context, evidence count,
|
|
79
83
|
dossier summaries, and recent session notes.
|
|
80
84
|
|
|
85
|
+
Refresh live Obsidian vault notes after case evidence, dossiers, or sessions
|
|
86
|
+
change:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cia case vault refresh <case-id>
|
|
90
|
+
cia case vault refresh <case-id> --force
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Open the workspace in Obsidian:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cia obsidian open .
|
|
97
|
+
```
|
|
98
|
+
|
|
81
99
|
## Evidence
|
|
82
100
|
|
|
83
101
|
Append evidence:
|
|
@@ -105,19 +123,27 @@ original query parameters, compact facts, and workspace-local file pointers.
|
|
|
105
123
|
|
|
106
124
|
## Knowledge Exports
|
|
107
125
|
|
|
108
|
-
|
|
126
|
+
For normal local review, use the live vault notes:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
cia case vault refresh <case-id> --force
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Export a verified case bundle only when sharing, handing off to a partner,
|
|
133
|
+
ingesting into LLM Wiki, or archiving a checkpoint:
|
|
109
134
|
|
|
110
135
|
```bash
|
|
111
136
|
cia case evidence verify <case-id>
|
|
112
137
|
cia case export <case-id> --target obsidian-llmwiki --mode private
|
|
113
138
|
```
|
|
114
139
|
|
|
115
|
-
Open `published/<case-slug>/` as
|
|
116
|
-
Codex, Claude Code, or ChatGPT as the case context. The case manifest
|
|
117
|
-
the source of truth.
|
|
140
|
+
Open `published/<case-slug>/` as a portable Obsidian vault, or give it to LLM
|
|
141
|
+
Wiki, Codex, Claude Code, or ChatGPT as the case context. The case manifest
|
|
142
|
+
remains the source of truth for the export.
|
|
118
143
|
|
|
119
144
|
For install commands, Obsidian opening steps, LLM Wiki ingestion, and agent
|
|
120
|
-
prompts, see [
|
|
145
|
+
prompts, see [Obsidian vault workflow](obsidian-vault.md) and
|
|
146
|
+
[Knowledge exports](knowledge-exports.md).
|
|
121
147
|
|
|
122
148
|
## Dossiers
|
|
123
149
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Knowledge Exports
|
|
2
2
|
|
|
3
|
-
Chain Insights
|
|
4
|
-
|
|
3
|
+
Chain Insights workspaces are Obsidian-compatible vaults. Normal local
|
|
4
|
+
investigation work happens in the workspace; exports are portable, redacted,
|
|
5
|
+
and shareable bundles for partner handoff, LLM Wiki ingestion, and archive.
|
|
6
|
+
|
|
7
|
+
Chain Insights can export a verified case as a local knowledge bundle for LLM
|
|
8
|
+
Wiki, Codex, Claude Code, ChatGPT, Obsidian, and any agent that can read a
|
|
5
9
|
folder of Markdown and JSON files.
|
|
6
10
|
|
|
7
11
|
The export is local-first. Chain Insights writes files under your initialized
|
package/docs/mcp-proxy.md
CHANGED
|
@@ -4,6 +4,11 @@ The Chain Insights stdio proxy lets AI agents consume Chain Insights tools as
|
|
|
4
4
|
an MCP server. It connects to the configured GraphRAG MCP endpoint and adds
|
|
5
5
|
local wallet, case, evidence, and graph-report behavior.
|
|
6
6
|
|
|
7
|
+
Chain Insights workspaces are Obsidian-compatible vaults. Use live vault
|
|
8
|
+
refresh and local notes for normal review; use `case_export` only when an agent
|
|
9
|
+
needs a handoff bundle for sharing, partner review, LLM Wiki ingestion, or
|
|
10
|
+
archive.
|
|
11
|
+
|
|
7
12
|
## Basic Configuration
|
|
8
13
|
|
|
9
14
|
Use this MCP server configuration:
|
|
@@ -88,15 +93,21 @@ The proxy:
|
|
|
88
93
|
| `case_resume` | Load case context, evidence count, dossiers, and latest session |
|
|
89
94
|
| `case_add_evidence` | Append a report or note to the evidence manifest |
|
|
90
95
|
| `case_verify_evidence` | Verify saved evidence integrity |
|
|
91
|
-
| `case_export` | Export a case for Obsidian,
|
|
96
|
+
| `case_export` | Export a case for Obsidian, LLM Wiki, Codex, Claude Code, and ChatGPT |
|
|
92
97
|
| `case_update_dossier` | Add a durable finding to an address/entity dossier |
|
|
93
98
|
| `case_start_session` | Start an investigation session |
|
|
94
99
|
| `case_end_session` | End a session with findings and next steps |
|
|
95
100
|
|
|
101
|
+
For normal local review, refresh live vault notes from the CLI with
|
|
102
|
+
`cia case vault refresh <case-id> --force` and inspect the same workspace in
|
|
103
|
+
Obsidian, VS Code, Codex, or Claude Code.
|
|
104
|
+
|
|
96
105
|
`case_export` writes the same local bundle as `cia case export`. Use it after
|
|
97
106
|
`case_verify_evidence` when an agent needs Obsidian, LLM Wiki, Codex, Claude
|
|
98
|
-
Code, or ChatGPT-ready files
|
|
99
|
-
|
|
107
|
+
Code, or ChatGPT-ready files for handoff. See
|
|
108
|
+
[Obsidian vault workflow](obsidian-vault.md) and
|
|
109
|
+
[Knowledge exports](knowledge-exports.md) for vault opening, sharing, and LLM
|
|
110
|
+
Wiki ingestion steps.
|
|
100
111
|
|
|
101
112
|
Remote graph tools are discovered from the configured GraphRAG MCP endpoint. The
|
|
102
113
|
expected primitive graph tools are `usage_status`, `graph_query`, and
|
|
@@ -141,10 +152,21 @@ chain-insights mcp call graph_query \
|
|
|
141
152
|
Hosted GraphRAG MCP can allow anonymous `graph_query` calls before wallet
|
|
142
153
|
setup. The default public free graph_query quota is 10 execution seconds per IP
|
|
143
154
|
per UTC day, reset on the UTC calendar day. `usage_status` returns only the
|
|
144
|
-
current caller's quota status.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
current caller's quota status. Wallet users receive the same daily public grant
|
|
156
|
+
first; after it is exhausted, x402 payment continues automatically when
|
|
157
|
+
`wallet ready` reports ready.
|
|
158
|
+
|
|
159
|
+
Public free access is intended for look-and-feel demos with bounded single
|
|
160
|
+
`graph_query` calls. Public free access does not include `graph_query_batch`;
|
|
161
|
+
use a tester access key or paid x402 mode for regular usage and batches. Use
|
|
162
|
+
explicit LIMIT and pagination in your query when you want bounded result sets.
|
|
163
|
+
|
|
164
|
+
Staging UAT on 2026-05-31 showed the 10-second grant was enough for exact
|
|
165
|
+
Bittensor address checks, sample address reads, sample flow reads, and the
|
|
166
|
+
free-to-paid handoff. The tested address
|
|
167
|
+
`5EkTMF1noWnWupGxQqtPczW2FFB7ktdVwjaZ22Cam54U93Xx` returned no indexed live or
|
|
168
|
+
archive rows on staging, but bounded sample reads still returned Bittensor
|
|
169
|
+
topology data inside the same daily grant.
|
|
148
170
|
|
|
149
171
|
For custom graph reads, install the shipped `chain-insights-cypher` skill. Its
|
|
150
172
|
Memgraph examples reference distinguishes staging-tested GraphRAG MCP query
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Obsidian Vault Workflow
|
|
2
|
+
|
|
3
|
+
Chain Insights workspaces are Obsidian-compatible investigation vaults.
|
|
4
|
+
Obsidian is the first-class human review UI, while Chain Insights case files,
|
|
5
|
+
evidence manifests, reports, graph JSON, and exports remain plain local files.
|
|
6
|
+
Obsidian plugin is not required.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Install Chain Insights:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g chain-insights
|
|
14
|
+
cia --version
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Install Obsidian when you want the vault UI:
|
|
18
|
+
|
|
19
|
+
- Windows and macOS: download the installer from
|
|
20
|
+
<https://obsidian.md/download> and open it.
|
|
21
|
+
- Linux AppImage: download the AppImage from <https://obsidian.md/download>,
|
|
22
|
+
then run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
chmod u+x Obsidian-<version>.AppImage
|
|
26
|
+
./Obsidian-<version>.AppImage --no-sandbox
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Linux Flatpak:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
flatpak install flathub md.obsidian.Obsidian
|
|
33
|
+
flatpak run md.obsidian.Obsidian
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Create An Investigation Vault
|
|
37
|
+
|
|
38
|
+
Create a normal local folder and initialize it:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
mkdir -p ~/work/chain-insights-investigations
|
|
42
|
+
cd ~/work/chain-insights-investigations
|
|
43
|
+
cia init .
|
|
44
|
+
cia obsidian open .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If the app association is not available, open Obsidian manually, choose
|
|
48
|
+
**Open folder as vault**, and select the initialized workspace folder.
|
|
49
|
+
|
|
50
|
+
The workspace includes Chain Insights runtime metadata, case state, generated
|
|
51
|
+
vault notes, Obsidian settings, evidence, entities, canvases, reports, and
|
|
52
|
+
published handoff bundles. The files remain readable in any editor.
|
|
53
|
+
|
|
54
|
+
## Work A Case
|
|
55
|
+
|
|
56
|
+
Open a case:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cia case open "Exchange deposit clustering" \
|
|
60
|
+
--tags aml,bittensor \
|
|
61
|
+
--description "Trace source funds into exchange entities"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Run investigation tools with `--case` so evidence and report pointers attach to
|
|
65
|
+
the case:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cia mcp trace-victim-funds \
|
|
69
|
+
--network bittensor \
|
|
70
|
+
--victim-addresses 5GTjfJaLpBNrgybhY24NqhDnKW9r94z72RSYLxeodxJfSkj5 \
|
|
71
|
+
--case 1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Refresh live vault notes whenever case evidence, dossiers, or sessions change:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cia case vault refresh 1 --force
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use Obsidian for review of `Home.md`, case notes, entity notes, evidence notes,
|
|
81
|
+
and canvases. Use the CLI or MCP tools for live GraphRAG MCP reads and evidence
|
|
82
|
+
capture.
|
|
83
|
+
|
|
84
|
+
## VS Code And Agent Operator Setup
|
|
85
|
+
|
|
86
|
+
Open the same folder in VS Code, Codex, Claude Code, or another local agent.
|
|
87
|
+
The agent and Obsidian should operate over the same workspace path:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
code .
|
|
91
|
+
codex
|
|
92
|
+
claude
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Agents should read the workspace notes, case files, evidence manifests, graph
|
|
96
|
+
JSON, and report pointers before running new tools. When a result changes the
|
|
97
|
+
case, refresh the vault notes again:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
cia case vault refresh 1 --force
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## LLM Wiki Overlay
|
|
104
|
+
|
|
105
|
+
LLM Wiki is an optional overlay for agent-native retrieval and compiled case
|
|
106
|
+
knowledge. Use the live workspace for normal local work, then export a selected
|
|
107
|
+
case when you need an ingestion bundle:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
cia case evidence verify 1
|
|
111
|
+
cia case export 1 --target obsidian-llmwiki --mode private
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The exported `published/<case-slug>/LLMWIKI.md`,
|
|
115
|
+
`manifest.chain-insights.json`, `graph.chain-insights.json`, and Markdown notes
|
|
116
|
+
can be ingested into an LLM Wiki topic while the original workspace remains the
|
|
117
|
+
auditable source.
|
|
118
|
+
|
|
119
|
+
## Sharing
|
|
120
|
+
|
|
121
|
+
Normal local investigation work stays in the initialized vault. Export only
|
|
122
|
+
when you need sharing, partner handoff, LLM Wiki ingestion, or archive.
|
|
123
|
+
|
|
124
|
+
Use `private` only inside the investigation team. Use `partner` for controlled
|
|
125
|
+
handoff after review. Use `public` for demos and public writeups where address
|
|
126
|
+
aliasing and redaction metadata are required.
|
|
127
|
+
|
|
128
|
+
Do not share `.chain-insights/` runtime state, local credentials, wallet
|
|
129
|
+
material, debug tokens, or unreviewed private notes. Review generated bundles
|
|
130
|
+
before sending them outside the team.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chain-insights",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "AML investigation CLI and MCP proxy for blockchain risk, fund-flow tracing, case reports, and GraphRAG MCP access",
|
|
5
5
|
"homepage": "https://chain-insights.ai",
|
|
6
6
|
"repository": {
|
|
@@ -96,14 +96,14 @@ Every new AML tool needs:
|
|
|
96
96
|
`cia case export` and MCP `case_export` are developer-experience surfaces, not
|
|
97
97
|
new graph primitives. Keep CLI and MCP adapters thin over the shared export
|
|
98
98
|
service, and keep `manifest.chain-insights.json`, case evidence, graph reports,
|
|
99
|
-
and source report artifacts canonical. Obsidian,
|
|
99
|
+
and source report artifacts canonical. Obsidian, LLM Wiki, Codex, Claude Code,
|
|
100
100
|
and ChatGPT files are generated views.
|
|
101
101
|
|
|
102
102
|
Case export changes need:
|
|
103
103
|
|
|
104
104
|
- CLI help and MCP tool descriptions.
|
|
105
105
|
- Private, partner, and public redaction behavior.
|
|
106
|
-
- Obsidian/
|
|
106
|
+
- Obsidian/LLM Wiki/agent artifact tests.
|
|
107
107
|
- Clean-workspace dogfood that opens a case, verifies evidence, exports it,
|
|
108
108
|
and inspects `manifest.chain-insights.json`, `Graph.canvas`, and
|
|
109
109
|
`graph.chain-insights.json`.
|
|
@@ -278,7 +278,7 @@ cia case export <case> --target obsidian-llmwiki --mode private
|
|
|
278
278
|
```
|
|
279
279
|
|
|
280
280
|
Treat `manifest.chain-insights.json`, case evidence, and report artifacts as
|
|
281
|
-
canonical. Treat Obsidian,
|
|
281
|
+
canonical. Treat Obsidian, LLM Wiki, Codex, Claude Code, and ChatGPT notes as
|
|
282
282
|
views over that evidence.
|
|
283
283
|
|
|
284
284
|
## When Asked "What Next?"
|
package/dist/cases-Cp9DUbEV.mjs
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import "./frontmatter-D0ccQnUM.mjs";
|
|
2
|
-
import { t as DossierStore } from "./dossier-Bjpcbcxa.mjs";
|
|
3
|
-
import { t as CaseStore } from "./store-CTtqQtaE.mjs";
|
|
4
|
-
import { t as EvidenceStore } from "./evidence-D96PTzOQ.mjs";
|
|
5
|
-
import { t as SessionStore } from "./session-DROyhebe.mjs";
|
|
6
|
-
export { CaseStore, DossierStore, EvidenceStore, SessionStore };
|
package/dist/cases-sTY5aXav.cjs
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
require("./frontmatter-Dvqa5HX6.cjs");
|
|
2
|
-
const require_dossier = require("./dossier-BXy57V4-.cjs");
|
|
3
|
-
const require_store = require("./store-CqPfs47P.cjs");
|
|
4
|
-
const require_evidence = require("./evidence-CvEesemA.cjs");
|
|
5
|
-
const require_session = require("./session-BT7VpbAd.cjs");
|
|
6
|
-
exports.CaseStore = require_store.CaseStore;
|
|
7
|
-
exports.DossierStore = require_dossier.DossierStore;
|
|
8
|
-
exports.EvidenceStore = require_evidence.EvidenceStore;
|
|
9
|
-
exports.SessionStore = require_session.SessionStore;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"export-BqTCO9lP.mjs","names":[],"sources":["../src/export/paths.ts","../src/export/schema.ts","../src/export/canvas.ts","../src/export/graph.ts","../src/export/markdown.ts","../src/export/redaction.ts","../src/export/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { lstat, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport function safeSlug(value: string): string {\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80)\n return slug || 'case-export'\n}\n\nexport function safeFilename(value: string): string {\n const parsed = path.parse(value)\n const name = safeSlug(parsed.name)\n const ext = parsed.ext.toLowerCase().replace(/[^.a-z0-9]/g, '')\n return `${name}${ext || '.md'}`\n}\n\nexport function assertInsideDirectory(root: string, candidate: string): void {\n const resolvedRoot = path.resolve(root)\n const resolvedCandidate = path.resolve(candidate)\n const relative = path.relative(resolvedRoot, resolvedCandidate)\n if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) return\n throw new Error(`Refusing to write outside export directory: ${candidate}`)\n}\n\nexport async function assertNoSymlink(filePath: string): Promise<void> {\n try {\n const stat = await lstat(filePath)\n if (stat.isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${filePath}`)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return\n throw err\n }\n}\n\nexport async function writePrivateFile(\n root: string,\n relativePath: string,\n content: string,\n): Promise<{ path: string; sha256: string; bytes: number }> {\n const filePath = path.join(root, relativePath)\n assertInsideDirectory(root, filePath)\n await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 })\n await assertNoSymlink(filePath)\n await writeFile(filePath, content, { mode: 0o600 })\n const bytes = Buffer.byteLength(content, 'utf8')\n const sha256 = createHash('sha256').update(content).digest('hex')\n return { path: relativePath, sha256, bytes }\n}\n","import * as z from 'zod'\n\nexport const CaseExportTargetSchema = z.enum(['obsidian-llmwiki'])\nexport type CaseExportTarget = z.infer<typeof CaseExportTargetSchema>\n\nexport const CaseExportModeSchema = z.enum(['private', 'partner', 'public'])\nexport type CaseExportMode = z.infer<typeof CaseExportModeSchema>\n\nconst caseIdRegex = /^\\d{8}_\\d{3}_[a-z0-9][a-z0-9-]*$/\n\nexport const CaseExportOptionsSchema = z.object({\n caseId: z.string().regex(caseIdRegex),\n target: CaseExportTargetSchema.default('obsidian-llmwiki'),\n mode: CaseExportModeSchema.default('private'),\n outputDir: z.string().optional(),\n})\nexport type CaseExportOptions = z.infer<typeof CaseExportOptionsSchema>\n\nexport const ExportedFileSchema = z.object({\n path: z.string().min(1),\n sha256: z.string().regex(/^[a-f0-9]{64}$/),\n bytes: z.number().int().nonnegative(),\n})\nexport type ExportedFile = z.infer<typeof ExportedFileSchema>\n\nexport const CaseExportManifestSchema = z.object({\n schema: z.literal('chain-insights.case_export.v1'),\n case_id: z.string().regex(caseIdRegex),\n case_name: z.string().min(1),\n exported_at: z.string().datetime(),\n mode: CaseExportModeSchema,\n target: CaseExportTargetSchema,\n source_workspace: z.string().min(1),\n verification: z.object({\n evidence_manifest_verified: z.boolean(),\n verified_at: z.string().datetime(),\n evidence_count: z.number().int().nonnegative(),\n }),\n files: z.array(ExportedFileSchema),\n redactions: z.array(z.string()),\n warnings: z.array(z.string()),\n})\nexport type CaseExportManifest = z.infer<typeof CaseExportManifestSchema>\n\nexport const JsonCanvasNodeSchema = z.object({\n id: z.string().min(1),\n type: z.enum(['text', 'file', 'link', 'group']),\n x: z.number(),\n y: z.number(),\n width: z.number().positive(),\n height: z.number().positive(),\n text: z.string().optional(),\n file: z.string().optional(),\n url: z.string().optional(),\n label: z.string().optional(),\n color: z.string().optional(),\n})\n\nexport const JsonCanvasEdgeSchema = z.object({\n id: z.string().min(1),\n fromNode: z.string().min(1),\n toNode: z.string().min(1),\n fromSide: z.enum(['top', 'right', 'bottom', 'left']).optional(),\n toSide: z.enum(['top', 'right', 'bottom', 'left']).optional(),\n toEnd: z.enum(['none', 'arrow']).optional(),\n label: z.string().optional(),\n color: z.string().optional(),\n})\n\nexport const JsonCanvasSchema = z.object({\n nodes: z.array(JsonCanvasNodeSchema),\n edges: z.array(JsonCanvasEdgeSchema),\n})\nexport type JsonCanvas = z.infer<typeof JsonCanvasSchema>\n\nexport type CaseExportResult = {\n manifestPath: string\n outputDir: string\n fileCount: number\n warnings: string[]\n nextFile: string\n}\n","import { safeFilename } from './paths.js'\nimport { JsonCanvasSchema, type JsonCanvas } from './schema.js'\n\nfunction roleColor(roles: string[]): string {\n if (roles.includes('victim')) return '1'\n if (roles.includes('suspect') || roles.includes('scam_candidate')) return '2'\n if (roles.includes('deposit')) return '3'\n if (roles.includes('exchange')) return '5'\n if (roles.includes('service')) return '6'\n return '#808080'\n}\n\nfunction nodeRoles(node: Record<string, unknown>): string[] {\n return Array.isArray(node['roles']) ? node['roles'].map(String) : []\n}\n\nfunction nodeLabel(node: Record<string, unknown>): string {\n return String(node['address'] ?? node['id'] ?? 'unknown')\n}\n\nexport function graphNodeId(node: Record<string, unknown>, index: number): string {\n return String(node['id'] ?? node['address'] ?? `node-${index + 1}`)\n}\n\nexport function entityNotePath(entityId: string): string {\n return `Entities/${safeFilename(entityId)}`\n}\n\nexport function graphToCanvas(graph: { nodes: Record<string, unknown>[]; edges: Record<string, unknown>[] }): JsonCanvas {\n const nodes = [\n {\n id: 'case',\n type: 'file' as const,\n file: 'Case.md',\n x: 0,\n y: 0,\n width: 360,\n height: 120,\n color: '4',\n },\n ]\n\n const nodeIdMap = new Map<string, string>()\n graph.nodes.forEach((node, index) => {\n const rawId = graphNodeId(node, index)\n const canvasId = `entity-${index + 1}`\n nodeIdMap.set(rawId, canvasId)\n nodes.push({\n id: canvasId,\n type: 'file' as const,\n file: entityNotePath(rawId),\n x: 420 + (index % 4) * 340,\n y: Math.floor(index / 4) * 220,\n width: 300,\n height: 120,\n color: roleColor(nodeRoles(node)),\n })\n })\n\n const edges = graph.edges.flatMap((edge, index) => {\n const from = nodeIdMap.get(String(edge['source'] ?? ''))\n const to = nodeIdMap.get(String(edge['target'] ?? ''))\n if (!from || !to) return []\n return [{\n id: `edge-${index + 1}`,\n fromNode: from,\n toNode: to,\n fromSide: 'right' as const,\n toSide: 'left' as const,\n toEnd: 'arrow' as const,\n label: String(edge['edge_type'] ?? 'related_to'),\n }]\n })\n\n for (const [index, node] of graph.nodes.entries()) {\n edges.push({\n id: `case-link-${index + 1}`,\n fromNode: 'case',\n toNode: `entity-${index + 1}`,\n fromSide: 'right' as const,\n toSide: 'left' as const,\n toEnd: 'arrow' as const,\n label: nodeLabel(node),\n })\n }\n\n return JsonCanvasSchema.parse({ nodes, edges })\n}\n","import { readFile, readdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { normalizeGraphPayload, type NormalizedGraphPayload } from '../viz/graph-normalizer.js'\nimport { extractGraphFromCase } from '../viz/data-extractor.js'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction nodeId(node: Record<string, unknown>): string {\n return String(node['id'] ?? node['address'] ?? '')\n}\n\nfunction edgeKey(edge: Record<string, unknown>): string {\n return `${String(edge['source'] ?? '')}->${String(edge['target'] ?? '')}:${String(edge['edge_type'] ?? 'related_to')}`\n}\n\nfunction mergeGraphs(graphs: NormalizedGraphPayload[]): NormalizedGraphPayload {\n const nodes = new Map<string, Record<string, unknown>>()\n const edges = new Map<string, Record<string, unknown>>()\n for (const graph of graphs) {\n for (const rawNode of graph.nodes) {\n const id = nodeId(rawNode)\n if (id) nodes.set(id, { ...(nodes.get(id) ?? {}), ...rawNode, id })\n }\n for (const rawEdge of graph.edges) {\n if (typeof rawEdge['source'] !== 'string' || typeof rawEdge['target'] !== 'string') continue\n edges.set(edgeKey(rawEdge), { ...(edges.get(edgeKey(rawEdge)) ?? {}), ...rawEdge })\n }\n }\n return {\n schema: 'chain-insights.graph.v1',\n nodes: [...nodes.values()],\n edges: [...edges.values()],\n flows: graphs.flatMap(graph => graph.flows),\n edge_anchors: graphs.flatMap(graph => graph.edge_anchors),\n metadata: {\n source: 'case-export',\n graph_count: graphs.length,\n generated_at: new Date().toISOString(),\n },\n }\n}\n\nexport async function loadCaseExportGraph(caseId: string): Promise<NormalizedGraphPayload> {\n const paths = workspaceOutputPaths()\n const files = await readdir(paths.reportGraphsRoot).catch(() => [])\n const graphs: NormalizedGraphPayload[] = []\n for (const file of files.filter(name => name.endsWith('.graph.json')).sort()) {\n const parsed = JSON.parse(await readFile(path.join(paths.reportGraphsRoot, file), 'utf8')) as unknown\n if (isRecord(parsed) && parsed['schema'] === 'chain-insights.graph.v1') {\n graphs.push(normalizeGraphPayload(parsed))\n }\n }\n if (graphs.length > 0) return mergeGraphs(graphs)\n\n const fallback = await extractGraphFromCase(caseId)\n return normalizeGraphPayload({\n schema: 'chain-insights.graph.v1',\n nodes: fallback.nodes,\n edges: fallback.edges,\n flows: [],\n edge_anchors: [],\n metadata: fallback.metadata,\n })\n}\n","import type { CaseExportMode } from './schema.js'\n\nexport type MarkdownCase = {\n id: string\n name: string\n status: string\n tags: string[]\n description?: string\n}\n\nexport function frontmatter(values: Record<string, unknown>): string {\n const lines = ['---']\n for (const [key, value] of Object.entries(values)) {\n if (Array.isArray(value)) {\n lines.push(`${key}:`)\n for (const item of value) lines.push(` - ${JSON.stringify(String(item))}`)\n } else {\n lines.push(`${key}: ${JSON.stringify(value)}`)\n }\n }\n lines.push('---', '')\n return lines.join('\\n')\n}\n\nexport function renderReadme(caseName: string): string {\n return [\n `# ${caseName} Export`,\n '',\n 'Open this directory as an Obsidian vault or give it to an LLMWiki-style knowledge workflow.',\n '',\n 'Start with:',\n '',\n '- `Case.md`',\n '- `Agent Console.md`',\n '- `LLMWIKI.md`',\n '- `graph.chain-insights.json` when present',\n '',\n ].join('\\n')\n}\n\nexport function renderCaseMarkdown(input: {\n caseInfo: MarkdownCase\n mode: CaseExportMode\n evidenceVerified: boolean\n evidenceCount: number\n entityCount: number\n}): string {\n return frontmatter({\n type: 'chain-insights-case',\n case_id: input.caseInfo.id,\n status: input.caseInfo.status,\n tags: input.caseInfo.tags,\n contains_sensitive_data: input.mode !== 'public',\n }) + [\n `# ${input.caseInfo.name}`,\n '',\n `Case ID: \\`${input.caseInfo.id}\\``,\n `Status: ${input.caseInfo.status}`,\n `Evidence manifest: ${input.evidenceVerified ? 'verified' : 'failed'}`,\n `Evidence files: ${input.evidenceCount}`,\n `Entities: ${input.entityCount}`,\n '',\n '## Summary',\n '',\n input.caseInfo.description || 'No description recorded.',\n '',\n '## Start Here',\n '',\n '- [[Agent Console]]',\n '- [[LLMWIKI]]',\n '- [[Sources/evidence-manifest]]',\n '',\n ].join('\\n')\n}\n\nexport function renderAgentConsole(caseName: string): string {\n return [\n '# Agent Console',\n '',\n `Case: [[Case|${caseName}]]`,\n '',\n '## Reading Order',\n '',\n '1. [[Case]]',\n '2. [[LLMWIKI]]',\n '3. `graph.chain-insights.json`',\n '4. [[Sources/evidence-manifest]]',\n '5. Entity and evidence notes linked from the case.',\n '',\n '## Agent Prompts',\n '',\n '- [[Prompts/Codex]]',\n '- [[Prompts/Claude-Code]]',\n '- [[Prompts/ChatGPT]]',\n '',\n '## Rules',\n '',\n '- Treat Chain Insights case evidence and manifests as canonical.',\n '- Use Chain Insights tools for fresh graph facts.',\n '- Preserve full blockchain addresses exactly unless this is a public redacted export.',\n '',\n ].join('\\n')\n}\n\nexport function renderLlmWiki(): string {\n return [\n '# LLMWiki Entry',\n '',\n 'This directory is a Chain Insights case export.',\n '',\n 'Canonical machine files:',\n '',\n '- `manifest.chain-insights.json`',\n '- `graph.chain-insights.json`',\n '- `Graph.canvas`',\n '',\n 'Human and agent notes:',\n '',\n '- `Case.md`',\n '- `Agent Console.md`',\n '- `Entities/`',\n '- `Evidence/`',\n '- `Prompts/`',\n '',\n ].join('\\n')\n}\n\nexport function renderLlmsTxt(): string {\n return [\n '# Chain Insights Case Export',\n '',\n 'Read these files first:',\n '- Case.md',\n '- Agent Console.md',\n '- graph.chain-insights.json',\n '- Entities/',\n '- Evidence/',\n '',\n 'Source of truth:',\n '- manifest.chain-insights.json',\n '- Sources/evidence-manifest.md',\n '',\n ].join('\\n')\n}\n\nexport function renderPrompt(agentName: string): string {\n return [\n `# ${agentName} Case Prompt`,\n '',\n 'You are reading a Chain Insights case export.',\n '',\n 'Treat `manifest.chain-insights.json`, `Sources/evidence-manifest.md`, and original case evidence as canonical.',\n 'Use generated prose for orientation, not as a replacement for evidence.',\n 'Use Chain Insights MCP tools for fresh graph facts when available.',\n '',\n ].join('\\n')\n}\n","import type { CaseExportMode } from './schema.js'\n\nconst EVM_ADDRESS_RE = /\\b0x[a-fA-F0-9]{40}\\b/g\nconst SUBSTRATE_ADDRESS_RE = /\\b5[1-9A-HJ-NP-Za-km-z]{20,64}\\b/g\nconst SECRET_PATTERNS = [\n /\\bci_test_[A-Za-z0-9_-]+\\b/g,\n /\\b(?:privateKey|walletPrivateKey|secret|token|authorization)\\s*[:=]\\s*[\"']?[^\"'\\s]+/gi,\n /\\b0x[a-fA-F0-9]{64}\\b/g,\n]\n\nexport type Redactor = {\n text(input: string): string\n value<T>(input: T): T\n aliasFor(address: string): string\n redactions(): string[]\n}\n\nexport function createRedactor(mode: CaseExportMode): Redactor {\n const aliases = new Map<string, string>()\n const redactions = new Set<string>()\n\n function aliasFor(address: string): string {\n const existing = aliases.get(address)\n if (existing) return existing\n const alias = `addr_${String(aliases.size + 1).padStart(3, '0')}`\n aliases.set(address, alias)\n redactions.add(`aliased:${alias}`)\n return alias\n }\n\n function redactSecrets(input: string): string {\n let output = input\n for (const pattern of SECRET_PATTERNS) {\n output = output.replace(pattern, () => {\n redactions.add('secret')\n return '[redacted-secret]'\n })\n }\n return output\n }\n\n function text(input: string): string {\n let output = redactSecrets(input)\n if (mode === 'public') {\n output = output.replace(SUBSTRATE_ADDRESS_RE, match => aliasFor(match))\n output = output.replace(EVM_ADDRESS_RE, match => aliasFor(match))\n }\n return output\n }\n\n function value<T>(input: T): T {\n if (typeof input === 'string') return text(input) as T\n if (Array.isArray(input)) return input.map(item => value(item)) as T\n if (input && typeof input === 'object') {\n return Object.fromEntries(\n Object.entries(input).map(([key, entry]) => [key, value(entry)]),\n ) as T\n }\n return input\n }\n\n return {\n text,\n value,\n aliasFor,\n redactions: () => [...redactions].sort(),\n }\n}\n","import { mkdir, readFile, readdir } from 'node:fs/promises'\nimport path from 'node:path'\nimport { CaseStore, DossierStore, EvidenceStore, parseFrontmatter } from '../cases/index.js'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport { entityNotePath, graphNodeId, graphToCanvas } from './canvas.js'\nimport { loadCaseExportGraph } from './graph.js'\nimport { renderAgentConsole, renderCaseMarkdown, renderLlmsTxt, renderLlmWiki, renderPrompt, renderReadme } from './markdown.js'\nimport { safeFilename, safeSlug, writePrivateFile } from './paths.js'\nimport { createRedactor } from './redaction.js'\nimport { CaseExportManifestSchema, CaseExportOptionsSchema, type CaseExportOptions, type CaseExportResult, type ExportedFile } from './schema.js'\n\ntype EvidenceDoc = {\n id: string\n filename: string\n source: string\n timestamp: string\n body: string\n}\n\nasync function readEvidence(caseId: string): Promise<EvidenceDoc[]> {\n const paths = workspaceOutputPaths()\n const dir = path.join(paths.casesRoot, caseId, 'evidence')\n const files = await readdir(dir).catch(() => [])\n const docs: EvidenceDoc[] = []\n for (const filename of files.filter(file => file.endsWith('.md')).sort()) {\n const raw = await readFile(path.join(dir, filename), 'utf8')\n const { frontmatter, body } = parseFrontmatter(raw)\n docs.push({\n id: frontmatter['id'] || filename.replace(/\\.md$/, ''),\n filename,\n source: frontmatter['source'] || 'unknown',\n timestamp: frontmatter['timestamp'] || '',\n body,\n })\n }\n return docs\n}\n\nasync function writeFiles(root: string, entries: Array<[string, string]>): Promise<ExportedFile[]> {\n const written: ExportedFile[] = []\n for (const [relativePath, content] of entries) {\n written.push(await writePrivateFile(root, relativePath, content))\n }\n return written\n}\n\nexport async function exportCase(rawOptions: CaseExportOptions): Promise<CaseExportResult> {\n const options = CaseExportOptionsSchema.parse(rawOptions)\n const workspace = workspaceOutputPaths()\n const caseInfo = await CaseStore.get(options.caseId)\n const redactor = createRedactor(options.mode)\n const evidenceVerification = await EvidenceStore.verifyManifest(options.caseId)\n const evidenceDocs = await readEvidence(options.caseId)\n const dossiers = await DossierStore.listSummaries(options.caseId)\n const graph = redactor.value(await loadCaseExportGraph(options.caseId))\n const canvas = graphToCanvas(graph)\n const outputRoot = path.resolve(options.outputDir ?? path.join(workspace.root, 'published', safeSlug(caseInfo.name)))\n await mkdir(outputRoot, { recursive: true, mode: 0o700 })\n\n const entries: Array<[string, string]> = [\n ['README.md', renderReadme(redactor.text(caseInfo.name))],\n ['Case.md', renderCaseMarkdown({\n caseInfo: {\n id: caseInfo.id,\n name: redactor.text(caseInfo.name),\n status: caseInfo.status,\n tags: caseInfo.tags,\n description: redactor.text(caseInfo.description),\n },\n mode: options.mode,\n evidenceVerified: evidenceVerification.ok,\n evidenceCount: evidenceVerification.count,\n entityCount: dossiers.length,\n })],\n ['LLMWIKI.md', renderLlmWiki()],\n ['llms.txt', renderLlmsTxt()],\n ['Agent Console.md', renderAgentConsole(redactor.text(caseInfo.name))],\n ['Prompts/Codex.md', renderPrompt('Codex')],\n ['Prompts/Claude-Code.md', renderPrompt('Claude Code')],\n ['Prompts/ChatGPT.md', renderPrompt('ChatGPT')],\n ['Sources/evidence-manifest.md', `# Evidence Manifest\\n\\nVerified: ${evidenceVerification.ok ? 'yes' : 'no'}\\nEvidence files: ${evidenceVerification.count}\\n`],\n ['Sources/reports-index.md', '# Reports Index\\n\\nGraph and report artifacts are exported when present.\\n'],\n ['graph.chain-insights.json', JSON.stringify(graph, null, 2) + '\\n'],\n ['Graph.canvas', JSON.stringify(canvas, null, 2) + '\\n'],\n ]\n\n for (const evidence of evidenceDocs) {\n entries.push([\n path.join('Evidence', safeFilename(evidence.id)),\n redactor.text([\n `# Evidence: ${evidence.source}`,\n '',\n `Source file: \\`${evidence.filename}\\``,\n `Captured: ${evidence.timestamp || 'unknown'}`,\n '',\n evidence.body,\n '',\n ].join('\\n')),\n ])\n }\n\n const entityPaths = new Set<string>()\n for (const dossier of dossiers) {\n const entityId = options.mode === 'public' ? redactor.aliasFor(dossier.address) : dossier.address\n const entityPath = path.join('Entities', safeFilename(entityId))\n entityPaths.add(entityPath)\n entries.push([\n entityPath,\n redactor.text([\n `# Entity: ${entityId}`,\n '',\n `Type: ${dossier.type}`,\n `First seen: ${dossier.firstSeen || 'unknown'}`,\n `Last seen: ${dossier.lastSeen || 'unknown'}`,\n `Risk tags: ${dossier.riskTags || 'none'}`,\n '',\n ].join('\\n')),\n ])\n }\n\n for (const [index, node] of graph.nodes.entries()) {\n const entityId = graphNodeId(node, index)\n const entityPath = entityNotePath(entityId)\n if (entityPaths.has(entityPath)) continue\n entityPaths.add(entityPath)\n entries.push([\n entityPath,\n [\n `# Entity: ${entityId}`,\n '',\n `Address: ${String(node['address'] ?? entityId)}`,\n `Roles: ${Array.isArray(node['roles']) ? node['roles'].map(String).join(', ') || 'none' : 'none'}`,\n `Node type: ${String(node['node_type'] ?? 'unknown')}`,\n '',\n '## Graph Links',\n '',\n '- [[Graph.canvas]]',\n '',\n ].join('\\n'),\n ])\n }\n\n const files = await writeFiles(outputRoot, entries)\n const exportedAt = new Date().toISOString()\n const manifest = CaseExportManifestSchema.parse({\n schema: 'chain-insights.case_export.v1',\n case_id: caseInfo.id,\n case_name: redactor.text(caseInfo.name),\n exported_at: exportedAt,\n mode: options.mode,\n target: options.target,\n source_workspace: workspace.root,\n verification: {\n evidence_manifest_verified: evidenceVerification.ok,\n verified_at: exportedAt,\n evidence_count: evidenceVerification.count,\n },\n files,\n redactions: redactor.redactions(),\n warnings: evidenceVerification.ok ? [] : [`Evidence manifest failed: ${(evidenceVerification.tampered ?? []).join(', ')}`],\n })\n const manifestFile = await writePrivateFile(outputRoot, 'manifest.chain-insights.json', JSON.stringify(manifest, null, 2) + '\\n')\n\n return {\n manifestPath: path.join(outputRoot, manifestFile.path),\n outputDir: outputRoot,\n fileCount: files.length + 1,\n warnings: manifest.warnings,\n nextFile: 'Agent Console.md',\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init-DLBL_nVG.mjs","names":[],"sources":["../src/workspace/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { LOCAL_GRAPH_MCP_ENDPOINT } from '../config/mcp-endpoint.js'\n\nexport interface InitWorkspaceOptions {\n targetDir: string\n force?: boolean\n}\n\nexport interface InitWorkspaceResult {\n workspaceRoot: string\n filesWritten: string[]\n}\n\nconst WORKSPACE_DIRS = [\n '.chain-insights',\n '.chain-insights/schema',\n '.chain-insights/runtime',\n '.chain-insights/runtime/logs',\n '.chain-insights/runtime-skill',\n 'cases',\n 'imports',\n 'reports',\n 'reports/graphs',\n 'reports/tables',\n 'templates',\n]\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction workspaceJson(workspaceRoot: string): string {\n return JSON.stringify({\n schema: 'chain-insights.workspace.v1',\n name: 'Chain Insights Investigations',\n workspace_root: workspaceRoot,\n default_network: 'bittensor',\n graph_mcp_endpoint: LOCAL_GRAPH_MCP_ENDPOINT,\n cases_dir: 'cases',\n imports_dir: 'imports',\n reports_dir: 'reports',\n templates_dir: 'templates',\n created_at: todayIso(),\n }, null, 2) + '\\n'\n}\n\nconst README = `# Chain Insights Investigations\n\nThis is a workspace for Chain Insights AML investigations.\n\n## Start\n\n\\`\\`\\`bash\nchain-insights mcp tools --refresh\nchain-insights wallet ready --check-only\n\\`\\`\\`\n\n## Layout\n\n\\`\\`\\`text\n.chain-insights/ Workspace metadata\ncases/ Case exports and notes\nimports/ External reports, CSVs, screenshots, raw notes\nreports/ Final or interim analyst reports\nreports/graphs/ Graph JSON for visualization\nreports/tables/ Compact tabular extracts\ntemplates/ Reusable case/report templates\n.chain-insights/schema/ Runtime graph schema captures\n.chain-insights/runtime/ Workspace-local runtime process state and debug logs\n.chain-insights/runtime-skill/ Workspace-specific agent schema notes\n\\`\\`\\`\n`\n\nconst AGENTS = `# Agent Instructions\n\nYou are operating inside a Chain Insights investigation workspace.\n\n- Read README.md first.\n- If this directory is not initialized, run \\`cia init .\\` before investigation-producing commands.\n- Do not rerun init in an existing workspace unless replacing scaffolding with \\`--force\\`.\n- Read .chain-insights/runtime-skill/SKILL.md before graph queries.\n- Preserve full blockchain addresses exactly.\n- Do not guess the network for graph queries.\n- Capture or refresh graph schema before the first case query.\n- Save compact evidence with original graph field names.\n- Put canonical graph JSON in reports/graphs/ and analyst tables in reports/tables/.\n- Evidence files should summarize and point to graph/table outputs; do not paste large raw JSON blobs into evidence Markdown.\n- Investigation output must stay in this initialized workspace.\n- Never write cases, evidence, reports, graph JSON, HTML, schema captures, or logs to ~/.chain-insights.\n- Keep theories lightweight until evidence supports them.\n`\n\nconst CLAUDE = AGENTS\n\nconst CASE_BRIEF = `# Case Brief\n\n## Summary\n\nStatus:\nNetwork:\nCurrent Assessment:\n\n## Known Addresses\n\n## Claims To Validate\n\n## Evidence\n\n## Next Steps\n`\n\nconst IMPORTS_README = `# External Investigation Inputs\n\nPut user-provided or third-party investigation material here before turning it\ninto case evidence.\n\nExamples:\n\n- Exchange support exports\n- CSV extracts\n- Screenshots\n- Raw notes\n- Partner reports\n\nFiles in this directory are inputs, not verified evidence. When an import\nsupports a claim, summarize it into the case evidence manifest and reference\nthe original file path.\n`\n\nconst TEMPLATES_README = `# Reusable Workspace Templates\n\nStore local report, case, prompt, and evidence templates here.\n\nTemplates are optional workspace helpers. They are not evidence and should not\nbe treated as case state until copied into a case, evidence file, dossier, or\nreport.\n`\n\nconst RUNTIME_SKILL = `---\nname: chain-insights-runtime-schema\ndescription: Workspace-local Chain Insights runtime schema notes. Refresh this after connecting to a graph MCP endpoint.\n---\n\n# Runtime Graph Schema\n\nBefore the first investigation query, capture the live graph schema into:\n\n\\`\\`\\`text\n.chain-insights/schema/<network>.graph-schema.json\n\\`\\`\\`\n\nUse \\`graph_query_batch\\` for schema capture. Prefix current topology reads\nwith \\`USE live_topology\\`, historical topology reads with\n\\`USE archive_topology\\`, and fact reads with \\`USE facts\\`, for example:\n\n\\`\\`\\`bash\ncia mcp call graph_query_batch network=<network> 'queries=[{\"id\":\"node_labels\",\"query\":\"USE live_topology MATCH (n:Address) RETURN \\\"Address\\\" AS node_label, count(n) AS sample_count LIMIT 1\"},{\"id\":\"archive_flow_sample\",\"query\":\"USE archive_topology MATCH (:Address)-[f:FLOWS_TO]->(:Address) RETURN f.period_granularity AS granularity, f.amount_sum AS amount_sum LIMIT 20\"}]'\n\\`\\`\\`\n\nThen update this file with observed labels, relationship types, and allowed\nproperty names for the active network.\n\nRules:\n\n- Prefer \\`graph_query\\` and \\`graph_query_batch\\` for graph-language reads.\n- Use \\`USE live_topology\\` for recent topology, \\`USE archive_topology\\`\n for historical topology, and \\`USE facts\\` for labels, features,\n risk scores, assets, and enrichment. Address facts can be reached through\n relationships such as \\`(:Address)-[:HAS_FEATURE]->(:AddressFeature)\\`.\n Archived money-flow topology is exposed as\n \\`(:Address)-[:FLOWS_TO]->(:Address)\\` with \\`period_granularity\\`,\n \\`period_start_date\\`, and \\`period_end_date\\` on the relationship.\n- Preserve source schema field names in evidence and generated data files.\n- Do not rename, reinterpret, or add unit labels to graph fields unless the\n schema or query result explicitly supports that interpretation.\n- Keep evidence compact: select only the fields needed to support the claim.\n Avoid storing whole node or relationship property blobs in evidence unless\n the purpose of the query is schema discovery or debugging.\n- When using BFS, fixed-depth traversal fallbacks, or any manual \\`FLOWS_TO\\`\n traversal, treat exchange hot wallets as terminal endpoints only. Do not\n expand from, through, or classify exchange nodes as deposit, suspect, or\n intermediate candidates. In Cypher, require every non-terminal traversal node\n to satisfy \\`is_exchange IS NULL\\`; only the final exchange endpoint should\n satisfy \\`is_exchange IS NOT NULL\\`.\n- Keep analysis products separate from evidence: graph JSON belongs under\n \\`reports/graphs/\\`, tabular extracts under \\`reports/tables/\\`, and analyst\n narrative under \\`reports/\\`.\n- Evidence Markdown should be a short provenance record with key facts and\n pointers. Large JSON belongs in \\`reports/tables/\\`, not inline in evidence.\n\nTrace tool chaining:\n\n1. Use \\`trace_victim_funds\\` when the user gives victim/source addresses.\n2. Pass returned \\`continuation.candidate_deposit_addresses\\` to\n \\`trace_deposit_sources\\`; do not make victim tracing run deposit traceback\n internally.\n3. Pass high-confidence \\`continuation.candidate_suspect_addresses\\` from\n deposit traceback to \\`trace_suspect_funds\\`.\n4. Use \\`trace_suspect_funds\\` when the user gives suspected scammer, mule,\n operator, or laundering-ring addresses. \\`incident_timestamp_ms\\` is\n optional.\n5. Use \\`address_risk\\` for single-address enrichment, and\n \\`graph_query_batch\\` only when the role-specific tools do not answer the\n exact question.\n\nAll trace tools return \\`chain-insights.trace.v1\\`. Preserve full addresses in\n\\`input.addresses\\`, \\`addresses[].address\\`, \\`edges[].from_address\\`,\n\\`edges[].to_address\\`, \\`paths[].addresses\\`, \\`candidate_labels[].address\\`,\nand \\`continuation\\` address lists.\n`\n\nconst SCHEMA_README = `# Runtime Schema Captures\n\nStore graph schema captures here, for example:\n\n\\`\\`\\`text\nbittensor.graph-schema.json\n\\`\\`\\`\n\nSchema captures should be generated before the first case query in a fresh\nworkspace, then referenced by evidence, reports, and runtime skill notes.\n`\n\nfunction workspaceFiles(workspaceRoot: string): Array<[string, string]> {\n return [\n ['.chain-insights/workspace.json', workspaceJson(workspaceRoot)],\n ['README.md', README],\n ['AGENTS.md', AGENTS],\n ['CLAUDE.md', CLAUDE],\n ['imports/README.md', IMPORTS_README],\n ['templates/README.md', TEMPLATES_README],\n ['templates/case-brief.md', CASE_BRIEF],\n ['.chain-insights/runtime-skill/SKILL.md', RUNTIME_SKILL],\n ['.chain-insights/schema/README.md', SCHEMA_README],\n ['.chain-insights/runtime/.keep', ''],\n ['.chain-insights/runtime/logs/.keep', ''],\n ]\n}\n\nasync function assertNoFileCollisions(workspaceRoot: string): Promise<void> {\n for (const [relativePath] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await access(filePath)\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n continue\n }\n throw err\n }\n }\n}\n\nexport async function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult> {\n const workspaceRoot = path.resolve(options.targetDir)\n if (!options.force) {\n await assertNoFileCollisions(workspaceRoot)\n }\n\n for (const dir of WORKSPACE_DIRS) {\n await mkdir(path.join(workspaceRoot, dir), { recursive: true })\n }\n\n const filesWritten: string[] = []\n const flag = options.force ? 'w' : 'wx'\n for (const [relativePath, content] of workspaceFiles(workspaceRoot)) {\n const filePath = path.join(workspaceRoot, relativePath)\n try {\n await writeFile(filePath, content, { mode: 0o600, flag })\n filesWritten.push(relativePath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new Error(`Refusing to overwrite ${filePath}. Re-run with --force to replace workspace files.`)\n }\n throw err\n }\n }\n\n return { workspaceRoot, filesWritten }\n}\n"],"mappings":";;;;AAcA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,WAAmB;CAC1B,wBAAO,IAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAEA,SAAS,cAAc,eAA+B;CACpD,OAAO,KAAK,UAAU;EACpB,QAAQ;EACR,MAAM;EACN,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,aAAa;EACb,aAAa;EACb,eAAe;EACf,YAAY,SAAS;CACvB,GAAG,MAAM,CAAC,IAAI;AAChB;AAEA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;AA2Bf,MAAM,SAAS;;;;;;;;;;;;;;;;;;AAmBf,MAAM,SAAS;AAEf,MAAM,aAAa;;;;;;;;;;;;;;;;AAiBnB,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,MAAM,mBAAmB;;;;;;;;AASzB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEtB,MAAM,gBAAgB;;;;;;;;;;;AAYtB,SAAS,eAAe,eAAgD;CACtE,OAAO;EACL,CAAC,kCAAkC,cAAc,aAAa,CAAC;EAC/D,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,aAAa,MAAM;EACpB,CAAC,qBAAqB,cAAc;EACpC,CAAC,uBAAuB,gBAAgB;EACxC,CAAC,2BAA2B,UAAU;EACtC,CAAC,0CAA0C,aAAa;EACxD,CAAC,oCAAoC,aAAa;EAClD,CAAC,iCAAiC,EAAE;EACpC,CAAC,sCAAsC,EAAE;CAC3C;AACF;AAEA,eAAe,uBAAuB,eAAsC;CAC1E,KAAK,MAAM,CAAC,iBAAiB,eAAe,aAAa,GAAG;EAC1D,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,OAAO,QAAQ;GACrB,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;EACtG,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C;GAEF,MAAM;EACR;CACF;AACF;AAEA,eAAsB,cAAc,SAA6D;CAC/F,MAAM,gBAAgB,KAAK,QAAQ,QAAQ,SAAS;CACpD,IAAI,CAAC,QAAQ,OACX,MAAM,uBAAuB,aAAa;CAG5C,KAAK,MAAM,OAAO,gBAChB,MAAM,MAAM,KAAK,KAAK,eAAe,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CAGhE,MAAM,eAAyB,CAAC;CAChC,MAAM,OAAO,QAAQ,QAAQ,MAAM;CACnC,KAAK,MAAM,CAAC,cAAc,YAAY,eAAe,aAAa,GAAG;EACnE,MAAM,WAAW,KAAK,KAAK,eAAe,YAAY;EACtD,IAAI;GACF,MAAM,UAAU,UAAU,SAAS;IAAE,MAAM;IAAO;GAAK,CAAC;GACxD,aAAa,KAAK,YAAY;EAChC,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAC1C,MAAM,IAAI,MAAM,yBAAyB,SAAS,kDAAkD;GAEtG,MAAM;EACR;CACF;CAEA,OAAO;EAAE;EAAe;CAAa;AACvC"}
|