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,211 +1,14 @@
1
- import { n as extractGraphFromCase } from "./data-extractor-DZUJu1Bz.mjs";
1
+ import { n as extractGraphFromCase } from "./data-extractor-B4nHw1wZ.mjs";
2
2
  import { t as parseFrontmatter } from "./frontmatter-D0ccQnUM.mjs";
3
3
  import { n as workspaceOutputPaths } from "./output-root-BRhzhhXZ.mjs";
4
4
  import { t as DossierStore } from "./dossier-Bjpcbcxa.mjs";
5
- import { t as CaseStore } from "./store-CTtqQtaE.mjs";
5
+ import { t as CaseStore } from "./store-C2B_AssI.mjs";
6
6
  import { t as EvidenceStore } from "./evidence-D96PTzOQ.mjs";
7
- import "./cases-Cp9DUbEV.mjs";
7
+ import "./cases-TVcAifxu.mjs";
8
+ import { a as CaseExportOptionsSchema, c as writePrivateFile, i as CaseExportManifestSchema, n as graphNodeId, o as safeFilename, r as graphToCanvas, s as safeSlug, t as entityNotePath } from "./canvas-Cn-maEIh.mjs";
8
9
  import { t as normalizeGraphPayload } from "./graph-normalizer-CXP06jKh.mjs";
9
10
  import path from "node:path";
