@swarmvaultai/engine 0.1.7 → 0.1.8

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.d.ts CHANGED
@@ -688,6 +688,10 @@ declare function createProvider(id: string, config: ProviderConfig, rootDir: str
688
688
  declare function getProviderForTask(rootDir: string, task: keyof Awaited<ReturnType<typeof loadVaultConfig>>["config"]["tasks"]): Promise<ProviderAdapter>;
689
689
  declare function assertProviderCapability(provider: ProviderAdapter, capability: ProviderCapability): void;
690
690
 
691
+ declare function listSchedules(rootDir: string): Promise<ScheduleStateRecord[]>;
692
+ declare function runSchedule(rootDir: string, jobId: string): Promise<ScheduledRunResult>;
693
+ declare function serveSchedules(rootDir: string, pollMs?: number): Promise<ScheduleController>;
694
+
691
695
  interface VaultSchema {
692
696
  path: string;
693
697
  content: string;
@@ -747,10 +751,6 @@ declare function startGraphServer(rootDir: string, port?: number): Promise<{
747
751
  }>;
748
752
  declare function exportGraphHtml(rootDir: string, outputPath: string): Promise<string>;
749
753
 
750
- declare function listSchedules(rootDir: string): Promise<ScheduleStateRecord[]>;
751
- declare function runSchedule(rootDir: string, jobId: string): Promise<ScheduledRunResult>;
752
- declare function serveSchedules(rootDir: string, pollMs?: number): Promise<ScheduleController>;
753
-
754
754
  declare function watchVault(rootDir: string, options?: WatchOptions): Promise<WatchController>;
755
755
 
756
756
  declare function createWebSearchAdapter(id: string, config: WebSearchProviderConfig, rootDir: string): Promise<WebSearchAdapter>;
package/dist/index.js CHANGED
@@ -1410,8 +1410,8 @@ import matter2 from "gray-matter";
1410
1410
  import { z as z4 } from "zod";
1411
1411
 
1412
1412
  // src/orchestration.ts
1413
- import path7 from "path";
1414
1413
  import { spawn } from "child_process";
1414
+ import path7 from "path";
1415
1415
  import { z as z2 } from "zod";
1416
1416
  var orchestrationRoleResultSchema = z2.object({
1417
1417
  summary: z2.string().optional(),
@@ -1982,199 +1982,6 @@ async function runDeepLint(rootDir, structuralFindings, options = {}) {
1982
1982
  );
1983
1983
  }
1984
1984
 
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
1985
  // src/markdown.ts
2179
1986
  import matter3 from "gray-matter";
2180
1987
  function uniqueStrings(values) {
@@ -2804,10 +2611,7 @@ function buildOutputPage(input) {
2804
2611
  ] : input.outputFormat === "chart" || input.outputFormat === "image" ? [
2805
2612
  `# ${input.title ?? input.question}`,
2806
2613
  "",
2807
- ...primaryOutputAsset(outputAssets) ? [
2808
- `![${input.title ?? input.question}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`,
2809
- ""
2810
- ] : [],
2614
+ ...primaryOutputAsset(outputAssets) ? [`![${input.title ?? input.question}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`, ""] : [],
2811
2615
  input.answer,
2812
2616
  "",
2813
2617
  ...outputAssetSection(outputAssets),
@@ -2939,10 +2743,7 @@ function buildExploreHubPage(input) {
2939
2743
  ] : input.outputFormat === "chart" || input.outputFormat === "image" ? [
2940
2744
  `# ${title}`,
2941
2745
  "",
2942
- ...primaryOutputAsset(outputAssets) ? [
2943
- `![${title}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`,
2944
- ""
2945
- ] : [],
2746
+ ...primaryOutputAsset(outputAssets) ? [`![${title}](${assetMarkdownPath(primaryOutputAsset(outputAssets)?.path ?? "")})`, ""] : [],
2946
2747
  "## Root Question",
2947
2748
  "",
2948
2749
  input.question,
@@ -2986,63 +2787,257 @@ function buildExploreHubPage(input) {
2986
2787
  };
2987
2788
  }
2988
2789
 
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;
2790
+ // src/output-artifacts.ts
2791
+ import { z as z5 } from "zod";
2792
+ function escapeXml(value) {
2793
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3017
2794
  }
3018
- function normalizeOutputFormat(value, fallback = "markdown") {
3019
- return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
2795
+ function clampNumber(value, min, max) {
2796
+ return Math.min(max, Math.max(min, value));
3020
2797
  }
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
- });
2798
+ var chartSpecSchema = z5.object({
2799
+ kind: z5.enum(["bar", "line"]).default("bar"),
2800
+ title: z5.string().min(1),
2801
+ subtitle: z5.string().optional(),
2802
+ xLabel: z5.string().optional(),
2803
+ yLabel: z5.string().optional(),
2804
+ seriesLabel: z5.string().optional(),
2805
+ data: z5.array(
2806
+ z5.object({
2807
+ label: z5.string().min(1),
2808
+ value: z5.number().finite()
2809
+ })
2810
+ ).min(2).max(12),
2811
+ notes: z5.array(z5.string().min(1)).max(5).optional()
2812
+ });
2813
+ var sceneSpecSchema = z5.object({
2814
+ title: z5.string().min(1),
2815
+ alt: z5.string().min(1),
2816
+ background: z5.string().optional(),
2817
+ width: z5.number().int().positive().max(2400).optional(),
2818
+ height: z5.number().int().positive().max(2400).optional(),
2819
+ elements: z5.array(
2820
+ z5.object({
2821
+ kind: z5.enum(["shape", "label"]),
2822
+ shape: z5.enum(["rect", "circle", "line"]).optional(),
2823
+ x: z5.number().finite(),
2824
+ y: z5.number().finite(),
2825
+ width: z5.number().finite().optional(),
2826
+ height: z5.number().finite().optional(),
2827
+ radius: z5.number().finite().optional(),
2828
+ text: z5.string().optional(),
2829
+ fontSize: z5.number().finite().optional(),
2830
+ fill: z5.string().optional(),
2831
+ stroke: z5.string().optional(),
2832
+ strokeWidth: z5.number().finite().optional(),
2833
+ opacity: z5.number().finite().optional()
2834
+ })
2835
+ ).min(1).max(32)
2836
+ });
2837
+ function renderChartSvg(spec) {
2838
+ const width = 1200;
2839
+ const height = 720;
2840
+ const margin = { top: 110, right: 80, bottom: 110, left: 110 };
2841
+ const chartWidth = width - margin.left - margin.right;
2842
+ const chartHeight = height - margin.top - margin.bottom;
2843
+ const values = spec.data.map((item) => item.value);
2844
+ const maxValue = Math.max(...values, 1);
2845
+ const minValue = Math.min(...values, 0);
2846
+ const domainMin = Math.min(0, minValue);
2847
+ const domainMax = maxValue <= domainMin ? domainMin + 1 : maxValue;
2848
+ const ticks = 5;
2849
+ const tickValues = Array.from({ length: ticks + 1 }, (_, index) => domainMin + (domainMax - domainMin) * index / ticks);
2850
+ const projectY = (value) => margin.top + chartHeight - (value - domainMin) / (domainMax - domainMin || 1) * chartHeight;
2851
+ const zeroY = projectY(0);
2852
+ const step = chartWidth / Math.max(1, spec.data.length);
2853
+ const barWidth = Math.min(84, step * 0.6);
2854
+ const points = spec.data.map((item, index) => {
2855
+ const centerX = margin.left + step * index + step / 2;
2856
+ const y = projectY(item.value);
2857
+ return { ...item, centerX, y };
2858
+ });
2859
+ const gridLines = tickValues.map((value) => {
2860
+ const y = projectY(value);
2861
+ return [
2862
+ `<line x1="${margin.left}" y1="${y}" x2="${width - margin.right}" y2="${y}" stroke="#dbe4ec" stroke-width="1" />`,
2863
+ `<text x="${margin.left - 16}" y="${y + 4}" text-anchor="end" font-size="14" fill="#475569">${escapeXml(value.toFixed(0))}</text>`
2864
+ ].join("");
2865
+ }).join("");
2866
+ const bars = spec.kind === "bar" ? points.map((point) => {
2867
+ const top = Math.min(point.y, zeroY);
2868
+ const barHeight = Math.max(8, Math.abs(zeroY - point.y));
2869
+ return [
2870
+ `<rect x="${point.centerX - barWidth / 2}" y="${top}" width="${barWidth}" height="${barHeight}" rx="12" fill="#0ea5e9" opacity="0.92" />`,
2871
+ `<text x="${point.centerX}" y="${top - 10}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
2872
+ point.value.toFixed(0)
2873
+ )}</text>`
2874
+ ].join("");
2875
+ }).join("") : "";
2876
+ const linePath = spec.kind === "line" ? points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.centerX} ${point.y}`).join(" ") : "";
2877
+ const lineMarks = spec.kind === "line" ? [
2878
+ `<path d="${linePath}" fill="none" stroke="#0ea5e9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" />`,
2879
+ ...points.map(
2880
+ (point) => `<circle cx="${point.centerX}" cy="${point.y}" r="8" fill="#f8fafc" stroke="#0ea5e9" stroke-width="4" />
2881
+ <text x="${point.centerX}" y="${point.y - 18}" text-anchor="middle" font-size="13" fill="#0f172a">${escapeXml(
2882
+ point.value.toFixed(0)
2883
+ )}</text>`
2884
+ )
2885
+ ].join("") : "";
2886
+ const labels = points.map(
2887
+ (point) => `<text x="${point.centerX}" y="${height - margin.bottom + 28}" text-anchor="middle" font-size="14" fill="#334155">${escapeXml(
2888
+ point.label
2889
+ )}</text>`
2890
+ ).join("");
2891
+ const notes = (spec.notes ?? []).map(
2892
+ (note, index) => `<text x="${margin.left}" y="${height - 26 - index * 18}" font-size="13" fill="#475569">${escapeXml(note)}</text>`
2893
+ ).join("");
2894
+ const svg = [
2895
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}">`,
2896
+ '<rect width="100%" height="100%" fill="#f8fafc" />',
2897
+ `<text x="${margin.left}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
2898
+ spec.subtitle ? `<text x="${margin.left}" y="86" font-size="18" fill="#475569">${escapeXml(spec.subtitle)}</text>` : "",
2899
+ gridLines,
2900
+ `<line x1="${margin.left}" y1="${zeroY}" x2="${width - margin.right}" y2="${zeroY}" stroke="#0f172a" stroke-width="2" />`,
2901
+ `<line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${height - margin.bottom}" stroke="#0f172a" stroke-width="2" />`,
2902
+ bars,
2903
+ lineMarks,
2904
+ labels,
2905
+ spec.xLabel ? `<text x="${margin.left + chartWidth / 2}" y="${height - 46}" text-anchor="middle" font-size="15" fill="#475569">${escapeXml(spec.xLabel)}</text>` : "",
2906
+ 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>` : "",
2907
+ spec.seriesLabel ? `<text x="${width - margin.right}" y="56" text-anchor="end" font-size="15" fill="#475569">${escapeXml(spec.seriesLabel)}</text>` : "",
2908
+ notes,
2909
+ "</svg>"
2910
+ ].filter(Boolean).join("");
2911
+ return { svg, width, height };
2912
+ }
2913
+ function renderSceneSvg(spec) {
2914
+ const width = clampNumber(spec.width ?? 1200, 480, 1600);
2915
+ const height = clampNumber(spec.height ?? 720, 320, 1200);
2916
+ const elements = spec.elements.map((element) => {
2917
+ const opacity = element.opacity === void 0 ? 1 : clampNumber(element.opacity, 0, 1);
2918
+ if (element.kind === "label") {
2919
+ return `<text x="${element.x}" y="${element.y}" font-size="${clampNumber(element.fontSize ?? 28, 10, 72)}" fill="${escapeXml(
2920
+ element.fill ?? "#0f172a"
2921
+ )}" opacity="${opacity}" font-family="'Avenir Next', 'Segoe UI', sans-serif">${escapeXml(element.text ?? "")}</text>`;
2922
+ }
2923
+ switch (element.shape) {
2924
+ case "circle":
2925
+ return `<circle cx="${element.x}" cy="${element.y}" r="${Math.max(6, element.radius ?? 40)}" fill="${escapeXml(
2926
+ element.fill ?? "#dbeafe"
2927
+ )}" stroke="${escapeXml(element.stroke ?? "#0ea5e9")}" stroke-width="${Math.max(1, element.strokeWidth ?? 2)}" opacity="${opacity}" />`;
2928
+ case "line":
2929
+ 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}" />`;
2930
+ default:
2931
+ return `<rect x="${element.x}" y="${element.y}" width="${Math.max(8, element.width ?? 160)}" height="${Math.max(
2932
+ 8,
2933
+ element.height ?? 120
2934
+ )}" rx="22" fill="${escapeXml(element.fill ?? "#e2e8f0")}" stroke="${escapeXml(element.stroke ?? "#94a3b8")}" stroke-width="${Math.max(
2935
+ 1,
2936
+ element.strokeWidth ?? 2
2937
+ )}" opacity="${opacity}" />`;
2938
+ }
2939
+ }).join("");
2940
+ const svg = [
2941
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
2942
+ spec.alt
2943
+ )}">`,
2944
+ `<rect width="100%" height="100%" fill="${escapeXml(spec.background ?? "#f8fafc")}" />`,
2945
+ `<text x="48" y="64" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(spec.title)}</text>`,
2946
+ elements,
2947
+ `</svg>`
2948
+ ].join("");
2949
+ return { svg, width, height };
2950
+ }
2951
+ function renderRasterPosterSvg(input) {
2952
+ const width = clampNumber(input.width ?? 1200, 480, 1600);
2953
+ const height = clampNumber(input.height ?? 720, 320, 1200);
2954
+ const inset = 42;
2955
+ const svg = [
2956
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(
2957
+ input.alt
2958
+ )}">`,
2959
+ '<rect width="100%" height="100%" fill="#f8fafc" />',
2960
+ `<text x="${inset}" y="56" font-size="34" font-weight="700" fill="#0f172a">${escapeXml(input.title)}</text>`,
2961
+ `<image href="${escapeXml(input.rasterFileName)}" x="${inset}" y="92" width="${width - inset * 2}" height="${height - 148}" preserveAspectRatio="xMidYMid meet" />`,
2962
+ `</svg>`
2963
+ ].join("");
2964
+ return { svg, width, height };
2965
+ }
2966
+ function buildOutputAssetManifest(input) {
2967
+ return `${JSON.stringify(
2968
+ {
2969
+ slug: input.slug,
2970
+ format: input.format,
2971
+ question: input.question,
2972
+ title: input.title,
2973
+ answer: input.answer,
2974
+ citations: input.citations,
2975
+ assets: input.assets,
2976
+ spec: input.spec
2977
+ },
2978
+ null,
2979
+ 2
2980
+ )}
2981
+ `;
2982
+ }
2983
+
2984
+ // src/outputs.ts
2985
+ import fs7 from "fs/promises";
2986
+ import path11 from "path";
2987
+ import matter5 from "gray-matter";
2988
+
2989
+ // src/pages.ts
2990
+ import fs6 from "fs/promises";
2991
+ import path10 from "path";
2992
+ import matter4 from "gray-matter";
2993
+ function normalizeStringArray(value) {
2994
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
2995
+ }
2996
+ function normalizeProjectIds(value) {
2997
+ return normalizeStringArray(value);
2998
+ }
2999
+ function normalizeSourceHashes(value) {
3000
+ if (!value || typeof value !== "object") {
3001
+ return {};
3002
+ }
3003
+ return Object.fromEntries(
3004
+ Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
3005
+ );
3006
+ }
3007
+ function normalizePageStatus(value, fallback = "active") {
3008
+ return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : fallback;
3009
+ }
3010
+ function normalizePageManager(value, fallback = "system") {
3011
+ return value === "human" || value === "system" ? value : fallback;
3012
+ }
3013
+ function normalizeOutputFormat(value, fallback = "markdown") {
3014
+ return value === "report" || value === "slides" || value === "chart" || value === "image" ? value : fallback;
3015
+ }
3016
+ function normalizeOutputAssets(value) {
3017
+ if (!Array.isArray(value)) {
3018
+ return [];
3019
+ }
3020
+ return value.reduce((assets, item) => {
3021
+ if (!item || typeof item !== "object") {
3022
+ return assets;
3023
+ }
3024
+ const candidate = item;
3025
+ const id = typeof candidate.id === "string" ? candidate.id : "";
3026
+ const role = candidate.role;
3027
+ const assetPath = typeof candidate.path === "string" ? candidate.path : "";
3028
+ const mimeType = typeof candidate.mimeType === "string" ? candidate.mimeType : "";
3029
+ if (!id || !assetPath || !mimeType || role !== "primary" && role !== "preview" && role !== "manifest" && role !== "poster") {
3030
+ return assets;
3031
+ }
3032
+ assets.push({
3033
+ id,
3034
+ role,
3035
+ path: assetPath,
3036
+ mimeType,
3037
+ width: typeof candidate.width === "number" ? candidate.width : void 0,
3038
+ height: typeof candidate.height === "number" ? candidate.height : void 0,
3039
+ dataPath: typeof candidate.dataPath === "string" ? candidate.dataPath : void 0
3040
+ });
3046
3041
  return assets;
3047
3042
  }, []);
3048
3043
  }
