openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
191 lines (190 loc) • 7.86 kB
JavaScript
import { y as resolveStateDir } from "./paths-mvMm5bYV.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/cli/program/register.transcripts.ts
const TRANSCRIPTS_STATE_SUBDIR = "transcripts";
function safeSegment(value) {
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "session";
}
function stateRootDir() {
return path.join(resolveStateDir(), TRANSCRIPTS_STATE_SUBDIR);
}
function dateFromSessionId(sessionId) {
return sessionId.match(/^transcript-(\d{4})-(\d{2})-(\d{2})T/)?.slice(1, 4).join("-");
}
function sessionDir(date, sessionId) {
return path.join(stateRootDir(), date, safeSegment(sessionId));
}
function readDateFromSessionDir(sessionDirValue) {
const candidate = path.basename(path.dirname(sessionDirValue));
if (!/^\d{4}-\d{2}-\d{2}$/.test(candidate)) throw new Error(`invalid transcripts date directory: ${candidate}`);
return candidate;
}
function formatSelector(entry) {
return `${entry.date}/${entry.session.sessionId}`;
}
function parseQualifiedSelector(selector) {
const match = selector.match(/^(\d{4}-\d{2}-\d{2})\/(.+)$/);
if (!match?.[1] || !match[2]) return null;
return {
date: match[1],
sessionId: match[2]
};
}
function writeLine(value) {
process.stdout.write(`${value}\n`);
}
function writeJson(value) {
writeLine(JSON.stringify(value, null, 2));
}
function isNodeError(err, code) {
return Boolean(err && typeof err === "object" && "code" in err && err.code === code);
}
async function pathExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch (err) {
if (isNodeError(err, "ENOENT")) return false;
throw err;
}
}
async function readJsonFile(filePath) {
return JSON.parse(await fs.readFile(filePath, "utf8"));
}
function formatErrorMessage(err) {
return err instanceof Error ? err.message : String(err);
}
async function readStoredSession(sessionDirLocal, options = {}) {
const metadataPath = path.join(sessionDirLocal, "metadata.json");
try {
const session = await readJsonFile(metadataPath);
const summaryPath = path.join(sessionDirLocal, "summary.md");
return {
session,
sessionDir: sessionDirLocal,
date: readDateFromSessionDir(sessionDirLocal),
summaryPath,
hasSummary: await pathExists(summaryPath)
};
} catch (err) {
if (isNodeError(err, "ENOENT")) return null;
if (options.ignoreInvalid) return null;
throw new Error(`invalid transcripts metadata at ${metadataPath}: ${formatErrorMessage(err)}`, { cause: err });
}
}
async function listStoredSessionDirs() {
let entries;
try {
entries = await fs.readdir(stateRootDir(), { withFileTypes: true });
} catch (err) {
if (isNodeError(err, "ENOENT")) return [];
throw err;
}
const dirs = [];
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const firstLevelDir = path.join(stateRootDir(), entry.name);
if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.name)) continue;
const nestedEntries = await fs.readdir(firstLevelDir, { withFileTypes: true });
dirs.push(...nestedEntries.filter((nestedEntry) => nestedEntry.isDirectory()).map((nestedEntry) => path.join(firstLevelDir, nestedEntry.name)));
}
return dirs;
}
function assertRequestedSession(entry, sessionId) {
if (entry.session.sessionId !== sessionId) throw new Error(`transcripts metadata mismatch for ${sessionId}: found ${entry.session.sessionId}`);
return entry;
}
async function requireStoredSession(selector) {
const qualified = parseQualifiedSelector(selector);
if (qualified) {
const session = await readStoredSession(sessionDir(qualified.date, qualified.sessionId));
if (!session) throw new Error(`transcripts session not found: ${selector}`);
return assertRequestedSession(session, qualified.sessionId);
}
const idDate = dateFromSessionId(selector);
const session = idDate ? await readStoredSession(sessionDir(idDate, selector)) : null;
if (session) return assertRequestedSession(session, selector);
const matches = (await listStoredSessions()).filter((entry) => entry.session.sessionId === selector);
if (matches.length === 1 && matches[0]) return assertRequestedSession(matches[0], selector);
if (matches.length > 1) throw new Error(`multiple transcripts sessions match ${selector}; use one of: ${matches.map(formatSelector).join(", ")}`);
throw new Error(`transcripts session not found: ${selector}`);
}
async function listStoredSessions() {
const dirs = await listStoredSessionDirs();
return (await Promise.all(dirs.map((dir) => readStoredSession(dir, { ignoreInvalid: true })))).filter((session) => session !== null).toSorted((left, right) => (right.session.startedAt ?? "").localeCompare(left.session.startedAt ?? ""));
}
function formatSessionLine(entry) {
const title = entry.session.title?.trim() || "Transcripts";
const started = entry.session.startedAt || "unknown";
const summary = entry.hasSummary ? entry.summaryPath : "no summary.md";
return `${formatSelector(entry)}\t${started}\t${title}\t${summary}`;
}
async function listCommand(options) {
const sessions = await listStoredSessions();
if (options.json) {
writeJson(sessions.map((entry) => ({
sessionId: entry.session.sessionId,
selector: formatSelector(entry),
date: entry.date,
title: entry.session.title,
startedAt: entry.session.startedAt,
stoppedAt: entry.session.stoppedAt,
source: entry.session.source,
path: entry.sessionDir,
summaryPath: entry.summaryPath,
hasSummary: entry.hasSummary
})));
return;
}
if (sessions.length === 0) {
writeLine("No transcripts found.");
return;
}
for (const session of sessions) writeLine(formatSessionLine(session));
}
async function showCommand(sessionId, options) {
const session = await requireStoredSession(sessionId);
if (options.json) {
const summary = session.hasSummary ? await fs.readFile(session.summaryPath, "utf8") : null;
writeJson({
session: session.session,
selector: formatSelector(session),
path: session.sessionDir,
summaryPath: session.summaryPath,
summary
});
return;
}
if (!session.hasSummary) throw new Error(`summary.md not found for transcripts session: ${sessionId}`);
process.stdout.write(await fs.readFile(session.summaryPath, "utf8"));
}
async function pathCommand(selector, options) {
const session = await requireStoredSession(selector);
const selectedPath = options.dir ? session.sessionDir : options.metadata ? path.join(session.sessionDir, "metadata.json") : options.transcript ? path.join(session.sessionDir, "transcript.jsonl") : session.summaryPath;
if (options.json) {
writeJson({
sessionId: session.session.sessionId,
selector: formatSelector(session),
path: selectedPath,
exists: await pathExists(selectedPath)
});
return;
}
writeLine(selectedPath);
}
/** Register transcript list/show/path inspection commands. */
function registerTranscriptsCli(program) {
const transcripts = program.command("transcripts").description("Inspect stored transcripts");
transcripts.command("list").description("List stored transcript sessions").option("--json", "Print JSON").action(async (options) => {
await listCommand(options);
});
transcripts.command("show").description("Print a transcript summary markdown file").argument("<session>", "Transcripts session id or YYYY-MM-DD/session selector").option("--json", "Print JSON").action(async (sessionId, options) => {
await showCommand(sessionId, options);
});
transcripts.command("path").description("Print a stored transcripts artifact path").argument("<session>", "Transcripts session id or YYYY-MM-DD/session selector").option("--dir", "Print the session directory").option("--metadata", "Print metadata.json").option("--transcript", "Print transcript.jsonl").option("--json", "Print JSON").action(async (sessionId, options) => {
await pathCommand(sessionId, options);
});
}
//#endregion
export { registerTranscriptsCli };