nodebench-mcp 2.44.0 → 2.45.0
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/dist/benchmarks/dogfoodJudge.js +25 -25
- package/dist/benchmarks/dogfoodJudge.js.map +1 -1
- package/dist/tools/deepSimTools.js +120 -2
- package/dist/tools/deepSimTools.js.map +1 -1
- package/dist/tools/founderTools.js +818 -7
- package/dist/tools/founderTools.js.map +1 -1
- package/dist/tools/reconTools.js +99 -0
- package/dist/tools/reconTools.js.map +1 -1
- package/dist/tools/toolRegistry.js +26 -0
- package/dist/tools/toolRegistry.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,6 +10,116 @@
|
|
|
10
10
|
* every piece of context it needs.
|
|
11
11
|
*/
|
|
12
12
|
import { getDb } from "../db.js";
|
|
13
|
+
/* ------------------------------------------------------------------ */
|
|
14
|
+
/* founder_packets schema bootstrap (idempotent) */
|
|
15
|
+
/* ------------------------------------------------------------------ */
|
|
16
|
+
let _packetSchemaReady = false;
|
|
17
|
+
function ensurePacketSchema() {
|
|
18
|
+
if (_packetSchemaReady)
|
|
19
|
+
return;
|
|
20
|
+
const db = getDb();
|
|
21
|
+
db.exec(`
|
|
22
|
+
CREATE TABLE IF NOT EXISTS founder_packets (
|
|
23
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
24
|
+
packetId TEXT UNIQUE NOT NULL,
|
|
25
|
+
entityId TEXT NOT NULL,
|
|
26
|
+
packetType TEXT NOT NULL,
|
|
27
|
+
packetJson TEXT NOT NULL,
|
|
28
|
+
createdAt TEXT NOT NULL DEFAULT (datetime('now'))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_founder_packets_entity ON founder_packets(entityId);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_founder_packets_type ON founder_packets(packetType);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_founder_packets_created ON founder_packets(createdAt);
|
|
34
|
+
`);
|
|
35
|
+
_packetSchemaReady = true;
|
|
36
|
+
}
|
|
37
|
+
/* ------------------------------------------------------------------ */
|
|
38
|
+
/* JSON flatten / diff helpers */
|
|
39
|
+
/* ------------------------------------------------------------------ */
|
|
40
|
+
/** Flatten a nested object into dot-notation paths. Arrays become path.0, path.1, etc. */
|
|
41
|
+
function flattenObject(obj, prefix = "", out = {}) {
|
|
42
|
+
if (obj === null || obj === undefined) {
|
|
43
|
+
if (prefix)
|
|
44
|
+
out[prefix] = obj;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(obj)) {
|
|
48
|
+
if (obj.length === 0 && prefix) {
|
|
49
|
+
out[prefix] = obj;
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < obj.length; i++) {
|
|
52
|
+
flattenObject(obj[i], prefix ? `${prefix}.${i}` : String(i), out);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
if (typeof obj === "object") {
|
|
57
|
+
const record = obj;
|
|
58
|
+
const keys = Object.keys(record);
|
|
59
|
+
if (keys.length === 0 && prefix) {
|
|
60
|
+
out[prefix] = obj;
|
|
61
|
+
}
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
flattenObject(record[key], prefix ? `${prefix}.${key}` : key, out);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
// Primitive
|
|
68
|
+
if (prefix)
|
|
69
|
+
out[prefix] = obj;
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function computePacketDiff(current, prior) {
|
|
73
|
+
const flatCurrent = flattenObject(current);
|
|
74
|
+
const flatPrior = flattenObject(prior);
|
|
75
|
+
const currentKeys = new Set(Object.keys(flatCurrent));
|
|
76
|
+
const priorKeys = new Set(Object.keys(flatPrior));
|
|
77
|
+
const newSinceLastTime = [];
|
|
78
|
+
const resolvedSinceLastTime = [];
|
|
79
|
+
const changedFields = [];
|
|
80
|
+
const stableFields = [];
|
|
81
|
+
// Fields in current but not in prior
|
|
82
|
+
for (const key of currentKeys) {
|
|
83
|
+
if (!priorKeys.has(key)) {
|
|
84
|
+
newSinceLastTime.push(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Fields in prior but not in current
|
|
88
|
+
for (const key of priorKeys) {
|
|
89
|
+
if (!currentKeys.has(key)) {
|
|
90
|
+
resolvedSinceLastTime.push(key);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Fields in both — compare values
|
|
94
|
+
for (const key of currentKeys) {
|
|
95
|
+
if (!priorKeys.has(key))
|
|
96
|
+
continue;
|
|
97
|
+
const cv = JSON.stringify(flatCurrent[key]);
|
|
98
|
+
const pv = JSON.stringify(flatPrior[key]);
|
|
99
|
+
if (cv === pv) {
|
|
100
|
+
stableFields.push(key);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
changedFields.push({
|
|
104
|
+
path: key,
|
|
105
|
+
previous: flatPrior[key],
|
|
106
|
+
current: flatCurrent[key],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Drift score: fraction of all unique keys that differ
|
|
111
|
+
const allKeys = new Set([...currentKeys, ...priorKeys]);
|
|
112
|
+
const totalKeys = allKeys.size;
|
|
113
|
+
const diffCount = newSinceLastTime.length + resolvedSinceLastTime.length + changedFields.length;
|
|
114
|
+
const driftScore = totalKeys > 0 ? Math.round((diffCount / totalKeys) * 1000) / 1000 : 0;
|
|
115
|
+
return {
|
|
116
|
+
newSinceLastTime,
|
|
117
|
+
resolvedSinceLastTime,
|
|
118
|
+
changedFields,
|
|
119
|
+
stableFields,
|
|
120
|
+
driftScore: Math.min(driftScore, 1.0),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
13
123
|
const GATHER_PROTOCOL = [
|
|
14
124
|
{
|
|
15
125
|
id: "company_identity",
|
|
@@ -219,6 +329,70 @@ const GATHER_PROTOCOL = [
|
|
|
219
329
|
},
|
|
220
330
|
];
|
|
221
331
|
/* ------------------------------------------------------------------ */
|
|
332
|
+
/* Importance scoring for event ranking */
|
|
333
|
+
/* ------------------------------------------------------------------ */
|
|
334
|
+
const EVENT_TYPE_WEIGHTS = {
|
|
335
|
+
"product.phase.completed": 0.9,
|
|
336
|
+
"contradiction.detected": 0.85,
|
|
337
|
+
"important_change.flagged": 0.8,
|
|
338
|
+
"packet.generated": 0.7,
|
|
339
|
+
"state.changed": 0.6,
|
|
340
|
+
"engine.trace.completed": 0.5,
|
|
341
|
+
"action.completed": 0.4,
|
|
342
|
+
};
|
|
343
|
+
const DEFAULT_EVENT_WEIGHT = 0.3;
|
|
344
|
+
const RECENCY_DECAY_PER_DAY = 0.1;
|
|
345
|
+
const THESIS_RELEVANCE_BOOST = 1.3;
|
|
346
|
+
const IMPORTANCE_THRESHOLD = 0.3;
|
|
347
|
+
function scoreEvents(rawEvents, missionKeywords) {
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
350
|
+
const scored = rawEvents.map((e) => {
|
|
351
|
+
const eventType = e.eventType ?? "";
|
|
352
|
+
const baseWeight = EVENT_TYPE_WEIGHTS[eventType] ?? DEFAULT_EVENT_WEIGHT;
|
|
353
|
+
// Recency decay: today=1.0, yesterday=0.9, ...
|
|
354
|
+
const ts = e.timestamp ?? 0;
|
|
355
|
+
const daysAgo = Math.max(0, Math.floor((now - ts) / msPerDay));
|
|
356
|
+
const recencyMultiplier = Math.max(0.1, 1.0 - daysAgo * RECENCY_DECAY_PER_DAY);
|
|
357
|
+
// Thesis relevance boost
|
|
358
|
+
const summary = (e.summary ?? "").toLowerCase();
|
|
359
|
+
const matchesMission = missionKeywords.length > 0 &&
|
|
360
|
+
missionKeywords.some((kw) => summary.includes(kw));
|
|
361
|
+
const relevanceMultiplier = matchesMission ? THESIS_RELEVANCE_BOOST : 1.0;
|
|
362
|
+
const score = Math.round(baseWeight * recencyMultiplier * relevanceMultiplier * 1000) / 1000;
|
|
363
|
+
return {
|
|
364
|
+
type: e.eventType,
|
|
365
|
+
entity: e.entityId,
|
|
366
|
+
summary: e.summary,
|
|
367
|
+
actor: e.actor,
|
|
368
|
+
timestamp: e.timestamp,
|
|
369
|
+
importanceScore: score,
|
|
370
|
+
};
|
|
371
|
+
});
|
|
372
|
+
// Sort descending by score
|
|
373
|
+
scored.sort((a, b) => b.importanceScore - a.importanceScore);
|
|
374
|
+
const ranked = scored.filter((e) => e.importanceScore >= IMPORTANCE_THRESHOLD);
|
|
375
|
+
const suppressedCount = scored.length - ranked.length;
|
|
376
|
+
const topSignal = ranked[0] ?? null;
|
|
377
|
+
return { ranked, suppressedCount, topSignal };
|
|
378
|
+
}
|
|
379
|
+
/** Extract lowercase keywords from a mission string for fuzzy matching. */
|
|
380
|
+
function extractMissionKeywords(diffs) {
|
|
381
|
+
// Pull keywords from the most recent state diff's reason/fields
|
|
382
|
+
if (diffs.length === 0)
|
|
383
|
+
return [];
|
|
384
|
+
const latest = diffs[0];
|
|
385
|
+
const reason = (latest.reason ?? "").toLowerCase();
|
|
386
|
+
const fields = (latest.changedFields ?? "").toLowerCase();
|
|
387
|
+
const combined = `${reason} ${fields}`;
|
|
388
|
+
// Split on non-alpha, filter short/stop words
|
|
389
|
+
const stopWords = new Set(["the", "and", "for", "was", "that", "with", "from", "are", "this", "has", "its", "not", "but"]);
|
|
390
|
+
return combined
|
|
391
|
+
.split(/[^a-z]+/)
|
|
392
|
+
.filter((w) => w.length > 3 && !stopWords.has(w))
|
|
393
|
+
.slice(0, 20);
|
|
394
|
+
}
|
|
395
|
+
/* ------------------------------------------------------------------ */
|
|
222
396
|
/* Tools */
|
|
223
397
|
/* ------------------------------------------------------------------ */
|
|
224
398
|
export const founderTools = [
|
|
@@ -326,6 +500,10 @@ export const founderTools = [
|
|
|
326
500
|
.prepare("SELECT * FROM causal_trajectory_scores ORDER BY createdAt DESC LIMIT 1")
|
|
327
501
|
.all();
|
|
328
502
|
if (recentEvents.length > 0 || importantChanges.length > 0 || stateDiffs.length > 0) {
|
|
503
|
+
// Extract mission keywords from most recent state diff for thesis relevance
|
|
504
|
+
const missionKeywords = extractMissionKeywords(stateDiffs);
|
|
505
|
+
// Score and rank events by importance
|
|
506
|
+
const { ranked, suppressedCount, topSignal } = scoreEvents(recentEvents, missionKeywords);
|
|
329
507
|
sessionMemory = {
|
|
330
508
|
source: "causal_memory_auto_hydrate",
|
|
331
509
|
period: "last_7_days",
|
|
@@ -335,13 +513,9 @@ export const founderTools = [
|
|
|
335
513
|
weeklyActions,
|
|
336
514
|
milestones,
|
|
337
515
|
trajectory: trajectory[0] ?? null,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
summary: e.summary,
|
|
342
|
-
actor: e.actor,
|
|
343
|
-
timestamp: e.timestamp,
|
|
344
|
-
})),
|
|
516
|
+
topSignal,
|
|
517
|
+
suppressedCount,
|
|
518
|
+
events: ranked,
|
|
345
519
|
changes: importantChanges.map((c) => ({
|
|
346
520
|
category: c.changeCategory,
|
|
347
521
|
impact: c.impactScore,
|
|
@@ -665,5 +839,642 @@ export const founderTools = [
|
|
|
665
839
|
};
|
|
666
840
|
},
|
|
667
841
|
},
|
|
842
|
+
// ─── 4. founder_packet_history_diff ──────────────────────────────
|
|
843
|
+
{
|
|
844
|
+
name: "founder_packet_history_diff",
|
|
845
|
+
description: "Compares the most recent Founder Artifact Packet for an entity against " +
|
|
846
|
+
"prior packets stored in the founder_packets SQLite table. Returns a " +
|
|
847
|
+
"structured diff: newSinceLastTime, resolvedSinceLastTime, changedFields, " +
|
|
848
|
+
"stableFields, and a driftScore (0.0–1.0). If only one packet exists, " +
|
|
849
|
+
"returns it as a baseline. If none exist, suggests running " +
|
|
850
|
+
"founder_deep_context_gather first.",
|
|
851
|
+
inputSchema: {
|
|
852
|
+
type: "object",
|
|
853
|
+
properties: {
|
|
854
|
+
entityId: {
|
|
855
|
+
type: "string",
|
|
856
|
+
description: "The entity ID to look up packets for.",
|
|
857
|
+
},
|
|
858
|
+
packetType: {
|
|
859
|
+
type: "string",
|
|
860
|
+
description: "Optional packet type filter (e.g. weekly_reset, pre_delegation, important_change).",
|
|
861
|
+
},
|
|
862
|
+
limit: {
|
|
863
|
+
type: "number",
|
|
864
|
+
description: "Max number of recent packets to retrieve for comparison (default 2, max 10).",
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
required: ["entityId"],
|
|
868
|
+
},
|
|
869
|
+
annotations: { readOnlyHint: true },
|
|
870
|
+
handler: async (args) => {
|
|
871
|
+
ensurePacketSchema();
|
|
872
|
+
const db = getDb();
|
|
873
|
+
const entityId = args.entityId;
|
|
874
|
+
const packetType = args.packetType ?? null;
|
|
875
|
+
const limit = Math.min(Math.max(args.limit ?? 2, 1), 10);
|
|
876
|
+
// Query recent packets for this entity
|
|
877
|
+
let rows;
|
|
878
|
+
if (packetType) {
|
|
879
|
+
rows = db
|
|
880
|
+
.prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
|
|
881
|
+
FROM founder_packets
|
|
882
|
+
WHERE entityId = ? AND packetType = ?
|
|
883
|
+
ORDER BY createdAt DESC
|
|
884
|
+
LIMIT ?`)
|
|
885
|
+
.all(entityId, packetType, limit);
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
rows = db
|
|
889
|
+
.prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
|
|
890
|
+
FROM founder_packets
|
|
891
|
+
WHERE entityId = ?
|
|
892
|
+
ORDER BY createdAt DESC
|
|
893
|
+
LIMIT ?`)
|
|
894
|
+
.all(entityId, limit);
|
|
895
|
+
}
|
|
896
|
+
// No packets found
|
|
897
|
+
if (rows.length === 0) {
|
|
898
|
+
return {
|
|
899
|
+
noPackets: true,
|
|
900
|
+
entityId,
|
|
901
|
+
suggestion: "Run founder_deep_context_gather first to generate and store a packet.",
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
// Parse packet JSON safely
|
|
905
|
+
const packets = rows.map((row) => {
|
|
906
|
+
let parsed = {};
|
|
907
|
+
try {
|
|
908
|
+
parsed = JSON.parse(row.packetJson);
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
parsed = { _parseError: true, raw: row.packetJson };
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
packetId: row.packetId,
|
|
915
|
+
entityId: row.entityId,
|
|
916
|
+
packetType: row.packetType,
|
|
917
|
+
createdAt: row.createdAt,
|
|
918
|
+
data: parsed,
|
|
919
|
+
};
|
|
920
|
+
});
|
|
921
|
+
// Only one packet — return as baseline
|
|
922
|
+
if (packets.length === 1) {
|
|
923
|
+
return {
|
|
924
|
+
isFirstPacket: true,
|
|
925
|
+
entityId,
|
|
926
|
+
packet: {
|
|
927
|
+
packetId: packets[0].packetId,
|
|
928
|
+
packetType: packets[0].packetType,
|
|
929
|
+
createdAt: packets[0].createdAt,
|
|
930
|
+
},
|
|
931
|
+
note: "First packet for this entity. Future calls will produce diffs.",
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
// 2+ packets — diff the most recent against the one before it
|
|
935
|
+
const latest = packets[0];
|
|
936
|
+
const prior = packets[1];
|
|
937
|
+
const diff = computePacketDiff(latest.data, prior.data);
|
|
938
|
+
return {
|
|
939
|
+
entityId,
|
|
940
|
+
latest: {
|
|
941
|
+
packetId: latest.packetId,
|
|
942
|
+
packetType: latest.packetType,
|
|
943
|
+
createdAt: latest.createdAt,
|
|
944
|
+
},
|
|
945
|
+
prior: {
|
|
946
|
+
packetId: prior.packetId,
|
|
947
|
+
packetType: prior.packetType,
|
|
948
|
+
createdAt: prior.createdAt,
|
|
949
|
+
},
|
|
950
|
+
diff: {
|
|
951
|
+
newSinceLastTime: diff.newSinceLastTime,
|
|
952
|
+
resolvedSinceLastTime: diff.resolvedSinceLastTime,
|
|
953
|
+
changedFields: diff.changedFields,
|
|
954
|
+
stableFields: diff.stableFields,
|
|
955
|
+
driftScore: diff.driftScore,
|
|
956
|
+
},
|
|
957
|
+
summary: {
|
|
958
|
+
totalFieldsCompared: diff.newSinceLastTime.length +
|
|
959
|
+
diff.resolvedSinceLastTime.length +
|
|
960
|
+
diff.changedFields.length +
|
|
961
|
+
diff.stableFields.length,
|
|
962
|
+
newCount: diff.newSinceLastTime.length,
|
|
963
|
+
resolvedCount: diff.resolvedSinceLastTime.length,
|
|
964
|
+
changedCount: diff.changedFields.length,
|
|
965
|
+
stableCount: diff.stableFields.length,
|
|
966
|
+
driftScore: diff.driftScore,
|
|
967
|
+
driftLevel: diff.driftScore < 0.1
|
|
968
|
+
? "minimal"
|
|
969
|
+
: diff.driftScore < 0.3
|
|
970
|
+
? "low"
|
|
971
|
+
: diff.driftScore < 0.6
|
|
972
|
+
? "moderate"
|
|
973
|
+
: diff.driftScore < 0.85
|
|
974
|
+
? "high"
|
|
975
|
+
: "extreme",
|
|
976
|
+
},
|
|
977
|
+
packetsAvailable: packets.length,
|
|
978
|
+
};
|
|
979
|
+
},
|
|
980
|
+
},
|
|
981
|
+
// ─── 5. export_artifact_packet ──────────────────────────────────
|
|
982
|
+
{
|
|
983
|
+
name: "export_artifact_packet",
|
|
984
|
+
description: "Formats a Founder Artifact Packet or memo for export to a specific audience and format. " +
|
|
985
|
+
"Applies audience-specific framing (founder, investor, banker, developer, teammate) and " +
|
|
986
|
+
"renders into the requested format (markdown, html, json, plaintext). Always includes " +
|
|
987
|
+
"provenance metadata (timestamp, version, exportId) for traceability.",
|
|
988
|
+
inputSchema: {
|
|
989
|
+
type: "object",
|
|
990
|
+
properties: {
|
|
991
|
+
content: {
|
|
992
|
+
type: "object",
|
|
993
|
+
description: "The raw packet/memo content to format for export.",
|
|
994
|
+
},
|
|
995
|
+
format: {
|
|
996
|
+
type: "string",
|
|
997
|
+
enum: ["markdown", "html", "json", "plaintext"],
|
|
998
|
+
description: "Output format for the exported artifact.",
|
|
999
|
+
},
|
|
1000
|
+
audience: {
|
|
1001
|
+
type: "string",
|
|
1002
|
+
enum: ["founder", "investor", "banker", "developer", "teammate"],
|
|
1003
|
+
description: "Target audience — controls tone, ordering, and which sections are emphasized.",
|
|
1004
|
+
},
|
|
1005
|
+
title: {
|
|
1006
|
+
type: "string",
|
|
1007
|
+
description: "Override title for the exported artifact. Defaults to packet title or 'Artifact Packet'.",
|
|
1008
|
+
},
|
|
1009
|
+
includeMetadata: {
|
|
1010
|
+
type: "boolean",
|
|
1011
|
+
description: "Include generation timestamp, tool version, and provenance block. Defaults to true.",
|
|
1012
|
+
},
|
|
1013
|
+
},
|
|
1014
|
+
required: ["content", "format", "audience"],
|
|
1015
|
+
},
|
|
1016
|
+
handler: async (args) => {
|
|
1017
|
+
const content = args.content;
|
|
1018
|
+
const format = args.format;
|
|
1019
|
+
const audience = args.audience;
|
|
1020
|
+
const titleOverride = args.title ?? null;
|
|
1021
|
+
const includeMetadata = args.includeMetadata ?? true;
|
|
1022
|
+
const validFormats = ["markdown", "html", "json", "plaintext"];
|
|
1023
|
+
const validAudiences = ["founder", "investor", "banker", "developer", "teammate"];
|
|
1024
|
+
if (!validFormats.includes(format)) {
|
|
1025
|
+
return {
|
|
1026
|
+
error: true,
|
|
1027
|
+
message: `Invalid format: ${format}. Must be one of: ${validFormats.join(", ")}.`,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
if (!validAudiences.includes(audience)) {
|
|
1031
|
+
return {
|
|
1032
|
+
error: true,
|
|
1033
|
+
message: `Invalid audience: ${audience}. Must be one of: ${validAudiences.join(", ")}.`,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
const NODEBENCH_VERSION = "1.0.0";
|
|
1037
|
+
const exportId = `exp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1038
|
+
const generatedAt = new Date().toISOString();
|
|
1039
|
+
// ── Extract common fields from content ──────────────────────────
|
|
1040
|
+
const entity = content.canonicalEntity ?? {};
|
|
1041
|
+
const companyName = entity.name ?? content.companyName ?? "Unknown Company";
|
|
1042
|
+
const mission = entity.mission ?? content.mission ?? "";
|
|
1043
|
+
const wedge = entity.wedge ?? content.wedge ?? "";
|
|
1044
|
+
const identityConfidence = entity.identityConfidence ?? null;
|
|
1045
|
+
const contradictions = content.contradictions ?? [];
|
|
1046
|
+
const nextActions = content.nextActions ?? [];
|
|
1047
|
+
const operatingMemo = content.operatingMemo ?? "";
|
|
1048
|
+
const whatChanged = content.whatChanged ?? [];
|
|
1049
|
+
const nearbyEntities = content.nearbyEntities ?? [];
|
|
1050
|
+
const keyEvidence = content.keyEvidence ?? [];
|
|
1051
|
+
const agentInstructions = content.agentInstructions ?? null;
|
|
1052
|
+
const title = titleOverride ?? content.title ?? "Artifact Packet";
|
|
1053
|
+
const audienceConfigs = {
|
|
1054
|
+
founder: {
|
|
1055
|
+
tone: "informal",
|
|
1056
|
+
sectionOrder: [
|
|
1057
|
+
"summary",
|
|
1058
|
+
"whatChanged",
|
|
1059
|
+
"contradictions",
|
|
1060
|
+
"nextMoves",
|
|
1061
|
+
"initiatives",
|
|
1062
|
+
"agents",
|
|
1063
|
+
"operatingMemo",
|
|
1064
|
+
"evidence",
|
|
1065
|
+
],
|
|
1066
|
+
emphasisSections: ["contradictions", "nextMoves"],
|
|
1067
|
+
excludeSections: [],
|
|
1068
|
+
headerPrefix: "",
|
|
1069
|
+
},
|
|
1070
|
+
investor: {
|
|
1071
|
+
tone: "formal",
|
|
1072
|
+
sectionOrder: [
|
|
1073
|
+
"metrics",
|
|
1074
|
+
"summary",
|
|
1075
|
+
"marketContext",
|
|
1076
|
+
"risks",
|
|
1077
|
+
"initiatives",
|
|
1078
|
+
"evidence",
|
|
1079
|
+
],
|
|
1080
|
+
emphasisSections: ["metrics", "risks"],
|
|
1081
|
+
excludeSections: ["agents", "agentInstructions"],
|
|
1082
|
+
headerPrefix: "Investment Memo: ",
|
|
1083
|
+
},
|
|
1084
|
+
banker: {
|
|
1085
|
+
tone: "formal",
|
|
1086
|
+
sectionOrder: [
|
|
1087
|
+
"companySnapshot",
|
|
1088
|
+
"financialSignals",
|
|
1089
|
+
"riskFactors",
|
|
1090
|
+
"comparables",
|
|
1091
|
+
"summary",
|
|
1092
|
+
"evidence",
|
|
1093
|
+
],
|
|
1094
|
+
emphasisSections: ["companySnapshot", "financialSignals", "riskFactors"],
|
|
1095
|
+
excludeSections: ["agents", "agentInstructions", "operatingMemo"],
|
|
1096
|
+
headerPrefix: "Memo: ",
|
|
1097
|
+
},
|
|
1098
|
+
developer: {
|
|
1099
|
+
tone: "technical",
|
|
1100
|
+
sectionOrder: [
|
|
1101
|
+
"architectureChanges",
|
|
1102
|
+
"technicalDecisions",
|
|
1103
|
+
"apiChanges",
|
|
1104
|
+
"whatChanged",
|
|
1105
|
+
"nextMoves",
|
|
1106
|
+
"agents",
|
|
1107
|
+
],
|
|
1108
|
+
emphasisSections: ["architectureChanges", "apiChanges"],
|
|
1109
|
+
excludeSections: ["nearbyEntities", "operatingMemo"],
|
|
1110
|
+
headerPrefix: "",
|
|
1111
|
+
},
|
|
1112
|
+
teammate: {
|
|
1113
|
+
tone: "conversational",
|
|
1114
|
+
sectionOrder: ["delegationBrief", "actionItems", "whatChanged", "context"],
|
|
1115
|
+
emphasisSections: ["delegationBrief", "actionItems"],
|
|
1116
|
+
excludeSections: ["evidence", "nearbyEntities"],
|
|
1117
|
+
headerPrefix: "",
|
|
1118
|
+
},
|
|
1119
|
+
};
|
|
1120
|
+
const config = audienceConfigs[audience];
|
|
1121
|
+
const sections = [];
|
|
1122
|
+
// Summary / company snapshot
|
|
1123
|
+
if (audience === "banker") {
|
|
1124
|
+
sections.push({
|
|
1125
|
+
key: "companySnapshot",
|
|
1126
|
+
heading: "Company Snapshot",
|
|
1127
|
+
body: [
|
|
1128
|
+
companyName ? `Company: ${companyName}` : "",
|
|
1129
|
+
mission ? `Mission: ${mission}` : "",
|
|
1130
|
+
wedge ? `Wedge: ${wedge}` : "",
|
|
1131
|
+
identityConfidence !== null
|
|
1132
|
+
? `Identity Confidence: ${Math.round(identityConfidence * 100)}%`
|
|
1133
|
+
: "",
|
|
1134
|
+
]
|
|
1135
|
+
.filter(Boolean)
|
|
1136
|
+
.join("\n"),
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
else if (audience === "investor") {
|
|
1140
|
+
sections.push({
|
|
1141
|
+
key: "metrics",
|
|
1142
|
+
heading: "Key Metrics & Traction",
|
|
1143
|
+
body: [
|
|
1144
|
+
companyName ? `Company: ${companyName}` : "",
|
|
1145
|
+
identityConfidence !== null
|
|
1146
|
+
? `Identity Confidence: ${Math.round(identityConfidence * 100)}%`
|
|
1147
|
+
: "",
|
|
1148
|
+
`Active Initiatives: ${(content.initiatives ?? []).length}`,
|
|
1149
|
+
`Open Contradictions: ${contradictions.length}`,
|
|
1150
|
+
`Pending Actions: ${nextActions.length}`,
|
|
1151
|
+
]
|
|
1152
|
+
.filter(Boolean)
|
|
1153
|
+
.join("\n"),
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
sections.push({
|
|
1157
|
+
key: "summary",
|
|
1158
|
+
heading: audience === "teammate" ? "Context" : "Summary",
|
|
1159
|
+
body: operatingMemo || `${companyName} — ${mission}`,
|
|
1160
|
+
});
|
|
1161
|
+
// What changed
|
|
1162
|
+
if (!config.excludeSections.includes("whatChanged") &&
|
|
1163
|
+
whatChanged.length > 0) {
|
|
1164
|
+
sections.push({
|
|
1165
|
+
key: "whatChanged",
|
|
1166
|
+
heading: audience === "developer" ? "Recent Changes" : "What Changed",
|
|
1167
|
+
items: whatChanged.map((c) => ({
|
|
1168
|
+
text: c.description ??
|
|
1169
|
+
c.summary ??
|
|
1170
|
+
JSON.stringify(c),
|
|
1171
|
+
meta: c.type ?? undefined,
|
|
1172
|
+
})),
|
|
1173
|
+
body: "",
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
// Contradictions / risks
|
|
1177
|
+
if (!config.excludeSections.includes("contradictions") &&
|
|
1178
|
+
contradictions.length > 0) {
|
|
1179
|
+
const heading = audience === "investor" || audience === "banker"
|
|
1180
|
+
? "Risk Factors"
|
|
1181
|
+
: "Contradictions & Tensions";
|
|
1182
|
+
sections.push({
|
|
1183
|
+
key: audience === "investor" || audience === "banker"
|
|
1184
|
+
? "riskFactors"
|
|
1185
|
+
: "contradictions",
|
|
1186
|
+
heading,
|
|
1187
|
+
items: contradictions.map((c) => ({
|
|
1188
|
+
text: c.title ??
|
|
1189
|
+
c.description ??
|
|
1190
|
+
JSON.stringify(c),
|
|
1191
|
+
meta: c.severity ?? undefined,
|
|
1192
|
+
})),
|
|
1193
|
+
body: "",
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
// Next actions / moves
|
|
1197
|
+
if (nextActions.length > 0) {
|
|
1198
|
+
const heading = audience === "teammate"
|
|
1199
|
+
? "Action Items"
|
|
1200
|
+
: audience === "founder"
|
|
1201
|
+
? "Next 3 Moves"
|
|
1202
|
+
: audience === "developer"
|
|
1203
|
+
? "Technical Decisions & Next Steps"
|
|
1204
|
+
: "Recommended Actions";
|
|
1205
|
+
const items = (audience === "founder" ? nextActions.slice(0, 3) : nextActions).map((a) => ({
|
|
1206
|
+
text: a.label ?? a.title ?? JSON.stringify(a),
|
|
1207
|
+
meta: [
|
|
1208
|
+
a.priority ? `priority: ${a.priority}` : "",
|
|
1209
|
+
a.owner ? `owner: ${a.owner}` : "",
|
|
1210
|
+
]
|
|
1211
|
+
.filter(Boolean)
|
|
1212
|
+
.join(", ") || undefined,
|
|
1213
|
+
}));
|
|
1214
|
+
sections.push({
|
|
1215
|
+
key: audience === "teammate" ? "actionItems" : "nextMoves",
|
|
1216
|
+
heading,
|
|
1217
|
+
items,
|
|
1218
|
+
body: "",
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
// Delegation brief (teammate only)
|
|
1222
|
+
if (audience === "teammate") {
|
|
1223
|
+
sections.push({
|
|
1224
|
+
key: "delegationBrief",
|
|
1225
|
+
heading: "Delegation Brief",
|
|
1226
|
+
body: agentInstructions
|
|
1227
|
+
? `Focus: ${agentInstructions.focus ?? "See action items"}\nScope: ${agentInstructions.scope ?? "As assigned"}`
|
|
1228
|
+
: "No specific delegation instructions provided. See action items above.",
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
// Market context / comparables
|
|
1232
|
+
if (nearbyEntities.length > 0 &&
|
|
1233
|
+
!config.excludeSections.includes("nearbyEntities")) {
|
|
1234
|
+
const heading = audience === "banker"
|
|
1235
|
+
? "Comparables"
|
|
1236
|
+
: audience === "investor"
|
|
1237
|
+
? "Market Context"
|
|
1238
|
+
: "Nearby Entities";
|
|
1239
|
+
sections.push({
|
|
1240
|
+
key: audience === "banker" ? "comparables" : "marketContext",
|
|
1241
|
+
heading,
|
|
1242
|
+
items: nearbyEntities.map((e) => ({
|
|
1243
|
+
text: e.name ?? JSON.stringify(e),
|
|
1244
|
+
meta: e.relationship ?? undefined,
|
|
1245
|
+
})),
|
|
1246
|
+
body: "",
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
// Financial signals (banker)
|
|
1250
|
+
if (audience === "banker") {
|
|
1251
|
+
const signals = content.financialSignals ?? [];
|
|
1252
|
+
sections.push({
|
|
1253
|
+
key: "financialSignals",
|
|
1254
|
+
heading: "Financial Signals",
|
|
1255
|
+
body: signals.length > 0
|
|
1256
|
+
? signals
|
|
1257
|
+
.map((s) => `${s.label ?? "Signal"}: ${s.value ?? "N/A"}`)
|
|
1258
|
+
.join("\n")
|
|
1259
|
+
: "No financial signals available in this packet.",
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
// Architecture / API changes (developer)
|
|
1263
|
+
if (audience === "developer") {
|
|
1264
|
+
const archChanges = content.architectureChanges ?? [];
|
|
1265
|
+
const apiChanges = content.apiChanges ?? [];
|
|
1266
|
+
if (archChanges.length > 0 ||
|
|
1267
|
+
whatChanged.some((c) => (c.type ?? "").includes("arch"))) {
|
|
1268
|
+
sections.push({
|
|
1269
|
+
key: "architectureChanges",
|
|
1270
|
+
heading: "Architecture Changes",
|
|
1271
|
+
items: archChanges.length > 0
|
|
1272
|
+
? archChanges.map((a) => ({
|
|
1273
|
+
text: a.description ?? JSON.stringify(a),
|
|
1274
|
+
}))
|
|
1275
|
+
: [
|
|
1276
|
+
{
|
|
1277
|
+
text: "See recent changes for architecture-related updates.",
|
|
1278
|
+
},
|
|
1279
|
+
],
|
|
1280
|
+
body: "",
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
if (apiChanges.length > 0) {
|
|
1284
|
+
sections.push({
|
|
1285
|
+
key: "apiChanges",
|
|
1286
|
+
heading: "API Changes",
|
|
1287
|
+
items: apiChanges.map((a) => ({
|
|
1288
|
+
text: a.description ?? JSON.stringify(a),
|
|
1289
|
+
})),
|
|
1290
|
+
body: "",
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
// Evidence
|
|
1295
|
+
if (!config.excludeSections.includes("evidence") &&
|
|
1296
|
+
keyEvidence.length > 0) {
|
|
1297
|
+
sections.push({
|
|
1298
|
+
key: "evidence",
|
|
1299
|
+
heading: "Key Evidence",
|
|
1300
|
+
items: keyEvidence.map((e) => ({
|
|
1301
|
+
text: e.claim ??
|
|
1302
|
+
e.description ??
|
|
1303
|
+
JSON.stringify(e),
|
|
1304
|
+
meta: e.source ?? undefined,
|
|
1305
|
+
})),
|
|
1306
|
+
body: "",
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
// ── Sort sections by audience config order ──────────────────────
|
|
1310
|
+
const orderMap = new Map(config.sectionOrder.map((key, i) => [key, i]));
|
|
1311
|
+
sections.sort((a, b) => {
|
|
1312
|
+
const aOrder = orderMap.get(a.key) ?? 999;
|
|
1313
|
+
const bOrder = orderMap.get(b.key) ?? 999;
|
|
1314
|
+
return aOrder - bOrder;
|
|
1315
|
+
});
|
|
1316
|
+
// Filter out excluded sections
|
|
1317
|
+
const filteredSections = sections.filter((s) => !config.excludeSections.includes(s.key));
|
|
1318
|
+
// ── Metadata block ──────────────────────────────────────────────
|
|
1319
|
+
const metadataBlock = includeMetadata
|
|
1320
|
+
? {
|
|
1321
|
+
generatedAt,
|
|
1322
|
+
nodebenchVersion: NODEBENCH_VERSION,
|
|
1323
|
+
exportId,
|
|
1324
|
+
audience,
|
|
1325
|
+
format,
|
|
1326
|
+
}
|
|
1327
|
+
: null;
|
|
1328
|
+
// ── Render helpers ──────────────────────────────────────────────
|
|
1329
|
+
function renderSectionMarkdown(s, emphasized) {
|
|
1330
|
+
const marker = emphasized ? " **[KEY]**" : "";
|
|
1331
|
+
let out = `## ${s.heading}${marker}\n\n`;
|
|
1332
|
+
if (s.body)
|
|
1333
|
+
out += `${s.body}\n\n`;
|
|
1334
|
+
if (s.items) {
|
|
1335
|
+
for (const item of s.items) {
|
|
1336
|
+
out += item.meta
|
|
1337
|
+
? `- **${item.text}** _(${item.meta})_\n`
|
|
1338
|
+
: `- ${item.text}\n`;
|
|
1339
|
+
}
|
|
1340
|
+
out += "\n";
|
|
1341
|
+
}
|
|
1342
|
+
return out;
|
|
1343
|
+
}
|
|
1344
|
+
function renderSectionPlaintext(s) {
|
|
1345
|
+
let out = `${s.heading.toUpperCase()}\n${"=".repeat(s.heading.length)}\n\n`;
|
|
1346
|
+
if (s.body)
|
|
1347
|
+
out += `${s.body}\n\n`;
|
|
1348
|
+
if (s.items) {
|
|
1349
|
+
for (const item of s.items) {
|
|
1350
|
+
out += item.meta
|
|
1351
|
+
? ` - ${item.text} (${item.meta})\n`
|
|
1352
|
+
: ` - ${item.text}\n`;
|
|
1353
|
+
}
|
|
1354
|
+
out += "\n";
|
|
1355
|
+
}
|
|
1356
|
+
return out;
|
|
1357
|
+
}
|
|
1358
|
+
const escapeHtml = (str) => str
|
|
1359
|
+
.replace(/&/g, "&")
|
|
1360
|
+
.replace(/</g, "<")
|
|
1361
|
+
.replace(/>/g, ">")
|
|
1362
|
+
.replace(/"/g, """);
|
|
1363
|
+
// ── Render by format ────────────────────────────────────────────
|
|
1364
|
+
if (format === "json") {
|
|
1365
|
+
const jsonOutput = {
|
|
1366
|
+
title: `${config.headerPrefix}${title}`,
|
|
1367
|
+
audience,
|
|
1368
|
+
tone: config.tone,
|
|
1369
|
+
sections: Object.fromEntries(filteredSections.map((s) => [
|
|
1370
|
+
s.key,
|
|
1371
|
+
{
|
|
1372
|
+
heading: s.heading,
|
|
1373
|
+
...(s.body ? { body: s.body } : {}),
|
|
1374
|
+
...(s.items ? { items: s.items } : {}),
|
|
1375
|
+
emphasized: config.emphasisSections.includes(s.key),
|
|
1376
|
+
},
|
|
1377
|
+
])),
|
|
1378
|
+
};
|
|
1379
|
+
if (metadataBlock)
|
|
1380
|
+
jsonOutput._metadata = metadataBlock;
|
|
1381
|
+
return { format: "json", exported: jsonOutput };
|
|
1382
|
+
}
|
|
1383
|
+
if (format === "markdown") {
|
|
1384
|
+
let md = `# ${config.headerPrefix}${title}\n\n`;
|
|
1385
|
+
if (config.tone === "formal")
|
|
1386
|
+
md += `> Prepared for ${audience} audience\n\n`;
|
|
1387
|
+
md += "---\n\n";
|
|
1388
|
+
for (const s of filteredSections) {
|
|
1389
|
+
md += renderSectionMarkdown(s, config.emphasisSections.includes(s.key));
|
|
1390
|
+
}
|
|
1391
|
+
if (metadataBlock) {
|
|
1392
|
+
md += "---\n\n";
|
|
1393
|
+
md += `_Generated: ${generatedAt} | NodeBench v${NODEBENCH_VERSION} | Export ID: ${exportId}_\n`;
|
|
1394
|
+
}
|
|
1395
|
+
return { format: "markdown", exported: md };
|
|
1396
|
+
}
|
|
1397
|
+
if (format === "plaintext") {
|
|
1398
|
+
let txt = `${config.headerPrefix}${title}\n${"=".repeat((config.headerPrefix + title).length)}\n\n`;
|
|
1399
|
+
for (const s of filteredSections) {
|
|
1400
|
+
txt += renderSectionPlaintext(s);
|
|
1401
|
+
}
|
|
1402
|
+
if (metadataBlock) {
|
|
1403
|
+
txt += `${"—".repeat(40)}\n`;
|
|
1404
|
+
txt += `Generated: ${generatedAt}\nNodeBench v${NODEBENCH_VERSION}\nExport ID: ${exportId}\n`;
|
|
1405
|
+
}
|
|
1406
|
+
return { format: "plaintext", exported: txt };
|
|
1407
|
+
}
|
|
1408
|
+
// HTML format — self-contained with inline CSS matching glass card DNA
|
|
1409
|
+
if (format === "html") {
|
|
1410
|
+
let body = "";
|
|
1411
|
+
for (const s of filteredSections) {
|
|
1412
|
+
const isKey = config.emphasisSections.includes(s.key);
|
|
1413
|
+
body += `<section class="card${isKey ? " emphasized" : ""}">\n`;
|
|
1414
|
+
body += ` <h2>${escapeHtml(s.heading)}${isKey ? ' <span class="badge">KEY</span>' : ""}</h2>\n`;
|
|
1415
|
+
if (s.body)
|
|
1416
|
+
body += ` <p>${escapeHtml(s.body).replace(/\n/g, "<br>")}</p>\n`;
|
|
1417
|
+
if (s.items) {
|
|
1418
|
+
body += " <ul>\n";
|
|
1419
|
+
for (const item of s.items) {
|
|
1420
|
+
body += item.meta
|
|
1421
|
+
? ` <li><strong>${escapeHtml(item.text)}</strong> <span class="meta">(${escapeHtml(item.meta)})</span></li>\n`
|
|
1422
|
+
: ` <li>${escapeHtml(item.text)}</li>\n`;
|
|
1423
|
+
}
|
|
1424
|
+
body += " </ul>\n";
|
|
1425
|
+
}
|
|
1426
|
+
body += "</section>\n";
|
|
1427
|
+
}
|
|
1428
|
+
let metaHtml = "";
|
|
1429
|
+
if (metadataBlock) {
|
|
1430
|
+
metaHtml = `<footer class="meta-footer">Generated: ${escapeHtml(generatedAt)} · NodeBench v${escapeHtml(NODEBENCH_VERSION)} · Export ID: ${escapeHtml(exportId)}</footer>`;
|
|
1431
|
+
}
|
|
1432
|
+
const html = `<!DOCTYPE html>
|
|
1433
|
+
<html lang="en">
|
|
1434
|
+
<head>
|
|
1435
|
+
<meta charset="UTF-8">
|
|
1436
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1437
|
+
<title>${escapeHtml(config.headerPrefix + title)}</title>
|
|
1438
|
+
<style>
|
|
1439
|
+
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
|
|
1440
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1441
|
+
body {
|
|
1442
|
+
font-family: 'Manrope', system-ui, sans-serif;
|
|
1443
|
+
background: #09090b; color: #fafafa;
|
|
1444
|
+
padding: 2rem; max-width: 800px; margin: 0 auto;
|
|
1445
|
+
line-height: 1.6;
|
|
1446
|
+
}
|
|
1447
|
+
h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.25rem; }
|
|
1448
|
+
.subtitle { color: #a1a1aa; font-size: 0.85rem; margin-bottom: 1.5rem; }
|
|
1449
|
+
.card {
|
|
1450
|
+
background: rgba(255,255,255,0.02);
|
|
1451
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
1452
|
+
border-radius: 12px; padding: 1.25rem;
|
|
1453
|
+
margin-bottom: 1rem;
|
|
1454
|
+
}
|
|
1455
|
+
.card.emphasized { border-color: #d97757; }
|
|
1456
|
+
.card h2 { font-size: 0.95rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 0.75rem; color: #e4e4e7; }
|
|
1457
|
+
.badge { background: #d97757; color: #09090b; font-size: 0.65rem; padding: 2px 6px; border-radius: 4px; vertical-align: middle; letter-spacing: 0.05em; }
|
|
1458
|
+
.card p { color: #d4d4d8; font-size: 0.9rem; margin-bottom: 0.5rem; }
|
|
1459
|
+
.card ul { list-style: none; padding: 0; }
|
|
1460
|
+
.card li { padding: 0.3rem 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 0.9rem; color: #d4d4d8; }
|
|
1461
|
+
.card li:last-child { border-bottom: none; }
|
|
1462
|
+
.card li strong { color: #fafafa; }
|
|
1463
|
+
.meta { color: #71717a; font-size: 0.8rem; }
|
|
1464
|
+
.meta-footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.06); color: #71717a; font-size: 0.75rem; text-align: center; }
|
|
1465
|
+
</style>
|
|
1466
|
+
</head>
|
|
1467
|
+
<body>
|
|
1468
|
+
<h1>${escapeHtml(config.headerPrefix + title)}</h1>
|
|
1469
|
+
<div class="subtitle">Prepared for ${escapeHtml(audience)} audience</div>
|
|
1470
|
+
${body}
|
|
1471
|
+
${metaHtml}
|
|
1472
|
+
</body>
|
|
1473
|
+
</html>`;
|
|
1474
|
+
return { format: "html", exported: html };
|
|
1475
|
+
}
|
|
1476
|
+
return { error: true, message: `Unhandled format: ${format}` };
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
668
1479
|
];
|
|
669
1480
|
//# sourceMappingURL=founderTools.js.map
|