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
@@ -0,0 +1,201 @@
1
+ import path from "node:path";
2
+ import { readFile, readdir } from "node:fs/promises";
3
+ import os from "node:os";
4
+ const BUILTIN_PLAYBOOKS = {
5
+ "trace-funds": `---
6
+ name: trace-funds
7
+ description: Trace stolen funds from a victim address to exchange deposits using Chain Insights GraphRAG
8
+ version: 1.0.0
9
+ params:
10
+ - name: address
11
+ type: string
12
+ required: true
13
+ - name: network
14
+ type: string
15
+ required: false
16
+ default: bittensor
17
+ ---
18
+
19
+ ## Step 1: Screen Victim Address
20
+
21
+ \`\`\`tool
22
+ address_risk
23
+ \`\`\`
24
+
25
+ \`\`\`params
26
+ address: {{address}}
27
+ network: {{network}}
28
+ \`\`\`
29
+
30
+ ## Step 2: Trace Funds To Exchanges
31
+
32
+ \`\`\`tool
33
+ track_funds
34
+ \`\`\`
35
+
36
+ \`\`\`params
37
+ trusted_addresses: {{address}}
38
+ network: {{network}}
39
+ \`\`\`
40
+ `,
41
+ "scam-topology": `---
42
+ name: scam-topology
43
+ description: Build laundering topology and scam labels from a victim incident
44
+ version: 1.0.0
45
+ params:
46
+ - name: address
47
+ type: string
48
+ required: true
49
+ - name: incident_timestamp_ms
50
+ type: string
51
+ required: true
52
+ - name: network
53
+ type: string
54
+ required: false
55
+ default: bittensor
56
+ - name: activity_policy
57
+ type: string
58
+ required: false
59
+ default: node_relative_only
60
+ ---
61
+
62
+ ## Step 1: Screen Known Scam Address
63
+
64
+ \`\`\`tool
65
+ address_risk
66
+ \`\`\`
67
+
68
+ \`\`\`params
69
+ address: {{address}}
70
+ network: {{network}}
71
+ \`\`\`
72
+
73
+ ## Step 2: Build Scam Topology
74
+
75
+ The victim-only traversal is outward from victim/source funds. The
76
+ primary traversal is a node-relative novelty wave: each new node expands only
77
+ once, repeated targets remain as non-expanding convergence context, and
78
+ downstream edges must be active at or after the current node's wave-arrival
79
+ timestamp. Set activity_policy to global_incident_only to filter every wave
80
+ against incident_timestamp_ms instead. Exchange terminal safety stops exchange
81
+ endpoints, and label candidates are reviewable, not automatic writes.
82
+ Contract summary: victim-only traversal is outward from victim/source funds;
83
+ node-relative novelty wave by default; global_incident_only is available;
84
+ exchange terminal safety; scam_labels are ML-ready flags.
85
+
86
+ \`\`\`tool
87
+ scam_topology
88
+ \`\`\`
89
+
90
+ \`\`\`params
91
+ victim_address: {{address}}
92
+ incident_timestamp_ms: {{incident_timestamp_ms}}
93
+ network: {{network}}
94
+ activity_policy: {{activity_policy}}
95
+ \`\`\`
96
+ `,
97
+ "risk-check": `---
98
+ name: risk-check
99
+ description: Screen an address for Chain Insights risk, behavior, counterparties, exchange connections, and AML patterns
100
+ version: 1.0.0
101
+ params:
102
+ - name: address
103
+ type: string
104
+ required: true
105
+ - name: network
106
+ type: string
107
+ required: false
108
+ default: bittensor
109
+ ---
110
+
111
+ ## Step 1: Address Risk
112
+
113
+ \`\`\`tool
114
+ address_risk
115
+ \`\`\`
116
+
117
+ \`\`\`params
118
+ address: {{address}}
119
+ network: {{network}}
120
+ \`\`\`
121
+ `,
122
+ "entity-profile": `---
123
+ name: entity-profile
124
+ description: Build an entity profile from Chain Insights address identity, metrics, risk, counterparties, and labels
125
+ version: 1.0.0
126
+ params:
127
+ - name: address
128
+ type: string
129
+ required: true
130
+ - name: network
131
+ type: string
132
+ required: false
133
+ default: bittensor
134
+ ---
135
+
136
+ ## Step 1: Entity Profile
137
+
138
+ \`\`\`tool
139
+ address_risk
140
+ \`\`\`
141
+
142
+ \`\`\`params
143
+ address: {{address}}
144
+ network: {{network}}
145
+ \`\`\`
146
+ `
147
+ };
148
+ //#endregion
149
+ //#region src/playbooks/resolver.ts
150
+ function userDir() {
151
+ return path.join(os.homedir(), ".chain-insights", "playbooks");
152
+ }
153
+ /**
154
+ * Resolve a playbook name to its markdown content.
155
+ * Checks user directory (~/.chain-insights/playbooks/<name>.md) first,
156
+ * then falls back to the built-in BUILTIN_PLAYBOOKS map.
157
+ * Security: sanitizes name to prevent path traversal (T-05-01).
158
+ */
159
+ async function resolvePlaybookContent(name) {
160
+ const safeName = name.replace(/[^a-z0-9_-]/gi, "");
161
+ if (!safeName) throw new Error(`Invalid playbook name: ${name}`);
162
+ const userPath = path.join(userDir(), `${safeName}.md`);
163
+ try {
164
+ return await readFile(userPath, "utf8");
165
+ } catch {}
166
+ const builtin = BUILTIN_PLAYBOOKS[safeName];
167
+ if (builtin !== void 0) return builtin;
168
+ throw new Error(`Playbook not found: "${safeName}". Run \`chain-insights playbook list\` to see available playbooks.`);
169
+ }
170
+ /**
171
+ * List all available playbooks — user dir first (overrides), then built-ins.
172
+ * Returns array of { name, source } objects.
173
+ */
174
+ async function listPlaybooks() {
175
+ const result = [];
176
+ const seen = /* @__PURE__ */ new Set();
177
+ try {
178
+ const userFiles = await readdir(userDir());
179
+ for (const file of userFiles) {
180
+ if (!file.endsWith(".md")) continue;
181
+ const name = file.slice(0, -3);
182
+ seen.add(name);
183
+ result.push({
184
+ name,
185
+ source: "user"
186
+ });
187
+ }
188
+ } catch {}
189
+ for (const name of Object.keys(BUILTIN_PLAYBOOKS)) {
190
+ if (seen.has(name)) continue;
191
+ result.push({
192
+ name,
193
+ source: "builtin"
194
+ });
195
+ }
196
+ return result;
197
+ }
198
+ //#endregion
199
+ export { listPlaybooks, resolvePlaybookContent };
200
+
201
+ //# sourceMappingURL=resolver-C2ZS7oC8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver-C2ZS7oC8.mjs","names":[],"sources":["../src/playbooks/builtins.ts","../src/playbooks/resolver.ts"],"sourcesContent":["// Built-in playbook definitions tied to the current GraphRAG public MCP tools.\n// Source of truth inspected in rbmk/repos/ml/graphrag/src/mcp_server/tools.\n\nexport const KNOWN_GRAPHRAG_PUBLIC_TOOLS = [\n 'address_risk',\n 'scam_topology',\n 'track_funds',\n 'graph_query',\n 'graph_query_batch',\n] as const\n\nexport const TRACE_FUNDS_PLAYBOOK = `---\nname: trace-funds\ndescription: Trace stolen funds from a victim address to exchange deposits using Chain Insights GraphRAG\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Screen Victim Address\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n\n## Step 2: Trace Funds To Exchanges\n\n\\`\\`\\`tool\ntrack_funds\n\\`\\`\\`\n\n\\`\\`\\`params\ntrusted_addresses: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const RISK_CHECK_PLAYBOOK = `---\nname: risk-check\ndescription: Screen an address for Chain Insights risk, behavior, counterparties, exchange connections, and AML patterns\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Address Risk\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const ENTITY_PROFILE_PLAYBOOK = `---\nname: entity-profile\ndescription: Build an entity profile from Chain Insights address identity, metrics, risk, counterparties, and labels\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n---\n\n## Step 1: Entity Profile\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n`\n\nexport const SCAM_TOPOLOGY_PLAYBOOK = `---\nname: scam-topology\ndescription: Build laundering topology and scam labels from a victim incident\nversion: 1.0.0\nparams:\n - name: address\n type: string\n required: true\n - name: incident_timestamp_ms\n type: string\n required: true\n - name: network\n type: string\n required: false\n default: bittensor\n - name: activity_policy\n type: string\n required: false\n default: node_relative_only\n---\n\n## Step 1: Screen Known Scam Address\n\n\\`\\`\\`tool\naddress_risk\n\\`\\`\\`\n\n\\`\\`\\`params\naddress: {{address}}\nnetwork: {{network}}\n\\`\\`\\`\n\n## Step 2: Build Scam Topology\n\nThe victim-only traversal is outward from victim/source funds. The\nprimary traversal is a node-relative novelty wave: each new node expands only\nonce, repeated targets remain as non-expanding convergence context, and\ndownstream edges must be active at or after the current node's wave-arrival\ntimestamp. Set activity_policy to global_incident_only to filter every wave\nagainst incident_timestamp_ms instead. Exchange terminal safety stops exchange\nendpoints, and label candidates are reviewable, not automatic writes.\nContract summary: victim-only traversal is outward from victim/source funds;\nnode-relative novelty wave by default; global_incident_only is available;\nexchange terminal safety; scam_labels are ML-ready flags.\n\n\\`\\`\\`tool\nscam_topology\n\\`\\`\\`\n\n\\`\\`\\`params\nvictim_address: {{address}}\nincident_timestamp_ms: {{incident_timestamp_ms}}\nnetwork: {{network}}\nactivity_policy: {{activity_policy}}\n\\`\\`\\`\n`\n\nexport const BUILTIN_PLAYBOOKS: Record<string, string> = {\n 'trace-funds': TRACE_FUNDS_PLAYBOOK,\n 'scam-topology': SCAM_TOPOLOGY_PLAYBOOK,\n 'risk-check': RISK_CHECK_PLAYBOOK,\n 'entity-profile': ENTITY_PROFILE_PLAYBOOK,\n}\n","import path from 'node:path'\nimport { access, readFile, readdir } from 'node:fs/promises'\nimport os from 'node:os'\nimport { BUILTIN_PLAYBOOKS } from './builtins.js'\n\nfunction userDir(): string {\n return path.join(os.homedir(), '.chain-insights', 'playbooks')\n}\n\n/**\n * Resolve a playbook name to its absolute file path.\n * Checks user directory (~/.chain-insights/playbooks/) first, then built-in directory.\n * Security: sanitizes name to prevent path traversal (T-05-01).\n *\n * @deprecated Prefer resolvePlaybookContent() which returns markdown directly.\n */\nexport async function resolvePlaybook(name: string): Promise<string> {\n // Security: sanitize name to prevent path traversal (per security threat T-05-01)\n const safeName = name.replace(/[^a-z0-9_-]/gi, '')\n if (!safeName) throw new Error(`Invalid playbook name: ${name}`)\n\n const userPath = path.join(userDir(), `${safeName}.md`)\n\n try {\n await access(userPath)\n return userPath\n } catch {\n // Fall through to built-in check\n }\n\n // Check built-in playbooks map (T-05-01: no filesystem path exposure for built-ins)\n if (safeName in BUILTIN_PLAYBOOKS) {\n // Return a sentinel path for legacy callers — content comes from BUILTIN_PLAYBOOKS\n return `builtin:${safeName}`\n }\n\n throw new Error(\n `Playbook not found: \"${safeName}\". Run \\`chain-insights playbook list\\` to see available playbooks.`\n )\n}\n\n/**\n * Resolve a playbook name to its markdown content.\n * Checks user directory (~/.chain-insights/playbooks/<name>.md) first,\n * then falls back to the built-in BUILTIN_PLAYBOOKS map.\n * Security: sanitizes name to prevent path traversal (T-05-01).\n */\nexport async function resolvePlaybookContent(name: string): Promise<string> {\n // Security: sanitize name to prevent path traversal (per security threat T-05-01)\n const safeName = name.replace(/[^a-z0-9_-]/gi, '')\n if (!safeName) throw new Error(`Invalid playbook name: ${name}`)\n\n // 1. Check user directory first (user playbooks override built-ins)\n const userPath = path.join(userDir(), `${safeName}.md`)\n try {\n return await readFile(userPath, 'utf8')\n } catch {\n // Not in user dir — fall through\n }\n\n // 2. Fall back to built-in map\n const builtin = BUILTIN_PLAYBOOKS[safeName]\n if (builtin !== undefined) return builtin\n\n throw new Error(\n `Playbook not found: \"${safeName}\". Run \\`chain-insights playbook list\\` to see available playbooks.`\n )\n}\n\n/**\n * List all available playbooks — user dir first (overrides), then built-ins.\n * Returns array of { name, source } objects.\n */\nexport async function listPlaybooks(): Promise<Array<{ name: string; source: 'builtin' | 'user' }>> {\n const result: Array<{ name: string; source: 'builtin' | 'user' }> = []\n const seen = new Set<string>()\n\n // User playbooks first\n try {\n const userFiles = await readdir(userDir())\n for (const file of userFiles) {\n if (!file.endsWith('.md')) continue\n const name = file.slice(0, -3) // remove .md\n seen.add(name)\n result.push({ name, source: 'user' })\n }\n } catch {\n // User dir doesn't exist — that's fine\n }\n\n // Built-in playbooks from the embedded map (not filesystem)\n for (const name of Object.keys(BUILTIN_PLAYBOOKS)) {\n if (seen.has(name)) continue // user override takes precedence\n result.push({ name, source: 'builtin' })\n }\n\n return result\n}\n"],"mappings":";;;AA6JA,MAAa,oBAA4C;CACvD,eAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,iBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,cAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CAClB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CACnB;;;AC7JD,SAAS,UAAkB;AACzB,QAAO,KAAK,KAAK,GAAG,SAAS,EAAE,mBAAmB,YAAY;;;;;;;;AAyChE,eAAsB,uBAAuB,MAA+B;CAE1E,MAAM,WAAW,KAAK,QAAQ,iBAAiB,GAAG;AAClD,KAAI,CAAC,SAAU,OAAM,IAAI,MAAM,0BAA0B,OAAO;CAGhE,MAAM,WAAW,KAAK,KAAK,SAAS,EAAE,GAAG,SAAS,KAAK;AACvD,KAAI;AACF,SAAO,MAAM,SAAS,UAAU,OAAO;SACjC;CAKR,MAAM,UAAU,kBAAkB;AAClC,KAAI,YAAY,KAAA,EAAW,QAAO;AAElC,OAAM,IAAI,MACR,wBAAwB,SAAS,qEAClC;;;;;;AAOH,eAAsB,gBAA8E;CAClG,MAAM,SAA8D,EAAE;CACtE,MAAM,uBAAO,IAAI,KAAa;AAG9B,KAAI;EACF,MAAM,YAAY,MAAM,QAAQ,SAAS,CAAC;AAC1C,OAAK,MAAM,QAAQ,WAAW;AAC5B,OAAI,CAAC,KAAK,SAAS,MAAM,CAAE;GAC3B,MAAM,OAAO,KAAK,MAAM,GAAG,GAAG;AAC9B,QAAK,IAAI,KAAK;AACd,UAAO,KAAK;IAAE;IAAM,QAAQ;IAAQ,CAAC;;SAEjC;AAKR,MAAK,MAAM,QAAQ,OAAO,KAAK,kBAAkB,EAAE;AACjD,MAAI,KAAK,IAAI,KAAK,CAAE;AACpB,SAAO,KAAK;GAAE;GAAM,QAAQ;GAAW,CAAC;;AAG1C,QAAO"}
@@ -0,0 +1,203 @@
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_promises = require("node:fs/promises");
5
+ let node_os = require("node:os");
6
+ node_os = require_chunk.__toESM(node_os, 1);
7
+ const BUILTIN_PLAYBOOKS = {
8
+ "trace-funds": `---
9
+ name: trace-funds
10
+ description: Trace stolen funds from a victim address to exchange deposits using Chain Insights GraphRAG
11
+ version: 1.0.0
12
+ params:
13
+ - name: address
14
+ type: string
15
+ required: true
16
+ - name: network
17
+ type: string
18
+ required: false
19
+ default: bittensor
20
+ ---
21
+
22
+ ## Step 1: Screen Victim Address
23
+
24
+ \`\`\`tool
25
+ address_risk
26
+ \`\`\`
27
+
28
+ \`\`\`params
29
+ address: {{address}}
30
+ network: {{network}}
31
+ \`\`\`
32
+
33
+ ## Step 2: Trace Funds To Exchanges
34
+
35
+ \`\`\`tool
36
+ track_funds
37
+ \`\`\`
38
+
39
+ \`\`\`params
40
+ trusted_addresses: {{address}}
41
+ network: {{network}}
42
+ \`\`\`
43
+ `,
44
+ "scam-topology": `---
45
+ name: scam-topology
46
+ description: Build laundering topology and scam labels from a victim incident
47
+ version: 1.0.0
48
+ params:
49
+ - name: address
50
+ type: string
51
+ required: true
52
+ - name: incident_timestamp_ms
53
+ type: string
54
+ required: true
55
+ - name: network
56
+ type: string
57
+ required: false
58
+ default: bittensor
59
+ - name: activity_policy
60
+ type: string
61
+ required: false
62
+ default: node_relative_only
63
+ ---
64
+
65
+ ## Step 1: Screen Known Scam Address
66
+
67
+ \`\`\`tool
68
+ address_risk
69
+ \`\`\`
70
+
71
+ \`\`\`params
72
+ address: {{address}}
73
+ network: {{network}}
74
+ \`\`\`
75
+
76
+ ## Step 2: Build Scam Topology
77
+
78
+ The victim-only traversal is outward from victim/source funds. The
79
+ primary traversal is a node-relative novelty wave: each new node expands only
80
+ once, repeated targets remain as non-expanding convergence context, and
81
+ downstream edges must be active at or after the current node's wave-arrival
82
+ timestamp. Set activity_policy to global_incident_only to filter every wave
83
+ against incident_timestamp_ms instead. Exchange terminal safety stops exchange
84
+ endpoints, and label candidates are reviewable, not automatic writes.
85
+ Contract summary: victim-only traversal is outward from victim/source funds;
86
+ node-relative novelty wave by default; global_incident_only is available;
87
+ exchange terminal safety; scam_labels are ML-ready flags.
88
+
89
+ \`\`\`tool
90
+ scam_topology
91
+ \`\`\`
92
+
93
+ \`\`\`params
94
+ victim_address: {{address}}
95
+ incident_timestamp_ms: {{incident_timestamp_ms}}
96
+ network: {{network}}
97
+ activity_policy: {{activity_policy}}
98
+ \`\`\`
99
+ `,
100
+ "risk-check": `---
101
+ name: risk-check
102
+ description: Screen an address for Chain Insights risk, behavior, counterparties, exchange connections, and AML patterns
103
+ version: 1.0.0
104
+ params:
105
+ - name: address
106
+ type: string
107
+ required: true
108
+ - name: network
109
+ type: string
110
+ required: false
111
+ default: bittensor
112
+ ---
113
+
114
+ ## Step 1: Address Risk
115
+
116
+ \`\`\`tool
117
+ address_risk
118
+ \`\`\`
119
+
120
+ \`\`\`params
121
+ address: {{address}}
122
+ network: {{network}}
123
+ \`\`\`
124
+ `,
125
+ "entity-profile": `---
126
+ name: entity-profile
127
+ description: Build an entity profile from Chain Insights address identity, metrics, risk, counterparties, and labels
128
+ version: 1.0.0
129
+ params:
130
+ - name: address
131
+ type: string
132
+ required: true
133
+ - name: network
134
+ type: string
135
+ required: false
136
+ default: bittensor
137
+ ---
138
+
139
+ ## Step 1: Entity Profile
140
+
141
+ \`\`\`tool
142
+ address_risk
143
+ \`\`\`
144
+
145
+ \`\`\`params
146
+ address: {{address}}
147
+ network: {{network}}
148
+ \`\`\`
149
+ `
150
+ };
151
+ //#endregion
152
+ //#region src/playbooks/resolver.ts
153
+ function userDir() {
154
+ return node_path.default.join(node_os.default.homedir(), ".chain-insights", "playbooks");
155
+ }
156
+ /**
157
+ * Resolve a playbook name to its markdown content.
158
+ * Checks user directory (~/.chain-insights/playbooks/<name>.md) first,
159
+ * then falls back to the built-in BUILTIN_PLAYBOOKS map.
160
+ * Security: sanitizes name to prevent path traversal (T-05-01).
161
+ */
162
+ async function resolvePlaybookContent(name) {
163
+ const safeName = name.replace(/[^a-z0-9_-]/gi, "");
164
+ if (!safeName) throw new Error(`Invalid playbook name: ${name}`);
165
+ const userPath = node_path.default.join(userDir(), `${safeName}.md`);
166
+ try {
167
+ return await (0, node_fs_promises.readFile)(userPath, "utf8");
168
+ } catch {}
169
+ const builtin = BUILTIN_PLAYBOOKS[safeName];
170
+ if (builtin !== void 0) return builtin;
171
+ throw new Error(`Playbook not found: "${safeName}". Run \`chain-insights playbook list\` to see available playbooks.`);
172
+ }
173
+ /**
174
+ * List all available playbooks — user dir first (overrides), then built-ins.
175
+ * Returns array of { name, source } objects.
176
+ */
177
+ async function listPlaybooks() {
178
+ const result = [];
179
+ const seen = /* @__PURE__ */ new Set();
180
+ try {
181
+ const userFiles = await (0, node_fs_promises.readdir)(userDir());
182
+ for (const file of userFiles) {
183
+ if (!file.endsWith(".md")) continue;
184
+ const name = file.slice(0, -3);
185
+ seen.add(name);
186
+ result.push({
187
+ name,
188
+ source: "user"
189
+ });
190
+ }
191
+ } catch {}
192
+ for (const name of Object.keys(BUILTIN_PLAYBOOKS)) {
193
+ if (seen.has(name)) continue;
194
+ result.push({
195
+ name,
196
+ source: "builtin"
197
+ });
198
+ }
199
+ return result;
200
+ }
201
+ //#endregion
202
+ exports.listPlaybooks = listPlaybooks;
203
+ exports.resolvePlaybookContent = resolvePlaybookContent;
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
@@ -0,0 +1,148 @@
1
+ require("./chunk-CZWwpsFl.cjs");
2
+ const require_version = require("./version-BNGtdpmH.cjs");
3
+ const require_config = require("./config-Bmdl5hdk.cjs");
4
+ const require_client = require("./client-D4fZgIaO.cjs");
5
+ const require_viz = require("./viz-ClezVXrJ.cjs");
6
+ const require_store = require("./store-BiUhQOIf.cjs");
7
+ const require_evidence = require("./evidence-BGcdKxuV.cjs");
8
+ let _modelcontextprotocol_sdk_client_index_js = require("@modelcontextprotocol/sdk/client/index.js");
9
+ let _modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
10
+ //#region src/playbooks/runner.ts
11
+ /** Sleep for ms milliseconds. */
12
+ function sleep(ms) {
13
+ return new Promise((resolve) => setTimeout(resolve, ms));
14
+ }
15
+ /** Check if an error is a timeout/abort error. */
16
+ function isTimeoutError(err) {
17
+ if (!(err instanceof Error)) return false;
18
+ return err.name === "AbortError" || err.code === "ECONNRESET";
19
+ }
20
+ /** Check if an error is a payment failure. */
21
+ function isPaymentError(err) {
22
+ if (!(err instanceof Error)) return false;
23
+ const msg = err.message.toLowerCase();
24
+ return msg.includes("http 402") || msg.includes("status 402") || msg.includes("payment required") || msg.includes("x402");
25
+ }
26
+ /**
27
+ * Call an MCP tool with retry logic on timeout (up to 3 total attempts).
28
+ * Returns the text result or throws on non-retryable error.
29
+ */
30
+ async function callWithRetry(client, toolName, params) {
31
+ const MAX_ATTEMPTS = 3;
32
+ let lastErr;
33
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) try {
34
+ return (await client.callTool({
35
+ name: toolName,
36
+ arguments: params
37
+ })).content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n");
38
+ } catch (err) {
39
+ if (isTimeoutError(err) && attempt < MAX_ATTEMPTS) {
40
+ lastErr = err;
41
+ await sleep(1e3);
42
+ continue;
43
+ }
44
+ throw err;
45
+ }
46
+ throw lastErr;
47
+ }
48
+ async function validateStepTools(client, steps) {
49
+ const result = await client.listTools();
50
+ const available = new Set(result.tools.map((tool) => tool.name));
51
+ const missing = [...new Set(steps.map((step) => step.tool).filter((tool) => !available.has(tool)))];
52
+ if (missing.length === 0) return;
53
+ const availableList = [...available].sort().join(", ") || "none";
54
+ throw new Error(`Unknown MCP tool(s) in playbook: ${missing.join(", ")}. Available tools: ${availableList}. Run \`chain-insights mcp tools --refresh\` to inspect the live MCP schema.`);
55
+ }
56
+ const PlaybookRunner = {
57
+ /**
58
+ * Execute a playbook definition step-by-step against the live MCP.
59
+ *
60
+ * @param playbook - Parsed and validated PlaybookDefinition
61
+ * @param opts - Runner options (caseId, from, dryRun, params)
62
+ */
63
+ async run(playbook, opts) {
64
+ const startIndex = (opts.from ?? 1) - 1;
65
+ const stepsToRun = playbook.steps.slice(startIndex);
66
+ const totalSteps = playbook.steps.length;
67
+ if (opts.dryRun) {
68
+ console.log(`Playbook: ${playbook.name} (dry run — no MCP calls)`);
69
+ console.log(`Steps: ${totalSteps} total, starting from ${startIndex + 1}`);
70
+ console.log("");
71
+ for (const step of stepsToRun) console.log(`Step ${step.index}/${totalSteps}: ${step.tool} (params: ${JSON.stringify(step.params)})`);
72
+ console.log("");
73
+ console.log("Cost: unknown (MCP pricing not available without live connection)");
74
+ return;
75
+ }
76
+ const config = await require_config.loadConfig();
77
+ const mcpFetch = await require_client.createConfiguredMcpFetch(config);
78
+ let caseId;
79
+ if (opts.caseId) caseId = (await require_store.CaseStore.get(opts.caseId)).id;
80
+ else {
81
+ caseId = (await require_store.CaseStore.create({
82
+ name: `quick-${playbook.name}-${Date.now()}`,
83
+ tags: [
84
+ "quick",
85
+ "playbook",
86
+ playbook.name
87
+ ],
88
+ description: `Auto-created for one-off playbook run: ${playbook.name}`
89
+ })).id;
90
+ console.log(`Created quick case: ${caseId}`);
91
+ }
92
+ const client = new _modelcontextprotocol_sdk_client_index_js.Client({
93
+ name: "chain-insights-playbook",
94
+ version: require_version.PACKAGE_VERSION
95
+ });
96
+ await client.connect(new _modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(config.mcpEndpoint), { fetch: mcpFetch }));
97
+ let evidenceCount = 0;
98
+ try {
99
+ await validateStepTools(client, stepsToRun);
100
+ for (const step of stepsToRun) {
101
+ console.log(`Step ${step.index}/${totalSteps}: ${step.label}...`);
102
+ let result;
103
+ try {
104
+ result = await callWithRetry(client, step.tool, step.params);
105
+ } catch (err) {
106
+ if (isPaymentError(err)) if (process.stdin.isTTY) {
107
+ const { createInterface } = await import("node:readline");
108
+ const rl = createInterface({
109
+ input: process.stdin,
110
+ output: process.stdout
111
+ });
112
+ const answer = await new Promise((resolve) => {
113
+ rl.question(`Payment required for step ${step.index}. (retry/skip/abort): `, resolve);
114
+ });
115
+ rl.close();
116
+ if (answer.trim().toLowerCase() === "retry") result = await callWithRetry(client, step.tool, step.params);
117
+ else if (answer.trim().toLowerCase() === "skip") {
118
+ console.log(`Step ${step.index} skipped.`);
119
+ continue;
120
+ } else throw new Error(`Aborted at step ${step.index} due to payment failure.`);
121
+ } else throw new Error(`Payment required for step ${step.index} but no interactive terminal available. Configure wallet with \`chain-insights config set walletPrivateKey <key>\`. Aborting.`);
122
+ else {
123
+ const completedMsg = step.index - 1 - startIndex > 0 ? `Completed: steps ${startIndex + 1}..${step.index - 1}.` : "No steps completed before failure.";
124
+ console.error(`Step ${step.index} failed: ${err.message}. ${completedMsg} Run with --from ${step.index} to resume.`);
125
+ throw err;
126
+ }
127
+ }
128
+ await require_evidence.EvidenceStore.append(caseId, {
129
+ source: step.tool,
130
+ content: result,
131
+ queryParams: JSON.stringify(step.params)
132
+ });
133
+ evidenceCount++;
134
+ console.log(` (${result.length} chars stored)`);
135
+ }
136
+ if (playbook.name === "trace-funds") try {
137
+ const viz = await require_viz.generateVisualization({ caseId });
138
+ console.log(`Visualization generated: ${viz.htmlPath}`);
139
+ } catch {
140
+ console.log("No transaction data to visualize.");
141
+ }
142
+ console.log(`Playbook complete. Case: ${caseId}. Evidence: ${evidenceCount} entries.`);
143
+ } finally {
144
+ await client.close();
145
+ }
146
+ } };
147
+ //#endregion
148
+ exports.PlaybookRunner = PlaybookRunner;