chain-insights 0.2.16

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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/bin/cli.js +10 -0
  4. package/bin/install.cjs +252 -0
  5. package/bin/mcp-proxy.cjs +10 -0
  6. package/dist/active-BSrxLKwn.mjs +50 -0
  7. package/dist/active-BSrxLKwn.mjs.map +1 -0
  8. package/dist/active-Dv7Tu-O4.cjs +68 -0
  9. package/dist/app-BjjuQM0B.mjs +155 -0
  10. package/dist/app-BjjuQM0B.mjs.map +1 -0
  11. package/dist/app-Dq1TdB6p.cjs +161 -0
  12. package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
  13. package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
  14. package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
  15. package/dist/assets/bg-pattern.png +0 -0
  16. package/dist/assets/logo.png +0 -0
  17. package/dist/call-args-DQA2QcRA.cjs +27 -0
  18. package/dist/call-args-Lk_wOJxd.mjs +29 -0
  19. package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
  20. package/dist/capabilities-CB97WMA5.cjs +83 -0
  21. package/dist/capabilities-DliMBim-.mjs +84 -0
  22. package/dist/capabilities-DliMBim-.mjs.map +1 -0
  23. package/dist/cases-By7INiOa.mjs +6 -0
  24. package/dist/cases-CDcNU91B.cjs +9 -0
  25. package/dist/chunk-CZWwpsFl.cjs +43 -0
  26. package/dist/cli.cjs +752 -0
  27. package/dist/cli.d.cts +1 -0
  28. package/dist/cli.d.mts +1 -0
  29. package/dist/cli.mjs +753 -0
  30. package/dist/cli.mjs.map +1 -0
  31. package/dist/client-D4Bq0rp9.mjs +111 -0
  32. package/dist/client-D4Bq0rp9.mjs.map +1 -0
  33. package/dist/client-D4fZgIaO.cjs +132 -0
  34. package/dist/config-Bmdl5hdk.cjs +67 -0
  35. package/dist/config-BwrBYmiC.mjs +44 -0
  36. package/dist/config-BwrBYmiC.mjs.map +1 -0
  37. package/dist/data-extractor-BNGj7ECT.cjs +347 -0
  38. package/dist/data-extractor-DFzsa5CS.mjs +336 -0
  39. package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
  40. package/dist/dossier-BsroDgD3.mjs +76 -0
  41. package/dist/dossier-BsroDgD3.mjs.map +1 -0
  42. package/dist/dossier-DtxREpPm.cjs +76 -0
  43. package/dist/evidence-BGcdKxuV.cjs +200 -0
  44. package/dist/evidence-BhvFW-y_.mjs +195 -0
  45. package/dist/evidence-BhvFW-y_.mjs.map +1 -0
  46. package/dist/format-Ce1RObVl.mjs +22 -0
  47. package/dist/format-Ce1RObVl.mjs.map +1 -0
  48. package/dist/format-DOrPvXEr.cjs +20 -0
  49. package/dist/frontmatter-D8wWCeOa.mjs +26 -0
  50. package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
  51. package/dist/frontmatter-DgAuai7E.cjs +35 -0
  52. package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
  53. package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
  54. package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
  55. package/dist/graph-reports-C4TBjCkM.mjs +63 -0
  56. package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
  57. package/dist/graph-reports-DU05YCei.cjs +64 -0
  58. package/dist/html-generator-CAv81IWH.cjs +85 -0
  59. package/dist/html-generator-V6Bp0uRb.mjs +68 -0
  60. package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
  61. package/dist/index.cjs +31 -0
  62. package/dist/index.d.cts +187 -0
  63. package/dist/index.d.cts.map +1 -0
  64. package/dist/index.d.mts +187 -0
  65. package/dist/index.d.mts.map +1 -0
  66. package/dist/index.mjs +9 -0
  67. package/dist/init-BjuFt54X.cjs +232 -0
  68. package/dist/init-CaOsHTIo.mjs +232 -0
  69. package/dist/init-CaOsHTIo.mjs.map +1 -0
  70. package/dist/mcp-proxy.cjs +1257 -0
  71. package/dist/mcp-proxy.d.cts +12 -0
  72. package/dist/mcp-proxy.d.cts.map +1 -0
  73. package/dist/mcp-proxy.d.mts +12 -0
  74. package/dist/mcp-proxy.d.mts.map +1 -0
  75. package/dist/mcp-proxy.mjs +1255 -0
  76. package/dist/mcp-proxy.mjs.map +1 -0
  77. package/dist/output-root-CFYms3ad.cjs +43 -0
  78. package/dist/output-root-CmWM7aV2.mjs +33 -0
  79. package/dist/output-root-CmWM7aV2.mjs.map +1 -0
  80. package/dist/parser-BUIWW1OH.cjs +182 -0
  81. package/dist/parser-DO0_SssG.mjs +182 -0
  82. package/dist/parser-DO0_SssG.mjs.map +1 -0
  83. package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
  84. package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
  85. package/dist/public-tools-XSpkz2ky.cjs +2556 -0
  86. package/dist/resolver-C2ZS7oC8.mjs +201 -0
  87. package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
  88. package/dist/resolver-zYbu4wDV.cjs +203 -0
  89. package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
  90. package/dist/runner-1Eq55OYb.cjs +148 -0
  91. package/dist/runner-BhUHbiHG.mjs +149 -0
  92. package/dist/runner-BhUHbiHG.mjs.map +1 -0
  93. package/dist/schema-4XpzDFQM.cjs +55 -0
  94. package/dist/schema-8d0rVIdZ.mjs +37 -0
  95. package/dist/schema-8d0rVIdZ.mjs.map +1 -0
  96. package/dist/schema-cache-9CksD7tX.mjs +34 -0
  97. package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
  98. package/dist/schema-cache-CgWRCN2N.cjs +36 -0
  99. package/dist/selector-CkFcTXzz.cjs +10 -0
  100. package/dist/selector-xjm6NTHI.mjs +12 -0
  101. package/dist/selector-xjm6NTHI.mjs.map +1 -0
  102. package/dist/server-BkM5xrXb.mjs +45 -0
  103. package/dist/server-BkM5xrXb.mjs.map +1 -0
  104. package/dist/server-DXowbpfi.cjs +54 -0
  105. package/dist/session-BpNylyuJ.cjs +115 -0
  106. package/dist/session-CcTgYxsj.mjs +115 -0
  107. package/dist/session-CcTgYxsj.mjs.map +1 -0
  108. package/dist/setup-DOpKPrlx.cjs +81 -0
  109. package/dist/setup-DyrWHuwQ.mjs +80 -0
  110. package/dist/setup-DyrWHuwQ.mjs.map +1 -0
  111. package/dist/store-BiUhQOIf.cjs +230 -0
  112. package/dist/store-BoWE-Gtl.mjs +225 -0
  113. package/dist/store-BoWE-Gtl.mjs.map +1 -0
  114. package/dist/templates/graph.html +1406 -0
  115. package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
  116. package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
  117. package/dist/tool-visibility-CwgY205r.cjs +36 -0
  118. package/dist/tools-Cp2jAAAb.mjs +100 -0
  119. package/dist/tools-Cp2jAAAb.mjs.map +1 -0
  120. package/dist/tools-f_vJUZAF.cjs +139 -0
  121. package/dist/topup-server-BZuQifvh.cjs +940 -0
  122. package/dist/topup-server-DUjyFftI.mjs +919 -0
  123. package/dist/topup-server-DUjyFftI.mjs.map +1 -0
  124. package/dist/version-1gP19Lhi.mjs +8 -0
  125. package/dist/version-1gP19Lhi.mjs.map +1 -0
  126. package/dist/version-BNGtdpmH.cjs +18 -0
  127. package/dist/viz-BlCJe6Tk.mjs +35 -0
  128. package/dist/viz-BlCJe6Tk.mjs.map +1 -0
  129. package/dist/viz-ClezVXrJ.cjs +44 -0
  130. package/dist/wallet-BMelXBYP.mjs +104 -0
  131. package/dist/wallet-BMelXBYP.mjs.map +1 -0
  132. package/dist/wallet-RnvvSpV2.cjs +146 -0
  133. package/docs/architecture.md +145 -0
  134. package/docs/contributing.md +68 -0
  135. package/docs/debugging.md +68 -0
  136. package/docs/development.md +44 -0
  137. package/docs/graph-tools.md +251 -0
  138. package/docs/images/graph-mcp-iframe.png +0 -0
  139. package/docs/images/graph-visualization.png +0 -0
  140. package/docs/images/topup-page.png +0 -0
  141. package/docs/investigation-workspaces.md +151 -0
  142. package/docs/mcp-proxy.md +180 -0
  143. package/package.json +59 -0
  144. package/skills/chain-insights-developer-experience/SKILL.md +101 -0
  145. package/skills/chain-insights-investigation/SKILL.md +285 -0
  146. package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
  147. package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
  148. package/skills/chain-insights-trace-funds/SKILL.md +249 -0
  149. package/skills/ci-case/SKILL.md +43 -0
  150. package/skills/ci-status/SKILL.md +45 -0
  151. package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
  152. package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
  153. package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chain Swarm AML
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # Chain Insights
2
+
3
+ Chain Insights is an AML investigation framework on top of GraphRAG MCP. It
4
+ turns graph access into analyst-ready workflows: address screening, fund-flow
5
+ tracing, scam topology discovery, case files, evidence, dossiers, reports, and
6
+ graph visualizations.
7
+
8
+ GraphRAG MCP exposes generic graph tools. Chain Insights adds AML tools and
9
+ investigation workflow around them.
10
+
11
+ ## What You Can Do Today
12
+
13
+ | Tool | Use it for |
14
+ | --- | --- |
15
+ | `address_risk` | Screen one address for risk, behavior, neighborhood context, and exchange exposure |
16
+ | `track_funds` | Trace victim/source funds through intermediaries to exchange deposit candidates |
17
+ | `scam_topology` | Expand a known victim incident into reviewable scam infrastructure and label candidates |
18
+ | `graph_query` | Run one read-only GQL/Cypher query against a GraphRAG MCP graph layer |
19
+ | `graph_query_batch` | Run related read-only graph queries as one MCP call |
20
+
21
+ ## Quick Start
22
+
23
+ From an installed package:
24
+
25
+ ```bash
26
+ cia --version
27
+ ```
28
+
29
+ From a local checkout:
30
+
31
+ ```bash
32
+ npm install
33
+ npm run build
34
+ npm install -g .
35
+ cia --version
36
+ ```
37
+
38
+ Create an investigation workspace:
39
+
40
+ ```bash
41
+ mkdir -p ./chain-insights-investigations
42
+ cd ./chain-insights-investigations
43
+ cia init .
44
+ ```
45
+
46
+ Check the configured endpoint and current GraphRAG MCP capabilities:
47
+
48
+ ```bash
49
+ cia config get graphMcpEndpoint
50
+ cia mcp networks
51
+ cia mcp tools --refresh
52
+ ```
53
+
54
+ If network or tool discovery fails, fix endpoint/auth first; the CLI can still
55
+ initialize workspaces and manage cases without a reachable GraphRAG MCP
56
+ endpoint.
57
+
58
+ Open a case and run a small investigation:
59
+
60
+ ```bash
61
+ cia case open "First Chain Insights investigation" \
62
+ --tags aml,bittensor \
63
+ --description "Screen and trace a known source address"
64
+
65
+ cia mcp track-funds \
66
+ --network bittensor \
67
+ --trusted-addresses 5GTjfJaLpBNrgybhY24NqhDnKW9r94z72RSYLxeodxJfSkj5 \
68
+ --case 1
69
+ ```
70
+
71
+ Then inspect:
72
+
73
+ ```bash
74
+ cia case show 1
75
+ find reports cases -maxdepth 3 -type f | sort
76
+ ```
77
+
78
+ ## Demo
79
+
80
+ Run a direct live topology query:
81
+
82
+ ```bash
83
+ cia mcp call graph_query \
84
+ network=bittensor \
85
+ "query=USE live_topology MATCH (n) WHERE n.address IS NOT NULL RETURN n.labels AS labels, n.address AS address LIMIT 10"
86
+ ```
87
+
88
+ Run a batch across graph layers:
89
+
90
+ ```bash
91
+ cia mcp call graph_query_batch \
92
+ network=bittensor \
93
+ '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"},{"id":"facts_sample","query":"USE facts MATCH (a:Address)-[:HAS_FEATURE]->(f:AddressFeature) RETURN a.address AS address, f.sent_count AS sent_count LIMIT 3"}]'
94
+ ```
95
+
96
+ Run victim incident topology:
97
+
98
+ ```bash
99
+ cia mcp scam-topology \
100
+ --network bittensor \
101
+ --victim-address 5... \
102
+ --incident-timestamp-ms 1715532228001 \
103
+ --max-hops 16 \
104
+ --case 1
105
+ ```
106
+
107
+ ## How It Fits Together
108
+
109
+ ```text
110
+ Agent or CLI user
111
+ -> Chain Insights CLI / MCP proxy
112
+ -> local config, wallet, workspace, cases, evidence, reports
113
+ -> GraphRAG MCP
114
+ -> live_topology, archive_topology, facts
115
+ ```
116
+
117
+ Chain Insights stores investigation outputs in initialized local workspaces.
118
+ GraphRAG MCP performs graph-language reads against network-specific graph
119
+ layers.
120
+
121
+ ## Topology And Facts
122
+
123
+ Graph queries must choose a layer explicitly:
124
+
125
+ | Layer | Backing data |
126
+ | --- | --- |
127
+ | `live_topology` | Memgraph RAM topology for current graph traversal |
128
+ | `archive_topology` | StarRocks historical topology for warehouse-scale flow reads |
129
+ | `facts` | StarRocks facts for feature and enrichment reads |
130
+
131
+ Use `graph_query_batch` when related reads should share one call and one
132
+ result envelope.
133
+
134
+ ## AML Tools
135
+
136
+ The high-level AML tools are Chain Insights workflows built around graph access
137
+ and local case state:
138
+
139
+ - `address_risk` starts a single-address screen with risk, behavior,
140
+ neighborhood context, and exchange exposure.
141
+ - `track_funds` traces trusted victim/source funds through intermediaries to
142
+ exchange deposit candidates.
143
+ - `scam_topology` expands from a known victim incident into reviewable
144
+ topology, safety decisions, and ML-ready label candidates.
145
+
146
+ When a case is provided, tools can save compact evidence pointers and graph
147
+ reports under the workspace instead of embedding large payloads in case notes.
148
+
149
+ ## Docs Map
150
+
151
+ | Doc | Use it for |
152
+ | --- | --- |
153
+ | [Graph tools](docs/graph-tools.md) | GraphRAG MCP layers, `graph_query`, `graph_query_batch`, AML tool contracts, graph reports, evidence pointers |
154
+ | [Investigation workspaces](docs/investigation-workspaces.md) | `cia init`, case layout, evidence, dossiers, imports, templates, sessions, reports |
155
+ | [MCP proxy](docs/mcp-proxy.md) | Stdio proxy behavior, agent installers, local tools, auth modes, Inspector validation |
156
+ | [Architecture](docs/architecture.md) | Product layers, data flow, local storage, security model, config keys |
157
+ | [Development](docs/development.md) | Build, test, and local install commands |
158
+ | [Contributing](docs/contributing.md) | Development workflow, pull requests, release expectations |
159
+ | [Debugging](docs/debugging.md) | Local troubleshooting, diagnostics, debug workflows |
160
+
161
+ ## What It Is Not
162
+
163
+ Chain Insights is not a custodial wallet, hosted case database, or replacement
164
+ for analyst review. It does not write risk labels automatically. Investigation
165
+ data stays in the local workspace unless the operator exports or shares it.
package/bin/cli.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // CJS shim — bridges the npm bin entry (CJS, no build step) to the
5
+ // ESM dist built by tsdown. Dynamic import() is the correct bridge pattern
6
+ // (see references/get-shit-done/bin/gsd-sdk.js for the GSD precedent).
7
+ import('../dist/cli.mjs').catch((err) => {
8
+ console.error('Failed to load chain-insights:', err.message);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Chain Insights installer — CJS, stdlib-only.
5
+ // Runs before node_modules exists; zero npm imports allowed.
6
+ // Extension is .cjs (not .js) because package.json has "type": "module" —
7
+ // a .js file would be treated as ESM and require() calls would crash.
8
+ // Adapted from GSD reference: references/get-shit-done/bin/install.js
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+
14
+ // ANSI colors — no chalk
15
+ const cyan = '\x1b[36m';
16
+ const green = '\x1b[32m';
17
+ const bold = '\x1b[1m';
18
+ const dim = '\x1b[2m';
19
+ const reset = '\x1b[0m';
20
+
21
+ // Parse args
22
+ const args = process.argv.slice(2);
23
+ const hasClaude = args.includes('--claude');
24
+ const hasCodex = args.includes('--codex');
25
+ const hasHermes = args.includes('--hermes');
26
+ const hasLocal = args.includes('--local');
27
+
28
+ if (!hasClaude && !hasCodex && !hasHermes && !hasLocal) {
29
+ console.log(`\n${bold}chain-insights installer${reset}`);
30
+ console.log(`\nUsage: node bin/install.cjs --claude | --codex | --hermes`);
31
+ console.log(` ${cyan}--claude${reset} Install Claude Code skills globally to ~/.claude/skills/`);
32
+ console.log(` ${cyan}--codex${reset} Install Codex skills globally to ~/.codex/skills/ and register MCP`);
33
+ console.log(` ${cyan}--hermes${reset} Install Hermes skills globally to ~/.hermes/skills/chain-insights/ and register MCP`);
34
+ console.log(` ${cyan}--local${reset} Install skills locally to ./.claude/commands/chain-insights/`);
35
+ console.log('');
36
+ process.exit(0);
37
+ }
38
+
39
+ const homeDir = os.homedir();
40
+ const dataDir = path.join(homeDir, '.chain-insights');
41
+ const configPath = path.join(dataDir, 'config.json');
42
+ const srcSkillsDir = path.join(__dirname, '..', 'skills');
43
+
44
+ // Determine skills targets
45
+ const skillsTargets = [];
46
+ if (hasClaude) skillsTargets.push({ name: 'Claude Code', dir: path.join(homeDir, '.claude', 'skills') });
47
+ if (hasCodex) skillsTargets.push({ name: 'Codex', dir: path.join(homeDir, '.codex', 'skills') });
48
+ if (hasHermes) skillsTargets.push({ name: 'Hermes', dir: path.join(homeDir, '.hermes', 'skills', 'chain-insights') });
49
+ if (hasLocal) skillsTargets.push({ name: 'Local Claude commands', dir: path.join(process.cwd(), '.claude', 'commands', 'chain-insights') });
50
+
51
+ // ─── 1. Copy agent skills ─────────────────────────────────────────────────
52
+
53
+ function copyCommandsAsClaudeSkills(srcDir, targetDir) {
54
+ if (!fs.existsSync(srcDir)) {
55
+ console.error(`Skills source not found: ${srcDir}`);
56
+ return;
57
+ }
58
+
59
+ fs.mkdirSync(targetDir, { recursive: true });
60
+
61
+ // Remove stale ci-* skill dirs before copying (clean reinstall)
62
+ const existing = fs.readdirSync(targetDir, { withFileTypes: true });
63
+ for (const entry of existing) {
64
+ if (entry.isDirectory() && entry.name.startsWith('ci-')) {
65
+ fs.rmSync(path.join(targetDir, entry.name), { recursive: true, force: true });
66
+ }
67
+ }
68
+
69
+ const copyTree = (src, dest) => {
70
+ const stat = fs.statSync(src);
71
+ if (stat.isDirectory()) {
72
+ fs.mkdirSync(dest, { recursive: true });
73
+ for (const child of fs.readdirSync(src)) {
74
+ copyTree(path.join(src, child), path.join(dest, child));
75
+ }
76
+ return;
77
+ }
78
+ if (stat.isFile()) {
79
+ fs.copyFileSync(src, dest);
80
+ }
81
+ };
82
+
83
+ // Recurse into skills/ directory; each subdirectory becomes a skill dir.
84
+ const skillDirs = fs.readdirSync(srcDir, { withFileTypes: true })
85
+ .filter(e => e.isDirectory());
86
+
87
+ for (const skillDir of skillDirs) {
88
+ const skillSrc = path.join(srcDir, skillDir.name);
89
+ const skillDest = path.join(targetDir, skillDir.name);
90
+ fs.rmSync(skillDest, { recursive: true, force: true });
91
+ copyTree(skillSrc, skillDest);
92
+ }
93
+ }
94
+
95
+ for (const target of skillsTargets) {
96
+ copyCommandsAsClaudeSkills(srcSkillsDir, target.dir);
97
+ }
98
+
99
+ // ─── 2. Create ~/.chain-insights/ config directory ────────────────────────
100
+
101
+ if (!fs.existsSync(dataDir)) {
102
+ fs.mkdirSync(dataDir, { recursive: true });
103
+ }
104
+
105
+ // ─── 3. Write default config.json if absent ───────────────────────────────
106
+
107
+ if (!fs.existsSync(configPath)) {
108
+ const defaultConfig = {
109
+ mcpEndpoint: 'http://localhost:4000',
110
+ mcpAuthToken: '',
111
+ walletAddress: '',
112
+ graphMcpMode: 'paid',
113
+ serverPort: 4321,
114
+ dataDir: dataDir,
115
+ version: '1',
116
+ };
117
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
118
+ // Owner-readable only — config may contain MCP auth token (ASVS L1 V4.3.2 / T-02-01)
119
+ fs.chmodSync(configPath, 0o600);
120
+ }
121
+
122
+ // ─── 4. Register MCP proxy in supported clients ───────────────────────────
123
+
124
+ const proxyBinPath = path.resolve(__dirname, 'mcp-proxy.cjs');
125
+ const { execFileSync } = require('child_process');
126
+
127
+ if (hasClaude) {
128
+ try {
129
+ execFileSync(
130
+ 'claude',
131
+ ['mcp', 'add', 'chain-insights-proxy', '--scope', 'user', '--', 'node', proxyBinPath],
132
+ { stdio: 'pipe' }
133
+ );
134
+ console.log(` ${cyan}Claude MCP:${reset} registered (chain-insights-proxy) at ${proxyBinPath}`);
135
+ } catch {
136
+ console.log(` ${dim}Claude MCP:${reset} run manually: claude mcp add chain-insights-proxy --scope user -- node ${proxyBinPath}`);
137
+ }
138
+ }
139
+
140
+ function tomlQuoted(value) {
141
+ return JSON.stringify(value);
142
+ }
143
+
144
+ function installCodexMcp(configFile, proxyPath) {
145
+ fs.mkdirSync(path.dirname(configFile), { recursive: true });
146
+ let content = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf8') : '';
147
+ const block = [
148
+ '[mcp_servers.chain-insights]',
149
+ 'command = "node"',
150
+ `args = [${tomlQuoted(proxyPath)}]`,
151
+ '',
152
+ ].join('\n');
153
+
154
+ const heading = '[mcp_servers.chain-insights]';
155
+ const start = content.indexOf(heading);
156
+ if (start >= 0) {
157
+ let end = content.length;
158
+ const rest = content.slice(start + heading.length);
159
+ const nextSection = rest.search(/\n\[/);
160
+ if (nextSection >= 0) end = start + heading.length + nextSection + 1;
161
+ content = `${content.slice(0, start)}${block}${content.slice(end)}`;
162
+ } else {
163
+ const separator = content.endsWith('\n') || content.length === 0 ? '' : '\n';
164
+ content = `${content}${separator}\n${block}`;
165
+ }
166
+
167
+ fs.writeFileSync(configFile, content, 'utf8');
168
+ }
169
+
170
+ if (hasCodex) {
171
+ const codexConfig = path.join(homeDir, '.codex', 'config.toml');
172
+ installCodexMcp(codexConfig, proxyBinPath);
173
+ console.log(` ${cyan}Codex MCP:${reset} registered in ${codexConfig}`);
174
+ }
175
+
176
+ function yamlQuoted(value) {
177
+ return JSON.stringify(value);
178
+ }
179
+
180
+ function findTopLevelSectionEnd(lines, start) {
181
+ for (let i = start + 1; i < lines.length; i++) {
182
+ const line = lines[i];
183
+ if (line.trim() === '') continue;
184
+ if (!line.startsWith(' ') && !line.startsWith('\t')) return i;
185
+ }
186
+ return lines.length;
187
+ }
188
+
189
+ function findHermesServerEnd(lines, start, sectionEnd) {
190
+ for (let i = start + 1; i < sectionEnd; i++) {
191
+ const line = lines[i];
192
+ if (/^ [^ ].*:/.test(line)) return i;
193
+ }
194
+ return sectionEnd;
195
+ }
196
+
197
+ function installHermesMcp(configFile, proxyPath) {
198
+ fs.mkdirSync(path.dirname(configFile), { recursive: true });
199
+
200
+ const serverBlock = [
201
+ ' chain-insights:',
202
+ ' command: "node"',
203
+ ' args:',
204
+ ` - ${yamlQuoted(proxyPath)}`,
205
+ ' enabled: true',
206
+ ];
207
+
208
+ let content = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf8') : '';
209
+ if (/^mcp_servers:\s*\{\s*\}\s*$/m.test(content)) {
210
+ content = content.replace(/^mcp_servers:\s*\{\s*\}\s*$/m, ['mcp_servers:', ...serverBlock].join('\n'));
211
+ fs.writeFileSync(configFile, content.endsWith('\n') ? content : `${content}\n`, 'utf8');
212
+ return;
213
+ }
214
+
215
+ const lines = content.length ? content.split(/\r?\n/) : [];
216
+ if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
217
+
218
+ const sectionStart = lines.findIndex(line => line === 'mcp_servers:');
219
+ if (sectionStart < 0) {
220
+ if (lines.length > 0) lines.push('');
221
+ lines.push('mcp_servers:', ...serverBlock);
222
+ fs.writeFileSync(configFile, `${lines.join('\n')}\n`, 'utf8');
223
+ return;
224
+ }
225
+
226
+ const sectionEnd = findTopLevelSectionEnd(lines, sectionStart);
227
+ const serverStart = lines.findIndex((line, index) => index > sectionStart && index < sectionEnd && line === ' chain-insights:');
228
+ if (serverStart >= 0) {
229
+ const serverEnd = findHermesServerEnd(lines, serverStart, sectionEnd);
230
+ lines.splice(serverStart, serverEnd - serverStart, ...serverBlock);
231
+ } else {
232
+ lines.splice(sectionStart + 1, 0, ...serverBlock);
233
+ }
234
+
235
+ fs.writeFileSync(configFile, `${lines.join('\n')}\n`, 'utf8');
236
+ }
237
+
238
+ if (hasHermes) {
239
+ const hermesConfig = path.join(homeDir, '.hermes', 'config.yaml');
240
+ installHermesMcp(hermesConfig, proxyBinPath);
241
+ console.log(` ${cyan}Hermes MCP:${reset} registered in ${hermesConfig}`);
242
+ }
243
+
244
+ // ─── 5. Print installation summary ────────────────────────────────────────
245
+
246
+ console.log(`\n${bold}${green}Chain Insights installed${reset}`);
247
+ for (const target of skillsTargets) {
248
+ console.log(` ${cyan}${target.name} skills:${reset} ${target.dir}`);
249
+ }
250
+ console.log(` ${cyan}Config:${reset} ${configPath}`);
251
+ console.log(` ${cyan}Data dir:${reset} ${dataDir}`);
252
+ console.log(`\n${dim}Run ${reset}${cyan}chain-insights status${reset}${dim} to verify the installation.${reset}\n`);
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // CJS shim for stdio MCP proxy — spawned by Claude Code via ~/.claude.json mcpServers config.
5
+ // IMPORTANT: Do not write to stdout here — stdout is owned by StdioServerTransport.
6
+ // Use process.stderr.write() exclusively for pre-load errors.
7
+ import('../dist/mcp-proxy.mjs').then(({ createProxy }) => createProxy()).catch((err) => {
8
+ process.stderr.write(`Failed to load chain-insights MCP proxy: ${err.message}\n`);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,50 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ //#region src/workspace/active.ts
6
+ var active_exports = /* @__PURE__ */ __exportAll({
7
+ activeCasesRoot: () => activeCasesRoot,
8
+ activeDataDir: () => activeDataDir,
9
+ findActiveWorkspace: () => findActiveWorkspace
10
+ });
11
+ function workspaceFromRoot(rootCandidate) {
12
+ const root = path.resolve(rootCandidate);
13
+ const metadataDir = path.join(root, ".chain-insights");
14
+ const markerPath = path.join(metadataDir, "workspace.json");
15
+ if (!fs.existsSync(markerPath)) return null;
16
+ const parsed = JSON.parse(fs.readFileSync(markerPath, "utf8"));
17
+ if (parsed.schema !== "chain-insights.workspace.v1") return null;
18
+ const workspaceRoot = path.resolve(parsed.workspace_root ?? root);
19
+ const casesDir = parsed.cases_dir ?? "cases";
20
+ return {
21
+ root: workspaceRoot,
22
+ metadataDir: path.join(workspaceRoot, ".chain-insights"),
23
+ casesRoot: path.resolve(workspaceRoot, casesDir)
24
+ };
25
+ }
26
+ function findActiveWorkspace(startDir = process.cwd()) {
27
+ const envWorkspace = process.env["CHAIN_INSIGHTS_WORKSPACE"]?.trim();
28
+ if (envWorkspace) {
29
+ const active = workspaceFromRoot(envWorkspace);
30
+ if (active) return active;
31
+ }
32
+ let current = path.resolve(startDir);
33
+ while (true) {
34
+ const active = workspaceFromRoot(current);
35
+ if (active) return active;
36
+ const parent = path.dirname(current);
37
+ if (parent === current) return null;
38
+ current = parent;
39
+ }
40
+ }
41
+ function activeCasesRoot() {
42
+ return findActiveWorkspace()?.casesRoot ?? path.join(os.homedir(), ".chain-insights", "cases");
43
+ }
44
+ function activeDataDir(fallbackDataDir) {
45
+ return findActiveWorkspace()?.root ?? fallbackDataDir ?? path.join(os.homedir(), ".chain-insights");
46
+ }
47
+ //#endregion
48
+ export { active_exports as n, findActiveWorkspace as r, activeCasesRoot as t };
49
+
50
+ //# sourceMappingURL=active-BSrxLKwn.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-BSrxLKwn.mjs","names":[],"sources":["../src/workspace/active.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\ninterface WorkspaceConfig {\n schema?: string\n workspace_root?: string\n cases_dir?: string\n}\n\nexport interface ActiveWorkspace {\n root: string\n metadataDir: string\n casesRoot: string\n}\n\nfunction workspaceFromRoot(rootCandidate: string): ActiveWorkspace | null {\n const root = path.resolve(rootCandidate)\n const metadataDir = path.join(root, '.chain-insights')\n const markerPath = path.join(metadataDir, 'workspace.json')\n if (!fs.existsSync(markerPath)) return null\n\n const parsed = JSON.parse(fs.readFileSync(markerPath, 'utf8')) as WorkspaceConfig\n if (parsed.schema !== 'chain-insights.workspace.v1') return null\n\n const workspaceRoot = path.resolve(parsed.workspace_root ?? root)\n const casesDir = parsed.cases_dir ?? 'cases'\n return {\n root: workspaceRoot,\n metadataDir: path.join(workspaceRoot, '.chain-insights'),\n casesRoot: path.resolve(workspaceRoot, casesDir),\n }\n}\n\nexport function findActiveWorkspace(startDir = process.cwd()): ActiveWorkspace | null {\n const envWorkspace = process.env['CHAIN_INSIGHTS_WORKSPACE']?.trim()\n if (envWorkspace) {\n const active = workspaceFromRoot(envWorkspace)\n if (active) return active\n }\n\n let current = path.resolve(startDir)\n while (true) {\n const active = workspaceFromRoot(current)\n if (active) return active\n\n const parent = path.dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport function activeMetadataDir(): string {\n return findActiveWorkspace()?.metadataDir ?? path.join(os.homedir(), '.chain-insights')\n}\n\nexport function activeCasesRoot(): string {\n return findActiveWorkspace()?.casesRoot ?? path.join(os.homedir(), '.chain-insights', 'cases')\n}\n\nexport function activeDataDir(fallbackDataDir?: string): string {\n return findActiveWorkspace()?.root ?? fallbackDataDir ?? path.join(os.homedir(), '.chain-insights')\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAS,kBAAkB,eAA+C;CACxE,MAAM,OAAO,KAAK,QAAQ,cAAc;CACxC,MAAM,cAAc,KAAK,KAAK,MAAM,kBAAkB;CACtD,MAAM,aAAa,KAAK,KAAK,aAAa,iBAAiB;AAC3D,KAAI,CAAC,GAAG,WAAW,WAAW,CAAE,QAAO;CAEvC,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,YAAY,OAAO,CAAC;AAC9D,KAAI,OAAO,WAAW,8BAA+B,QAAO;CAE5D,MAAM,gBAAgB,KAAK,QAAQ,OAAO,kBAAkB,KAAK;CACjE,MAAM,WAAW,OAAO,aAAa;AACrC,QAAO;EACL,MAAM;EACN,aAAa,KAAK,KAAK,eAAe,kBAAkB;EACxD,WAAW,KAAK,QAAQ,eAAe,SAAS;EACjD;;AAGH,SAAgB,oBAAoB,WAAW,QAAQ,KAAK,EAA0B;CACpF,MAAM,eAAe,QAAQ,IAAI,6BAA6B,MAAM;AACpE,KAAI,cAAc;EAChB,MAAM,SAAS,kBAAkB,aAAa;AAC9C,MAAI,OAAQ,QAAO;;CAGrB,IAAI,UAAU,KAAK,QAAQ,SAAS;AACpC,QAAO,MAAM;EACX,MAAM,SAAS,kBAAkB,QAAQ;AACzC,MAAI,OAAQ,QAAO;EAEnB,MAAM,SAAS,KAAK,QAAQ,QAAQ;AACpC,MAAI,WAAW,QAAS,QAAO;AAC/B,YAAU;;;AAQd,SAAgB,kBAA0B;AACxC,QAAO,qBAAqB,EAAE,aAAa,KAAK,KAAK,GAAG,SAAS,EAAE,mBAAmB,QAAQ;;AAGhG,SAAgB,cAAc,iBAAkC;AAC9D,QAAO,qBAAqB,EAAE,QAAQ,mBAAmB,KAAK,KAAK,GAAG,SAAS,EAAE,kBAAkB"}
@@ -0,0 +1,68 @@
1
+ const require_chunk = require("./chunk-CZWwpsFl.cjs");
2
+ let node_path = require("node:path");
3
+ node_path = require_chunk.__toESM(node_path, 1);
4
+ let node_fs = require("node:fs");
5
+ node_fs = require_chunk.__toESM(node_fs, 1);
6
+ let node_os = require("node:os");
7
+ node_os = require_chunk.__toESM(node_os, 1);
8
+ //#region src/workspace/active.ts
9
+ var active_exports = /* @__PURE__ */ require_chunk.__exportAll({
10
+ activeCasesRoot: () => activeCasesRoot,
11
+ activeDataDir: () => activeDataDir,
12
+ findActiveWorkspace: () => findActiveWorkspace
13
+ });
14
+ function workspaceFromRoot(rootCandidate) {
15
+ const root = node_path.default.resolve(rootCandidate);
16
+ const metadataDir = node_path.default.join(root, ".chain-insights");
17
+ const markerPath = node_path.default.join(metadataDir, "workspace.json");
18
+ if (!node_fs.default.existsSync(markerPath)) return null;
19
+ const parsed = JSON.parse(node_fs.default.readFileSync(markerPath, "utf8"));
20
+ if (parsed.schema !== "chain-insights.workspace.v1") return null;
21
+ const workspaceRoot = node_path.default.resolve(parsed.workspace_root ?? root);
22
+ const casesDir = parsed.cases_dir ?? "cases";
23
+ return {
24
+ root: workspaceRoot,
25
+ metadataDir: node_path.default.join(workspaceRoot, ".chain-insights"),
26
+ casesRoot: node_path.default.resolve(workspaceRoot, casesDir)
27
+ };
28
+ }
29
+ function findActiveWorkspace(startDir = process.cwd()) {
30
+ const envWorkspace = process.env["CHAIN_INSIGHTS_WORKSPACE"]?.trim();
31
+ if (envWorkspace) {
32
+ const active = workspaceFromRoot(envWorkspace);
33
+ if (active) return active;
34
+ }
35
+ let current = node_path.default.resolve(startDir);
36
+ while (true) {
37
+ const active = workspaceFromRoot(current);
38
+ if (active) return active;
39
+ const parent = node_path.default.dirname(current);
40
+ if (parent === current) return null;
41
+ current = parent;
42
+ }
43
+ }
44
+ function activeCasesRoot() {
45
+ return findActiveWorkspace()?.casesRoot ?? node_path.default.join(node_os.default.homedir(), ".chain-insights", "cases");
46
+ }
47
+ function activeDataDir(fallbackDataDir) {
48
+ return findActiveWorkspace()?.root ?? fallbackDataDir ?? node_path.default.join(node_os.default.homedir(), ".chain-insights");
49
+ }
50
+ //#endregion
51
+ Object.defineProperty(exports, "activeCasesRoot", {
52
+ enumerable: true,
53
+ get: function() {
54
+ return activeCasesRoot;
55
+ }
56
+ });
57
+ Object.defineProperty(exports, "active_exports", {
58
+ enumerable: true,
59
+ get: function() {
60
+ return active_exports;
61
+ }
62
+ });
63
+ Object.defineProperty(exports, "findActiveWorkspace", {
64
+ enumerable: true,
65
+ get: function() {
66
+ return findActiveWorkspace;
67
+ }
68
+ });