/** * preview.ts: render the Matrix welcome screen outside omp. * * npx tsx scripts/preview.ts * * Ctrl+C to quit. Useful for tuning the config file without restarting omp. * It reads the same config as the extension, so what you see here is what omp * will show. * * IMPORTANT: everything below runs only when this file is the process entry * point. omp's extension loader imports every `.ts` file it finds in a scanned * extensions directory *before* it decides whether the module exports a valid * factory, so a module with top-level side effects takes over the terminal and * can kill the host process on import. Keep this file inert when imported. */ import { pathToFileURL } from "node:url"; import { loadConfig } from "../config.js"; import { MatrixWelcome, type WelcomeInfo } from "../index.js"; function main(): void { const { config, source, warnings } = loadConfig(); for (const warning of warnings) { process.stderr.write(`config warning: ${warning}\n`); } process.stderr.write(`config: ${source ?? "built-in defaults"}\n`); const info: WelcomeInfo = { version: "17.2.12", modelName: "claude-opus-5", providerName: "anthropic", sessions: [ { name: "oh-my-pi", timeAgo: "12m ago" }, { name: "portfolio-site", timeAgo: "3h ago" }, { name: "scratch", timeAgo: "2d ago" }, ], cwd: "~/code/oh-my-pi", }; // Dynamic insert/delete plus iteration on teardown, so Set over Record. const handles = new Set(); const timers = { setInterval: (cb: () => void, ms: number) => { const handle = setInterval(cb, ms); handles.add(handle); return handle; }, clearTimer: (handle: unknown) => { // Identity lookup keeps the already-typed handle, so no cast is needed. for (const known of handles) { if (known !== handle) continue; clearInterval(known); handles.delete(known); return; } }, }; const restore = () => { for (const handle of handles) clearInterval(handle); handles.clear(); process.stdout.write("\x1b[?25h\x1b[2J\x1b[H"); }; const screen = new MatrixWelcome( info, () => { restore(); process.exit(0); }, timers, config, ); process.stdout.write("\x1b[?25l\x1b[2J"); screen.start({ requestRender() { const width = process.stdout.columns ?? 100; process.stdout.write("\x1b[H" + screen.render(width).join("\n")); }, }); process.on("SIGINT", () => { restore(); process.exit(0); }); // Keep alive when the countdown is disabled. setTimeout(() => {}, (config.countdown + 5) * 1000); } // Bun (and Node >= 24) set `import.meta.main` on the entry module only. let isDirectRun = "main" in import.meta && import.meta.main === true; const entry = process.argv[1]; if (!isDirectRun && entry) { try { // omp imports with a `?mtime=` cache-buster, so compare without the query. isDirectRun = pathToFileURL(entry).href === import.meta.url.split("?")[0]; } catch { isDirectRun = false; } } if (isDirectRun) main();