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
package/README.md CHANGED
@@ -46,14 +46,20 @@ npm install -g .
46
46
  cia --version
47
47
  ```
48
48
 
49
- Create an investigation workspace:
49
+ Create an investigation vault:
50
50
 
51
51
  ```bash
52
52
  mkdir -p ./chain-insights-investigations
53
53
  cd ./chain-insights-investigations
54
54
  cia init .
55
+ cia obsidian open .
55
56
  ```
56
57
 
58
+ Chain Insights workspaces are Obsidian-compatible vaults and plain local
59
+ folders. Obsidian is a first-class review UI, but it is not required to use the
60
+ workspace files. See the
61
+ [Obsidian vault workflow](docs/obsidian-vault.md).
62
+
57
63
  ## Configure GraphRAG MCP Endpoint
58
64
 
59
65
  `cia` uses `graphMcpEndpoint` for all GraphRAG MCP calls. The npm package does
@@ -114,8 +120,11 @@ GraphRAG MCP endpoint.
114
120
  Hosted GraphRAG MCP lets new users try `graph_query` with a small public free
115
121
  quota before setting up paid access. The default public free graph_query quota
116
122
  is 10 execution seconds per IP per UTC day. Use `usage_status` to see the
117
- current caller quota. When the free quota is exhausted, prepare a wallet or use
118
- an invited tester access key and retry.
123
+ current caller quota. Prepared wallet users receive the daily public grant
124
+ first, then paid access continues automatically after the grant is exhausted.
125
+ If you do not have a prepared wallet yet, use bounded single `graph_query`
126
+ calls for the demo, then prepare a wallet or use an invited tester access key
127
+ when the quota is exhausted.
119
128
 
120
129
  Open a case and run a small investigation:
121
130
 
@@ -137,9 +146,11 @@ cia case show 1
137
146
  find reports cases -maxdepth 3 -type f | sort
138
147
  ```
139
148
 
140
- ## Export To Obsidian, LLM Wiki, And Agents
149
+ ## Export Only When Sharing
141
150
 
142
- After a case has evidence, export a local knowledge bundle:
151
+ Normal local work happens in the investigation vault. Export only when you need
152
+ to share a case, hand it off to a partner, ingest it into LLM Wiki, or archive a
153
+ review checkpoint.
143
154
 
144
155
  ```bash
145
156
  cia case evidence verify 1
@@ -150,10 +161,11 @@ The export writes Markdown notes, `manifest.chain-insights.json`,
150
161
  `graph.chain-insights.json`, `Graph.canvas`, LLM Wiki entrypoints, and prompts
151
162
  for Codex, Claude Code, and ChatGPT under `published/<case-slug>/`.
152
163
 
153
- Private exports may include full addresses. Use `--mode public` only for
154
- shareable demos; public mode aliases addresses and removes secrets by default.
155
- Install and opening steps live in
156
- [Knowledge exports](docs/knowledge-exports.md).
164
+ Private exports may include full addresses. Use `--mode partner` for controlled
165
+ handoff after review. Use `--mode public` only for shareable demos; public mode
166
+ aliases addresses and removes secrets by default. Vault workflow guidance lives
167
+ in [Obsidian vault workflow](docs/obsidian-vault.md); export bundle details
168
+ live in [Knowledge exports](docs/knowledge-exports.md).
157
169
 
158
170
  ## Demo
159
171
 
@@ -173,6 +185,10 @@ cia mcp call graph_query_batch \
173
185
  '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"}]'
174
186
  ```
175
187
 
188
+ For no-wallet public demos, prefer the single-query example first. Batch calls
189
+ reserve worst-case execution time and can ask for paid access even when a small
190
+ free balance remains.
191
+
176
192
  Run suspect topology without requiring an incident timestamp:
177
193
 
