feat: add decision center diary imports

This commit is contained in:
Codex
2026-05-18 02:36:40 +00:00
parent feb2550fac
commit 54c1f8e165
10 changed files with 736 additions and 95 deletions
+118 -1
View File
@@ -74,7 +74,7 @@ function readMarkdownFile(path: string): { absolutePath: string; markdown: strin
const absolutePath = resolve(repoRoot, path);
const markdown = readFileSync(absolutePath, "utf8");
if (markdown.trim().length === 0) throw new Error(`markdown file is empty: ${absolutePath}`);
if (markdown.length > 1_000_000) throw new Error(`markdown file is too large: ${absolutePath}`);
if (markdown.length > 4_500_000) throw new Error(`markdown file is too large: ${absolutePath}`);
return { absolutePath, markdown };
}
@@ -143,6 +143,64 @@ async function uploadMeetingAsync(args: string[], fetcher: (path: string, init?:
return { file: absolutePath, result: unwrapProxyResponse(await decisionProxyAsync(fetcher, endpoint, { method: "POST", body })) };
}
function summarizeDiaryImportResult(result: unknown, includeEntries: boolean): unknown {
const body = typeof result === "object" && result !== null && !Array.isArray(result) ? result as Record<string, unknown> : {};
const entries = Array.isArray(body.entries) ? body.entries : [];
const summaryEntries = entries.map((item) => {
const record = typeof item === "object" && item !== null && !Array.isArray(item) ? item as Record<string, unknown> : {};
return {
id: record.id,
date: record.date,
month: record.month,
title: record.title,
markdownPath: record.markdownPath,
summary: record.summary,
sourceFile: record.sourceFile,
updatedAt: record.updatedAt,
};
});
return {
...body,
entries: includeEntries ? summaryEntries : summaryEntries.slice(0, 20),
entriesOmitted: includeEntries ? 0 : Math.max(0, summaryEntries.length - 20),
outputPolicy: includeEntries
? { bounded: false, entries: summaryEntries.length }
: { bounded: true, entriesShown: Math.min(summaryEntries.length, 20), fullCommand: "Re-run with --include-entries to show every imported day summary." },
};
}
function diaryImportPayload(args: string[]): { absolutePath: string; payload: Record<string, unknown> } {
const file = positionalArgs(args)[0];
if (!file) throw new Error("decision diary import requires markdown file");
const { absolutePath, markdown } = readMarkdownFile(file);
return {
absolutePath,
payload: {
markdown,
sourceFile: optionValue(args, ["--source-file", "--source-path", "--source"]) ?? absolutePath,
tags: splitList(optionValues(args, ["--tag", "--tags"])),
},
};
}
function importDiary(args: string[]): unknown {
const { absolutePath, payload } = diaryImportPayload(args);
const result = unwrapProxyResponse(decisionProxy("/api/diary/import", { method: "POST", body: payload }));
const body = typeof result === "object" && result !== null && !Array.isArray(result) && "body" in result
? (result as { body?: unknown }).body
: result;
return { file: absolutePath, result: { ...(typeof result === "object" && result !== null && !Array.isArray(result) && "upstream" in result ? { upstream: (result as { upstream?: unknown }).upstream } : {}), body: summarizeDiaryImportResult(body, args.includes("--include-entries")) } };
}
async function importDiaryAsync(args: string[], fetcher: (path: string, init?: { method?: string; body?: unknown }) => Promise<unknown>): Promise<unknown> {
const { absolutePath, payload } = diaryImportPayload(args);
const result = unwrapProxyResponse(await decisionProxyAsync(fetcher, "/api/diary/import", { method: "POST", body: payload }));
const body = typeof result === "object" && result !== null && !Array.isArray(result) && "body" in result
? (result as { body?: unknown }).body
: result;
return { file: absolutePath, result: { ...(typeof result === "object" && result !== null && !Array.isArray(result) && "upstream" in result ? { upstream: (result as { upstream?: unknown }).upstream } : {}), body: summarizeDiaryImportResult(body, args.includes("--include-entries")) } };
}
function listRecords(args: string[]): unknown {
const params = new URLSearchParams();
const type = optionValue(args, ["--type"]);
@@ -175,6 +233,49 @@ async function listRecordsAsync(args: string[], fetcher: (path: string, init?: {
return unwrapProxyResponse(await decisionProxyAsync(fetcher, `/api/records${query ? `?${query}` : ""}`));
}
function diaryQuery(args: string[]): string {
const params = new URLSearchParams();
const month = optionValue(args, ["--month"]);
const from = optionValue(args, ["--from"]);
const to = optionValue(args, ["--to"]);
const sourceFile = optionValue(args, ["--source-file", "--source-path", "--source"]);
const limit = optionValue(args, ["--limit"]);
if (month !== undefined) params.set("month", month);
if (from !== undefined) params.set("from", from);
if (to !== undefined) params.set("to", to);
if (sourceFile !== undefined) params.set("sourceFile", sourceFile);
if (limit !== undefined) params.set("limit", limit);
if (args.includes("--include-body")) params.set("includeBody", "true");
const query = params.toString();
return query ? `?${query}` : "";
}
function listDiary(args: string[]): unknown {
return unwrapProxyResponse(decisionProxy(`/api/diary/entries${diaryQuery(args)}`));
}
async function listDiaryAsync(args: string[], fetcher: (path: string, init?: { method?: string; body?: unknown }) => Promise<unknown>): Promise<unknown> {
return unwrapProxyResponse(await decisionProxyAsync(fetcher, `/api/diary/entries${diaryQuery(args)}`));
}
function listDiaryMonths(): unknown {
return unwrapProxyResponse(decisionProxy("/api/diary/months"));
}
async function listDiaryMonthsAsync(fetcher: (path: string, init?: { method?: string; body?: unknown }) => Promise<unknown>): Promise<unknown> {
return unwrapProxyResponse(await decisionProxyAsync(fetcher, "/api/diary/months"));
}
function showDiary(key: string | undefined): unknown {
if (!key) throw new Error("decision diary show requires entry id or YYYY-MM-DD date");
return unwrapProxyResponse(decisionProxy(`/api/diary/entries/${encodeURIComponent(key)}`));
}
async function showDiaryAsync(key: string | undefined, fetcher: (path: string, init?: { method?: string; body?: unknown }) => Promise<unknown>): Promise<unknown> {
if (!key) throw new Error("decision diary show requires entry id or YYYY-MM-DD date");
return unwrapProxyResponse(await decisionProxyAsync(fetcher, `/api/diary/entries/${encodeURIComponent(key)}`));
}
function showRecord(id: string | undefined): unknown {
if (!id) throw new Error("decision show requires record id");
return unwrapProxyResponse(decisionProxy(`/api/records/${encodeURIComponent(id)}`));
@@ -187,6 +288,14 @@ async function showRecordAsync(id: string | undefined, fetcher: (path: string, i
export async function runDecisionCenterCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
const [action = "list", id] = args;
if (action === "diary") {
const [diaryAction = "list", diaryId] = args.slice(1);
if (diaryAction === "import") return importDiary(args.slice(2));
if (diaryAction === "list") return listDiary(args.slice(2));
if (diaryAction === "months") return listDiaryMonths();
if (diaryAction === "show") return showDiary(diaryId);
throw new Error("decision diary command must be one of: import, list, months, show");
}
if (action === "upload") return uploadMeeting(args.slice(1));
if (action === "list") return listRecords(args.slice(1));
if (action === "show") return showRecord(id);
@@ -200,6 +309,14 @@ export async function runDecisionCenterCommandAsync(
fetcher: (path: string, init?: { method?: string; body?: unknown }) => Promise<unknown>,
): Promise<unknown> {
const [action = "list", id] = args;
if (action === "diary") {
const [diaryAction = "list", diaryId] = args.slice(1);
if (diaryAction === "import") return importDiaryAsync(args.slice(2), fetcher);
if (diaryAction === "list") return listDiaryAsync(args.slice(2), fetcher);
if (diaryAction === "months") return listDiaryMonthsAsync(fetcher);
if (diaryAction === "show") return showDiaryAsync(diaryId, fetcher);
throw new Error("decision diary command must be one of: import, list, months, show");
}
if (action === "upload") return uploadMeetingAsync(args.slice(1), fetcher);
if (action === "list") return listRecordsAsync(args.slice(1), fetcher);
if (action === "show") return showRecordAsync(id, fetcher);