Skip to content

feat(cli): capture-video on-demand fetcher + capture pipeline robustness#1447

Open
ukimsanov wants to merge 1 commit into
mainfrom
feat/cli-capture-video
Open

feat(cli): capture-video on-demand fetcher + capture pipeline robustness#1447
ukimsanov wants to merge 1 commit into
mainfrom
feat/cli-capture-video

Conversation

@ukimsanov

@ukimsanov ukimsanov commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Context

For the hyperframes.dev website-to-video flow. Real-AI-test runs against heygen.com, huly.io, and heygen-showcase surfaced two gaps: (1) capture's logo / asset-captioning signals missed modern React/Tailwind builds; and (2) there was no CLI surface to pull the videos the manifest references.

Summary

New commandhyperframes capture-video <project>. On-demand downloader for entries in capture/extracted/video-manifest.json. Capture writes the manifest + preview PNGs but skips the mp4s; this pulls one entry by --index N (matched against the entry's index field — NOT array offset; the manifest can have gaps when a preview screenshot fails). SSRF-safe via safeFetch, 250 MB cap, content-type whitelist, race-free exclusive-create write. Layout-aware (standalone capture + W2H project layouts).

Capture pipeline fixes

  • Structural logo signals (assetCataloger + tokenExtractor): inBanner / inHomeLink / matchesTitleBrand. Class-substring alone caught 0/32 SVGs on heygen.com — modern builds don't put "logo" or "brand" in any className.
  • Content-hash SVG slugs (assetDownloader): svg-<8char-sha1>.svg. Label-derived slugs mis-attributed partner-logo carousels (heygen-logo.svg actually contained Google, hubspot-logo.svg contained Trivago).
  • SVG → PNG rasterization before Gemini Vision (contentExtractor): raw-SVG-as-text was hallucinating wordmarks (VIVIENNE for HubSpot). Polarity detection inverts white-glyph SVGs that flatten to blank PNGs before captioning.
  • Double-escape \/ inside page.evaluate template literal (assetCataloger + tokenExtractor): the original /^https?:\/\/…/ collapsed to / mid-template and threw Unexpected token ^. Capture was 100% blocked on this until fixed.
  • asset-descriptions.md header branches on Gemini-key presence with an explicit "Vision OFF — catalog-derived" warning.

New lint rulelintMissingLocalAsset (cli/utils/lintProject.ts). Scans <video> / <img> / <source> src for local files that don't exist in the project. Empirically the most common sub-agent mistake across multi-URL runs (~5+ per run). Masks comment / style / script ranges before scanning.

Tests

  • 17 new tests for capture-video: safeFilename decoding/sanitization, VIDEO_CONTENT_TYPE_RE accept/reject, pickManifestEntry index-field lookup with gaps, URL-mismatch + bad-index rejection, --index over --url priority
  • 70 cases under lintProject.test.ts covering the new rule + existing rules

Test plan

  • bun run --filter @hyperframes/cli typecheck passes
  • bun run --filter @hyperframes/cli test passes
  • hyperframes capture <url>capture-video <dir> --list--index 0 downloads cleanly
  • Snippet output from capture-video passes hyperframes lint (id attribute present)

// `flag: "wx"` = exclusive create: throws EEXIST if outPath exists.
// Race-free check-and-create in one syscall.
try {
writeFileSync(outPath, buf, { flag: "wx" });
@ukimsanov ukimsanov force-pushed the feat/cli-capture-video branch 3 times, most recently from aaefdfd to e4e8b44 Compare June 14, 2026 22:47
For the hyperframes.dev website-to-video flow. Real-AI-test runs against
heygen.com, huly.io, and heygen-showcase surfaced two gaps: (1) capture's
logo / asset-captioning signals missed modern React/Tailwind builds; and
(2) there was no CLI surface to pull the videos the manifest references.

New command:

  • `hyperframes capture-video <project>` — on-demand downloader for
    entries in capture/extracted/video-manifest.json. Capture writes the
    manifest + preview PNGs but skips the mp4s; this pulls one entry by
    `--index N` (matched against the entry's `index` field, NOT array
    offset — gaps are possible when a preview screenshot fails). SSRF-safe
    via safeFetch, 250 MB cap, content-type whitelist, race-free
    exclusive-create write. Layout-aware (handles both standalone capture
    and W2H project layouts).

Capture pipeline fixes:

  • Structural logo signals (assetCataloger + tokenExtractor): inBanner /
    inHomeLink / matchesTitleBrand. Class-substring alone caught 0/32 SVGs
    on heygen.com — modern builds don't put 'logo' / 'brand' in any
    className.

  • Content-hash SVG slugs (assetDownloader): `svg-<8char-sha1>.svg` —
    label-derived slugs mis-attributed partner-logo carousels
    (heygen-logo.svg actually contained Google, hubspot-logo.svg contained
    Trivago, etc.). Content-hash names are invariant by construction.

  • SVG → PNG rasterization before Gemini Vision (contentExtractor): the
    raw-SVG-as-text path was hallucinating wordmarks (VIVIENNE for HubSpot,
    'wrestling' for Workday). Adds polarity detection so a white-glyph SVG
    flattened to a blank PNG gets inverted before captioning. LOGO tag in
    asset-descriptions.md when structural signals fire (independent of
    Gemini key presence).

  • Double-escape \/ inside the page.evaluate template literal in
    assetCataloger + tokenExtractor: the original `/^https?:\/\/.../`
    collapsed to `/` mid-template and threw `Unexpected token ^`. Capture
    was 100% blocked on this until the escape was fixed.

  • `asset-descriptions.md` header branches on Gemini-key presence with
    an explicit 'Vision OFF — catalog-derived descriptions' warning.

New lint rule:

  • `lintMissingLocalAsset` (cli/utils/lintProject): scans <video> / <img>
    / <source> src for local files that don't exist in the project.
    Empirically the most common sub-agent mistake across multi-URL runs
    (~5+ per run). Uses `resolveExistingLocalAsset` so the existence check
    matches the bundler's notion of 'resolves'. Masks comment / style /
    script ranges before scanning so a literal `<img src=missing.png>`
    inside a tutorial comment isn't reported.

Tests: 17 new for capture-video (safeFilename decoding/sanitization,
VIDEO_CONTENT_TYPE_RE accept/reject, pickManifestEntry index-field lookup
with gaps, URL-mismatch + bad-index rejection, --index over --url
priority); 70 cases under lintProject.test.ts covering the new rule and
existing rules.

Sibling PRs in this stack:
  • #PR_A1 — fix(producer): __dirname ESM banner shim
  • #PR_A2 — fix(core/lint): findRootTag masks comment/style/script
@ukimsanov ukimsanov force-pushed the feat/cli-capture-video branch from e4e8b44 to b56b7c5 Compare June 14, 2026 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants