Files
openclaw-taskboard/lib/wiki.ts

131 lines
3.4 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import type { WikiPage, WikiPageSummary } from "@/lib/types";
const WIKI_DIR = process.env.WIKI_DIR || path.join(process.cwd(), "wiki");
fs.mkdirSync(WIKI_DIR, { recursive: true });
function assertSafeFilename(filename: string) {
if (filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
throw new Error("invalid_filename");
}
}
function extractMetadata(content: string) {
const metadata = {
title: "",
created: "",
modified: "",
tags: [] as string[],
};
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (frontmatterMatch) {
const frontmatter = frontmatterMatch[1];
const titleMatch = frontmatter.match(/title:\s*(.+)/i);
const tagsMatch = frontmatter.match(/tags:\s*\[(.+)\]/i);
if (titleMatch) {
metadata.title = titleMatch[1].trim();
}
if (tagsMatch) {
metadata.tags = tagsMatch[1].split(",").map((tag) => tag.trim()).filter(Boolean);
}
}
if (!metadata.title) {
const headingMatch = content.match(/^#\s+(.+)$/m);
if (headingMatch) {
metadata.title = headingMatch[1].trim();
}
}
return metadata;
}
export function listWikiPages(): WikiPageSummary[] {
if (!fs.existsSync(WIKI_DIR)) {
return [];
}
return fs
.readdirSync(WIKI_DIR)
.filter((fileName) => fileName.endsWith(".md"))
.map((filename) => {
const filePath = path.join(WIKI_DIR, filename);
const stats = fs.statSync(filePath);
const content = fs.readFileSync(filePath, "utf8");
const metadata = extractMetadata(content);
return {
filename,
title: metadata.title || filename.replace(".md", "").replace(/-/g, " "),
created: stats.birthtime.toISOString(),
modified: stats.mtime.toISOString(),
tags: metadata.tags,
};
})
.sort((left, right) => new Date(right.modified).getTime() - new Date(left.modified).getTime());
}
export function readWikiPage(filename: string): WikiPage | null {
assertSafeFilename(filename);
const filePath = path.join(WIKI_DIR, filename);
if (!fs.existsSync(filePath)) {
return null;
}
const content = fs.readFileSync(filePath, "utf8");
const stats = fs.statSync(filePath);
const metadata = extractMetadata(content);
return {
filename,
content,
metadata: {
title: metadata.title || filename.replace(".md", ""),
created: stats.birthtime.toISOString(),
modified: stats.mtime.toISOString(),
tags: metadata.tags,
},
};
}
export function createWikiPage(title: string) {
const safeTitle = title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 80);
const timestamp = new Date().toISOString().slice(0, 10);
let filename = `${timestamp}-${safeTitle}.md`;
let counter = 1;
while (fs.existsSync(path.join(WIKI_DIR, filename))) {
filename = `${timestamp}-${safeTitle}-${counter}.md`;
counter += 1;
}
const content = `# ${title}
## Summary
Document the architecture, deployment notes, or runbook here.
`;
fs.writeFileSync(path.join(WIKI_DIR, filename), content, "utf8");
return filename;
}
export function updateWikiPage(filename: string, content: string) {
assertSafeFilename(filename);
fs.writeFileSync(path.join(WIKI_DIR, filename), content, "utf8");
}
export function deleteWikiPage(filename: string) {
assertSafeFilename(filename);
fs.unlinkSync(path.join(WIKI_DIR, filename));
}