#!/usr/bin/env node // Builds an HTML/manifest evidence bundle from Telegram QA scenario summaries. import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; type CliArgs = Record; type TelegramEvidenceEntry = { execution?: { packageSource?: { kind?: string }; provider?: { auth?: string; fixture?: string }; }; result?: { failure?: { reason?: string }; status?: string; timing?: { p50Ms?: number; rttMs?: number }; }; test?: { id?: string; title?: string }; }; type TelegramObservedMessage = { caption?: string; inlineButtonCount?: number; inlineButtons?: string[]; mediaKinds?: string[]; scenarioId?: string; scenarioTitle?: string; senderIsBot?: boolean; text?: string; }; type TelegramEvidenceArtifact = { alt?: string; inline?: boolean; kind: string; label: string; lane: string; path: string; required?: boolean; targetPath: string; width?: number; }; type TelegramEvidenceManifest = { schemaVersion: number; id: string; title: string; summary: string; scenario: string; comparison: { candidate: { expected: string; status: string; fixed: boolean; ref?: string; sha?: string; }; pass: boolean; }; artifacts: TelegramEvidenceArtifact[]; }; function parseArgs(argv: string[]): CliArgs { const args: CliArgs = {}; for (let index = 0; index < argv.length; index += 1) { const key = argv[index]; if (!key?.startsWith("--")) { throw new Error(`Unexpected argument: ${key}`); } const name = key.slice(2).replaceAll("-", "_"); const value = argv[index + 1]; if (!value || value.startsWith("--")) { throw new Error(`Missing value for ${key}`); } args[name] = value; index += 1; } return args; } function readJson(filePath: string): unknown { return JSON.parse(readFileSync(filePath, "utf8")); } function escapeHtml(value: unknown) { const text = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : ""; return text .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function formatMessageText(message: TelegramObservedMessage) { const text = typeof message.text === "string" ? message.text : ""; const caption = typeof message.caption === "string" ? message.caption : ""; const content = text || caption || ""; if (content.trim()) { return content; } const mediaKinds = Array.isArray(message.mediaKinds) ? message.mediaKinds : []; return mediaKinds.length > 0 ? `[${mediaKinds.join(", ")}]` : "[no text]"; } function evidenceEntries(summary: unknown): TelegramEvidenceEntry[] { if (!summary || typeof summary !== "object") { return []; } const entries = Reflect.get(summary, "entries"); return Array.isArray(entries) ? entries : []; } function evidenceCounts(summary: unknown) { const entries = evidenceEntries(summary); const failed = entries.filter((entry) => entry?.result?.status !== "pass").length; return { total: entries.length, passed: entries.length - failed, failed, }; } function evidenceCredentialSource(summary: unknown) { const entry = evidenceEntries(summary)[0]; return ( entry?.execution?.provider?.auth ?? entry?.execution?.provider?.fixture ?? entry?.execution?.packageSource?.kind ?? "unknown" ); } function renderScenarioList(summary: unknown) { const entries = evidenceEntries(summary); if (entries.length === 0) { return "
  • No evidence entries recorded.
  • "; } return entries .map((entry) => { const status = entry?.result?.status ?? "unknown"; const statusClass = status === "pass" ? "pass" : "fail"; const timing = entry?.result?.timing; const rttMs = timing?.p50Ms ?? timing?.rttMs; const rtt = typeof rttMs === "number" ? `, ${Math.round(rttMs)}ms RTT` : ""; return `
  • ${escapeHtml(status)} ${escapeHtml(entry?.test?.title ?? entry?.test?.id)} ${escapeHtml(entry?.test?.id ?? "")}${rtt}

    ${escapeHtml(entry?.result?.failure?.reason ?? "")}

  • `; }) .join("\n"); } function renderObservedMessages(observedMessages: unknown) { if (!Array.isArray(observedMessages) || observedMessages.length === 0) { return '

    No observed Telegram messages were recorded.

    '; } const messages: TelegramObservedMessage[] = observedMessages; return messages .map((message, index) => { const sender = message.senderIsBot ? "bot" : "user"; const scenario = message.scenarioTitle ?? message.scenarioId ?? ""; const text = formatMessageText(message); const buttons = Array.isArray(message.inlineButtons) ? message.inlineButtons : typeof message.inlineButtonCount === "number" && message.inlineButtonCount > 0 ? [`${message.inlineButtonCount} inline button(s)`] : []; return [ `
    `, `
    #${index + 1}${escapeHtml(sender)}${scenario ? `${escapeHtml(scenario)}` : ""}
    `, `
    ${escapeHtml(text)}
    `, buttons.length > 0 ? `
    ${buttons.map((button) => `${escapeHtml(button)}`).join("")}
    ` : "", "
    ", ] .filter(Boolean) .join("\n"); }) .join("\n"); } /** * Renders a self-contained Telegram evidence HTML report. */ export function renderTelegramEvidenceHtml({ observedMessages, summary, }: { observedMessages: unknown; summary: unknown; }): string { const counts = evidenceCounts(summary); const pass = counts.failed === 0 && counts.total > 0; return ` Mantis Telegram Live Evidence

    Mantis Telegram Live Evidence

    status: ${pass ? "pass" : "fail"} total: ${escapeHtml(counts.total ?? 0)} passed: ${escapeHtml(counts.passed ?? 0)} failed: ${escapeHtml(counts.failed ?? 0)} credentials: ${escapeHtml(evidenceCredentialSource(summary))}

    Scenarios

      ${renderScenarioList(summary)}

    Observed Telegram Messages

    ${renderObservedMessages(observedMessages)}
    `; } export function buildTelegramEvidenceManifest({ candidateRef, candidateSha, hasObservedMessages = true, scenarioLabel, summary, summaryArtifactPath = "qa-evidence.json", }: { candidateRef?: string; candidateSha?: string; hasObservedMessages?: boolean; scenarioLabel?: string; summary: unknown; summaryArtifactPath?: string; }): TelegramEvidenceManifest { const counts = evidenceCounts(summary); const pass = counts.failed === 0 && counts.total > 0; const scenarioNames = evidenceEntries(summary) .map((entry) => entry?.test?.id) .filter(Boolean); const scenario = scenarioLabel || scenarioNames.join(",") || "telegram-live"; const status = pass ? "pass" : "fail"; const artifacts = [ { kind: "desktopScreenshot", lane: "candidate", label: "Telegram live transcript", path: "telegram-live-desktop.png", targetPath: "telegram-live-desktop.png", alt: "Rendered Telegram live transcript in a Crabbox desktop browser", width: 720, inline: true, required: false, }, { kind: "motionPreview", lane: "candidate", label: "Telegram motion preview", path: "telegram-live-preview.gif", targetPath: "telegram-live-preview.gif", alt: "Animated Telegram live transcript capture", width: 720, inline: true, required: false, }, { kind: "motionClip", lane: "candidate", label: "Telegram change MP4", path: "telegram-live-change.mp4", targetPath: "telegram-live-change.mp4", required: false, }, { kind: "fullVideo", lane: "candidate", label: "Telegram desktop MP4", path: "telegram-live.mp4", targetPath: "telegram-live.mp4", required: false, }, { kind: "metadata", lane: "run", label: "Telegram QA evidence summary", path: summaryArtifactPath, targetPath: "summary.json", }, ...(hasObservedMessages ? [ { kind: "metadata", lane: "run", label: "Telegram observed messages", path: "telegram-qa-observed-messages.json", targetPath: "observed-messages.json", }, ] : []), { kind: "metadata", lane: "run", label: "Telegram transcript HTML", path: "telegram-live-transcript.html", targetPath: "telegram-live-transcript.html", }, { kind: "metadata", lane: "run", label: "Telegram preview metadata", path: "telegram-live-preview.json", targetPath: "telegram-live-preview.json", required: false, }, { kind: "metadata", lane: "run", label: "Telegram QA error", path: "error.txt", targetPath: "error.txt", required: false, }, { kind: "report", lane: "run", label: "Telegram QA report", path: "qa-suite-report.md", targetPath: "report.md", }, ]; return { schemaVersion: 1, id: "telegram-live", title: "Mantis Telegram Live QA", summary: "Mantis ran the Telegram live QA lane with Convex-leased credentials, rendered a redacted transcript in a Crabbox desktop browser, and captured screenshot/video evidence for PR review.", scenario, comparison: { candidate: { ...(candidateSha ? { sha: candidateSha } : {}), ...(candidateRef ? { ref: candidateRef } : {}), expected: "Telegram live QA scenarios pass", status, fixed: pass, }, pass, }, artifacts, }; } export function writeTelegramEvidence(rawArgs: string[] = process.argv.slice(2)): { manifest: TelegramEvidenceManifest; manifestPath: string; transcriptPath: string; } { const args = parseArgs(rawArgs); if (!args.output_dir) { throw new Error("Missing --output-dir."); } const outputDir = path.resolve(args.output_dir); mkdirSync(outputDir, { recursive: true }); const evidenceSummaryPath = path.join(outputDir, "qa-evidence.json"); const reportPath = path.join(outputDir, "qa-suite-report.md"); if (!existsSync(evidenceSummaryPath)) { throw new Error(`Missing Telegram QA evidence summary: ${evidenceSummaryPath}`); } const summary = readJson(evidenceSummaryPath); const counts = evidenceCounts(summary); const pass = counts.failed === 0 && counts.total > 0; if (!existsSync(reportPath)) { if (pass) { throw new Error(`Missing Telegram QA report for passing summary: ${reportPath}`); } writeFileSync(reportPath, "# Mantis Telegram Live QA\n\nTelegram QA report was unavailable.\n"); } const transcriptHtml = renderTelegramEvidenceHtml({ observedMessages: [], summary }); writeFileSync(path.join(outputDir, "telegram-live-transcript.html"), transcriptHtml, "utf8"); const manifest = buildTelegramEvidenceManifest({ candidateRef: args.candidate_ref, candidateSha: args.candidate_sha, hasObservedMessages: false, scenarioLabel: args.scenario_label, summary, summaryArtifactPath: path.basename(evidenceSummaryPath), }); writeFileSync( path.join(outputDir, "mantis-evidence.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8", ); return { manifest, manifestPath: path.join(outputDir, "mantis-evidence.json"), transcriptPath: path.join(outputDir, "telegram-live-transcript.html"), }; } const executedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; if (executedPath === fileURLToPath(import.meta.url)) { try { writeTelegramEvidence(); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); } }