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.
- package/README.md +28 -11
- 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 +24 -8
- package/docs/investigation-workspaces.md +36 -10
- package/docs/knowledge-exports.md +6 -2
- package/docs/mcp-proxy.md +29 -7
- 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
|
@@ -1,211 +1,14 @@
|
|
|
1
|
-
import { n as extractGraphFromCase } from "./data-extractor-
|
|
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-
|
|
5
|
+
import { t as CaseStore } from "./store-C2B_AssI.mjs";
|
|
6
6
|
import { t as EvidenceStore } from "./evidence-D96PTzOQ.mjs";
|
|
7
|
-
import "./cases-
|
|
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 {
|
|
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
|
|
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
|
-
"#
|
|
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-
|
|
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-
|
|
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-
|
|
6
|
+
const require_store = require("./store-CQhU8dz8.cjs");
|
|
7
7
|
const require_evidence = require("./evidence-CvEesemA.cjs");
|
|
8
|
-
require("./cases-
|
|
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
|
|
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
|
-
"#
|
|
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-
|
|
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-
|
|
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 };
|