import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"; import { dirname, extname, isAbsolute, relative, resolve, sep } from "node:path"; import { parseAgentManifest, type AgentManifest } from "../agents/manifest.js"; export type ManifestRecord = { path: string; manifest: AgentManifest; source: unknown; }; export type ManifestListing = { paths: string[]; }; async function walkJsonFiles(root: string): Promise { const output: string[] = []; const entries = await readdir(root, { withFileTypes: true }); for (const entry of entries) { const fullPath = resolve(root, entry.name); if (entry.isDirectory()) { output.push(...(await walkJsonFiles(fullPath))); continue; } if (entry.isFile() && extname(entry.name).toLowerCase() === ".json") { output.push(fullPath); } } return output; } function assertWorkspacePath(workspaceRoot: string, inputPath: string): string { const resolved = isAbsolute(inputPath) ? resolve(inputPath) : resolve(workspaceRoot, inputPath); const rel = relative(workspaceRoot, resolved); if (rel === ".." || rel.startsWith(`..${sep}`)) { throw new Error("Path is outside workspace root."); } return resolved; } function toRelativePath(workspaceRoot: string, absolutePath: string): string { return relative(workspaceRoot, absolutePath) || "."; } export class ManifestStore { private readonly workspaceRoot: string; private readonly manifestDirectory: string; constructor(input: { workspaceRoot: string; manifestDirectory?: string }) { this.workspaceRoot = resolve(input.workspaceRoot); this.manifestDirectory = assertWorkspacePath( this.workspaceRoot, input.manifestDirectory ?? ".ai_ops/manifests", ); } getManifestDirectory(): string { return this.manifestDirectory; } async list(): Promise { try { const files = await walkJsonFiles(this.manifestDirectory); const relPaths = files .map((filePath) => toRelativePath(this.workspaceRoot, filePath)) .sort((left, right) => left.localeCompare(right)); return { paths: relPaths, }; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { return { paths: [], }; } throw error; } } async read(pathInput: string): Promise { const absolutePath = assertWorkspacePath(this.workspaceRoot, pathInput); const sourceText = await readFile(absolutePath, "utf8"); const source = JSON.parse(sourceText) as unknown; const manifest = parseAgentManifest(source); return { path: toRelativePath(this.workspaceRoot, absolutePath), manifest, source, }; } async validate(source: unknown): Promise { return parseAgentManifest(source); } async save(pathInput: string, source: unknown): Promise { const manifest = parseAgentManifest(source); const absolutePath = assertWorkspacePath(this.workspaceRoot, pathInput); await mkdir(dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); return { path: toRelativePath(this.workspaceRoot, absolutePath), manifest, source: manifest, }; } }