Accept upstream's ce-review pipeline rewrite (6-stage persona-based architecture with structured JSON, confidence gating, three execution modes). Retire 4 overlapping review agents (security-sentinel, performance-oracle, data-migration-expert, data-integrity-guardian) replaced by upstream equivalents. Add 5 local review agents as conditional personas in the persona catalog (kieran-python, tiangolo- fastapi, kieran-typescript, julik-frontend-races, architecture- strategist). Accept upstream skill renames (file-todos→todo-create, resolve_todo_ parallel→todo-resolve), port local Assessment and worktree constraint additions to new files. Merge best-practices-researcher with upstream platform-agnostic discovery + local FastAPI mappings. Remove Rails/Ruby skills (dhh-rails-style, andrew-kane-gem-writer, dspy-ruby) per fork's FastAPI pivot. Component counts: 36 agents, 48 skills, 7 commands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
91 lines
2.9 KiB
JavaScript
Executable File
91 lines
2.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Export an Excalidraw JSON file to PNG using Playwright + the official Excalidraw library.
|
|
*
|
|
* Usage: node export_png.mjs <input.excalidraw> [output.png]
|
|
*
|
|
* All rendering happens locally. Diagram data never leaves the machine.
|
|
* The Excalidraw JS library is fetched from esm.sh CDN (code only, not user data).
|
|
*/
|
|
|
|
import { createRequire } from "module";
|
|
import { readFileSync, writeFileSync, copyFileSync } from "fs";
|
|
import { createServer } from "http";
|
|
import { join, extname, dirname } from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const RUNTIME_DIR = join(__dirname, ".export-runtime");
|
|
const HTML_PATH = join(__dirname, "export.html");
|
|
|
|
// Resolve playwright from the runtime directory, not the script's location
|
|
const require = createRequire(join(RUNTIME_DIR, "node_modules", "playwright", "index.mjs"));
|
|
const { chromium } = await import(join(RUNTIME_DIR, "node_modules", "playwright", "index.mjs"));
|
|
|
|
const inputPath = process.argv[2];
|
|
if (!inputPath) {
|
|
console.error("Usage: node export_png.mjs <input.excalidraw> [output.png]");
|
|
process.exit(1);
|
|
}
|
|
|
|
const outputPath = process.argv[3] || inputPath.replace(/\.excalidraw$/, ".png");
|
|
|
|
// Set up a temp serving directory
|
|
const SERVE_DIR = join(__dirname, ".export-tmp");
|
|
const { mkdirSync, rmSync } = await import("fs");
|
|
mkdirSync(SERVE_DIR, { recursive: true });
|
|
copyFileSync(HTML_PATH, join(SERVE_DIR, "export.html"));
|
|
copyFileSync(inputPath, join(SERVE_DIR, "diagram.excalidraw"));
|
|
|
|
const MIME = {
|
|
".html": "text/html",
|
|
".json": "application/json",
|
|
".excalidraw": "application/json",
|
|
};
|
|
|
|
const server = createServer((req, res) => {
|
|
const file = join(SERVE_DIR, req.url === "/" ? "export.html" : req.url);
|
|
try {
|
|
const data = readFileSync(file);
|
|
res.writeHead(200, { "Content-Type": MIME[extname(file)] || "application/octet-stream" });
|
|
res.end(data);
|
|
} catch {
|
|
res.writeHead(404);
|
|
res.end("Not found");
|
|
}
|
|
});
|
|
|
|
server.listen(0, "127.0.0.1", async () => {
|
|
const port = server.address().port;
|
|
|
|
let browser;
|
|
try {
|
|
browser = await chromium.launch({ headless: true });
|
|
const page = await browser.newPage();
|
|
|
|
page.on("pageerror", err => console.error("Page error:", err.message));
|
|
|
|
await page.goto(`http://127.0.0.1:${port}`);
|
|
|
|
await page.waitForFunction(
|
|
() => document.title.startsWith("READY") || document.title.startsWith("ERROR"),
|
|
{ timeout: 30000 }
|
|
);
|
|
|
|
const title = await page.title();
|
|
if (title.startsWith("ERROR")) {
|
|
console.error("Export failed:", title);
|
|
process.exit(1);
|
|
}
|
|
|
|
const dataUrl = await page.evaluate(() => window.__PNG_DATA__);
|
|
const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
writeFileSync(outputPath, Buffer.from(base64, "base64"));
|
|
console.log(outputPath);
|
|
} finally {
|
|
if (browser) await browser.close();
|
|
server.close();
|
|
rmSync(SERVE_DIR, { recursive: true, force: true });
|
|
}
|
|
});
|