import fs from "node:fs/promises"; import path from "node:path"; import { pathExists } from "openclaw/plugin-sdk/security-runtime"; import { compileMemoryWikiVault } from "./compile.js"; import type { ResolvedMemoryWikiConfig } from "./config.js"; import { appendMemoryWikiLog } from "./log.js"; import { renderMarkdownFence, renderWikiMarkdown, slugifyWikiSegment } from "./markdown.js"; import { initializeMemoryWikiVault } from "./vault.js"; type IngestMemoryWikiSourceResult = { sourcePath: string; pageId: string; pagePath: string; title: string; bytes: number; created: boolean; indexUpdatedFiles: string[]; }; function resolveSourceTitle(sourcePath: string, explicitTitle?: string): string { if (explicitTitle?.trim()) { return explicitTitle.trim(); } return path.basename(sourcePath, path.extname(sourcePath)).replace(/[-_]+/g, " ").trim(); } function assertUtf8Text(buffer: Buffer, sourcePath: string): string { const preview = buffer.subarray(0, Math.min(buffer.length, 4096)); if (preview.includes(0)) { throw new Error(`Cannot ingest binary file as markdown source: ${sourcePath}`); } return buffer.toString("utf8"); } export async function ingestMemoryWikiSource(params: { config: ResolvedMemoryWikiConfig; inputPath: string; title?: string; nowMs?: number; }): Promise { await initializeMemoryWikiVault(params.config, { nowMs: params.nowMs }); const sourcePath = path.resolve(params.inputPath); const buffer = await fs.readFile(sourcePath); const content = assertUtf8Text(buffer, sourcePath); const title = resolveSourceTitle(sourcePath, params.title); const slug = slugifyWikiSegment(title); const pageId = `source.${slug}`; const pageRelativePath = path.join("sources", `${slug}.md`); const pagePath = path.join(params.config.vault.path, pageRelativePath); const created = !(await pathExists(pagePath)); const timestamp = new Date(params.nowMs ?? Date.now()).toISOString(); const markdown = renderWikiMarkdown({ frontmatter: { pageType: "source", id: pageId, title, sourceType: "local-file", sourcePath, ingestedAt: timestamp, updatedAt: timestamp, status: "active", }, body: [ `# ${title}`, "", "## Source", `- Type: \`local-file\``, `- Path: \`${sourcePath}\``, `- Bytes: ${buffer.byteLength}`, `- Updated: ${timestamp}`, "", "## Content", renderMarkdownFence(content, "text"), "", "## Notes", "", "", "", ].join("\n"), }); await fs.writeFile(pagePath, markdown, "utf8"); await appendMemoryWikiLog(params.config.vault.path, { type: "ingest", timestamp, details: { inputPath: sourcePath, pageId, pagePath: pageRelativePath.split(path.sep).join("/"), bytes: buffer.byteLength, created, }, }); const compile = await compileMemoryWikiVault(params.config); return { sourcePath, pageId, pagePath: pageRelativePath.split(path.sep).join("/"), title, bytes: buffer.byteLength, created, indexUpdatedFiles: compile.updatedFiles, }; }