10
- import { lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
11
- import * as z from "zod";
12
- import { createHash } from "node:crypto";
13
- //#region src/export/paths.ts
14
- function safeSlug(value) {
15
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "case-export";
16
- }
17
- function safeFilename(value) {
18
- const parsed = path.parse(value);
19
- return `${safeSlug(parsed.name)}${parsed.ext.toLowerCase().replace(/[^.a-z0-9]/g, "") || ".md"}`;
20
- }
21
- function assertInsideDirectory(root, candidate) {
22
- const resolvedRoot = path.resolve(root);
23
- const resolvedCandidate = path.resolve(candidate);
24
- const relative = path.relative(resolvedRoot, resolvedCandidate);
25
- if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) return;
26
- throw new Error(`Refusing to write outside export directory: ${candidate}`);
27
- }
28
- async function assertNoSymlink(filePath) {
29
- try {
30
- if ((await lstat(filePath)).isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${filePath}`);
31
- } catch (err) {
32
- if (err.code === "ENOENT") return;
33
- throw err;
34
- }
35
- }
36
- async function writePrivateFile(root, relativePath, content) {
37
- const filePath = path.join(root, relativePath);
38
- assertInsideDirectory(root, filePath);
39
- await mkdir(path.dirname(filePath), {
40
- recursive: true,
41
- mode: 448
42
- });
43
- await assertNoSymlink(filePath);
44
- await writeFile(filePath, content, { mode: 384 });
45
- const bytes = Buffer.byteLength(content, "utf8");
46
- return {
47
- path: relativePath,
48
- sha256: createHash("sha256").update(content).digest("hex"),
49
- bytes
50
- };
51
- }
52
- //#endregion
53
- //#region src/export/schema.ts
54
- const CaseExportTargetSchema = z.enum(["obsidian-llmwiki"]);
55
- const CaseExportModeSchema = z.enum([
56
- "private",
57
- "partner",
58
- "public"
59
- ]);
60
- const caseIdRegex = /^\d{8}_\d{3}_[a-z0-9][a-z0-9-]*$/;
61
- const CaseExportOptionsSchema = z.object({
62
- caseId: z.string().regex(caseIdRegex),
63
- target: CaseExportTargetSchema.default("obsidian-llmwiki"),
64
- mode: CaseExportModeSchema.default("private"),
65
- outputDir: z.string().optional()
66
- });
67
- const ExportedFileSchema = z.object({
68
- path: z.string().min(1),
69
- sha256: z.string().regex(/^[a-f0-9]{64}$/),
70
- bytes: z.number().int().nonnegative()
71
- });
72
- const CaseExportManifestSchema = z.object({
73
- schema: z.literal("chain-insights.case_export.v1"),
74
- case_id: z.string().regex(caseIdRegex),
75
- case_name: z.string().min(1),
76
- exported_at: z.string().datetime(),
77
- mode: CaseExportModeSchema,
78
- target: CaseExportTargetSchema,
79
- source_workspace: z.string().min(1),
80
- verification: z.object({
81
- evidence_manifest_verified: z.boolean(),
82
- verified_at: z.string().datetime(),
83
- evidence_count: z.number().int().nonnegative()
84
- }),
85
- files: z.array(ExportedFileSchema),
86
- redactions: z.array(z.string()),
87
- warnings: z.array(z.string())
88
- });
89
- const JsonCanvasNodeSchema = z.object({
90
- id: z.string().min(1),
91
- type: z.enum([
92
- "text",
93
- "file",
94
- "link",
95
- "group"
96
- ]),
97
- x: z.number(),
98
- y: z.number(),
99
- width: z.number().positive(),
100
- height: z.number().positive(),
101
- text: z.string().optional(),
102
- file: z.string().optional(),
103
- url: z.string().optional(),
104
- label: z.string().optional(),
105
- color: z.string().optional()
106
- });
107
- const JsonCanvasEdgeSchema = z.object({
108
- id: z.string().min(1),
109
- fromNode: z.string().min(1),
110
- toNode: z.string().min(1),
111
- fromSide: z.enum([
112
- "top",
113
- "right",
114
- "bottom",
115
- "left"
116
- ]).optional(),
117
- toSide: z.enum([
118
- "top",
119
- "right",
120
- "bottom",
121
- "left"
122
- ]).optional(),
123
- toEnd: z.enum(["none", "arrow"]).optional(),
124
- label: z.string().optional(),
125
- color: z.string().optional()
126
- });
127
- const JsonCanvasSchema = z.object({
128
- nodes: z.array(JsonCanvasNodeSchema),
129
- edges: z.array(JsonCanvasEdgeSchema)
130
- });
131
- //#endregion
132
- //#region src/export/canvas.ts
133
- function roleColor(roles) {
134
- if (roles.includes("victim")) return "1";
135
- if (roles.includes("suspect") || roles.includes("scam_candidate")) return "2";
136
- if (roles.includes("deposit")) return "3";
137
- if (roles.includes("exchange")) return "5";
138
- if (roles.includes("service")) return "6";
139
- return "#808080";
140
- }
141
- function nodeRoles(node) {
142
- return Array.isArray(node["roles"]) ? node["roles"].map(String) : [];
143
- }
144
- function nodeLabel(node) {
145
- return String(node["address"] ?? node["id"] ?? "unknown");
146
- }
147
- function graphNodeId(node, index) {
148
- return String(node["id"] ?? node["address"] ?? `node-${index + 1}`);
149
- }
150
- function entityNotePath(entityId) {
151
- return `Entities/${safeFilename(entityId)}`;
152
- }
153
- function graphToCanvas(graph) {
154
- const nodes = [{
155
- id: "case",
156
- type: "file",
157
- file: "Case.md",
158
- x: 0,
159
- y: 0,
160
- width: 360,
161
- height: 120,
162
- color: "4"
163
- }];
164
- const nodeIdMap = /* @__PURE__ */ new Map();
165
- graph.nodes.forEach((node, index) => {
166
- const rawId = graphNodeId(node, index);
167
- const canvasId = `entity-${index + 1}`;
168
- nodeIdMap.set(rawId, canvasId);
169
- nodes.push({
170
- id: canvasId,
171
- type: "file",
172
- file: entityNotePath(rawId),
173
- x: 420 + index % 4 * 340,
174
- y: Math.floor(index / 4) * 220,
175
- width: 300,
176
- height: 120,
177
- color: roleColor(nodeRoles(node))
178
- });
179
- });
180
- const edges = graph.edges.flatMap((edge, index) => {
181
- const from = nodeIdMap.get(String(edge["source"] ?? ""));
182
- const to = nodeIdMap.get(String(edge["target"] ?? ""));
183
- if (!from || !to) return [];
184
- return [{
185
- id: `edge-${index + 1}`,
186
- fromNode: from,
187
- toNode: to,
188
- fromSide: "right",
189
- toSide: "left",
190
- toEnd: "arrow",
191
- label: String(edge["edge_type"] ?? "related_to")
192
- }];
193
- });
194
- for (const [index, node] of graph.nodes.entries()) edges.push({
195
- id: `case-link-${index + 1}`,
196
- fromNode: "case",
197
- toNode: `entity-${index + 1}`,
198
- fromSide: "right",
199
- toSide: "left",
200
- toEnd: "arrow",
201
- label: nodeLabel(node)
202
- });
203
- return JsonCanvasSchema.parse({
204
- nodes,
205
- edges
206
- });
207
- }
208
- //#endregion
11
+ import { mkdir, readFile, readdir } from "node:fs/promises";
209
12
  //#region src/export/graph.ts
210
13
  function isRecord(value) {
211
14
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -283,7 +86,7 @@ function renderReadme(caseName) {
283
86
  return [
284
87
  `# ${caseName} Export`,
285
88
  "",
286
- "Open this directory as an Obsidian vault or give it to an LLMWiki-style knowledge workflow.",
89
+ "Open this directory as an Obsidian vault or give it to an LLM Wiki-style knowledge workflow.",
287
90
  "",
288
91
  "Start with:",
289
92
  "",
@@ -352,7 +155,7 @@ function renderAgentConsole(caseName) {
352
155
  }
353
156
  function renderLlmWiki() {
354
157
  return [
355
- "# LLMWiki Entry",
158
+ "# LLM Wiki Entry",
356
159
  "",
357
160
  "This directory is a Chain Insights case export.",
358
161
  "",
@@ -588,4 +391,4 @@ async function exportCase(rawOptions) {
588
391
  //#endregion
589
392
  export { exportCase };
590
393
 
591
- //# sourceMappingURL=export-BqTCO9lP.mjs.map
394
+ //# sourceMappingURL=export-CBhcJuZ6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-CBhcJuZ6.mjs","names":[],"sources":["../src/export/graph.ts","../src/export/markdown.ts","../src/export/redaction.ts","../src/export/index.ts"],"sourcesContent":["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 LLM Wiki-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 '# LLM Wiki 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,214 +1,16 @@
1
1
  const require_chunk = require("./chunk-DakpK96I.cjs");
2
- const require_data_extractor = require("./data-extractor-Cavd7wHk.cjs");
2
+ const require_data_extractor = require("./data-extractor-DS4rzy3M.cjs");
3
3
  const require_frontmatter = require("./frontmatter-Dvqa5HX6.cjs");
4
4
  const require_output_root = require("./output-root-YIbl6PwF.cjs");
5
5
  const require_dossier = require("./dossier-BXy57V4-.cjs");
6
- const require_store = require("./store-CqPfs47P.cjs");
6
+ const require_store = require("./store-CQhU8dz8.cjs");
7
7
  const require_evidence = require("./evidence-CvEesemA.cjs");
8
- require("./cases-sTY5aXav.cjs");
8
+ require("./cases-Bz_9XKEw.cjs");
9
+ const require_canvas = require("./canvas-p-oKCMjc.cjs");
9
10
  const require_graph_normalizer = require("./graph-normalizer-DbjlbMpz.cjs");
10
11
  let node_path = require("node:path");
11
12
  node_path = require_chunk.__toESM(node_path, 1);
12
13
  let node_fs_promises = require("node:fs/promises");
13
- let zod = require("zod");
14
- zod = require_chunk.__toESM(zod, 1);
15
- let node_crypto = require("node:crypto");
16
- //#region src/export/paths.ts
17
- function safeSlug(value) {
18
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "case-export";
19
- }
20
- function safeFilename(value) {
21
- const parsed = node_path.default.parse(value);
22
- return `${safeSlug(parsed.name)}${parsed.ext.toLowerCase().replace(/[^.a-z0-9]/g, "") || ".md"}`;
23
- }
24
- function assertInsideDirectory(root, candidate) {
25
- const resolvedRoot = node_path.default.resolve(root);
26
- const resolvedCandidate = node_path.default.resolve(candidate);
27
- const relative = node_path.default.relative(resolvedRoot, resolvedCandidate);
28
- if (relative === "" || !relative.startsWith("..") && !node_path.default.isAbsolute(relative)) return;
29
- throw new Error(`Refusing to write outside export directory: ${candidate}`);
30
- }
31
- async function assertNoSymlink(filePath) {
32
- try {
33
- if ((await (0, node_fs_promises.lstat)(filePath)).isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${filePath}`);
34
- } catch (err) {
35
- if (err.code === "ENOENT") return;
36
- throw err;
37
- }
38
- }
39
- async function writePrivateFile(root, relativePath, content) {
40
- const filePath = node_path.default.join(root, relativePath);
41
- assertInsideDirectory(root, filePath);
42
- await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), {
43
- recursive: true,
44
- mode: 448
45
- });
46
- await assertNoSymlink(filePath);
47
- await (0, node_fs_promises.writeFile)(filePath, content, { mode: 384 });
48
- const bytes = Buffer.byteLength(content, "utf8");
49
- return {
50
- path: relativePath,
51
- sha256: (0, node_crypto.createHash)("sha256").update(content).digest("hex"),
52
- bytes
53
- };
54
- }
55
- //#endregion
56
- //#region src/export/schema.ts
57
- const CaseExportTargetSchema = zod.enum(["obsidian-llmwiki"]);
58
- const CaseExportModeSchema = zod.enum([
59
- "private",
60
- "partner",
61
- "public"
62
- ]);
63
- const caseIdRegex = /^\d{8}_\d{3}_[a-z0-9][a-z0-9-]*$/;
64
- const CaseExportOptionsSchema = zod.object({
65
- caseId: zod.string().regex(caseIdRegex),
66
- target: CaseExportTargetSchema.default("obsidian-llmwiki"),
67
- mode: CaseExportModeSchema.default("private"),
68
- outputDir: zod.string().optional()
69
- });
70
- const ExportedFileSchema = zod.object({
71
- path: zod.string().min(1),
72
- sha256: zod.string().regex(/^[a-f0-9]{64}$/),
73
- bytes: zod.number().int().nonnegative()
74
- });
75
- const CaseExportManifestSchema = zod.object({
76
- schema: zod.literal("chain-insights.case_export.v1"),
77
- case_id: zod.string().regex(caseIdRegex),
78
- case_name: zod.string().min(1),
79
- exported_at: zod.string().datetime(),
80
- mode: CaseExportModeSchema,
81
- target: CaseExportTargetSchema,
82
- source_workspace: zod.string().min(1),
83
- verification: zod.object({
84
- evidence_manifest_verified: zod.boolean(),
85
- verified_at: zod.string().datetime(),
86
- evidence_count: zod.number().int().nonnegative()
87
- }),
88
- files: zod.array(ExportedFileSchema),
89
- redactions: zod.array(zod.string()),
90
- warnings: zod.array(zod.string())
91
- });
92
- const JsonCanvasNodeSchema = zod.object({
93
- id: zod.string().min(1),
94
- type: zod.enum([
95
- "text",
96
- "file",
97
- "link",
98
- "group"
99
- ]),
100
- x: zod.number(),
101
- y: zod.number(),
102
- width: zod.number().positive(),
103
- height: zod.number().positive(),
104
- text: zod.string().optional(),
105
- file: zod.string().optional(),
106
- url: zod.string().optional(),
107
- label: zod.string().optional(),
108
- color: zod.string().optional()
109
- });
110
- const JsonCanvasEdgeSchema = zod.object({
111
- id: zod.string().min(1),
112
- fromNode: zod.string().min(1),
113
- toNode: zod.string().min(1),
114
- fromSide: zod.enum([
115
- "top",
116
- "right",
117
- "bottom",
118
- "left"
119
- ]).optional(),
120
- toSide: zod.enum([
121
- "top",
122
- "right",
123
- "bottom",
124
- "left"
125
- ]).optional(),
126
- toEnd: zod.enum(["none", "arrow"]).optional(),
127
- label: zod.string().optional(),
128
- color: zod.string().optional()
129
- });
130
- const JsonCanvasSchema = zod.object({
131
- nodes: zod.array(JsonCanvasNodeSchema),
132
- edges: zod.array(JsonCanvasEdgeSchema)
133
- });
134
- //#endregion
135
- //#region src/export/canvas.ts
136
- function roleColor(roles) {
137
- if (roles.includes("victim")) return "1";
138
- if (roles.includes("suspect") || roles.includes("scam_candidate")) return "2";
139
- if (roles.includes("deposit")) return "3";
140
- if (roles.includes("exchange")) return "5";
141
- if (roles.includes("service")) return "6";
142
- return "#808080";
143
- }
144
- function nodeRoles(node) {
145
- return Array.isArray(node["roles"]) ? node["roles"].map(String) : [];
146
- }
147
- function nodeLabel(node) {
148
- return String(node["address"] ?? node["id"] ?? "unknown");
149
- }
150
- function graphNodeId(node, index) {
151
- return String(node["id"] ?? node["address"] ?? `node-${index + 1}`);
152
- }
153
- function entityNotePath(entityId) {
154
- return `Entities/${safeFilename(entityId)}`;
155
- }
156
- function graphToCanvas(graph) {
157
- const nodes = [{
158
- id: "case",
159
- type: "file",
160
- file: "Case.md",
161
- x: 0,
162
- y: 0,
163
- width: 360,
164
- height: 120,
165
- color: "4"
166
- }];
167
- const nodeIdMap = /* @__PURE__ */ new Map();
168
- graph.nodes.forEach((node, index) => {
169
- const rawId = graphNodeId(node, index);
170
- const canvasId = `entity-${index + 1}`;
171
- nodeIdMap.set(rawId, canvasId);
172
- nodes.push({
173
- id: canvasId,
174
- type: "file",
175
- file: entityNotePath(rawId),
176
- x: 420 + index % 4 * 340,
177
- y: Math.floor(index / 4) * 220,
178
- width: 300,
179
- height: 120,
180
- color: roleColor(nodeRoles(node))
181
- });
182
- });
183
- const edges = graph.edges.flatMap((edge, index) => {
184
- const from = nodeIdMap.get(String(edge["source"] ?? ""));
185
- const to = nodeIdMap.get(String(edge["target"] ?? ""));
186
- if (!from || !to) return [];
187
- return [{
188
- id: `edge-${index + 1}`,
189
- fromNode: from,
190
- toNode: to,
191
- fromSide: "right",
192
- toSide: "left",
193
- toEnd: "arrow",
194
- label: String(edge["edge_type"] ?? "related_to")
195
- }];
196
- });
197
- for (const [index, node] of graph.nodes.entries()) edges.push({
198
- id: `case-link-${index + 1}`,
199
- fromNode: "case",
200
- toNode: `entity-${index + 1}`,
201
- fromSide: "right",
202
- toSide: "left",
203
- toEnd: "arrow",
204
- label: nodeLabel(node)
205
- });
206
- return JsonCanvasSchema.parse({
207
- nodes,
208
- edges
209
- });
210
- }
211
- //#endregion
212
14
  //#region src/export/graph.ts
213
15
  function isRecord(value) {
214
16
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -286,7 +88,7 @@ function renderReadme(caseName) {
286
88
  return [
287
89
  `# ${caseName} Export`,
288
90
  "",
289
- "Open this directory as an Obsidian vault or give it to an LLMWiki-style knowledge workflow.",
91
+ "Open this directory as an Obsidian vault or give it to an LLM Wiki-style knowledge workflow.",
290
92
  "",
291
93
  "Start with:",
292
94
  "",
@@ -355,7 +157,7 @@ function renderAgentConsole(caseName) {
355
157
  }
356
158
  function renderLlmWiki() {
357
159
  return [
358
- "# LLMWiki Entry",
160
+ "# LLM Wiki Entry",
359
161
  "",
360
162
  "This directory is a Chain Insights case export.",
361
163
  "",
@@ -474,11 +276,11 @@ async function readEvidence(caseId) {
474
276
  }
475
277
  async function writeFiles(root, entries) {
476
278
  const written = [];
477
- for (const [relativePath, content] of entries) written.push(await writePrivateFile(root, relativePath, content));
279
+ for (const [relativePath, content] of entries) written.push(await require_canvas.writePrivateFile(root, relativePath, content));
478
280
  return written;
479
281
  }
480
282
  async function exportCase(rawOptions) {
481
- const options = CaseExportOptionsSchema.parse(rawOptions);
283
+ const options = require_canvas.CaseExportOptionsSchema.parse(rawOptions);
482
284
  const workspace = require_output_root.workspaceOutputPaths();
483
285
  const caseInfo = await require_store.CaseStore.get(options.caseId);
484
286
  const redactor = createRedactor(options.mode);
@@ -486,8 +288,8 @@ async function exportCase(rawOptions) {
486
288
  const evidenceDocs = await readEvidence(options.caseId);
487
289
  const dossiers = await require_dossier.DossierStore.listSummaries(options.caseId);
488
290
  const graph = redactor.value(await loadCaseExportGraph(options.caseId));
489
- const canvas = graphToCanvas(graph);
490
- const outputRoot = node_path.default.resolve(options.outputDir ?? node_path.default.join(workspace.root, "published", safeSlug(caseInfo.name)));
291
+ const canvas = require_canvas.graphToCanvas(graph);
292
+ const outputRoot = node_path.default.resolve(options.outputDir ?? node_path.default.join(workspace.root, "published", require_canvas.safeSlug(caseInfo.name)));
491
293
  await (0, node_fs_promises.mkdir)(outputRoot, {
492
294
  recursive: true,
493
295
  mode: 448
@@ -518,7 +320,7 @@ async function exportCase(rawOptions) {
518
320
  ["graph.chain-insights.json", JSON.stringify(graph, null, 2) + "\n"],
519
321
  ["Graph.canvas", JSON.stringify(canvas, null, 2) + "\n"]
520
322
  ];
521
- for (const evidence of evidenceDocs) entries.push([node_path.default.join("Evidence", safeFilename(evidence.id)), redactor.text([
323
+ for (const evidence of evidenceDocs) entries.push([node_path.default.join("Evidence", require_canvas.safeFilename(evidence.id)), redactor.text([
522
324
  `# Evidence: ${evidence.source}`,
523
325
  "",
524
326
  `Source file: \`${evidence.filename}\``,
@@ -530,7 +332,7 @@ async function exportCase(rawOptions) {
530
332
  const entityPaths = /* @__PURE__ */ new Set();
531
333
  for (const dossier of dossiers) {
532
334
  const entityId = options.mode === "public" ? redactor.aliasFor(dossier.address) : dossier.address;
533
- const entityPath = node_path.default.join("Entities", safeFilename(entityId));
335
+ const entityPath = node_path.default.join("Entities", require_canvas.safeFilename(entityId));
534
336
  entityPaths.add(entityPath);
535
337
  entries.push([entityPath, redactor.text([
536
338
  `# Entity: ${entityId}`,
@@ -543,8 +345,8 @@ async function exportCase(rawOptions) {
543
345
  ].join("\n"))]);
544
346
  }
545
347
  for (const [index, node] of graph.nodes.entries()) {
546
- const entityId = graphNodeId(node, index);
547
- const entityPath = entityNotePath(entityId);
348
+ const entityId = require_canvas.graphNodeId(node, index);
349
+ const entityPath = require_canvas.entityNotePath(entityId);
548
350
  if (entityPaths.has(entityPath)) continue;
549
351
  entityPaths.add(entityPath);
550
352
  entries.push([entityPath, [
@@ -562,7 +364,7 @@ async function exportCase(rawOptions) {
562
364
  }
563
365
  const files = await writeFiles(outputRoot, entries);
564
366
  const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
565
- const manifest = CaseExportManifestSchema.parse({
367
+ const manifest = require_canvas.CaseExportManifestSchema.parse({
566
368
  schema: "chain-insights.case_export.v1",
567
369
  case_id: caseInfo.id,
568
370
  case_name: redactor.text(caseInfo.name),
@@ -579,7 +381,7 @@ async function exportCase(rawOptions) {
579
381
  redactions: redactor.redactions(),
580
382
  warnings: evidenceVerification.ok ? [] : [`Evidence manifest failed: ${(evidenceVerification.tampered ?? []).join(", ")}`]
581
383
  });
582
- const manifestFile = await writePrivateFile(outputRoot, "manifest.chain-insights.json", JSON.stringify(manifest, null, 2) + "\n");
384
+ const manifestFile = await require_canvas.writePrivateFile(outputRoot, "manifest.chain-insights.json", JSON.stringify(manifest, null, 2) + "\n");
583
385
  return {
584
386
  manifestPath: node_path.default.join(outputRoot, manifestFile.path),
585
387
  outputDir: outputRoot,
package/dist/index.cjs CHANGED
@@ -6,7 +6,7 @@ const require_wallet = require("./wallet-gC2jxh7j.cjs");
6
6
  const require_tools = require("./tools-BhTI3Lmg.cjs");
7
7
  const require_topup_server = require("./topup-server-DhYlOOBM.cjs");
8
8
  const require_client = require("./client-Db6IV1tv.cjs");
9
- const require_viz = require("./viz-Dqp3C5kb.cjs");
9
+ const require_viz = require("./viz-D1620cBX.cjs");
10
10
  exports.buildTopupInfo = require_tools.buildTopupInfo;
11
11
  exports.createApp = require_app.createApp;
12
12
  exports.createMcpFetchClient = require_client.createMcpFetchClient;
package/dist/index.mjs CHANGED
@@ -5,5 +5,5 @@ import { a as setWalletPrivateKey, i as normalizeWalletPrivateKey, n as encryptK
5
5
  import { a as getWalletAccount, i as getBalanceUsdc, n as formatWalletBalance, o as getWalletBalanceText, r as getBalanceEth, t as buildTopupInfo } from "./tools-v6kcdojg.mjs";
6
6
  import { i as generateArtifactHtml, n as startTopupServer, t as getTopupUrl } from "./topup-server-R3dNp-p8.mjs";
7
7
  import { i as createMcpFetchClient } from "./client-D4JE7fFF.mjs";
8
- import { t as generateVisualization } from "./viz-5y24S5X1.mjs";
8
+ import { t as generateVisualization } from "./viz-DB5XFG1z.mjs";
9
9
  export { buildTopupInfo, createApp, createMcpFetchClient, decryptKey, encryptKey, formatWalletBalance, generateArtifactHtml, generateVisualization, getBalanceEth, getBalanceUsdc, getTopupUrl, getWalletAccount, getWalletBalanceText, isWalletConfigured, loadConfig, normalizeWalletPrivateKey, resetConfigCache, saveConfig, setWalletPrivateKey, startServer, startTopupServer, walletAddressFromPrivateKey };