chain-insights 0.3.3 → 0.3.5
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.
- package/README.md +20 -7
- package/dist/canvas-Cn-maEIh.mjs +203 -0
- package/dist/canvas-Cn-maEIh.mjs.map +1 -0
- package/dist/canvas-p-oKCMjc.cjs +251 -0
- package/dist/cases-Bz_9XKEw.cjs +19 -0
- package/dist/cases-TVcAifxu.mjs +16 -0
- package/dist/cases-TVcAifxu.mjs.map +1 -0
- package/dist/cli.cjs +74 -28
- package/dist/cli.mjs +74 -28
- package/dist/cli.mjs.map +1 -1
- package/dist/{data-extractor-DZUJu1Bz.mjs → data-extractor-B4nHw1wZ.mjs} +2 -2
- package/dist/{data-extractor-DZUJu1Bz.mjs.map → data-extractor-B4nHw1wZ.mjs.map} +1 -1
- package/dist/{data-extractor-Cavd7wHk.cjs → data-extractor-DS4rzy3M.cjs} +1 -1
- package/dist/{export-BqTCO9lP.mjs → export-CBhcJuZ6.mjs} +8 -205
- package/dist/export-CBhcJuZ6.mjs.map +1 -0
- package/dist/{export-DsXgtCwO.cjs → export-D4v4-6F4.cjs} +16 -214
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{init-DLBL_nVG.mjs → init-CKQ6F07J.mjs} +22 -5
- package/dist/init-CKQ6F07J.mjs.map +1 -0
- package/dist/{init-zqbd7i-_.cjs → init-Dhw8F23z.cjs} +21 -4
- package/dist/mcp-proxy.cjs +20 -20
- package/dist/mcp-proxy.mjs +20 -20
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-wJoAFDFa.mjs → public-tools-CyUZEz9B.mjs} +3 -3
- package/dist/{public-tools-wJoAFDFa.mjs.map → public-tools-CyUZEz9B.mjs.map} +1 -1
- package/dist/{public-tools-BvMb3H2P.cjs → public-tools-xfVNz9NE.cjs} +2 -2
- package/dist/{runner-BhZ4lnF1.cjs → runner-CVo41fjz.cjs} +2 -2
- package/dist/{runner-DIJSbkjc.mjs → runner-DWuSy1Se.mjs} +3 -3
- package/dist/{runner-DIJSbkjc.mjs.map → runner-DWuSy1Se.mjs.map} +1 -1
- package/dist/{selector-CF2o5gxN.mjs → selector-BvXM9jbe.mjs} +2 -2
- package/dist/{selector-CF2o5gxN.mjs.map → selector-BvXM9jbe.mjs.map} +1 -1
- package/dist/{selector-DfAMZEC9.cjs → selector-Dps_ZFxq.cjs} +1 -1
- package/dist/{store-CTtqQtaE.mjs → store-C2B_AssI.mjs} +2 -2
- package/dist/{store-CTtqQtaE.mjs.map → store-C2B_AssI.mjs.map} +1 -1
- package/dist/{store-CqPfs47P.cjs → store-CQhU8dz8.cjs} +0 -18
- package/dist/vault-B2y78Ypu.cjs +560 -0
- package/dist/vault-z35Dohdq.mjs +560 -0
- package/dist/vault-z35Dohdq.mjs.map +1 -0
- package/dist/{viz-Dqp3C5kb.cjs → viz-D1620cBX.cjs} +3 -3
- package/dist/{viz-5y24S5X1.mjs → viz-DB5XFG1z.mjs} +4 -4
- package/dist/{viz-5y24S5X1.mjs.map → viz-DB5XFG1z.mjs.map} +1 -1
- package/docs/graph-tools.md +15 -6
- package/docs/investigation-workspaces.md +38 -9
- package/docs/knowledge-exports.md +204 -0
- package/docs/mcp-proxy.md +15 -3
- package/docs/obsidian-vault.md +130 -0
- package/package.json +1 -1
- package/skills/chain-insights-developer-experience/SKILL.md +2 -2
- package/skills/chain-insights-investigation/SKILL.md +1 -1
- package/dist/cases-Cp9DUbEV.mjs +0 -6
- package/dist/cases-sTY5aXav.cjs +0 -9
- package/dist/export-BqTCO9lP.mjs.map +0 -1
- 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
|
|
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
|
|
@@ -137,9 +143,11 @@ cia case show 1
|
|
|
137
143
|
find reports cases -maxdepth 3 -type f | sort
|
|
138
144
|
```
|
|
139
145
|
|
|
140
|
-
## Export
|
|
146
|
+
## Export Only When Sharing
|
|
141
147
|
|
|
142
|
-
|
|
148
|
+
Normal local work happens in the investigation vault. Export only when you need
|
|
149
|
+
to share a case, hand it off to a partner, ingest it into LLM Wiki, or archive a
|
|
150
|
+
review checkpoint.
|
|
143
151
|
|
|
144
152
|
```bash
|
|
145
153
|
cia case evidence verify 1
|
|
@@ -147,11 +155,14 @@ cia case export 1 --target obsidian-llmwiki --mode private
|
|
|
147
155
|
```
|
|
148
156
|
|
|
149
157
|
The export writes Markdown notes, `manifest.chain-insights.json`,
|
|
150
|
-
`graph.chain-insights.json`, `Graph.canvas`,
|
|
158
|
+
`graph.chain-insights.json`, `Graph.canvas`, LLM Wiki entrypoints, and prompts
|
|
151
159
|
for Codex, Claude Code, and ChatGPT under `published/<case-slug>/`.
|
|
152
160
|
|
|
153
|
-
Private exports may include full addresses. Use `--mode
|
|
154
|
-
|
|
161
|
+
Private exports may include full addresses. Use `--mode partner` for controlled
|
|
162
|
+
handoff after review. Use `--mode public` only for shareable demos; public mode
|
|
163
|
+
aliases addresses and removes secrets by default. Vault workflow guidance lives
|
|
164
|
+
in [Obsidian vault workflow](docs/obsidian-vault.md); export bundle details
|
|
165
|
+
live in [Knowledge exports](docs/knowledge-exports.md).
|
|
155
166
|
|
|
156
167
|
## Demo
|
|
157
168
|
|
|
@@ -247,7 +258,9 @@ reports under the workspace instead of embedding large payloads in case notes.
|
|
|
247
258
|
| Doc | Use it for |
|
|
248
259
|
| --- | --- |
|
|
249
260
|
| [Graph tools](docs/graph-tools.md) | GraphRAG MCP layers, `graph_query`, `graph_query_batch`, AML tool contracts, graph reports, evidence pointers |
|
|
250
|
-
| [
|
|
261
|
+
| [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 |
|
|
262
|
+
| [Investigation workspaces](docs/investigation-workspaces.md) | `cia init`, Obsidian-compatible vault layout, live note refresh, evidence, dossiers, imports, templates, sessions, reports |
|
|
263
|
+
| [Knowledge exports](docs/knowledge-exports.md) | Portable and redacted bundles for sharing, partner handoff, LLM Wiki ingestion, and archive |
|
|
251
264
|
| [MCP proxy](docs/mcp-proxy.md) | Stdio proxy behavior, endpoint configuration, agent installers, local tools, auth modes, Inspector validation |
|
|
252
265
|
| [Architecture](docs/architecture.md) | Product layers, data flow, local storage, security model, config keys |
|
|
253
266
|
| [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":""}
|