178
194
  ```bash
@@ -249,8 +265,9 @@ reports under the workspace instead of embedding large payloads in case notes.
249
265
  | Doc | Use it for |
250
266
  | --- | --- |
251
267
  | [Graph tools](docs/graph-tools.md) | GraphRAG MCP layers, `graph_query`, `graph_query_batch`, AML tool contracts, graph reports, evidence pointers |
252
- | [Investigation workspaces](docs/investigation-workspaces.md) | `cia init`, case layout, evidence, dossiers, imports, templates, sessions, reports |
253
- | [Knowledge exports](docs/knowledge-exports.md) | Install Obsidian and LLM Wiki, export verified cases, open vaults, ingest agent-ready Markdown and graph JSON |
268
+ | [Obsidian vault workflow](docs/obsidian-vault.md) | Create an investigation vault, open Obsidian, refresh live notes, and use VS Code, Codex, Claude Code, and LLM Wiki overlays |
269
+ | [Investigation workspaces](docs/investigation-workspaces.md) | `cia init`, Obsidian-compatible vault layout, live note refresh, evidence, dossiers, imports, templates, sessions, reports |
270
+ | [Knowledge exports](docs/knowledge-exports.md) | Portable and redacted bundles for sharing, partner handoff, LLM Wiki ingestion, and archive |
254
271
  | [MCP proxy](docs/mcp-proxy.md) | Stdio proxy behavior, endpoint configuration, agent installers, local tools, auth modes, Inspector validation |
255
272
  | [Architecture](docs/architecture.md) | Product layers, data flow, local storage, security model, config keys |
256
273
  | [Development](docs/development.md) | Build, test, and local install commands |
@@ -0,0 +1,203 @@
1
+ import path from "node:path";
2
+ import { lstat, mkdir, writeFile } from "node:fs/promises";
3
+ import * as z from "zod";
4
+ import { createHash } from "node:crypto";
5
+ //#region src/export/paths.ts
6
+ function safeSlug(value) {
7
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "case-export";
8
+ }
9
+ function safeFilename(value) {
10
+ const parsed = path.parse(value);
11
+ return `${safeSlug(parsed.name)}${parsed.ext.toLowerCase().replace(/[^.a-z0-9]/g, "") || ".md"}`;
12
+ }
13
+ function assertInsideDirectory(root, candidate) {
14
+ const resolvedRoot = path.resolve(root);
15
+ const resolvedCandidate = path.resolve(candidate);
16
+ const relative = path.relative(resolvedRoot, resolvedCandidate);
17
+ if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) return;
18
+ throw new Error(`Refusing to write outside export directory: ${candidate}`);
19
+ }
20
+ async function assertNoSymlink(filePath) {
21
+ try {
22
+ if ((await lstat(filePath)).isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${filePath}`);
23
+ } catch (err) {
24
+ if (err.code === "ENOENT") return;
25
+ throw err;
26
+ }
27
+ }
28
+ async function writePrivateFile(root, relativePath, content) {
29
+ const filePath = path.join(root, relativePath);
30
+ assertInsideDirectory(root, filePath);
31
+ await mkdir(path.dirname(filePath), {
32
+ recursive: true,
33
+ mode: 448
34
+ });
35
+ await assertNoSymlink(filePath);
36
+ await writeFile(filePath, content, { mode: 384 });
37
+ const bytes = Buffer.byteLength(content, "utf8");
38
+ return {
39
+ path: relativePath,
40
+ sha256: createHash("sha256").update(content).digest("hex"),
41
+ bytes
42
+ };
43
+ }
44
+ //#endregion
45
+ //#region src/export/schema.ts
46
+ const CaseExportTargetSchema = z.enum(["obsidian-llmwiki"]);
47
+ const CaseExportModeSchema = z.enum([
48
+ "private",
49
+ "partner",
50
+ "public"
51
+ ]);
52
+ const caseIdRegex = /^\d{8}_\d{3}_[a-z0-9][a-z0-9-]*$/;
53
+ const CaseExportOptionsSchema = z.object({
54
+ caseId: z.string().regex(caseIdRegex),
55
+ target: CaseExportTargetSchema.default("obsidian-llmwiki"),
56
+ mode: CaseExportModeSchema.default("private"),
57
+ outputDir: z.string().optional()
58
+ });
59
+ const ExportedFileSchema = z.object({
60
+ path: z.string().min(1),
61
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
62
+ bytes: z.number().int().nonnegative()
63
+ });
64
+ const CaseExportManifestSchema = z.object({
65
+ schema: z.literal("chain-insights.case_export.v1"),
66
+ case_id: z.string().regex(caseIdRegex),
67
+ case_name: z.string().min(1),
68
+ exported_at: z.string().datetime(),
69
+ mode: CaseExportModeSchema,
70
+ target: CaseExportTargetSchema,
71
+ source_workspace: z.string().min(1),
72
+ verification: z.object({
73
+ evidence_manifest_verified: z.boolean(),
74
+ verified_at: z.string().datetime(),
75
+ evidence_count: z.number().int().nonnegative()
76
+ }),
77
+ files: z.array(ExportedFileSchema),
78
+ redactions: z.array(z.string()),
79
+ warnings: z.array(z.string())
80
+ });
81
+ const JsonCanvasNodeSchema = z.object({
82
+ id: z.string().min(1),
83
+ type: z.enum([
84
+ "text",
85
+ "file",
86
+ "link",
87
+ "group"
88
+ ]),
89
+ x: z.number(),
90
+ y: z.number(),
91
+ width: z.number().positive(),
92
+ height: z.number().positive(),
93
+ text: z.string().optional(),
94
+ file: z.string().optional(),
95
+ url: z.string().optional(),
96
+ label: z.string().optional(),
97
+ color: z.string().optional()
98
+ });
99
+ const JsonCanvasEdgeSchema = z.object({
100
+ id: z.string().min(1),
101
+ fromNode: z.string().min(1),
102
+ toNode: z.string().min(1),
103
+ fromSide: z.enum([
104
+ "top",
105
+ "right",
106
+ "bottom",
107
+ "left"
108
+ ]).optional(),
109
+ toSide: z.enum([
110
+ "top",
111
+ "right",
112
+ "bottom",
113
+ "left"
114
+ ]).optional(),
115
+ toEnd: z.enum(["none", "arrow"]).optional(),
116
+ label: z.string().optional(),
117
+ color: z.string().optional()
118
+ });
119
+ const JsonCanvasSchema = z.object({
120
+ nodes: z.array(JsonCanvasNodeSchema),
121
+ edges: z.array(JsonCanvasEdgeSchema)
122
+ });
123
+ //#endregion
124
+ //#region src/export/canvas.ts
125
+ function roleColor(roles) {
126
+ if (roles.includes("victim")) return "1";
127
+ if (roles.includes("suspect") || roles.includes("scam_candidate")) return "2";
128
+ if (roles.includes("deposit")) return "3";
129
+ if (roles.includes("exchange")) return "5";
130
+ if (roles.includes("service")) return "6";
131
+ return "#808080";
132
+ }
133
+ function nodeRoles(node) {
134
+ return Array.isArray(node["roles"]) ? node["roles"].map(String) : [];
135
+ }
136
+ function nodeLabel(node) {
137
+ return String(node["address"] ?? node["id"] ?? "unknown");
138
+ }
139
+ function graphNodeId(node, index) {
140
+ return String(node["id"] ?? node["address"] ?? `node-${index + 1}`);
141
+ }
142
+ function entityNotePath(entityId) {
143
+ return `Entities/${safeFilename(entityId)}`;
144
+ }
145
+ function graphToCanvas(graph) {
146
+ const nodes = [{
147
+ id: "case",
148
+ type: "file",
149
+ file: "Case.md",
150
+ x: 0,
151
+ y: 0,
152
+ width: 360,
153
+ height: 120,
154
+ color: "4"
155
+ }];
156
+ const nodeIdMap = /* @__PURE__ */ new Map();
157
+ graph.nodes.forEach((node, index) => {
158
+ const rawId = graphNodeId(node, index);
159
+ const canvasId = `entity-${index + 1}`;
160
+ nodeIdMap.set(rawId, canvasId);
161
+ nodes.push({
162
+ id: canvasId,
163
+ type: "file",
164
+ file: entityNotePath(rawId),
165
+ x: 420 + index % 4 * 340,
166
+ y: Math.floor(index / 4) * 220,
167
+ width: 300,
168
+ height: 120,
169
+ color: roleColor(nodeRoles(node))
170
+ });
171
+ });
172
+ const edges = graph.edges.flatMap((edge, index) => {
173
+ const from = nodeIdMap.get(String(edge["source"] ?? ""));
174
+ const to = nodeIdMap.get(String(edge["target"] ?? ""));
175
+ if (!from || !to) return [];
176
+ return [{
177
+ id: `edge-${index + 1}`,
178
+ fromNode: from,
179
+ toNode: to,
180
+ fromSide: "right",
181
+ toSide: "left",
182
+ toEnd: "arrow",
183
+ label: String(edge["edge_type"] ?? "related_to")
184
+ }];
185
+ });
186
+ for (const [index, node] of graph.nodes.entries()) edges.push({
187
+ id: `case-link-${index + 1}`,
188
+ fromNode: "case",
189
+ toNode: `entity-${index + 1}`,
190
+ fromSide: "right",
191
+ toSide: "left",
192
+ toEnd: "arrow",
193
+ label: nodeLabel(node)
194
+ });
195
+ return JsonCanvasSchema.parse({
196
+ nodes,
197
+ edges
198
+ });
199
+ }
200
+ //#endregion
201
+ export { CaseExportOptionsSchema as a, writePrivateFile as c, CaseExportManifestSchema as i, graphNodeId as n, safeFilename as o, graphToCanvas as r, safeSlug as s, entityNotePath as t };
202
+
203
+ //# sourceMappingURL=canvas-Cn-maEIh.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-Cn-maEIh.mjs","names":[],"sources":["../src/export/paths.ts","../src/export/schema.ts","../src/export/canvas.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { lstat, mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport function safeSlug(value: string): string {\n const slug = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80)\n return slug || 'case-export'\n}\n\nexport function safeFilename(value: string): string {\n const parsed = path.parse(value)\n const name = safeSlug(parsed.name)\n const ext = parsed.ext.toLowerCase().replace(/[^.a-z0-9]/g, '')\n return `${name}${ext || '.md'}`\n}\n\nexport function assertInsideDirectory(root: string, candidate: string): void {\n const resolvedRoot = path.resolve(root)\n const resolvedCandidate = path.resolve(candidate)\n const relative = path.relative(resolvedRoot, resolvedCandidate)\n if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) return\n throw new Error(`Refusing to write outside export directory: ${candidate}`)\n}\n\nexport async function assertNoSymlink(filePath: string): Promise<void> {\n try {\n const stat = await lstat(filePath)\n if (stat.isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${filePath}`)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return\n throw err\n }\n}\n\nexport async function writePrivateFile(\n root: string,\n relativePath: string,\n content: string,\n): Promise<{ path: string; sha256: string; bytes: number }> {\n const filePath = path.join(root, relativePath)\n assertInsideDirectory(root, filePath)\n await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 })\n await assertNoSymlink(filePath)\n await writeFile(filePath, content, { mode: 0o600 })\n const bytes = Buffer.byteLength(content, 'utf8')\n const sha256 = createHash('sha256').update(content).digest('hex')\n return { path: relativePath, sha256, bytes }\n}\n","import * as z from 'zod'\n\nexport const CaseExportTargetSchema = z.enum(['obsidian-llmwiki'])\nexport type CaseExportTarget = z.infer<typeof CaseExportTargetSchema>\n\nexport const CaseExportModeSchema = z.enum(['private', 'partner', 'public'])\nexport type CaseExportMode = z.infer<typeof CaseExportModeSchema>\n\nconst caseIdRegex = /^\\d{8}_\\d{3}_[a-z0-9][a-z0-9-]*$/\n\nexport const CaseExportOptionsSchema = z.object({\n caseId: z.string().regex(caseIdRegex),\n target: CaseExportTargetSchema.default('obsidian-llmwiki'),\n mode: CaseExportModeSchema.default('private'),\n outputDir: z.string().optional(),\n})\nexport type CaseExportOptions = z.infer<typeof CaseExportOptionsSchema>\n\nexport const ExportedFileSchema = z.object({\n path: z.string().min(1),\n sha256: z.string().regex(/^[a-f0-9]{64}$/),\n bytes: z.number().int().nonnegative(),\n})\nexport type ExportedFile = z.infer<typeof ExportedFileSchema>\n\nexport const CaseExportManifestSchema = z.object({\n schema: z.literal('chain-insights.case_export.v1'),\n case_id: z.string().regex(caseIdRegex),\n case_name: z.string().min(1),\n exported_at: z.string().datetime(),\n mode: CaseExportModeSchema,\n target: CaseExportTargetSchema,\n source_workspace: z.string().min(1),\n verification: z.object({\n evidence_manifest_verified: z.boolean(),\n verified_at: z.string().datetime(),\n evidence_count: z.number().int().nonnegative(),\n }),\n files: z.array(ExportedFileSchema),\n redactions: z.array(z.string()),\n warnings: z.array(z.string()),\n})\nexport type CaseExportManifest = z.infer<typeof CaseExportManifestSchema>\n\nexport const JsonCanvasNodeSchema = z.object({\n id: z.string().min(1),\n type: z.enum(['text', 'file', 'link', 'group']),\n x: z.number(),\n y: z.number(),\n width: z.number().positive(),\n height: z.number().positive(),\n text: z.string().optional(),\n file: z.string().optional(),\n url: z.string().optional(),\n label: z.string().optional(),\n color: z.string().optional(),\n})\n\nexport const JsonCanvasEdgeSchema = z.object({\n id: z.string().min(1),\n fromNode: z.string().min(1),\n toNode: z.string().min(1),\n fromSide: z.enum(['top', 'right', 'bottom', 'left']).optional(),\n toSide: z.enum(['top', 'right', 'bottom', 'left']).optional(),\n toEnd: z.enum(['none', 'arrow']).optional(),\n label: z.string().optional(),\n color: z.string().optional(),\n})\n\nexport const JsonCanvasSchema = z.object({\n nodes: z.array(JsonCanvasNodeSchema),\n edges: z.array(JsonCanvasEdgeSchema),\n})\nexport type JsonCanvas = z.infer<typeof JsonCanvasSchema>\n\nexport type CaseExportResult = {\n manifestPath: string\n outputDir: string\n fileCount: number\n warnings: string[]\n nextFile: string\n}\n","import { safeFilename } from './paths.js'\nimport { JsonCanvasSchema, type JsonCanvas } from './schema.js'\n\nfunction roleColor(roles: string[]): string {\n if (roles.includes('victim')) return '1'\n if (roles.includes('suspect') || roles.includes('scam_candidate')) return '2'\n if (roles.includes('deposit')) return '3'\n if (roles.includes('exchange')) return '5'\n if (roles.includes('service')) return '6'\n return '#808080'\n}\n\nfunction nodeRoles(node: Record<string, unknown>): string[] {\n return Array.isArray(node['roles']) ? node['roles'].map(String) : []\n}\n\nfunction nodeLabel(node: Record<string, unknown>): string {\n return String(node['address'] ?? node['id'] ?? 'unknown')\n}\n\nexport function graphNodeId(node: Record<string, unknown>, index: number): string {\n return String(node['id'] ?? node['address'] ?? `node-${index + 1}`)\n}\n\nexport function entityNotePath(entityId: string): string {\n return `Entities/${safeFilename(entityId)}`\n}\n\nexport function graphToCanvas(graph: { nodes: Record<string, unknown>[]; edges: Record<string, unknown>[] }): JsonCanvas {\n const nodes = [\n {\n id: 'case',\n type: 'file' as const,\n file: 'Case.md',\n x: 0,\n y: 0,\n width: 360,\n height: 120,\n color: '4',\n },\n ]\n\n const nodeIdMap = new Map<string, string>()\n graph.nodes.forEach((node, index) => {\n const rawId = graphNodeId(node, index)\n const canvasId = `entity-${index + 1}`\n nodeIdMap.set(rawId, canvasId)\n nodes.push({\n id: canvasId,\n type: 'file' as const,\n file: entityNotePath(rawId),\n x: 420 + (index % 4) * 340,\n y: Math.floor(index / 4) * 220,\n width: 300,\n height: 120,\n color: roleColor(nodeRoles(node)),\n })\n })\n\n const edges = graph.edges.flatMap((edge, index) => {\n const from = nodeIdMap.get(String(edge['source'] ?? ''))\n const to = nodeIdMap.get(String(edge['target'] ?? ''))\n if (!from || !to) return []\n return [{\n id: `edge-${index + 1}`,\n fromNode: from,\n toNode: to,\n fromSide: 'right' as const,\n toSide: 'left' as const,\n toEnd: 'arrow' as const,\n label: String(edge['edge_type'] ?? 'related_to'),\n }]\n })\n\n for (const [index, node] of graph.nodes.entries()) {\n edges.push({\n id: `case-link-${index + 1}`,\n fromNode: 'case',\n toNode: `entity-${index + 1}`,\n fromSide: 'right' as const,\n toSide: 'left' as const,\n toEnd: 'arrow' as const,\n label: nodeLabel(node),\n })\n }\n\n return JsonCanvasSchema.parse({ nodes, edges })\n}\n"],"mappings":"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"}
@@ -0,0 +1,251 @@
1
+ const require_chunk = require("./chunk-DakpK96I.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 zod = require("zod");
6
+ zod = require_chunk.__toESM(zod, 1);
7
+ let node_crypto = require("node:crypto");
8
+ //#region src/export/paths.ts
9
+ function safeSlug(value) {
10
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "case-export";
11
+ }
12
+ function safeFilename(value) {
13
+ const parsed = node_path.default.parse(value);
14
+ return `${safeSlug(parsed.name)}${parsed.ext.toLowerCase().replace(/[^.a-z0-9]/g, "") || ".md"}`;
15
+ }
16
+ function assertInsideDirectory(root, candidate) {
17
+ const resolvedRoot = node_path.default.resolve(root);
18
+ const resolvedCandidate = node_path.default.resolve(candidate);
19
+ const relative = node_path.default.relative(resolvedRoot, resolvedCandidate);
20
+ if (relative === "" || !relative.startsWith("..") && !node_path.default.isAbsolute(relative)) return;
21
+ throw new Error(`Refusing to write outside export directory: ${candidate}`);
22
+ }
23
+ async function assertNoSymlink(filePath) {
24
+ try {
25
+ if ((await (0, node_fs_promises.lstat)(filePath)).isSymbolicLink()) throw new Error(`Refusing to write through symlink: ${filePath}`);
26
+ } catch (err) {
27
+ if (err.code === "ENOENT") return;
28
+ throw err;
29
+ }
30
+ }
31
+ async function writePrivateFile(root, relativePath, content) {
32
+ const filePath = node_path.default.join(root, relativePath);
33
+ assertInsideDirectory(root, filePath);
34
+ await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), {
35
+ recursive: true,
36
+ mode: 448
37
+ });
38
+ await assertNoSymlink(filePath);
39
+ await (0, node_fs_promises.writeFile)(filePath, content, { mode: 384 });
40
+ const bytes = Buffer.byteLength(content, "utf8");
41
+ return {
42
+ path: relativePath,
43
+ sha256: (0, node_crypto.createHash)("sha256").update(content).digest("hex"),
44
+ bytes
45
+ };
46
+ }
47
+ //#endregion
48
+ //#region src/export/schema.ts
49
+ const CaseExportTargetSchema = zod.enum(["obsidian-llmwiki"]);
50
+ const CaseExportModeSchema = zod.enum([
51
+ "private",
52
+ "partner",
53
+ "public"
54
+ ]);
55
+ const caseIdRegex = /^\d{8}_\d{3}_[a-z0-9][a-z0-9-]*$/;
56
+ const CaseExportOptionsSchema = zod.object({
57
+ caseId: zod.string().regex(caseIdRegex),
58
+ target: CaseExportTargetSchema.default("obsidian-llmwiki"),
59
+ mode: CaseExportModeSchema.default("private"),
60
+ outputDir: zod.string().optional()
61
+ });
62
+ const ExportedFileSchema = zod.object({
63
+ path: zod.string().min(1),
64
+ sha256: zod.string().regex(/^[a-f0-9]{64}$/),
65
+ bytes: zod.number().int().nonnegative()
66
+ });
67
+ const CaseExportManifestSchema = zod.object({
68
+ schema: zod.literal("chain-insights.case_export.v1"),
69
+ case_id: zod.string().regex(caseIdRegex),
70
+ case_name: zod.string().min(1),
71
+ exported_at: zod.string().datetime(),
72
+ mode: CaseExportModeSchema,
73
+ target: CaseExportTargetSchema,
74
+ source_workspace: zod.string().min(1),
75
+ verification: zod.object({
76
+ evidence_manifest_verified: zod.boolean(),
77
+ verified_at: zod.string().datetime(),
78
+ evidence_count: zod.number().int().nonnegative()
79
+ }),
80
+ files: zod.array(ExportedFileSchema),
81
+ redactions: zod.array(zod.string()),
82
+ warnings: zod.array(zod.string())
83
+ });
84
+ const JsonCanvasNodeSchema = zod.object({
85
+ id: zod.string().min(1),
86
+ type: zod.enum([
87
+ "text",
88
+ "file",
89
+ "link",
90
+ "group"
91
+ ]),
92
+ x: zod.number(),
93
+ y: zod.number(),
94
+ width: zod.number().positive(),
95
+ height: zod.number().positive(),
96
+ text: zod.string().optional(),
97
+ file: zod.string().optional(),
98
+ url: zod.string().optional(),
99
+ label: zod.string().optional(),
100
+ color: zod.string().optional()
101
+ });
102
+ const JsonCanvasEdgeSchema = zod.object({
103
+ id: zod.string().min(1),
104
+ fromNode: zod.string().min(1),
105
+ toNode: zod.string().min(1),
106
+ fromSide: zod.enum([
107
+ "top",
108
+ "right",
109
+ "bottom",
110
+ "left"
111
+ ]).optional(),
112
+ toSide: zod.enum([
113
+ "top",
114
+ "right",
115
+ "bottom",
116
+ "left"
117
+ ]).optional(),
118
+ toEnd: zod.enum(["none", "arrow"]).optional(),
119
+ label: zod.string().optional(),
120
+ color: zod.string().optional()
121
+ });
122
+ const JsonCanvasSchema = zod.object({
123
+ nodes: zod.array(JsonCanvasNodeSchema),
124
+ edges: zod.array(JsonCanvasEdgeSchema)
125
+ });
126
+ //#endregion
127
+ //#region src/export/canvas.ts
128
+ function roleColor(roles) {
129
+ if (roles.includes("victim")) return "1";
130
+ if (roles.includes("suspect") || roles.includes("scam_candidate")) return "2";
131
+ if (roles.includes("deposit")) return "3";
132
+ if (roles.includes("exchange")) return "5";
133
+ if (roles.includes("service")) return "6";
134
+ return "#808080";
135
+ }
136
+ function nodeRoles(node) {
137
+ return Array.isArray(node["roles"]) ? node["roles"].map(String) : [];
138
+ }
139
+ function nodeLabel(node) {
140
+ return String(node["address"] ?? node["id"] ?? "unknown");
141
+ }
142
+ function graphNodeId(node, index) {
143
+ return String(node["id"] ?? node["address"] ?? `node-${index + 1}`);
144
+ }
145
+ function entityNotePath(entityId) {
146
+ return `Entities/${safeFilename(entityId)}`;
147
+ }
148
+ function graphToCanvas(graph) {
149
+ const nodes = [{
150
+ id: "case",
151
+ type: "file",
152
+ file: "Case.md",
153
+ x: 0,
154
+ y: 0,
155
+ width: 360,
156
+ height: 120,
157
+ color: "4"
158
+ }];
159
+ const nodeIdMap = /* @__PURE__ */ new Map();
160
+ graph.nodes.forEach((node, index) => {
161
+ const rawId = graphNodeId(node, index);
162
+ const canvasId = `entity-${index + 1}`;
163
+ nodeIdMap.set(rawId, canvasId);
164
+ nodes.push({
165
+ id: canvasId,
166
+ type: "file",
167
+ file: entityNotePath(rawId),
168
+ x: 420 + index % 4 * 340,
169
+ y: Math.floor(index / 4) * 220,
170
+ width: 300,
171
+ height: 120,
172
+ color: roleColor(nodeRoles(node))
173
+ });
174
+ });
175
+ const edges = graph.edges.flatMap((edge, index) => {
176
+ const from = nodeIdMap.get(String(edge["source"] ?? ""));
177
+ const to = nodeIdMap.get(String(edge["target"] ?? ""));
178
+ if (!from || !to) return [];
179
+ return [{
180
+ id: `edge-${index + 1}`,
181
+ fromNode: from,
182
+ toNode: to,
183
+ fromSide: "right",
184
+ toSide: "left",
185
+ toEnd: "arrow",
186
+ label: String(edge["edge_type"] ?? "related_to")
187
+ }];
188
+ });
189
+ for (const [index, node] of graph.nodes.entries()) edges.push({
190
+ id: `case-link-${index + 1}`,
191
+ fromNode: "case",
192
+ toNode: `entity-${index + 1}`,
193
+ fromSide: "right",
194
+ toSide: "left",
195
+ toEnd: "arrow",
196
+ label: nodeLabel(node)
197
+ });
198
+ return JsonCanvasSchema.parse({
199
+ nodes,
200
+ edges
201
+ });
202
+ }
203
+ //#endregion
204
+ Object.defineProperty(exports, "CaseExportManifestSchema", {
205
+ enumerable: true,
206
+ get: function() {
207
+ return CaseExportManifestSchema;
208
+ }
209
+ });
210
+ Object.defineProperty(exports, "CaseExportOptionsSchema", {
211
+ enumerable: true,
212
+ get: function() {
213
+ return CaseExportOptionsSchema;
214
+ }
215
+ });
216
+ Object.defineProperty(exports, "entityNotePath", {
217
+ enumerable: true,
218
+ get: function() {
219
+ return entityNotePath;
220
+ }
221
+ });
222
+ Object.defineProperty(exports, "graphNodeId", {
223
+ enumerable: true,
224
+ get: function() {
225
+ return graphNodeId;
226
+ }
227
+ });
228
+ Object.defineProperty(exports, "graphToCanvas", {
229
+ enumerable: true,
230
+ get: function() {
231
+ return graphToCanvas;
232
+ }
233
+ });
234
+ Object.defineProperty(exports, "safeFilename", {
235
+ enumerable: true,
236
+ get: function() {
237
+ return safeFilename;
238
+ }
239
+ });
240
+ Object.defineProperty(exports, "safeSlug", {
241
+ enumerable: true,
242
+ get: function() {
243
+ return safeSlug;
244
+ }
245
+ });
246
+ Object.defineProperty(exports, "writePrivateFile", {
247
+ enumerable: true,
248
+ get: function() {
249
+ return writePrivateFile;
250
+ }
251
+ });
@@ -0,0 +1,19 @@
1
+ const require_chunk = require("./chunk-DakpK96I.cjs");
2
+ const require_dossier = require("./dossier-BXy57V4-.cjs");
3
+ const require_store = require("./store-CQhU8dz8.cjs");
4
+ const require_evidence = require("./evidence-CvEesemA.cjs");
5
+ const require_session = require("./session-BT7VpbAd.cjs");
6
+ //#region src/cases/index.ts
7
+ var cases_exports = /* @__PURE__ */ require_chunk.__exportAll({
8
+ CaseStore: () => require_store.CaseStore,
9
+ DossierStore: () => require_dossier.DossierStore,
10
+ EvidenceStore: () => require_evidence.EvidenceStore,
11
+ SessionStore: () => require_session.SessionStore
12
+ });
13
+ //#endregion
14
+ Object.defineProperty(exports, "cases_exports", {
15
+ enumerable: true,
16
+ get: function() {
17
+ return cases_exports;
18
+ }
19
+ });
@@ -0,0 +1,16 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { t as DossierStore } from "./dossier-Bjpcbcxa.mjs";
3
+ import { t as CaseStore } from "./store-C2B_AssI.mjs";
4
+ import { t as EvidenceStore } from "./evidence-D96PTzOQ.mjs";
5
+ import { t as SessionStore } from "./session-DROyhebe.mjs";
6
+ //#region src/cases/index.ts
7
+ var cases_exports = /* @__PURE__ */ __exportAll({
8
+ CaseStore: () => CaseStore,
9
+ DossierStore: () => DossierStore,
10
+ EvidenceStore: () => EvidenceStore,
11
+ SessionStore: () => SessionStore
12
+ });
13
+ //#endregion
14
+ export { cases_exports as t };
15
+
16
+ //# sourceMappingURL=cases-TVcAifxu.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cases-TVcAifxu.mjs","names":[],"sources":["../src/cases/index.ts"],"sourcesContent":["// Stable public surface for the cases module.\nexport { CaseStore, generateCaseId } from './store.js'\nexport { parseFrontmatter, serializeFrontmatter } from './frontmatter.js'\nexport { CaseSchema, EvidenceSchema, DossierSchema, SessionSchema, CaseStatusEnum } from './schema.js'\nexport type { Case, Evidence, Dossier, Session, CaseStatus } from './schema.js'\nexport { EvidenceStore } from './evidence.js'\nexport { DossierStore } from './dossier.js'\nexport { SessionStore } from './session.js'\n"],"mappings":""}