@@ -5091,7 +5086,13 @@ async function prepareOutputPageSave(rootDir, input) {
5091
5086
  }
5092
5087
  });
5093
5088
  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 ?? [] };
5089
+ return {
5090
+ page: output.page,
5091
+ savedPath: absolutePath,
5092
+ outputAssets: output.page.outputAssets ?? [],
5093
+ content: output.content,
5094
+ assetFiles: input.assetFiles ?? []
5095
+ };
5095
5096
  }
5096
5097
  async function persistOutputPage(rootDir, input) {
5097
5098
  const { paths } = await loadVaultConfig(rootDir);
@@ -5126,7 +5127,13 @@ async function prepareExploreHubSave(rootDir, input) {
5126
5127
  }
5127
5128
  });
5128
5129
  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 ?? [] };
5130
+ return {
5131
+ page: hub.page,
5132
+ savedPath: absolutePath,
5133
+ outputAssets: hub.page.outputAssets ?? [],
5134
+ content: hub.content,
5135
+ assetFiles: input.assetFiles ?? []
5136
+ };
5130
5137
  }
5131
5138
  async function persistExploreHub(rootDir, input) {
5132
5139
  const { paths } = await loadVaultConfig(rootDir);
@@ -5169,9 +5176,10 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
5169
5176
  await fs9.writeFile(targetPath, file.content, "utf8");
5170
5177
  }
