@blundergoat/goat-flow
Version:
AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.
97 lines • 3.88 kB
JavaScript
/**
* Dashboard shell and static-asset loaders.
* These helpers keep file-resolution and asset-shape validation out of the
* main HTTP server so route code can stay focused on request handling.
*/
import { readFileSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { getTemplatePath, resolveFirstExistingPackagePath } from "../paths.js";
/** Relative locations where the dashboard preset catalog may exist. */
const DASHBOARD_PRESET_CATALOG_PATHS = [
"dist/dashboard/preset-prompts.json",
"src/dashboard/preset-prompts.json",
];
const dashboardAssetCache = new Map();
/**
* Replace `<!-- include: path -->` markers with fragment file contents.
* Uses a recover fallback for missing fragments by embedding an HTML error comment so the
* dashboard shell still loads and the broken include is visible in source.
*
* @param shellPath - dashboard shell HTML file to assemble
* @returns assembled dashboard HTML with one-level includes expanded
*/
export function assembleDashboardHtml(shellPath) {
let html = readFileSync(shellPath, "utf-8");
const includePattern = /<!-- include: (.+?) -->/g;
html = html.replace(includePattern, (_, path) => {
const fragmentPath = join(dirname(shellPath), path);
try {
return readFileSync(fragmentPath, "utf-8");
}
catch {
return `<!-- ERROR: Could not include ${path} -->`;
}
});
return html;
}
/**
* Read the dashboard preset definitions shipped with the frontend bundle.
* Throws when the JSON schema is not the expected preset array.
*
* @returns validated preset prompt definitions
*/
export function loadDashboardPresets() {
const presetPath = resolveFirstExistingPackagePath(DASHBOARD_PRESET_CATALOG_PATHS);
const relativePath = DASHBOARD_PRESET_CATALOG_PATHS.find((candidate) => getTemplatePath(candidate) === presetPath) ?? DASHBOARD_PRESET_CATALOG_PATHS[0];
const raw = JSON.parse(readFileSync(presetPath, "utf-8"));
if (!Array.isArray(raw)) {
throw new Error(`${relativePath} must contain an array`);
}
return raw.map((entry, index) => {
if (typeof entry !== "object" ||
entry === null ||
Array.isArray(entry) ||
typeof entry.id !== "string" ||
typeof entry.name !== "string" ||
typeof entry.desc !== "string" ||
typeof entry.prompt !== "string" ||
typeof entry.cat !== "string") {
throw new Error(`${relativePath} has an invalid preset at index ${index}`);
}
return entry;
});
}
/** Resolve one asset to the actual file path used for this runtime mode. */
function resolveDashboardAssetPath(filename) {
return filename === "preset-prompts.json"
? resolveFirstExistingPackagePath(DASHBOARD_PRESET_CATALOG_PATHS)
: getTemplatePath(`dist/dashboard/${filename}`);
}
/**
* Read one bundled dashboard asset through a small mtime/size-aware memory cache.
*
* @param filename - dashboard asset filename relative to the bundled asset root
* @returns asset bytes and cache metadata for HTTP responses
*/
export function loadDashboardAssetCached(filename) {
const sourcePath = resolveDashboardAssetPath(filename);
const stats = statSync(sourcePath);
const cached = dashboardAssetCache.get(filename);
if (cached &&
cached.sourcePath === sourcePath &&
cached.mtimeMs === stats.mtimeMs &&
cached.size === stats.size) {
return cached;
}
const content = readFileSync(sourcePath);
const asset = {
content,
etag: `"${stats.size}-${Math.floor(stats.mtimeMs)}"`,
sourcePath,
mtimeMs: stats.mtimeMs,
size: stats.size,
};
dashboardAssetCache.set(filename, asset);
return asset;
}
//# sourceMappingURL=dashboard-assets.js.map