@swarmvaultai/engine 0.1.7 → 0.1.9

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/index.js CHANGED
@@ -1059,7 +1059,7 @@ import fs10 from "fs/promises";
1059
1059
  import path14 from "path";
1060
1060
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
1061
1061
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1062
- import { z as z7 } from "zod";
1062
+ import { z as z8 } from "zod";
1063
1063
 
1064
1064
  // src/schema.ts
1065
1065
  import fs4 from "fs/promises";
@@ -1172,7 +1172,7 @@ function buildSchemaPrompt(schema, instruction) {
1172
1172
  import fs9 from "fs/promises";
1173
1173
  import path13 from "path";
1174
1174
  import matter7 from "gray-matter";
1175
- import { z as z6 } from "zod";
1175
+ import { z as z7 } from "zod";
1176
1176
 
1177
1177
  // src/analysis.ts
1178
1178
  import path6 from "path";
@@ -1407,29 +1407,46 @@ function conflictConfidence(claimA, claimB) {
1407
1407
  import fs5 from "fs/promises";
1408
1408
  import path9 from "path";
1409
1409
  import matter2 from "gray-matter";
1410
- import { z as z4 } from "zod";
1410
+ import { z as z5 } from "zod";
1411
+
1412
+ // src/findings.ts
1413
+ import { z as z2 } from "zod";
1414
+ function normalizeFindingSeverity(value) {
1415
+ if (typeof value !== "string") {
1416
+ return "info";
1417
+ }
1418
+ const normalized = value.trim().toLowerCase();
1419
+ if (normalized === "error" || normalized === "critical" || normalized === "fatal" || normalized === "high" || normalized === "severe") {
1420
+ return "error";
1421
+ }
1422
+ if (normalized === "warning" || normalized === "warn" || normalized === "medium" || normalized === "moderate" || normalized === "caution") {
1423
+ return "warning";
1424
+ }
1425
+ return "info";
1426
+ }
1427
+ var findingSeveritySchema = z2.any().transform((value) => normalizeFindingSeverity(value));
1411
1428
 
1412
1429
  // src/orchestration.ts
1413
- import path7 from "path";
1414
1430
  import { spawn } from "child_process";
1415
- import { z as z2 } from "zod";
1416
- var orchestrationRoleResultSchema = z2.object({
1417
- summary: z2.string().optional(),
1418
- findings: z2.array(
1419
- z2.object({
1420
- severity: z2.enum(["error", "warning", "info"]).default("info"),
1421
- message: z2.string().min(1),
1422
- relatedPageIds: z2.array(z2.string()).optional(),
1423
- relatedSourceIds: z2.array(z2.string()).optional(),
1424
- suggestedQuery: z2.string().optional()
1431
+ import path7 from "path";
1432
+ import { z as z3 } from "zod";
1433
+ var orchestrationRoleResultSchema = z3.object({
1434
+ summary: z3.string().optional(),
1435
+ findings: z3.array(
1436
+ z3.object({
1437
+ severity: findingSeveritySchema,
1438
+ message: z3.string().min(1),
1439
+ relatedPageIds: z3.array(z3.string()).optional(),
1440
+ relatedSourceIds: z3.array(z3.string()).optional(),
1441
+ suggestedQuery: z3.string().optional()
1425
1442
  })
1426
1443
  ).default([]),
1427
- questions: z2.array(z2.string().min(1)).default([]),
1428
- proposals: z2.array(
1429
- z2.object({
1430
- path: z2.string().min(1),
1431
- content: z2.string().min(1),
1432
- reason: z2.string().min(1)
1444
+ questions: z3.array(z3.string().min(1)).default([]),
1445
+ proposals: z3.array(
1446
+ z3.object({
1447
+ path: z3.string().min(1),
1448
+ content: z3.string().min(1),
1449
+ reason: z3.string().min(1)
1433
1450
  })
1434
1451
  ).default([])
1435
1452
  });
@@ -1600,7 +1617,7 @@ function summarizeRoleQuestions(results) {
1600
1617
  // src/web-search/registry.ts
1601
1618
  import path8 from "path";
1602
1619
  import { pathToFileURL } from "url";
1603
- import { z as z3 } from "zod";
1620
+ import { z as z4 } from "zod";
1604
1621
 
1605
1622
  // src/web-search/http-json.ts
1606
1623
  function deepGet(value, pathValue) {
@@ -1682,10 +1699,10 @@ var HttpJsonWebSearchAdapter = class {
1682
1699
  };
1683
1700
 
1684
1701
  // src/web-search/registry.ts
1685
- var customWebSearchModuleSchema = z3.object({
1686
- createAdapter: z3.function({
1687
- input: [z3.string(), z3.custom(), z3.string()],
1688
- output: z3.promise(z3.custom())
1702
+ var customWebSearchModuleSchema = z4.object({
1703
+ createAdapter: z4.function({
1704
+ input: [z4.string(), z4.custom(), z4.string()],
1705
+ output: z4.promise(z4.custom())
1689
1706
  })
1690
1707
  });
1691
1708
  async function createWebSearchAdapter(id, config, rootDir) {
@@ -1720,15 +1737,15 @@ async function getWebSearchAdapterForTask(rootDir, task) {
1720
1737
  }
1721
1738
 
1722
1739
  // src/deep-lint.ts
1723
- var deepLintResponseSchema = z4.object({
1724
- findings: z4.array(
1725
- z4.object({
1726
- severity: z4.enum(["error", "warning", "info"]).default("info"),
1727
- code: z4.enum(["coverage_gap", "contradiction_candidate", "missing_citation", "candidate_page", "follow_up_question"]),
1728
- message: z4.string().min(1),
1729
- relatedSourceIds: z4.array(z4.string()).default([]),
1730
- relatedPageIds: z4.array(z4.string()).default([]),
1731
- suggestedQuery: z4.string().optional()
1740
+ var deepLintResponseSchema = z5.object({
1741
+ findings: z5.array(
1742
+ z5.object({
1743
+ severity: findingSeveritySchema,
1744
+ code: z5.enum(["coverage_gap", "contradiction_candidate", "missing_citation", "candidate_page", "follow_up_question"]),
1745
+ message: z5.string().min(1),
1746
+ relatedSourceIds: z5.array(z5.string()).default([]),
1747
+ relatedPageIds: z5.array(z5.string()).default([]),
1748
+ suggestedQuery: z5.string().optional()
1732
1749
  })
1733
1750
  ).max(20)
1734
1751
  });
@@ -1982,199 +1999,6 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
1982
1999
  );
1983
2000
  }
1984
2001
 
1985
- // src/output-artifacts.ts
1986
- import { z as z5 } from "zod";
1987
- function escapeXml(value) {
1988
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1989
- }
1990
- function clampNumber(value, min, max) {
1991
- return Math.min(max, Math.max(min, value));
1992
- }
1993
- var chartSpecSchema = z5.object({
1994
- kind: z5.enum(["bar", "line"]).default("bar"),
1995
- title: z5.string().min(1),
1996
- subtitle: z5.string().optional(),
1997
- xLabel: z5.string().optional(),
1998
- yLabel: z5.string().optional(),
1999
- seriesLabel: z5.string().optional(),
2000
- data: z5.array(
2001
- z5.object({
2002
- label: z5.string().min(1),
2003
- value: z5.number().finite()
2004
- })
2005
- ).min(2).max(12),
2006
- notes: z5.array(z5.string().min(1)).max(5).optional()
2007
- });
2008
- var sceneSpecSchema = z5.object({
2009
- title: z5.string().min(1),
2010
- alt: z5.string().min(1),
2011
- background: z5.string().optional(),
2012
- width: z5.number().int().positive().max(2400).optional(),
2013
- height: z5.number().int().positive().max(2400).optional(),
2014
- elements: z5.array(
2015
- z5.object({
2016
- kind: z5.enum(["shape", "label"]),
2017
- shape: z5.enum(["rect", "circle", "line"]).optional(),
2018
- x: z5.number().finite(),
2019
- y: z5.number().finite(),
2020
- width: z5.number().finite().optional(),
2021
- height: z5.number().finite().optional(),
2022
- radius: z5.number().finite().optional(),
2023
- text: z5.string().optional(),
2024
- fontSize: z5.number().finite().optional(),
2025
- fill: z5.string().optional(),
2026
- stroke: z5.string().optional(),
2027
- strokeWidth: z5.number().finite().optional(),
2028
- opacity: z5.number().finite().optional()
2029
- })
2030
- ).min(1).max(32)
2031
- });
2032
- function renderChartSvg(spec) {
2033
- const width = 1200;
2034
- const height = 720;
2035
- const margin = { top: 110, right: 80, bottom: 110, left: 110 };
2036
- const chartWidth = width - margin.left - margin.right;
2037
- const chartHeight = height - margin.top - margin.bottom;
2038
- const values = spec.data.map((item) => item.value);
2039
- const maxValue = Math.max(...values, 1);
2040
- const minValue = Math.min(...values, 0);
2041
- const domainMin = Math.min(0, minValue);
2042
- const domainMax = maxValue <= domainMin ? domainMin + 1 : maxValue;
2043
- const ticks = 5;
2044
- const tickValues = Array.from({ length: ticks + 1 }, (_, index) => domainMin + (domainMax - domainMin) * index / ticks);
2045
- const projectY = (value) => margin.top + chartHeight - (value - domainMin) / (domainMax - domainMin || 1) * chartHeight;
2046
- const zeroY = projectY(0);
2047
- const step = chartWidth / Math.max(1, spec.data.length);
2048
- const barWidth = Math.min(84, step * 0.6);
2049
- const points = spec.data.map((item, index) => {
2050
- const centerX = margin.left + step * index + step / 2;
2051
- const y = projectY(item.value);
2052
- return { ...item, centerX, y };
2053
- });
2054
- const gridLines = tickValues.map((value) => {
2055
- const y = projectY(value);
2056
- return [
2057
- `<line x1="${margin.left}" y1="${y}" x2="${width - margin.right}" y2="${y}" stroke="#dbe4ec" stroke-width="1" />`,
2058
- `<text x="${margin.left - 16}" y="${y + 4}" text-anchor="end" font-size="14" fill="#475569">${escapeXml(value.toFixed(0))}</text>`
2059
- ].join("");
2060
- }).join("");
2061
- const bars = spec.kind === "bar" ? points.map((point) => {
2062
- const top = Math.min(point.y, zeroY);
2063
- const barHeight = Math.max(8, Math.abs(zeroY - point.y));
2064
- return [
2065
- `<rect x="${point.centerX - barWidth / 2}" y="${top}" width="${barWidth}" height="${barHeight}" rx="12" fill="#0ea5e9" opacity="0.92" />`,
2066
- `<text x="${point.centerX}" y="${top - 10}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
2067
- point.value.toFixed(0)
2068
- )}</text>`
2069
- ].join("");
2070
- }).join("") : "";
2071
- const linePath = spec.kind === "line" ? points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.centerX} ${point.y}`).join(" ") : "";
2072
- const lineMarks = spec.kind === "line" ? [
2073
- `<path d="${linePath}" fill="none" stroke="#0ea5e9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" />`,
2074
- ...points.map(
2075
- (point) => `<circle cx="${point.centerX}" cy="${point.y}" r="8" fill="#f8fafc" stroke="#0ea5e9" stroke-width="4" />
2076
- <text x="${point.centerX}" y="${point.y - 18}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
2077
- point.value.toFixed(0)
2078
- )}</text>`
2079
- )
2080
- ].join("") : "";
2081
- const labels = points.map(
2082
- (point) => `<text x="${point.centerX}" y="${height - margin.bottom + 28}" text-anchor="middle" font-size="14" fill="#334155">${escapeXml(
2083
- point.label
2084
- )}</text>`
2085
- ).join("");
2086
- const notes = (spec.notes ?? []).map((note, index) => `<text x="${margin.left}" y="${height - 26 - index * 18}" font-size="13" fill="#475569">${escapeXml(note)}</text>`).join("");
2087
- const svg = [
2088
- `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}">`,
2089
- '<rect width="100%" height="100%" fill="#f8fafc" />',
2090
- `<text x="${margin.left}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
2091
- spec.subtitle ? `<text x="${margin.left}" y="86" font-size="18" fill="#475569">${escapeXml(spec.subtitle)}</text>` : "",
2092
- gridLines,
2093
- `<line x1="${margin.left}" y1="${zeroY}" x2="${width - margin.right}" y2="${zeroY}" stroke="#0f172a" stroke-width="2" />`,
2094
- `<line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${height - margin.bottom}" stroke="#0f172a" stroke-width="2" />`,
2095
- bars,
2096
- lineMarks,
2097
- labels,
2098
- spec.xLabel ? `<text x="${margin.left + chartWidth / 2}" y="${height - 46}" text-anchor="middle" font-size="15" fill="#475569">${escapeXml(spec.xLabel)}</text>` : "",
2099
- spec.yLabel ? `<text x="34" y="${margin.top + chartHeight / 2}" text-anchor="middle" font-size="15" fill="#475569" transform="rotate(-90 34 ${margin.top + chartHeight / 2})">${escapeXml(spec.yLabel)}</text>` : "",
2100
- spec.seriesLabel ? `<text x="${width - margin.right}" y="56" text-anchor="end" font-size="15" fill="#475569">${escapeXml(spec.seriesLabel)}</text>` : "",
2101
- notes,
2102
- "</svg>"
2103
- ].filter(Boolean).join("");
2104
- return { svg, width, height };
2105
- }
2106
- function renderSceneSvg(spec) {
2107
- const width = clampNumber(spec.width ?? 1200, 480, 1600);
2108
- const height = clampNumber(spec.height ?? 720, 320, 1200);
2109
- const elements = spec.elements.map((element) => {
2110
- const opacity = element.opacity === void 0 ? 1 : clampNumber(element.opacity, 0, 1);
2111
- if (element.kind === "label") {
2112
- return `<text x="${element.x}" y="${element.y}" font-size="${clampNumber(element.fontSize ?? 28, 10, 72)}" fill="${escapeXml(
2113
- element.fill ?? "#0f172a"
2114
- )}" opacity="${opacity}" font-family="'Avenir Next', 'Segoe UI', sans-serif">${escapeXml(element.text ?? "")}</text>`;
2115
- }
2116
- switch (element.shape) {
2117
- case "circle":
2118
- return `<circle cx="${element.x}" cy="${element.y}" r="${Math.max(6, element.radius ?? 40)}" fill="${escapeXml(
2119
- element.fill ?? "#dbeafe"
2120
- )}" stroke="${escapeXml(element.stroke ?? "#0ea5e9")}" stroke-width="${Math.max(1, element.strokeWidth ?? 2)}" opacity="${opacity}" />`;
2121
- case "line":
2122
- return `<line x1="${element.x}" y1="${element.y}" x2="${element.x + (element.width ?? 120)}" y2="${element.y + (element.height ?? 0)}" stroke="${escapeXml(element.stroke ?? "#475569")}" stroke-width="${Math.max(1, element.strokeWidth ?? 3)}" opacity="${opacity}" />`;
2123
- case "rect":
2124
- default:
2125
- return `<rect x="${element.x}" y="${element.y}" width="${Math.max(8, element.width ?? 160)}" height="${Math.max(
2126
- 8,
2127
- element.height ?? 120
2128
- )}" rx="22" fill="${escapeXml(element.fill ?? "#e2e8f0")}" stroke="${escapeXml(element.stroke ?? "#94a3b8")}" stroke-width="${Math.max(
2129
- 1,
2130
- element.strokeWidth ?? 2
2131
- )}" opacity="${opacity}" />`;
2132
- }
2133
- }).join("");
2134
- const svg = [
2135
- `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
2136
- spec.alt
2137
- )}">`,
2138
- `<rect width="100%" height="100%" fill="${escapeXml(spec.background ?? "#f8fafc")}" />`,
2139
- `<text x="48" y="64" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
2140
- elements,
2141
- `</svg>`
2142
- ].join("");
2143
- return { svg, width, height };
2144
- }
2145
- function renderRasterPosterSvg(input) {
2146
- const width = clampNumber(input.width ?? 1200, 480, 1600);
2147
- const height = clampNumber(input.height ?? 720, 320, 1200);
2148
- const inset = 42;
2149
- const svg = [
2150
- `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
2151
- input.alt
2152
- )}">`,
2153
- '<rect width="100%" height="100%" fill="#f8fafc" />',
2154
- `<text x="${inset}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(input.title)}</text>`,
2155
- `<image href="${escapeXml(input.rasterFileName)}" x="${inset}" y="92" width="${width - inset * 2}" height="${height - 148}" preserveAspectRatio="xMidYMid meet" />`,
2156
- `</svg>`
2157
- ].join("");
2158
- return { svg, width, height };
2159
- }
2160
- function buildOutputAssetManifest(input) {
2161
- return `${JSON.stringify(
2162
- {
2163
- slug: input.slug,
2164
- format: input.format,
2165
- question: input.question,
2166
- title: input.title,
2167
- answer: input.answer,
2168
- citations: input.citations,
2169
- assets: input.assets,
2170
- spec: input.spec
2171
- },
2172
- null,
2173
- 2
2174
- )}
2175
- `;
2176
- }
2177
-
2178
2002
  // src/markdown.ts
2179
2003
  import matter3 from "gray-matter";
2180
2004
  function uniqueStrings(values) {
@@ -2804,10 +2628,7 @@ function buildOutputPage(input) {
2804
2628
  ] : input.outputFormat === "chart" || input.outputFormat === "image" ? [
2805
2629
  `# ${input.title ?? input.question}`,
2806
2630
  "",
2807
- ...primaryOutputAsset(outputAssets) ? [
2808
- `![${input.title ?? input.question}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`,
2809
- ""
2810
- ] : [],
2631
+ ...primaryOutputAsset(outputAssets) ? [`![${input.title ?? input.question}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`, ""] : [],
2811
2632
  input.answer,
2812
2633
  "",
2813
2634
  ...outputAssetSection(outputAssets),
@@ -2939,10 +2760,7 @@ function buildExploreHubPage(input) {
2939
2760
  ] : input.outputFormat === "chart" || input.outputFormat === "image" ? [
2940
2761
  `# ${title}`,
2941
2762
  "",
2942
- ...primaryOutputAsset(outputAssets) ? [
2943
- `![${title}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`,
2944
- ""
2945
- ] : [],
2763
+ ...primaryOutputAsset(outputAssets) ? [`![${title}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`, ""] : [],
2946
2764
  "## Root Question",
2947
2765
  "",
2948
2766
  input.question,
@@ -2986,77 +2804,271 @@ function buildExploreHubPage(input) {
2986
2804
  };
2987
2805
  }
2988
2806
 
2989
- // src/outputs.ts
2990
- import fs7 from "fs/promises";
2991
- import path11 from "path";
2992
- import matter5 from "gray-matter";
2993
-
2994
- // src/pages.ts
2995
- import fs6 from "fs/promises";
2996
- import path10 from "path";
2997
- import matter4 from "gray-matter";
2998
- function normalizeStringArray(value) {
2999
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
3000
- }
3001
- function normalizeProjectIds(value) {
3002
- return normalizeStringArray(value);
3003
- }
3004
- function normalizeSourceHashes(value) {
3005
- if (!value || typeof value !== "object") {
3006
- return {};
3007
- }
3008
- return Object.fromEntries(
3009
- Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
3010
- );
3011
- }
3012
- function normalizePageStatus(value, fallback = "active") {
3013
- return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : fallback;
3014
- }
3015
- function normalizePageManager(value, fallback = "system") {
3016
- return value === "human" || value === "system" ? value : fallback;
3017
- }
3018
- function normalizeOutputFormat(value, fallback = "markdown") {
3019
- return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
3020
- }
3021
- function normalizeOutputAssets(value) {
3022
- if (!Array.isArray(value)) {
3023
- return [];
3024
- }
3025
- return value.reduce((assets, item) => {
3026
- if (!item || typeof item !== "object") {
3027
- return assets;
3028
- }
3029
- const candidate = item;
3030
- const id = typeof candidate.id === "string" ? candidate.id : "";
3031
- const role = candidate.role;
3032
- const assetPath = typeof candidate.path === "string" ? candidate.path : "";
3033
- const mimeType = typeof candidate.mimeType === "string" ? candidate.mimeType : "";
3034
- if (!id || !assetPath || !mimeType || role !== "primary" && role !== "preview" && role !== "manifest" && role !== "poster") {
3035
- return assets;
3036
- }
3037
- assets.push({
3038
- id,
3039
- role,
3040
- path: assetPath,
3041
- mimeType,
3042
- width: typeof candidate.width === "number" ? candidate.width : void 0,
3043
- height: typeof candidate.height === "number" ? candidate.height : void 0,
3044
- dataPath: typeof candidate.dataPath === "string" ? candidate.dataPath : void 0
3045
- });
3046
- return assets;
3047
- }, []);
2807
+ // src/output-artifacts.ts
2808
+ import { z as z6 } from "zod";
2809
+ function escapeXml(value) {
2810
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3048
2811
  }
3049
- function normalizeTimestamp(value, fallback) {
3050
- if (typeof value !== "string") {
3051
- return fallback;
3052
- }
3053
- const parsed = Date.parse(value);
3054
- return Number.isNaN(parsed) ? fallback : new Date(parsed).toISOString();
2812
+ function clampNumber(value, min, max) {
2813
+ return Math.min(max, Math.max(min, value));
3055
2814
  }
3056
- async function loadExistingManagedPageState(absolutePath, defaults = {}) {
3057
- const now = (/* @__PURE__ */ new Date()).toISOString();
3058
- const createdFallback = defaults.createdAt ?? now;
3059
- const updatedFallback = defaults.updatedAt ?? createdFallback;
2815
+ var chartSpecSchema = z6.object({
2816
+ kind: z6.enum(["bar", "line"]).default("bar"),
2817
+ title: z6.string().min(1),
2818
+ subtitle: z6.string().optional(),
2819
+ xLabel: z6.string().optional(),
2820
+ yLabel: z6.string().optional(),
2821
+ seriesLabel: z6.string().optional(),
2822
+ data: z6.array(
2823
+ z6.object({
2824
+ label: z6.string().min(1),
2825
+ value: z6.number().finite()
2826
+ })
2827
+ ).min(2).max(12),
2828
+ notes: z6.array(z6.string().min(1)).max(5).optional()
2829
+ });
2830
+ var sceneSpecSchema = z6.object({
2831
+ title: z6.string().min(1),
2832
+ alt: z6.string().min(1),
2833
+ background: z6.string().optional(),
2834
+ width: z6.number().int().positive().max(2400).optional(),
2835
+ height: z6.number().int().positive().max(2400).optional(),
2836
+ elements: z6.array(
2837
+ z6.object({
2838
+ kind: z6.enum(["shape", "label"]),
2839
+ shape: z6.enum(["rect", "circle", "line"]).optional(),
2840
+ x: z6.number().finite(),
2841
+ y: z6.number().finite(),
2842
+ width: z6.number().finite().optional(),
2843
+ height: z6.number().finite().optional(),
2844
+ radius: z6.number().finite().optional(),
2845
+ text: z6.string().optional(),
2846
+ fontSize: z6.number().finite().optional(),
2847
+ fill: z6.string().optional(),
2848
+ stroke: z6.string().optional(),
2849
+ strokeWidth: z6.number().finite().optional(),
2850
+ opacity: z6.number().finite().optional()
2851
+ })
2852
+ ).min(1).max(32)
2853
+ });
2854
+ function renderChartSvg(spec) {
2855
+ const width = 1200;
2856
+ const height = 720;
2857
+ const margin = { top: 110, right: 80, bottom: 110, left: 110 };
2858
+ const chartWidth = width - margin.left - margin.right;
2859
+ const chartHeight = height - margin.top - margin.bottom;
2860
+ const values = spec.data.map((item) => item.value);
2861
+ const maxValue = Math.max(...values, 1);
2862
+ const minValue = Math.min(...values, 0);
2863
+ const domainMin = Math.min(0, minValue);
2864
+ const domainMax = maxValue <= domainMin ? domainMin + 1 : maxValue;
2865
+ const ticks = 5;
2866
+ const tickValues = Array.from({ length: ticks + 1 }, (_, index) => domainMin + (domainMax - domainMin) * index / ticks);
2867
+ const projectY = (value) => margin.top + chartHeight - (value - domainMin) / (domainMax - domainMin || 1) * chartHeight;
2868
+ const zeroY = projectY(0);
2869
+ const step = chartWidth / Math.max(1, spec.data.length);
2870
+ const barWidth = Math.min(84, step * 0.6);
2871
+ const points = spec.data.map((item, index) => {
2872
+ const centerX = margin.left + step * index + step / 2;
2873
+ const y = projectY(item.value);
2874
+ return { ...item, centerX, y };
2875
+ });
2876
+ const gridLines = tickValues.map((value) => {
2877
+ const y = projectY(value);
2878
+ return [
2879
+ `<line x1="${margin.left}" y1="${y}" x2="${width - margin.right}" y2="${y}" stroke="#dbe4ec" stroke-width="1" />`,
2880
+ `<text x="${margin.left - 16}" y="${y + 4}" text-anchor="end" font-size="14" fill="#475569">${escapeXml(value.toFixed(0))}</text>`
2881
+ ].join("");
2882
+ }).join("");
2883
+ const bars = spec.kind === "bar" ? points.map((point) => {
2884
+ const top = Math.min(point.y, zeroY);
2885
+ const barHeight = Math.max(8, Math.abs(zeroY - point.y));
2886
+ return [
2887
+ `<rect x="${point.centerX - barWidth / 2}" y="${top}" width="${barWidth}" height="${barHeight}" rx="12" fill="#0ea5e9" opacity="0.92" />`,
2888
+ `<text x="${point.centerX}" y="${top - 10}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
2889
+ point.value.toFixed(0)
2890
+ )}</text>`
2891
+ ].join("");
2892
+ }).join("") : "";
2893
+ const linePath = spec.kind === "line" ? points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.centerX} ${point.y}`).join(" ") : "";
2894
+ const lineMarks = spec.kind === "line" ? [
2895
+ `<path d="${linePath}" fill="none" stroke="#0ea5e9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" />`,
2896
+ ...points.map(
2897
+ (point) => `<circle cx="${point.centerX}" cy="${point.y}" r="8" fill="#f8fafc" stroke="#0ea5e9" stroke-width="4" />
2898
+ <text x="${point.centerX}" y="${point.y - 18}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
2899
+ point.value.toFixed(0)
2900
+ )}</text>`
2901
+ )
2902
+ ].join("") : "";
2903
+ const labels = points.map(
2904
+ (point) => `<text x="${point.centerX}" y="${height - margin.bottom + 28}" text-anchor="middle" font-size="14" fill="#334155">${escapeXml(
2905
+ point.label
2906
+ )}</text>`
2907
+ ).join("");
2908
+ const notes = (spec.notes ?? []).map(
2909
+ (note, index) => `<text x="${margin.left}" y="${height - 26 - index * 18}" font-size="13" fill="#475569">${escapeXml(note)}</text>`
2910
+ ).join("");
2911
+ const svg = [
2912
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}">`,
2913
+ '<rect width="100%" height="100%" fill="#f8fafc" />',
2914
+ `<text x="${margin.left}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
2915
+ spec.subtitle ? `<text x="${margin.left}" y="86" font-size="18" fill="#475569">${escapeXml(spec.subtitle)}</text>` : "",
2916
+ gridLines,
2917
+ `<line x1="${margin.left}" y1="${zeroY}" x2="${width - margin.right}" y2="${zeroY}" stroke="#0f172a" stroke-width="2" />`,
2918
+ `<line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${height - margin.bottom}" stroke="#0f172a" stroke-width="2" />`,
2919
+ bars,
2920
+ lineMarks,
2921
+ labels,
2922
+ spec.xLabel ? `<text x="${margin.left + chartWidth / 2}" y="${height - 46}" text-anchor="middle" font-size="15" fill="#475569">${escapeXml(spec.xLabel)}</text>` : "",
2923
+ spec.yLabel ? `<text x="34" y="${margin.top + chartHeight / 2}" text-anchor="middle" font-size="15" fill="#475569" transform="rotate(-90 34 ${margin.top + chartHeight / 2})">${escapeXml(spec.yLabel)}</text>` : "",
2924
+ spec.seriesLabel ? `<text x="${width - margin.right}" y="56" text-anchor="end" font-size="15" fill="#475569">${escapeXml(spec.seriesLabel)}</text>` : "",
2925
+ notes,
2926
+ "</svg>"
2927
+ ].filter(Boolean).join("");
2928
+ return { svg, width, height };
2929
+ }
2930
+ function renderSceneSvg(spec) {
2931
+ const width = clampNumber(spec.width ?? 1200, 480, 1600);
2932
+ const height = clampNumber(spec.height ?? 720, 320, 1200);
2933
+ const elements = spec.elements.map((element) => {
2934
+ const opacity = element.opacity === void 0 ? 1 : clampNumber(element.opacity, 0, 1);
2935
+ if (element.kind === "label") {
2936
+ return `<text x="${element.x}" y="${element.y}" font-size="${clampNumber(element.fontSize ?? 28, 10, 72)}" fill="${escapeXml(
2937
+ element.fill ?? "#0f172a"
2938
+ )}" opacity="${opacity}" font-family="'Avenir Next', 'Segoe UI', sans-serif">${escapeXml(element.text ?? "")}</text>`;
2939
+ }
2940
+ switch (element.shape) {
2941
+ case "circle":
2942
+ return `<circle cx="${element.x}" cy="${element.y}" r="${Math.max(6, element.radius ?? 40)}" fill="${escapeXml(
2943
+ element.fill ?? "#dbeafe"
2944
+ )}" stroke="${escapeXml(element.stroke ?? "#0ea5e9")}" stroke-width="${Math.max(1, element.strokeWidth ?? 2)}" opacity="${opacity}" />`;
2945
+ case "line":
2946
+ return `<line x1="${element.x}" y1="${element.y}" x2="${element.x + (element.width ?? 120)}" y2="${element.y + (element.height ?? 0)}" stroke="${escapeXml(element.stroke ?? "#475569")}" stroke-width="${Math.max(1, element.strokeWidth ?? 3)}" opacity="${opacity}" />`;
2947
+ default:
2948
+ return `<rect x="${element.x}" y="${element.y}" width="${Math.max(8, element.width ?? 160)}" height="${Math.max(
2949
+ 8,
2950
+ element.height ?? 120
2951
+ )}" rx="22" fill="${escapeXml(element.fill ?? "#e2e8f0")}" stroke="${escapeXml(element.stroke ?? "#94a3b8")}" stroke-width="${Math.max(
2952
+ 1,
2953
+ element.strokeWidth ?? 2
2954
+ )}" opacity="${opacity}" />`;
2955
+ }
2956
+ }).join("");
2957
+ const svg = [
2958
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
2959
+ spec.alt
2960
+ )}">`,
2961
+ `<rect width="100%" height="100%" fill="${escapeXml(spec.background ?? "#f8fafc")}" />`,
2962
+ `<text x="48" y="64" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
2963
+ elements,
2964
+ `</svg>`
2965
+ ].join("");
2966
+ return { svg, width, height };
2967
+ }
2968
+ function renderRasterPosterSvg(input) {
2969
+ const width = clampNumber(input.width ?? 1200, 480, 1600);
2970
+ const height = clampNumber(input.height ?? 720, 320, 1200);
2971
+ const inset = 42;
2972
+ const svg = [
2973
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
2974
+ input.alt
2975
+ )}">`,
2976
+ '<rect width="100%" height="100%" fill="#f8fafc" />',
2977
+ `<text x="${inset}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(input.title)}</text>`,
2978
+ `<image href="${escapeXml(input.rasterFileName)}" x="${inset}" y="92" width="${width - inset * 2}" height="${height - 148}" preserveAspectRatio="xMidYMid meet" />`,
2979
+ `</svg>`
2980
+ ].join("");
2981
+ return { svg, width, height };
2982
+ }
2983
+ function buildOutputAssetManifest(input) {
2984
+ return `${JSON.stringify(
2985
+ {
2986
+ slug: input.slug,
2987
+ format: input.format,
2988
+ question: input.question,
2989
+ title: input.title,
2990
+ answer: input.answer,
2991
+ citations: input.citations,
2992
+ assets: input.assets,
2993
+ spec: input.spec
2994
+ },
2995
+ null,
2996
+ 2
2997
+ )}
2998
+ `;
2999
+ }
3000
+
3001
+ // src/outputs.ts
3002
+ import fs7 from "fs/promises";
3003
+ import path11 from "path";
3004
+ import matter5 from "gray-matter";
3005
+
3006
+ // src/pages.ts
3007
+ import fs6 from "fs/promises";
3008
+ import path10 from "path";
3009
+ import matter4 from "gray-matter";
3010
+ function normalizeStringArray(value) {
3011
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
3012
+ }
3013
+ function normalizeProjectIds(value) {
3014
+ return normalizeStringArray(value);
3015
+ }
3016
+ function normalizeSourceHashes(value) {
3017
+ if (!value || typeof value !== "object") {
3018
+ return {};
3019
+ }
3020
+ return Object.fromEntries(
3021
+ Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
3022
+ );
3023
+ }
3024
+ function normalizePageStatus(value, fallback = "active") {
3025
+ return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : fallback;
3026
+ }
3027
+ function normalizePageManager(value, fallback = "system") {
3028
+ return value === "human" || value === "system" ? value : fallback;
3029
+ }
3030
+ function normalizeOutputFormat(value, fallback = "markdown") {
3031
+ return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
3032
+ }
3033
+ function normalizeOutputAssets(value) {
3034
+ if (!Array.isArray(value)) {
3035
+ return [];
3036
+ }
3037
+ return value.reduce((assets, item) => {
3038
+ if (!item || typeof item !== "object") {
3039
+ return assets;
3040
+ }
3041
+ const candidate = item;
3042
+ const id = typeof candidate.id === "string" ? candidate.id : "";
3043
+ const role = candidate.role;
3044
+ const assetPath = typeof candidate.path === "string" ? candidate.path : "";
3045
+ const mimeType = typeof candidate.mimeType === "string" ? candidate.mimeType : "";
3046
+ if (!id || !assetPath || !mimeType || role !== "primary" && role !== "preview" && role !== "manifest" && role !== "poster") {
3047
+ return assets;
3048
+ }
3049
+ assets.push({
3050
+ id,
3051
+ role,
3052
+ path: assetPath,
3053
+ mimeType,
3054
+ width: typeof candidate.width === "number" ? candidate.width : void 0,
3055
+ height: typeof candidate.height === "number" ? candidate.height : void 0,
3056
+ dataPath: typeof candidate.dataPath === "string" ? candidate.dataPath : void 0
3057
+ });
3058
+ return assets;
3059
+ }, []);
3060
+ }
3061
+ function normalizeTimestamp(value, fallback) {
3062
+ if (typeof value !== "string") {
3063
+ return fallback;
3064
+ }
3065
+ const parsed = Date.parse(value);
3066
+ return Number.isNaN(parsed) ? fallback : new Date(parsed).toISOString();
3067
+ }
3068
+ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
3069
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3070
+ const createdFallback = defaults.createdAt ?? now;
3071
+ const updatedFallback = defaults.updatedAt ?? createdFallback;
3060
3072
  if (!await fileExists(absolutePath)) {
3061
3073
  return {
3062
3074
  status: defaults.status ?? "active",
@@ -5091,7 +5103,13 @@ async function prepareOutputPageSave(rootDir, input) {
5091
5103
  }
5092
5104
  });
5093
5105
  const absolutePath = path13.join(paths.wikiDir, output.page.path);
5094
- return { page: output.page, savedPath: absolutePath, outputAssets: output.page.outputAssets ?? [], content: output.content, assetFiles: input.assetFiles ?? [] };
5106
+ return {
5107
+ page: output.page,
5108
+ savedPath: absolutePath,
5109
+ outputAssets: output.page.outputAssets ?? [],
5110
+ content: output.content,
5111
+ assetFiles: input.assetFiles ?? []
5112
+ };
5095
5113
  }
5096
5114
  async function persistOutputPage(rootDir, input) {
5097
5115
  const { paths } = await loadVaultConfig(rootDir);
@@ -5126,7 +5144,13 @@ async function prepareExploreHubSave(rootDir, input) {
5126
5144
  }
5127
5145
  });
5128
5146
  const absolutePath = path13.join(paths.wikiDir, hub.page.path);
5129
- return { page: hub.page, savedPath: absolutePath, outputAssets: hub.page.outputAssets ?? [], content: hub.content, assetFiles: input.assetFiles ?? [] };
5147
+ return {
5148
+ page: hub.page,
5149
+ savedPath: absolutePath,
5150
+ outputAssets: hub.page.outputAssets ?? [],
5151
+ content: hub.content,
5152
+ assetFiles: input.assetFiles ?? []
5153
+ };
5130
5154
  }
5131
5155
  async function persistExploreHub(rootDir, input) {
5132
5156
  const { paths } = await loadVaultConfig(rootDir);
@@ -5169,9 +5193,10 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
5169
5193
  await fs9.writeFile(targetPath, file.content, "utf8");
5170
5194
  }
5171
5195
  }
5172
- const nextPages = sortGraphPages(
5173
- [...(previousGraph?.pages ?? []).filter((page) => !stagedPages.some((item) => item.page.id === page.id || item.page.path === page.path)), ...stagedPages.map((item) => item.page)]
5174
- );
5196
+ const nextPages = sortGraphPages([
5197
+ ...(previousGraph?.pages ?? []).filter((page) => !stagedPages.some((item) => item.page.id === page.id || item.page.path === page.path)),
5198
+ ...stagedPages.map((item) => item.page)
5199
+ ]);
5175
5200
  const graph = {
5176
5201
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5177
5202
  nodes: previousGraph?.nodes ?? [],
@@ -5308,8 +5333,8 @@ async function generateFollowUpQuestions(rootDir, question, answer) {
5308
5333
  Current answer:
5309
5334
  ${answer}`
5310
5335
  },
5311
- z6.object({
5312
- questions: z6.array(z6.string().min(1)).max(5)
5336
+ z7.object({
5337
+ questions: z7.array(z7.string().min(1)).max(5)
5313
5338
  })
5314
5339
  );
5315
5340
  return uniqueBy(response.questions, (item) => item).filter((item) => item !== question);
@@ -6144,9 +6169,7 @@ async function exploreVault(rootDir, options) {
6144
6169
  `Citations: ${query.citations.join(", ") || "none"}`
6145
6170
  ].join("\n")
6146
6171
  });
6147
- const orchestrationNotes = roleResults.flatMap(
6148
- (result) => result.findings.map((finding) => `- [${result.role}] ${finding.message}`)
6149
- );
6172
+ const orchestrationNotes = roleResults.flatMap((result) => result.findings.map((finding) => `- [${result.role}] ${finding.message}`));
6150
6173
  const enrichedAnswer = orchestrationNotes.length ? `${query.answer}
6151
6174
 
6152
6175
  ## Agent Review
@@ -6508,7 +6531,7 @@ async function bootstrapDemo(rootDir, input) {
6508
6531
  }
6509
6532
 
6510
6533
  // src/mcp.ts
6511
- var SERVER_VERSION = "0.1.7";
6534
+ var SERVER_VERSION = "0.1.9";
6512
6535
  async function createMcpServer(rootDir) {
6513
6536
  const server = new McpServer({
6514
6537
  name: "swarmvault",
@@ -6530,8 +6553,8 @@ async function createMcpServer(rootDir) {
6530
6553
  {
6531
6554
  description: "Search compiled wiki pages using the local full-text index.",
6532
6555
  inputSchema: {
6533
- query: z7.string().min(1).describe("Search query"),
6534
- limit: z7.number().int().min(1).max(25).optional().describe("Maximum number of results")
6556
+ query: z8.string().min(1).describe("Search query"),
6557
+ limit: z8.number().int().min(1).max(25).optional().describe("Maximum number of results")
6535
6558
  }
6536
6559
  },
6537
6560
  async ({ query, limit }) => {
@@ -6544,7 +6567,7 @@ async function createMcpServer(rootDir) {
6544
6567
  {
6545
6568
  description: "Read a generated wiki page by its path relative to wiki/.",
6546
6569
  inputSchema: {
6547
- path: z7.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
6570
+ path: z8.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
6548
6571
  }
6549
6572
  },
6550
6573
  async ({ path: relativePath }) => {
@@ -6560,7 +6583,7 @@ async function createMcpServer(rootDir) {
6560
6583
  {
6561
6584
  description: "List source manifests in the current workspace.",
6562
6585
  inputSchema: {
6563
- limit: z7.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
6586
+ limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
6564
6587
  }
6565
6588
  },
6566
6589
  async ({ limit }) => {
@@ -6573,9 +6596,9 @@ async function createMcpServer(rootDir) {
6573
6596
  {
6574
6597
  description: "Ask a question against the compiled vault and optionally save the answer.",
6575
6598
  inputSchema: {
6576
- question: z7.string().min(1).describe("Question to ask the vault"),
6577
- save: z7.boolean().optional().describe("Persist the answer to wiki/outputs"),
6578
- format: z7.enum(["markdown", "report", "slides"]).optional().describe("Output format")
6599
+ question: z8.string().min(1).describe("Question to ask the vault"),
6600
+ save: z8.boolean().optional().describe("Persist the answer to wiki/outputs"),
6601
+ format: z8.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
6579
6602
  }
6580
6603
  },
6581
6604
  async ({ question, save, format }) => {
@@ -6592,7 +6615,7 @@ async function createMcpServer(rootDir) {
6592
6615
  {
6593
6616
  description: "Ingest a local file path or URL into the SwarmVault workspace.",
6594
6617
  inputSchema: {
6595
- input: z7.string().min(1).describe("Local path or URL to ingest")
6618
+ input: z8.string().min(1).describe("Local path or URL to ingest")
6596
6619
  }
6597
6620
  },
6598
6621
  async ({ input }) => {
@@ -6605,7 +6628,7 @@ async function createMcpServer(rootDir) {
6605
6628
  {
6606
6629
  description: "Compile source manifests into wiki pages, graph data, and search index.",
6607
6630
  inputSchema: {
6608
- approve: z7.boolean().optional().describe("Stage a review bundle without applying active page changes")
6631
+ approve: z8.boolean().optional().describe("Stage a review bundle without applying active page changes")
6609
6632
  }
6610
6633
  },
6611
6634
  async ({ approve }) => {
@@ -6803,283 +6826,14 @@ function asTextResource(uri, text) {
6803
6826
  };
6804
6827
  }
6805
6828
 
6806
- // src/viewer.ts
6807
- import { execFile } from "child_process";
6829
+ // src/schedule.ts
6808
6830
  import fs11 from "fs/promises";
6809
- import http from "http";
6810
6831
  import path15 from "path";
6811
- import { promisify } from "util";
6812
- import matter8 from "gray-matter";
6813
- import mime2 from "mime-types";
6814
- var execFileAsync = promisify(execFile);
6815
- async function readViewerPage(rootDir, relativePath) {
6816
- const { paths } = await loadVaultConfig(rootDir);
6817
- const absolutePath = path15.resolve(paths.wikiDir, relativePath);
6818
- if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
6819
- return null;
6820
- }
6821
- const raw = await fs11.readFile(absolutePath, "utf8");
6822
- const parsed = matter8(raw);
6823
- return {
6824
- path: relativePath,
6825
- title: typeof parsed.data.title === "string" ? parsed.data.title : path15.basename(relativePath, path15.extname(relativePath)),
6826
- frontmatter: parsed.data,
6827
- content: parsed.content,
6828
- assets: normalizeOutputAssets(parsed.data.output_assets)
6829
- };
6830
- }
6831
- async function readViewerAsset(rootDir, relativePath) {
6832
- const { paths } = await loadVaultConfig(rootDir);
6833
- const absolutePath = path15.resolve(paths.wikiDir, relativePath);
6834
- if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
6835
- return null;
6836
- }
6837
- return {
6838
- buffer: await fs11.readFile(absolutePath),
6839
- mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
6840
- };
6841
- }
6842
- async function assetDataUrl(rootDir, relativePath) {
6843
- const asset = await readViewerAsset(rootDir, relativePath);
6844
- if (!asset) {
6845
- return void 0;
6846
- }
6847
- return `data:${asset.mimeType};base64,${asset.buffer.toString("base64")}`;
6848
- }
6849
- async function readJsonBody(request) {
6850
- const chunks = [];
6851
- for await (const chunk of request) {
6852
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
6853
- }
6854
- const raw = Buffer.concat(chunks).toString("utf8").trim();
6855
- if (!raw) {
6856
- return {};
6857
- }
6858
- return JSON.parse(raw);
6859
- }
6860
- async function ensureViewerDist(viewerDistDir) {
6861
- const indexPath = path15.join(viewerDistDir, "index.html");
6862
- if (await fileExists(indexPath)) {
6863
- return;
6864
- }
6865
- const viewerProjectDir = path15.dirname(viewerDistDir);
6866
- if (await fileExists(path15.join(viewerProjectDir, "package.json"))) {
6867
- await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
6868
- }
6869
- }
6870
- async function startGraphServer(rootDir, port) {
6871
- const { config, paths } = await loadVaultConfig(rootDir);
6872
- const effectivePort = port ?? config.viewer.port;
6873
- await ensureViewerDist(paths.viewerDistDir);
6874
- const server = http.createServer(async (request, response) => {
6875
- const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `localhost:${effectivePort}`}`);
6876
- if (url.pathname === "/api/graph") {
6877
- if (!await fileExists(paths.graphPath)) {
6878
- response.writeHead(404, { "content-type": "application/json" });
6879
- response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
6880
- return;
6881
- }
6882
- response.writeHead(200, { "content-type": "application/json" });
6883
- response.end(await fs11.readFile(paths.graphPath, "utf8"));
6884
- return;
6885
- }
6886
- if (url.pathname === "/api/search") {
6887
- if (!await fileExists(paths.searchDbPath)) {
6888
- response.writeHead(404, { "content-type": "application/json" });
6889
- response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
6890
- return;
6891
- }
6892
- const query = url.searchParams.get("q") ?? "";
6893
- const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
6894
- const kind = url.searchParams.get("kind") ?? "all";
6895
- const status = url.searchParams.get("status") ?? "all";
6896
- const project = url.searchParams.get("project") ?? "all";
6897
- const results = searchPages(paths.searchDbPath, query, {
6898
- limit: Number.isFinite(limit) ? limit : 10,
6899
- kind,
6900
- status,
6901
- project
6902
- });
6903
- response.writeHead(200, { "content-type": "application/json" });
6904
- response.end(JSON.stringify(results));
6905
- return;
6906
- }
6907
- if (url.pathname === "/api/page") {
6908
- const relativePath2 = url.searchParams.get("path") ?? "";
6909
- const page = await readViewerPage(rootDir, relativePath2);
6910
- if (!page) {
6911
- response.writeHead(404, { "content-type": "application/json" });
6912
- response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
6913
- return;
6914
- }
6915
- response.writeHead(200, { "content-type": "application/json" });
6916
- response.end(JSON.stringify(page));
6917
- return;
6918
- }
6919
- if (url.pathname === "/api/asset") {
6920
- const relativePath2 = url.searchParams.get("path") ?? "";
6921
- const asset = await readViewerAsset(rootDir, relativePath2);
6922
- if (!asset) {
6923
- response.writeHead(404, { "content-type": "application/json" });
6924
- response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
6925
- return;
6926
- }
6927
- response.writeHead(200, { "content-type": asset.mimeType });
6928
- response.end(asset.buffer);
6929
- return;
6930
- }
6931
- if (url.pathname === "/api/reviews" && request.method === "GET") {
6932
- response.writeHead(200, { "content-type": "application/json" });
6933
- response.end(JSON.stringify(await listApprovals(rootDir)));
6934
- return;
6935
- }
6936
- if (url.pathname === "/api/review" && request.method === "GET") {
6937
- const approvalId = url.searchParams.get("id") ?? "";
6938
- if (!approvalId) {
6939
- response.writeHead(400, { "content-type": "application/json" });
6940
- response.end(JSON.stringify({ error: "Missing approval id." }));
6941
- return;
6942
- }
6943
- response.writeHead(200, { "content-type": "application/json" });
6944
- response.end(JSON.stringify(await readApproval(rootDir, approvalId)));
6945
- return;
6946
- }
6947
- if (url.pathname === "/api/review" && request.method === "POST") {
6948
- const body = await readJsonBody(request);
6949
- const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
6950
- const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
6951
- const action = url.searchParams.get("action") ?? "";
6952
- if (!approvalId || action !== "accept" && action !== "reject") {
6953
- response.writeHead(400, { "content-type": "application/json" });
6954
- response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
6955
- return;
6956
- }
6957
- const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
6958
- response.writeHead(200, { "content-type": "application/json" });
6959
- response.end(JSON.stringify(result));
6960
- return;
6961
- }
6962
- if (url.pathname === "/api/candidates" && request.method === "GET") {
6963
- response.writeHead(200, { "content-type": "application/json" });
6964
- response.end(JSON.stringify(await listCandidates(rootDir)));
6965
- return;
6966
- }
6967
- if (url.pathname === "/api/candidate" && request.method === "POST") {
6968
- const body = await readJsonBody(request);
6969
- const target2 = typeof body.target === "string" ? body.target : "";
6970
- const action = url.searchParams.get("action") ?? "";
6971
- if (!target2 || action !== "promote" && action !== "archive") {
6972
- response.writeHead(400, { "content-type": "application/json" });
6973
- response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
6974
- return;
6975
- }
6976
- const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
6977
- response.writeHead(200, { "content-type": "application/json" });
6978
- response.end(JSON.stringify(result));
6979
- return;
6980
- }
6981
- const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
6982
- const target = path15.join(paths.viewerDistDir, relativePath);
6983
- const fallback = path15.join(paths.viewerDistDir, "index.html");
6984
- const filePath = await fileExists(target) ? target : fallback;
6985
- if (!await fileExists(filePath)) {
6986
- response.writeHead(503, { "content-type": "text/plain" });
6987
- response.end("Viewer build not found. Run `pnpm build` first.");
6988
- return;
6989
- }
6990
- response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
6991
- response.end(await fs11.readFile(filePath));
6992
- });
6993
- await new Promise((resolve) => {
6994
- server.listen(effectivePort, resolve);
6995
- });
6996
- return {
6997
- port: effectivePort,
6998
- close: async () => {
6999
- await new Promise((resolve, reject) => {
7000
- server.close((error) => {
7001
- if (error) {
7002
- reject(error);
7003
- return;
7004
- }
7005
- resolve();
7006
- });
7007
- });
7008
- }
7009
- };
7010
- }
7011
- async function exportGraphHtml(rootDir, outputPath) {
7012
- const { paths } = await loadVaultConfig(rootDir);
7013
- const graph = await readJsonFile(paths.graphPath);
7014
- if (!graph) {
7015
- throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
7016
- }
7017
- await ensureViewerDist(paths.viewerDistDir);
7018
- const indexPath = path15.join(paths.viewerDistDir, "index.html");
7019
- if (!await fileExists(indexPath)) {
7020
- throw new Error("Viewer build not found. Run `pnpm build` first.");
7021
- }
7022
- const pages = await Promise.all(
7023
- graph.pages.map(async (page) => {
7024
- const loaded = await readViewerPage(rootDir, page.path);
7025
- return loaded ? {
7026
- pageId: page.id,
7027
- path: loaded.path,
7028
- title: loaded.title,
7029
- kind: page.kind,
7030
- status: page.status,
7031
- projectIds: page.projectIds,
7032
- content: loaded.content,
7033
- assets: await Promise.all(
7034
- loaded.assets.map(async (asset) => ({
7035
- ...asset,
7036
- dataUrl: await assetDataUrl(rootDir, asset.path)
7037
- }))
7038
- )
7039
- } : null;
7040
- })
7041
- );
7042
- const rawHtml = await fs11.readFile(indexPath, "utf8");
7043
- const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
7044
- const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
7045
- const scriptPath = scriptMatch?.[1] ? path15.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
7046
- const stylePath = styleMatch?.[1] ? path15.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
7047
- if (!scriptPath || !await fileExists(scriptPath)) {
7048
- throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
7049
- }
7050
- const script = await fs11.readFile(scriptPath, "utf8");
7051
- const style = stylePath && await fileExists(stylePath) ? await fs11.readFile(stylePath, "utf8") : "";
7052
- const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
7053
- const html = [
7054
- "<!doctype html>",
7055
- '<html lang="en">',
7056
- " <head>",
7057
- ' <meta charset="UTF-8" />',
7058
- ' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
7059
- " <title>SwarmVault Graph Export</title>",
7060
- style ? ` <style>${style}</style>` : "",
7061
- " </head>",
7062
- " <body>",
7063
- ' <div id="root"></div>',
7064
- ` <script>window.__SWARMVAULT_EMBEDDED_DATA__ = ${embeddedData};</script>`,
7065
- ` <script type="module">${script}</script>`,
7066
- " </body>",
7067
- "</html>",
7068
- ""
7069
- ].filter(Boolean).join("\n");
7070
- await fs11.mkdir(path15.dirname(outputPath), { recursive: true });
7071
- await fs11.writeFile(outputPath, html, "utf8");
7072
- return path15.resolve(outputPath);
7073
- }
7074
-
7075
- // src/schedule.ts
7076
- import fs12 from "fs/promises";
7077
- import path16 from "path";
7078
- function scheduleStatePath(schedulesDir, jobId) {
7079
- return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
6832
+ function scheduleStatePath(schedulesDir, jobId) {
6833
+ return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
7080
6834
  }
7081
6835
  function scheduleLockPath(schedulesDir, jobId) {
7082
- return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
6836
+ return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
7083
6837
  }
7084
6838
  function parseEveryDuration(value) {
7085
6839
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -7182,13 +6936,13 @@ async function acquireJobLease(rootDir, jobId) {
7182
6936
  const { paths } = await loadVaultConfig(rootDir);
7183
6937
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
7184
6938
  await ensureDir(paths.schedulesDir);
7185
- const handle = await fs12.open(leasePath, "wx");
6939
+ const handle = await fs11.open(leasePath, "wx");
7186
6940
  await handle.writeFile(`${process.pid}
7187
6941
  ${(/* @__PURE__ */ new Date()).toISOString()}
7188
6942
  `);
7189
6943
  await handle.close();
7190
6944
  return async () => {
7191
- await fs12.rm(leasePath, { force: true });
6945
+ await fs11.rm(leasePath, { force: true });
7192
6946
  };
7193
6947
  }
7194
6948
  async function listSchedules(rootDir) {
@@ -7334,6 +7088,275 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
7334
7088
  };
7335
7089
  }
7336
7090
 
7091
+ // src/viewer.ts
7092
+ import { execFile } from "child_process";
7093
+ import fs12 from "fs/promises";
7094
+ import http from "http";
7095
+ import path16 from "path";
7096
+ import { promisify } from "util";
7097
+ import matter8 from "gray-matter";
7098
+ import mime2 from "mime-types";
7099
+ var execFileAsync = promisify(execFile);
7100
+ async function readViewerPage(rootDir, relativePath) {
7101
+ const { paths } = await loadVaultConfig(rootDir);
7102
+ const absolutePath = path16.resolve(paths.wikiDir, relativePath);
7103
+ if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
7104
+ return null;
7105
+ }
7106
+ const raw = await fs12.readFile(absolutePath, "utf8");
7107
+ const parsed = matter8(raw);
7108
+ return {
7109
+ path: relativePath,
7110
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path16.basename(relativePath, path16.extname(relativePath)),
7111
+ frontmatter: parsed.data,
7112
+ content: parsed.content,
7113
+ assets: normalizeOutputAssets(parsed.data.output_assets)
7114
+ };
7115
+ }
7116
+ async function readViewerAsset(rootDir, relativePath) {
7117
+ const { paths } = await loadVaultConfig(rootDir);
7118
+ const absolutePath = path16.resolve(paths.wikiDir, relativePath);
7119
+ if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
7120
+ return null;
7121
+ }
7122
+ return {
7123
+ buffer: await fs12.readFile(absolutePath),
7124
+ mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
7125
+ };
7126
+ }
7127
+ async function assetDataUrl(rootDir, relativePath) {
7128
+ const asset = await readViewerAsset(rootDir, relativePath);
7129
+ if (!asset) {
7130
+ return void 0;
7131
+ }
7132
+ return `data:${asset.mimeType};base64,${asset.buffer.toString("base64")}`;
7133
+ }
7134
+ async function readJsonBody(request) {
7135
+ const chunks = [];
7136
+ for await (const chunk of request) {
7137
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
7138
+ }
7139
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
7140
+ if (!raw) {
7141
+ return {};
7142
+ }
7143
+ return JSON.parse(raw);
7144
+ }
7145
+ async function ensureViewerDist(viewerDistDir) {
7146
+ const indexPath = path16.join(viewerDistDir, "index.html");
7147
+ if (await fileExists(indexPath)) {
7148
+ return;
7149
+ }
7150
+ const viewerProjectDir = path16.dirname(viewerDistDir);
7151
+ if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
7152
+ await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
7153
+ }
7154
+ }
7155
+ async function startGraphServer(rootDir, port) {
7156
+ const { config, paths } = await loadVaultConfig(rootDir);
7157
+ const effectivePort = port ?? config.viewer.port;
7158
+ await ensureViewerDist(paths.viewerDistDir);
7159
+ const server = http.createServer(async (request, response) => {
7160
+ const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `localhost:${effectivePort}`}`);
7161
+ if (url.pathname === "/api/graph") {
7162
+ if (!await fileExists(paths.graphPath)) {
7163
+ response.writeHead(404, { "content-type": "application/json" });
7164
+ response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
7165
+ return;
7166
+ }
7167
+ response.writeHead(200, { "content-type": "application/json" });
7168
+ response.end(await fs12.readFile(paths.graphPath, "utf8"));
7169
+ return;
7170
+ }
7171
+ if (url.pathname === "/api/search") {
7172
+ if (!await fileExists(paths.searchDbPath)) {
7173
+ response.writeHead(404, { "content-type": "application/json" });
7174
+ response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
7175
+ return;
7176
+ }
7177
+ const query = url.searchParams.get("q") ?? "";
7178
+ const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
7179
+ const kind = url.searchParams.get("kind") ?? "all";
7180
+ const status = url.searchParams.get("status") ?? "all";
7181
+ const project = url.searchParams.get("project") ?? "all";
7182
+ const results = searchPages(paths.searchDbPath, query, {
7183
+ limit: Number.isFinite(limit) ? limit : 10,
7184
+ kind,
7185
+ status,
7186
+ project
7187
+ });
7188
+ response.writeHead(200, { "content-type": "application/json" });
7189
+ response.end(JSON.stringify(results));
7190
+ return;
7191
+ }
7192
+ if (url.pathname === "/api/page") {
7193
+ const relativePath2 = url.searchParams.get("path") ?? "";
7194
+ const page = await readViewerPage(rootDir, relativePath2);
7195
+ if (!page) {
7196
+ response.writeHead(404, { "content-type": "application/json" });
7197
+ response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
7198
+ return;
7199
+ }
7200
+ response.writeHead(200, { "content-type": "application/json" });
7201
+ response.end(JSON.stringify(page));
7202
+ return;
7203
+ }
7204
+ if (url.pathname === "/api/asset") {
7205
+ const relativePath2 = url.searchParams.get("path") ?? "";
7206
+ const asset = await readViewerAsset(rootDir, relativePath2);
7207
+ if (!asset) {
7208
+ response.writeHead(404, { "content-type": "application/json" });
7209
+ response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
7210
+ return;
7211
+ }
7212
+ response.writeHead(200, { "content-type": asset.mimeType });
7213
+ response.end(asset.buffer);
7214
+ return;
7215
+ }
7216
+ if (url.pathname === "/api/reviews" && request.method === "GET") {
7217
+ response.writeHead(200, { "content-type": "application/json" });
7218
+ response.end(JSON.stringify(await listApprovals(rootDir)));
7219
+ return;
7220
+ }
7221
+ if (url.pathname === "/api/review" && request.method === "GET") {
7222
+ const approvalId = url.searchParams.get("id") ?? "";
7223
+ if (!approvalId) {
7224
+ response.writeHead(400, { "content-type": "application/json" });
7225
+ response.end(JSON.stringify({ error: "Missing approval id." }));
7226
+ return;
7227
+ }
7228
+ response.writeHead(200, { "content-type": "application/json" });
7229
+ response.end(JSON.stringify(await readApproval(rootDir, approvalId)));
7230
+ return;
7231
+ }
7232
+ if (url.pathname === "/api/review" && request.method === "POST") {
7233
+ const body = await readJsonBody(request);
7234
+ const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
7235
+ const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
7236
+ const action = url.searchParams.get("action") ?? "";
7237
+ if (!approvalId || action !== "accept" && action !== "reject") {
7238
+ response.writeHead(400, { "content-type": "application/json" });
7239
+ response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
7240
+ return;
7241
+ }
7242
+ const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
7243
+ response.writeHead(200, { "content-type": "application/json" });
7244
+ response.end(JSON.stringify(result));
7245
+ return;
7246
+ }
7247
+ if (url.pathname === "/api/candidates" && request.method === "GET") {
7248
+ response.writeHead(200, { "content-type": "application/json" });
7249
+ response.end(JSON.stringify(await listCandidates(rootDir)));
7250
+ return;
7251
+ }
7252
+ if (url.pathname === "/api/candidate" && request.method === "POST") {
7253
+ const body = await readJsonBody(request);
7254
+ const target2 = typeof body.target === "string" ? body.target : "";
7255
+ const action = url.searchParams.get("action") ?? "";
7256
+ if (!target2 || action !== "promote" && action !== "archive") {
7257
+ response.writeHead(400, { "content-type": "application/json" });
7258
+ response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
7259
+ return;
7260
+ }
7261
+ const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
7262
+ response.writeHead(200, { "content-type": "application/json" });
7263
+ response.end(JSON.stringify(result));
7264
+ return;
7265
+ }
7266
+ const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
7267
+ const target = path16.join(paths.viewerDistDir, relativePath);
7268
+ const fallback = path16.join(paths.viewerDistDir, "index.html");
7269
+ const filePath = await fileExists(target) ? target : fallback;
7270
+ if (!await fileExists(filePath)) {
7271
+ response.writeHead(503, { "content-type": "text/plain" });
7272
+ response.end("Viewer build not found. Run `pnpm build` first.");
7273
+ return;
7274
+ }
7275
+ response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
7276
+ response.end(await fs12.readFile(filePath));
7277
+ });
7278
+ await new Promise((resolve) => {
7279
+ server.listen(effectivePort, resolve);
7280
+ });
7281
+ return {
7282
+ port: effectivePort,
7283
+ close: async () => {
7284
+ await new Promise((resolve, reject) => {
7285
+ server.close((error) => {
7286
+ if (error) {
7287
+ reject(error);
7288
+ return;
7289
+ }
7290
+ resolve();
7291
+ });
7292
+ });
7293
+ }
7294
+ };
7295
+ }
7296
+ async function exportGraphHtml(rootDir, outputPath) {
7297
+ const { paths } = await loadVaultConfig(rootDir);
7298
+ const graph = await readJsonFile(paths.graphPath);
7299
+ if (!graph) {
7300
+ throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
7301
+ }
7302
+ await ensureViewerDist(paths.viewerDistDir);
7303
+ const indexPath = path16.join(paths.viewerDistDir, "index.html");
7304
+ if (!await fileExists(indexPath)) {
7305
+ throw new Error("Viewer build not found. Run `pnpm build` first.");
7306
+ }
7307
+ const pages = await Promise.all(
7308
+ graph.pages.map(async (page) => {
7309
+ const loaded = await readViewerPage(rootDir, page.path);
7310
+ return loaded ? {
7311
+ pageId: page.id,
7312
+ path: loaded.path,
7313
+ title: loaded.title,
7314
+ kind: page.kind,
7315
+ status: page.status,
7316
+ projectIds: page.projectIds,
7317
+ content: loaded.content,
7318
+ assets: await Promise.all(
7319
+ loaded.assets.map(async (asset) => ({
7320
+ ...asset,
7321
+ dataUrl: await assetDataUrl(rootDir, asset.path)
7322
+ }))
7323
+ )
7324
+ } : null;
7325
+ })
7326
+ );
7327
+ const rawHtml = await fs12.readFile(indexPath, "utf8");
7328
+ const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
7329
+ const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
7330
+ const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
7331
+ const stylePath = styleMatch?.[1] ? path16.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
7332
+ if (!scriptPath || !await fileExists(scriptPath)) {
7333
+ throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
7334
+ }
7335
+ const script = await fs12.readFile(scriptPath, "utf8");
7336
+ const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
7337
+ const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
7338
+ const html = [
7339
+ "<!doctype html>",
7340
+ '<html lang="en">',
7341
+ " <head>",
7342
+ ' <meta charset="UTF-8" />',
7343
+ ' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
7344
+ " <title>SwarmVault Graph Export</title>",
7345
+ style ? ` <style>${style}</style>` : "",
7346
+ " </head>",
7347
+ " <body>",
7348
+ ' <div id="root"></div>',
7349
+ ` <script>window.__SWARMVAULT_EMBEDDED_DATA__ = ${embeddedData};</script>`,
7350
+ ` <script type="module">${script}</script>`,
7351
+ " </body>",
7352
+ "</html>",
7353
+ ""
7354
+ ].filter(Boolean).join("\n");
7355
+ await fs12.mkdir(path16.dirname(outputPath), { recursive: true });
7356
+ await fs12.writeFile(outputPath, html, "utf8");
7357
+ return path16.resolve(outputPath);
7358
+ }
7359
+
7337
7360
  // src/watch.ts
7338
7361
  import path17 from "path";
7339
7362
  import process2 from "process";