#!/usr/bin/env node // ── Worker entry path bootstrap (must run before any producer/engine load) ── // The hf#677 worker_threads pools (`pngDecodeBlitWorkerPool`, // `shaderTransitionWorkerPool`) live in the producer package and try to // resolve their worker entry by probing for sibling `.js` files next to // `import.meta.url`. When this CLI is bundled by tsup, the producer code is // inlined into `cli.js`, but `import.meta.url` resolves to the producer's // own dist path (NOT cli.js) on some module-graph layouts — so the sibling // probe lands in a directory that does not contain the bundled workers. // We emit the worker entries next to cli.js (see tsup.config.ts) and tell // the pools where to find them via the published env-var overrides. The // pools have an explicit `workerEntryPath` factory option as the canonical // API, but setting the env vars here covers every call site without having // to thread the path through the renderOrchestrator → captureHdrStage → // captureHdrHybridLoop chain. import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { existsSync } from "node:fs"; (() => { const here = dirname(fileURLToPath(import.meta.url)); const shader = join(here, "shaderTransitionWorker.js"); const png = join(here, "pngDecodeBlitWorker.js"); if (!process.env.HF_SHADER_WORKER_ENTRY && existsSync(shader)) { process.env.HF_SHADER_WORKER_ENTRY = shader; } if (!process.env.HF_PNG_DECODE_BLIT_WORKER_ENTRY && existsSync(png)) { process.env.HF_PNG_DECODE_BLIT_WORKER_ENTRY = png; } })(); // ── Fast-path exits ───────────────────────────────────────────────────────── // Check --version before importing anything heavy. This makes // `hyperframes --version` near-instant (~10ms vs ~80ms). import { VERSION } from "./version.js"; const argv = process.argv.slice(2); const commandArg = argv[0]; const rootVersionRequested = commandArg === "--version" || commandArg === "-V" || (commandArg === undefined && (argv.includes("--version") || argv.includes("-V"))); if (rootVersionRequested) { console.log(VERSION); process.exit(0); } // ── Lazy imports ──────────────────────────────────────────────────────────── // Telemetry, update checks, and heavy modules are imported only when needed. // For --help we skip telemetry entirely. import { defineCommand, runMain } from "citty"; import type { ArgsDef, CommandDef } from "citty"; const isHelp = process.argv.includes("--help") || process.argv.includes("-h"); // --------------------------------------------------------------------------- // CLI definition — all commands are lazy-loaded via dynamic import() // --------------------------------------------------------------------------- const subCommands = { init: () => import("./commands/init.js").then((m) => m.default), add: () => import("./commands/add.js").then((m) => m.default), catalog: () => import("./commands/catalog.js").then((m) => m.default), play: () => import("./commands/play.js").then((m) => m.default), preview: () => import("./commands/preview.js").then((m) => m.default), publish: () => import("./commands/publish.js").then((m) => m.default), render: () => import("./commands/render.js").then((m) => m.default), lint: () => import("./commands/lint.js").then((m) => m.default), inspect: () => import("./commands/inspect.js").then((m) => m.default), layout: () => import("./commands/layout.js").then((m) => m.default), info: () => import("./commands/info.js").then((m) => m.default), compositions: () => import("./commands/compositions.js").then((m) => m.default), benchmark: () => import("./commands/benchmark.js").then((m) => m.default), browser: () => import("./commands/browser.js").then((m) => m.default), "remove-background": () => import("./commands/remove-background.js").then((m) => m.default), transcribe: () => import("./commands/transcribe.js").then((m) => m.default), tts: () => import("./commands/tts.js").then((m) => m.default), docs: () => import("./commands/docs.js").then((m) => m.default), doctor: () => import("./commands/doctor.js").then((m) => m.default), upgrade: () => import("./commands/upgrade.js").then((m) => m.default), skills: () => import("./commands/skills.js").then((m) => m.default), telemetry: () => import("./commands/telemetry.js").then((m) => m.default), validate: () => import("./commands/validate.js").then((m) => m.default), snapshot: () => import("./commands/snapshot.js").then((m) => m.default), capture: () => import("./commands/capture.js").then((m) => m.default), lambda: () => import("./commands/lambda.js").then((m) => m.default), }; const main = defineCommand({ meta: { name: "hyperframes", version: VERSION, description: "Create and render HTML video compositions", }, subCommands, }); // --------------------------------------------------------------------------- // Telemetry — lazy-loaded, captured references for exit handlers // --------------------------------------------------------------------------- const cliCommandArg = process.argv[2]; const command = cliCommandArg && cliCommandArg in subCommands ? cliCommandArg : "unknown"; const hasJsonFlag = process.argv.includes("--json"); // Captured references — populated when the lazy imports resolve. // Used in exit handlers where dynamic import() is unsafe (beforeExit loops, // exit handler is synchronous-only). let _flush: (() => Promise) | undefined; let _flushSync: (() => void) | undefined; let _printUpdateNotice: (() => void) | undefined; if (!isHelp && command !== "telemetry" && command !== "unknown") { import("./telemetry/index.js").then((mod) => { _flush = mod.flush; _flushSync = mod.flushSync; mod.showTelemetryNotice(); mod.trackCommand(command); if (mod.shouldTrack()) mod.incrementCommandCount(); }); } if (!isHelp && !hasJsonFlag && command !== "upgrade") { // Report any completed auto-install from the previous run first, before // kicking off the next check — so the user sees "updated to vX" once and // we don't over-print. import("./utils/autoUpdate.js").then((mod) => mod.reportCompletedUpdate()).catch(() => {}); import("./utils/updateCheck.js").then(async (mod) => { _printUpdateNotice = mod.printUpdateNotice; const result = await mod.checkForUpdate().catch(() => null); if (result?.updateAvailable) { const auto = await import("./utils/autoUpdate.js").catch(() => null); auto?.scheduleBackgroundInstall(result.latest, result.current); } }); } // Async flush for normal exit (beforeExit fires when the event loop drains) process.on("beforeExit", () => { _flush?.().catch(() => {}); if (!hasJsonFlag) _printUpdateNotice?.(); }); // Sync flush for process.exit() calls (exit event only allows synchronous code) process.on("exit", () => { _flushSync?.(); }); // Lazy-load help renderer — avoids allocating help data on non-help invocations async function showUsage( cmd: CommandDef, parent?: CommandDef, ): Promise { const { showUsage: impl } = await import("./help.js"); return impl(cmd as CommandDef, parent as CommandDef | undefined); } runMain(main, { showUsage });