5171
5178
  }
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
- );
5179
+ const nextPages = sortGraphPages([
5180
+ ...(previousGraph?.pages ?? []).filter((page) => !stagedPages.some((item) => item.page.id === page.id || item.page.path === page.path)),
5181
+ ...stagedPages.map((item) => item.page)
5182
+ ]);
5175
5183
  const graph = {
5176
5184
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5177
5185
  nodes: previousGraph?.nodes ?? [],
@@ -6144,9 +6152,7 @@ async function exploreVault(rootDir, options) {
6144
6152
  `Citations: ${query.citations.join(", ") || "none"}`
6145
6153
  ].join("\n")
6146
6154
  });
6147
- const orchestrationNotes = roleResults.flatMap(
6148
- (result) => result.findings.map((finding) => `- [${result.role}] ${finding.message}`)
6149
- );
6155
+ const orchestrationNotes = roleResults.flatMap((result) => result.findings.map((finding) => `- [${result.role}] ${finding.message}`));
6150
6156
  const enrichedAnswer = orchestrationNotes.length ? `${query.answer}
6151
6157
 
6152
6158
  ## Agent Review
@@ -6508,7 +6514,7 @@ async function bootstrapDemo(rootDir, input) {
6508
6514
  }
6509
6515
 
6510
6516
  // src/mcp.ts
6511
- var SERVER_VERSION = "0.1.7";
6517
+ var SERVER_VERSION = "0.1.8";
6512
6518
  async function createMcpServer(rootDir) {
6513
6519
  const server = new McpServer({
6514
6520
  name: "swarmvault",
@@ -6575,7 +6581,7 @@ async function createMcpServer(rootDir) {
6575
6581
  inputSchema: {
6576
6582
  question: z7.string().min(1).describe("Question to ask the vault"),
6577
6583
  save: z7.boolean().optional().describe("Persist the answer to wiki/outputs"),
6578
- format: z7.enum(["markdown", "report", "slides"]).optional().describe("Output format")
6584
+ format: z7.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
6579
6585
  }
6580
6586
  },
6581
6587
  async ({ question, save, format }) => {
@@ -6803,283 +6809,14 @@ function asTextResource(uri, text) {
6803
6809
  };
6804
6810
  }
6805
6811
 
6806
- // src/viewer.ts
6807
- import { execFile } from "child_process";
6812
+ // src/schedule.ts
6808
6813
  import fs11 from "fs/promises";
6809
- import http from "http";
6810
6814
  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
6815
  function scheduleStatePath(schedulesDir, jobId) {
7079
- return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
6816
+ return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
7080
6817
  }
7081
6818
  function scheduleLockPath(schedulesDir, jobId) {
7082
- return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
6819
+ return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
7083
6820
  }
7084
6821
  function parseEveryDuration(value) {
7085
6822
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -7182,13 +6919,13 @@ async function acquireJobLease(rootDir, jobId) {
7182
6919
  const { paths } = await loadVaultConfig(rootDir);
7183
6920
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
7184
6921
  await ensureDir(paths.schedulesDir);
7185
- const handle = await fs12.open(leasePath, "wx");
6922
+ const handle = await fs11.open(leasePath, "wx");
7186
6923
  await handle.writeFile(`${process.pid}
