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.
Files changed (54) hide show
  1. package/README.md +28 -11
  2. package/dist/canvas-Cn-maEIh.mjs +203 -0
  3. package/dist/canvas-Cn-maEIh.mjs.map +1 -0
  4. package/dist/canvas-p-oKCMjc.cjs +251 -0
  5. package/dist/cases-Bz_9XKEw.cjs +19 -0
  6. package/dist/cases-TVcAifxu.mjs +16 -0
  7. package/dist/cases-TVcAifxu.mjs.map +1 -0
  8. package/dist/cli.cjs +74 -28
  9. package/dist/cli.mjs +74 -28
  10. package/dist/cli.mjs.map +1 -1
  11. package/dist/{data-extractor-DZUJu1Bz.mjs → data-extractor-B4nHw1wZ.mjs} +2 -2
  12. package/dist/{data-extractor-DZUJu1Bz.mjs.map → data-extractor-B4nHw1wZ.mjs.map} +1 -1
  13. package/dist/{data-extractor-Cavd7wHk.cjs → data-extractor-DS4rzy3M.cjs} +1 -1
  14. package/dist/{export-BqTCO9lP.mjs → export-CBhcJuZ6.mjs} +8 -205
  15. package/dist/export-CBhcJuZ6.mjs.map +1 -0
  16. package/dist/{export-DsXgtCwO.cjs → export-D4v4-6F4.cjs} +16 -214
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.mjs +1 -1
  19. package/dist/{init-DLBL_nVG.mjs → init-CKQ6F07J.mjs} +22 -5
  20. package/dist/init-CKQ6F07J.mjs.map +1 -0
  21. package/dist/{init-zqbd7i-_.cjs → init-Dhw8F23z.cjs} +21 -4
  22. package/dist/mcp-proxy.cjs +20 -20
  23. package/dist/mcp-proxy.mjs +20 -20
  24. package/dist/mcp-proxy.mjs.map +1 -1
  25. package/dist/{public-tools-wJoAFDFa.mjs → public-tools-CyUZEz9B.mjs} +3 -3
  26. package/dist/{public-tools-wJoAFDFa.mjs.map → public-tools-CyUZEz9B.mjs.map} +1 -1
  27. package/dist/{public-tools-BvMb3H2P.cjs → public-tools-xfVNz9NE.cjs} +2 -2
  28. package/dist/{runner-BhZ4lnF1.cjs → runner-CVo41fjz.cjs} +2 -2
  29. package/dist/{runner-DIJSbkjc.mjs → runner-DWuSy1Se.mjs} +3 -3
  30. package/dist/{runner-DIJSbkjc.mjs.map → runner-DWuSy1Se.mjs.map} +1 -1
  31. package/dist/{selector-CF2o5gxN.mjs → selector-BvXM9jbe.mjs} +2 -2
  32. package/dist/{selector-CF2o5gxN.mjs.map → selector-BvXM9jbe.mjs.map} +1 -1
  33. package/dist/{selector-DfAMZEC9.cjs → selector-Dps_ZFxq.cjs} +1 -1
  34. package/dist/{store-CTtqQtaE.mjs → store-C2B_AssI.mjs} +2 -2
  35. package/dist/{store-CTtqQtaE.mjs.map → store-C2B_AssI.mjs.map} +1 -1
  36. package/dist/{store-CqPfs47P.cjs → store-CQhU8dz8.cjs} +0 -18
  37. package/dist/vault-B2y78Ypu.cjs +560 -0
  38. package/dist/vault-z35Dohdq.mjs +560 -0
  39. package/dist/vault-z35Dohdq.mjs.map +1 -0
  40. package/dist/{viz-Dqp3C5kb.cjs → viz-D1620cBX.cjs} +3 -3
  41. package/dist/{viz-5y24S5X1.mjs → viz-DB5XFG1z.mjs} +4 -4
  42. package/dist/{viz-5y24S5X1.mjs.map → viz-DB5XFG1z.mjs.map} +1 -1
  43. package/docs/graph-tools.md +24 -8
  44. package/docs/investigation-workspaces.md +36 -10
  45. package/docs/knowledge-exports.md +6 -2
  46. package/docs/mcp-proxy.md +29 -7
  47. package/docs/obsidian-vault.md +130 -0
  48. package/package.json +1 -1
  49. package/skills/chain-insights-developer-experience/SKILL.md +2 -2
  50. package/skills/chain-insights-investigation/SKILL.md +1 -1
  51. package/dist/cases-Cp9DUbEV.mjs +0 -6
  52. package/dist/cases-sTY5aXav.cjs +0 -9
  53. package/dist/export-BqTCO9lP.mjs.map +0 -1
  54. package/dist/init-DLBL_nVG.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"viz-5y24S5X1.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"}
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"}
@@ -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 `graph_query_batch` for related reads that should share one paid call.
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 `cia case evidence verify <case-id>` and
323
- `cia case export <case-id> --target obsidian-llmwiki --mode private` to produce
324
- an Obsidian, LLM Wiki, and agent-friendly bundle. The export uses
325
- `manifest.chain-insights.json`, `graph.chain-insights.json`, `Graph.canvas`,
326
- Markdown evidence/entity notes, and agent prompt files as views over the
327
- canonical case evidence and report artifacts. Install and ingestion steps live
328
- in [Knowledge exports](knowledge-exports.md).
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 that contains Chain Insights
4
- case state, evidence, reports, graph payloads, imports, templates, and runtime
5
- schema notes. Agents and humans should be able to inspect it with a normal
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, LLMWiki, and agent export bundles
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
- Export a verified case bundle:
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 an Obsidian vault, or give it to LLM Wiki,
116
- Codex, Claude Code, or ChatGPT as the case context. The case manifest remains
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 [Knowledge exports](knowledge-exports.md).
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 can export a verified case as a local knowledge bundle for
4
- Obsidian, LLM Wiki, Codex, Claude Code, ChatGPT, and any agent that can read a
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, LLMWiki, Codex, Claude Code, and ChatGPT |
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. See [Knowledge exports](knowledge-exports.md) for
99
- viewer installation, vault opening, and LLM Wiki ingestion steps.
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. Public free access does not include
145
- `graph_query_batch`; use a tester access key or paid x402 mode for regular
146
- usage and batches. Use explicit LIMIT and pagination in your query when you
147
- want bounded result sets.
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.4",
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, LLMWiki, Codex, Claude Code,
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/LLMWiki/agent artifact tests.
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, LLMWiki, Codex, Claude Code, and ChatGPT notes as
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?"
@@ -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 };
@@ -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"}