@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 +4 -4
- package/dist/index.js +547 -541
- package/package.json +1 -1
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
`?.path ?? "")})`,
|
|
2809
|
-
""
|
|
2810
|
-
] : [],
|
|
2614
|
+
...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
|
-
`?.path ?? "")})`,
|
|
2944
|
-
""
|
|
2945
|
-
] : [],
|
|
2746
|
+
...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/
|
|
2990
|
-
import
|
|
2991
|
-
|
|
2992
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3017
2794
|
}
|
|
3018
|
-
function
|
|
3019
|
-
return
|
|
2795
|
+
function clampNumber(value, min, max) {
|
|
2796
|
+
return Math.min(max, Math.max(min, value));
|
|
3020
2797
|
}
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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/
|
|
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
|
|
6816
|
+
return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
|
|
7080
6817
|
}
|
|
7081
6818
|
function scheduleLockPath(schedulesDir, jobId) {
|
|
7082
|
-
return
|
|
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
|
|
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
|
|
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";
|