7187
6924
  ${(/* @__PURE__ */ new Date()).toISOString()}
7188
6925
  `);
7189
6926
  await handle.close();
7190
6927
  return async () => {
7191
- await fs12.rm(leasePath, { force: true });
6928
+ await fs11.rm(leasePath, { force: true });
7192
6929
  };
7193
6930
  }
7194
6931
  async function listSchedules(rootDir) {
@@ -7334,6 +7071,275 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
7334
7071
  };
7335
7072
  }
7336
7073
 
7074
+ // src/viewer.ts
7075
+ import { execFile } from "child_process";
7076
+ import fs12 from "fs/promises";
7077
+ import http from "http";
7078
+ import path16 from "path";
7079
+ import { promisify } from "util";
7080
+ import matter8 from "gray-matter";
7081
+ import mime2 from "mime-types";
7082
+ var execFileAsync = promisify(execFile);
7083
+ async function readViewerPage(rootDir, relativePath) {
7084
+ const { paths } = await loadVaultConfig(rootDir);
7085
+ const absolutePath = path16.resolve(paths.wikiDir, relativePath);
7086
+ if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
7087
+ return null;
7088
+ }
7089
+ const raw = await fs12.readFile(absolutePath, "utf8");
7090
+ const parsed = matter8(raw);
7091
+ return {
7092
+ path: relativePath,
7093
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path16.basename(relativePath, path16.extname(relativePath)),
7094
+ frontmatter: parsed.data,
7095
+ content: parsed.content,
7096
+ assets: normalizeOutputAssets(parsed.data.output_assets)
7097
+ };
7098
+ }
7099
+ async function readViewerAsset(rootDir, relativePath) {
7100
+ const { paths } = await loadVaultConfig(rootDir);
7101
+ const absolutePath = path16.resolve(paths.wikiDir, relativePath);
7102
+ if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
7103
+ return null;
7104
+ }
7105
+ return {
7106
+ buffer: await fs12.readFile(absolutePath),
7107
+ mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
7108
+ };
7109
+ }
7110
+ async function assetDataUrl(rootDir, relativePath) {
7111
+ const asset = await readViewerAsset(rootDir, relativePath);
7112
+ if (!asset) {
7113
+ return void 0;
7114
+ }
7115
+ return `data:${asset.mimeType};base64,${asset.buffer.toString("base64")}`;
7116
+ }
7117
+ async function readJsonBody(request) {
7118
+ const chunks = [];
7119
+ for await (const chunk of request) {
7120
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
7121
+ }
7122
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
7123
+ if (!raw) {
7124
+ return {};
7125
+ }
7126
+ return JSON.parse(raw);
7127
+ }
7128
+ async function ensureViewerDist(viewerDistDir) {
7129
+ const indexPath = path16.join(viewerDistDir, "index.html");
7130
+ if (await fileExists(indexPath)) {
7131
+ return;
7132
+ }
7133
+ const viewerProjectDir = path16.dirname(viewerDistDir);
7134
+ if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
7135
+ await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
7136
+ }
7137
+ }
7138
+ async function startGraphServer(rootDir, port) {
7139
+ const { config, paths } = await loadVaultConfig(rootDir);
7140
+ const effectivePort = port ?? config.viewer.port;
7141
+ await ensureViewerDist(paths.viewerDistDir);
7142
+ const server = http.createServer(async (request, response) => {
7143
+ const url = new URL(request.url ?? "/", `http://${request.headers.host ?? `localhost:${effectivePort}`}`);
7144
+ if (url.pathname === "/api/graph") {
7145
+ if (!await fileExists(paths.graphPath)) {
7146
+ response.writeHead(404, { "content-type": "application/json" });
7147
+ response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
7148
+ return;
7149
+ }
7150
+ response.writeHead(200, { "content-type": "application/json" });
7151
+ response.end(await fs12.readFile(paths.graphPath, "utf8"));
7152
+ return;
7153
+ }
7154
+ if (url.pathname === "/api/search") {
7155
+ if (!await fileExists(paths.searchDbPath)) {
7156
+ response.writeHead(404, { "content-type": "application/json" });
7157
+ response.end(JSON.stringify({ error: "Search index not found. Run `swarmvault compile` first." }));
7158
+ return;
7159
+ }
7160
+ const query = url.searchParams.get("q") ?? "";
7161
+ const limit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
7162
+ const kind = url.searchParams.get("kind") ?? "all";
7163
+ const status = url.searchParams.get("status") ?? "all";
7164
+ const project = url.searchParams.get("project") ?? "all";
7165
+ const results = searchPages(paths.searchDbPath, query, {
7166
+ limit: Number.isFinite(limit) ? limit : 10,
7167
+ kind,
7168
+ status,
7169
+ project
7170
+ });
7171
+ response.writeHead(200, { "content-type": "application/json" });
7172
+ response.end(JSON.stringify(results));
7173
+ return;
7174
+ }
7175
+ if (url.pathname === "/api/page") {
7176
+ const relativePath2 = url.searchParams.get("path") ?? "";
7177
+ const page = await readViewerPage(rootDir, relativePath2);
7178
+ if (!page) {
7179
+ response.writeHead(404, { "content-type": "application/json" });
7180
+ response.end(JSON.stringify({ error: `Page not found: ${relativePath2}` }));
7181
+ return;
7182
+ }
7183
+ response.writeHead(200, { "content-type": "application/json" });
7184
+ response.end(JSON.stringify(page));
7185
+ return;
7186
+ }
7187
+ if (url.pathname === "/api/asset") {
7188
+ const relativePath2 = url.searchParams.get("path") ?? "";
7189
+ const asset = await readViewerAsset(rootDir, relativePath2);
7190
+ if (!asset) {
7191
+ response.writeHead(404, { "content-type": "application/json" });
7192
+ response.end(JSON.stringify({ error: `Asset not found: ${relativePath2}` }));
7193
+ return;
7194
+ }
7195
+ response.writeHead(200, { "content-type": asset.mimeType });
7196
+ response.end(asset.buffer);
7197
+ return;
7198
+ }
7199
+ if (url.pathname === "/api/reviews" && request.method === "GET") {
7200
+ response.writeHead(200, { "content-type": "application/json" });
7201
+ response.end(JSON.stringify(await listApprovals(rootDir)));
7202
+ return;
7203
+ }
7204
+ if (url.pathname === "/api/review" && request.method === "GET") {
7205
+ const approvalId = url.searchParams.get("id") ?? "";
7206
+ if (!approvalId) {
7207
+ response.writeHead(400, { "content-type": "application/json" });
7208
+ response.end(JSON.stringify({ error: "Missing approval id." }));
7209
+ return;
7210
+ }
7211
+ response.writeHead(200, { "content-type": "application/json" });
7212
+ response.end(JSON.stringify(await readApproval(rootDir, approvalId)));
7213
+ return;
7214
+ }
7215
+ if (url.pathname === "/api/review" && request.method === "POST") {
7216
+ const body = await readJsonBody(request);
7217
+ const approvalId = typeof body.approvalId === "string" ? body.approvalId : "";
7218
+ const targets = Array.isArray(body.targets) ? body.targets.filter((item) => typeof item === "string") : [];
7219
+ const action = url.searchParams.get("action") ?? "";
7220
+ if (!approvalId || action !== "accept" && action !== "reject") {
7221
+ response.writeHead(400, { "content-type": "application/json" });
7222
+ response.end(JSON.stringify({ error: "Missing approval id or invalid review action." }));
7223
+ return;
7224
+ }
7225
+ const result = action === "accept" ? await acceptApproval(rootDir, approvalId, targets) : await rejectApproval(rootDir, approvalId, targets);
7226
+ response.writeHead(200, { "content-type": "application/json" });
7227
+ response.end(JSON.stringify(result));
7228
+ return;
7229
+ }
7230
+ if (url.pathname === "/api/candidates" && request.method === "GET") {
7231
+ response.writeHead(200, { "content-type": "application/json" });
7232
+ response.end(JSON.stringify(await listCandidates(rootDir)));
7233
+ return;
7234
+ }
7235
+ if (url.pathname === "/api/candidate" && request.method === "POST") {
7236
+ const body = await readJsonBody(request);
7237
+ const target2 = typeof body.target === "string" ? body.target : "";
7238
+ const action = url.searchParams.get("action") ?? "";
7239
+ if (!target2 || action !== "promote" && action !== "archive") {
7240
+ response.writeHead(400, { "content-type": "application/json" });
7241
+ response.end(JSON.stringify({ error: "Missing candidate target or invalid candidate action." }));
7242
+ return;
7243
+ }
7244
+ const result = action === "promote" ? await promoteCandidate(rootDir, target2) : await archiveCandidate(rootDir, target2);
7245
+ response.writeHead(200, { "content-type": "application/json" });
7246
+ response.end(JSON.stringify(result));
7247
+ return;
7248
+ }
7249
+ const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
7250
+ const target = path16.join(paths.viewerDistDir, relativePath);
7251
+ const fallback = path16.join(paths.viewerDistDir, "index.html");
7252
+ const filePath = await fileExists(target) ? target : fallback;
7253
+ if (!await fileExists(filePath)) {
7254
+ response.writeHead(503, { "content-type": "text/plain" });
7255
+ response.end("Viewer build not found. Run `pnpm build` first.");
7256
+ return;
7257
+ }
7258
+ response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
7259
+ response.end(await fs12.readFile(filePath));
7260
+ });
7261
+ await new Promise((resolve) => {
7262
+ server.listen(effectivePort, resolve);
7263
+ });
7264
+ return {
7265
+ port: effectivePort,
7266
+ close: async () => {
7267
+ await new Promise((resolve, reject) => {
7268
+ server.close((error) => {
7269
+ if (error) {
7270
+ reject(error);
7271
+ return;
7272
+ }
7273
+ resolve();
7274
+ });
7275
+ });
7276
+ }
7277
+ };
7278
+ }
7279
+ async function exportGraphHtml(rootDir, outputPath) {
7280
+ const { paths } = await loadVaultConfig(rootDir);
7281
+ const graph = await readJsonFile(paths.graphPath);
7282
+ if (!graph) {
7283
+ throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
7284
+ }
7285
+ await ensureViewerDist(paths.viewerDistDir);
7286
+ const indexPath = path16.join(paths.viewerDistDir, "index.html");
7287
+ if (!await fileExists(indexPath)) {
7288
+ throw new Error("Viewer build not found. Run `pnpm build` first.");
7289
+ }
7290
+ const pages = await Promise.all(
7291
+ graph.pages.map(async (page) => {
7292
+ const loaded = await readViewerPage(rootDir, page.path);
7293
+ return loaded ? {
7294
+ pageId: page.id,
7295
+ path: loaded.path,
7296
+ title: loaded.title,
7297
+ kind: page.kind,
7298
+ status: page.status,
7299
+ projectIds: page.projectIds,
7300
+ content: loaded.content,
7301
+ assets: await Promise.all(
7302
+ loaded.assets.map(async (asset) => ({
7303
+ ...asset,
7304
+ dataUrl: await assetDataUrl(rootDir, asset.path)
7305
+ }))
7306
+ )
7307
+ } : null;
7308
+ })
7309
+ );
7310
+ const rawHtml = await fs12.readFile(indexPath, "utf8");
7311
+ const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
7312
+ const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
7313
+ const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
7314
+ const stylePath = styleMatch?.[1] ? path16.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
7315
+ if (!scriptPath || !await fileExists(scriptPath)) {
7316
+ throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
7317
+ }
7318
+ const script = await fs12.readFile(scriptPath, "utf8");
7319
+ const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
7320
+ const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
7321
+ const html = [
7322
+ "<!doctype html>",
7323
+ '<html lang="en">',
7324
+ " <head>",
7325
+ ' <meta charset="UTF-8" />',
7326
+ ' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
7327
+ " <title>SwarmVault Graph Export</title>",
7328
+ style ? ` <style>${style}</style>` : "",
7329
+ " </head>",
7330
+ " <body>",
7331
+ ' <div id="root"></div>',
7332
+ ` <script>window.__SWARMVAULT_EMBEDDED_DATA__ = ${embeddedData};</script>`,
7333
+ ` <script type="module">${script}</script>`,
7334
+ " </body>",
7335
+ "</html>",
7336
+ ""
7337
+ ].filter(Boolean).join("\n");
7338
+ await fs12.mkdir(path16.dirname(outputPath), { recursive: true });
7339
+ await fs12.writeFile(outputPath, html, "utf8");
7340
+ return path16.resolve(outputPath);
7341
+ }
7342
+
7337
7343
  // src/watch.ts
7338
7344
  import path17 from "path";
7339
7345
  import process2 from "process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/engine",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Core engine for SwarmVault: ingest, compile, query, lint, and provider abstractions.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",