#!/usr/bin/env bun /** * gstack-render — render a local HTML file through a browser: Aside when it * is running, otherwise gstack's own headless browser (the browse daemon). * * bun run ~/.claude/skills/gstack/bin/gstack-render.ts [options] [steps...] * * Options * --serve-root directory served over loopback (default: the file's dir) * --wait-selector wait until this selector is attached before any step * --wait-expr wait until this expression is truthy before any step * --wait-timeout budget for --wait-selector / --wait-expr (default 30000) * --timeout whole-render budget (default 120000; Aside caps a script at 120s) * --quiet on failure, suppress the transcript tail (ENGINE=, OK, EVAL and * PAGE_ERRORS lines always print) * * Steps (run in the order given; repeatable) * --pdf [--paper letter|a4|... | --paper-in WxH] [--margin ] [--margin-top ] ... * [--header ] [--footer ] [--page-numbers] [--tagged] [--outline] * [--print-background] [--prefer-css-page-size] [--landscape] [--wait-pagedjs] * --screenshot [--width ] [--height ] [--selector ] [--viewport-only] [--jpeg [--quality ]] * --eval [--out ] evaluate in the page (promises awaited); with --out the result is * written to the file (strings verbatim, data: URLs decoded to bytes, * anything else as JSON); without --out it is printed as EVAL : ... * * Output: `ENGINE=aside|browse` first (the engine that actually rendered — Aside * dying mid-run falls back to gstack's own browser), then one `OK ` line per * artifact, then, fenced between `═══ BEGIN/END UNTRUSTED WEB CONTENT ═══` lines * because they are page-controlled text, `EVAL : ` for inline evals and * `PAGE_ERRORS=[...]` when the page logged errors; exit 0. On failure: * `ERROR: ...`, exit 1. When NEITHER browser is * available the first line is `NEEDS_ASIDE` / `ASIDE_NOT_RUNNING` (the BROWSER * SETUP contract) and the error names both remedies: open Aside, or build * gstack's browser with ./setup (GSTACK_BROWSE_BIN / BROWSE_BIN override the * fallback binary). * * The file's directory is served on 127.0.0.1 for the duration of the render * (Aside refuses file:// URLs; the daemon gets the same origin so relative * fetches behave identically) — relative /