diff --git a/.fallowrc.jsonc b/.fallowrc.jsonc index 64fcc1831..0fb596860 100644 --- a/.fallowrc.jsonc +++ b/.fallowrc.jsonc @@ -43,6 +43,14 @@ "packages/engine/tests/**", "skills/**/test-corpus/**", "skills/**/scripts/**", + // Agent-invoked motion-graphics tools co-located with their docs (run via + // `node ` per grounding/PROTOCOL.md / categories/maps/module.md + // prose), not import-graph reachable. + "skills/motion-graphics/grounding/**", + "skills/motion-graphics/categories/**", + // Bundled @font-face data (read at runtime via fs.readFileSync, invisible + // to the import graph) + its manual rebuild tool. + "skills/**/fonts/**", // Golden snapshot files: data consumed by toMatchFileSnapshot, not importable modules. "packages/**/__goldens__/**", "registry/**", diff --git a/.gitignore b/.gitignore index d41fc8793..7c0320847 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,5 @@ test-outputs/ .claude/ docs/superpowers/ .worktrees +hyperframes-bench/ +tmp/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index d7fb4ef1b..d52ee1264 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,9 @@ packages/gcp-cloud-run/terraform/workflow.yaml # toMatchFileSnapshot golden files — vitest writes these; oxfmt must not reformat them. packages/**/__goldens__/ + +# vendored third-party bundles inside skills — never reformat +skills/**/assets/vendor/ +skills/**/*.min.js +# reference snippets with intentional pseudo-markup (literal "..." attributes) +skills/graphic-overlays/references/frames/polaroid.html diff --git a/AGENTS.md b/AGENTS.md index 19f951973..d7ff38352 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,19 @@ This repo ships AI agent skills via [vercel-labs/skills](https://github.com/verc npx skills add heygen-com/hyperframes ``` +**Video-creation workflows** route through one entry skill — `/hyperframes-read-first` orients you to the whole surface and maps "make me a video" intent to a concrete workflow. Consult it before invoking a specific workflow: + +- `/product-launch-video` — a **product** URL (or a pre-written script / text brief in no-capture mode) → product launch / promo video, up to ~3 min (sweet spot ~30-90s). +- `/website-to-video` — a **general** website / URL → a video _of_ the site (tour / showcase / social clip from captured screenshots + assets); for a product **launch / promo**, use `/product-launch-video`. +- `/faceless-explainer` — arbitrary text, **no URL and no website capture** → faceless explainer, up to ~3 min (sweet spot ~30-90s); every visual is LLM-invented (typography / abstract graphics / diagram / data-viz). +- `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (verbatim rail + embedded climax, or pure-cinematic embed); the footage itself is untouched (no NLE-style editing). +- `/graphic-overlays` — an existing talking-head / interview / podcast video (MP4) → the same footage packaged with designed **graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath, footage untouched. Replaces the removed `/footage-recut`. For plain captions/subtitles → `/embedded-captions`. +- `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → code-change explainer, up to ~3 min (changelog / feature reveal / fix / refactor). A PR link, not a product website. +- `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`. +- `/general-video` — fallback for any other video creation (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition); the original hyperframes flow — design → plan → layout → build → validate, any length. + +**Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) video composition into HyperFrames HTML — a source migration, separate from the creation workflows above. + ## Build & Test ```bash diff --git a/CLAUDE.md b/CLAUDE.md index 47657dbdc..d7ff38352 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,105 +2,87 @@ Open-source video rendering framework: write HTML, render video. -``` -packages/ - cli/ → hyperframes CLI (create, preview, lint, render) - core/ → Types, parsers, generators, linter, runtime, frame adapters - engine/ → Seekable page-to-video capture engine (Puppeteer + FFmpeg) - player/ → Embeddable web component - producer/ → Full rendering pipeline (capture + encode + audio mix) - studio/ → Browser-based composition editor UI -``` +## Skills -## Development +This repo ships AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. ```bash -bun install # Install dependencies -bun run build # Build all packages -bun run test # Run tests +npx skills add heygen-com/hyperframes ``` -**This repo uses bun**, not pnpm. Do NOT run `pnpm install` — it creates a `pnpm-lock.yaml` that should not exist. Workspace linking relies on bun's resolution from `"workspaces"` in root `package.json`. +**Video-creation workflows** route through one entry skill — `/hyperframes-read-first` orients you to the whole surface and maps "make me a video" intent to a concrete workflow. Consult it before invoking a specific workflow: -### Linting & Formatting +- `/product-launch-video` — a **product** URL (or a pre-written script / text brief in no-capture mode) → product launch / promo video, up to ~3 min (sweet spot ~30-90s). +- `/website-to-video` — a **general** website / URL → a video _of_ the site (tour / showcase / social clip from captured screenshots + assets); for a product **launch / promo**, use `/product-launch-video`. +- `/faceless-explainer` — arbitrary text, **no URL and no website capture** → faceless explainer, up to ~3 min (sweet spot ~30-90s); every visual is LLM-invented (typography / abstract graphics / diagram / data-viz). +- `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (verbatim rail + embedded climax, or pure-cinematic embed); the footage itself is untouched (no NLE-style editing). +- `/graphic-overlays` — an existing talking-head / interview / podcast video (MP4) → the same footage packaged with designed **graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath, footage untouched. Replaces the removed `/footage-recut`. For plain captions/subtitles → `/embedded-captions`. +- `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → code-change explainer, up to ~3 min (changelog / feature reveal / fix / refactor). A PR link, not a product website. +- `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`. +- `/general-video` — fallback for any other video creation (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition); the original hyperframes flow — design → plan → layout → build → validate, any length. -This project uses **oxlint** and **oxfmt** (not biome, not eslint, not prettier). +**Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) video composition into HyperFrames HTML — a source migration, separate from the creation workflows above. + +## Build & Test ```bash -bunx oxlint # Lint -bunx oxfmt # Format (write) -bunx oxfmt --check # Format (check only, used by pre-commit hook) +bun install # Install dependencies (NOT pnpm — do not create pnpm-lock.yaml) +bun run build # Build all packages +bun run test # Run all tests ``` -Always run both on changed files before committing. The lefthook pre-commit hook runs `bunx oxlint` and `bunx oxfmt --check` automatically. - -### Adding CLI Commands - -When adding a new CLI command: - -1. Define the command in `packages/cli/src/commands/.ts` using `defineCommand` from citty -2. **Export `examples`** in the same file — `export const examples: Example[] = [...]` (import `Example` from `./_examples.js`). These are displayed by `--help`. -3. Register it in `packages/cli/src/cli.ts` under `subCommands` (lazy-loaded) -4. **Add to help groups** in `packages/cli/src/help.ts` — add the command name and description to the appropriate `GROUPS` entry. Without this, the command won't appear in `hyperframes --help` even though it works. -5. **Document it** in `docs/packages/cli.mdx` — add a section with usage examples and flags. -6. Validate by running `npx tsx packages/cli/src/cli.ts --help` (command appears in the list) and `npx tsx packages/cli/src/cli.ts --help` (examples appear). - -### Regression Test Golden Baselines (producer) +### Linting & Formatting -`packages/producer/tests//output/output.mp4` baselines MUST be generated -inside `Dockerfile.test`, not on your host. CI renders inside that Docker image -with a specific Chrome + ffmpeg build; pixel-level output drifts across -different host Chrome/ffmpeg versions and will fail PSNR at dozens of -checkpoints even when the code is correct. +Uses **oxlint** and **oxfmt** (not eslint, not prettier, not biome). ```bash -# Build the test image once: -docker build -t hyperframes-producer:test -f Dockerfile.test . - -# Generate or update a baseline (runs the harness with --update inside Docker): -bun run --cwd packages/producer docker:test:update +bunx oxlint # Lint +bunx oxfmt # Format +bunx oxfmt --check # Check formatting (CI / pre-commit) ``` -Never run `bun run --cwd packages/producer test:update` directly from the -host to capture a baseline that will be committed — the resulting output.mp4 -will not match CI. Use it only for local-only experimentation. - -## Releasing +Always lint and format changed files before committing. Lefthook pre-commit hooks enforce this automatically. -All eight packages share one version. `.github/workflows/publish.yml` publishes -them when a `v*` tag is pushed (it also accepts a manual `workflow_dispatch` with -a version input). The CLI publishes as the unscoped `hyperframes` package; the -other seven as `@hyperframes/*`. +### Composition Validation -Cut a release from `main`: +After creating or editing any `.html` composition: ```bash -bun run release:prepare # e.g. 0.6.72 +npx hyperframes lint # Static HTML structure check +npx hyperframes validate # Runtime check (headless Chrome — catches JS errors, missing assets) ``` -Run it twice: +Both must pass before previewing or considering work complete. -1. **First run** writes `releases/v.md` and prepends an entry to - `docs/changelog.mdx`, then stops with a non-zero exit. Edit both files: write - the 1–2 sentence summary and remove the `` marker. -2. **Second run** (after the TODO is gone) bumps every package and plugin - manifest, creates the `chore: release v` commit, and tags - `v` (lightweight). +## Project Structure -Then push to trigger the publish: - -```bash -git push origin main --tags +``` +packages/ + cli/ → hyperframes CLI (create, preview, lint, render) + core/ → Types, parsers, generators, linter, runtime, frame adapters + engine/ → Seekable page-to-video capture engine (Puppeteer + FFmpeg) + player/ → Embeddable web component + producer/ → Full rendering pipeline (capture + encode + audio mix) + shader-transitions/ → WebGL shader transitions for compositions + studio/ → Browser-based composition editor UI +registry/ + blocks/ → Installable sub-composition scenes (50+) + components/ → Installable effects and snippets + examples/ → Starter project templates +docs/ → Mintlify documentation site (hyperframes.heygen.com) +skills/ → AI agent skill definitions ``` -Notes: +## Key Conventions -- Pre-release versions (`0.6.72-alpha.1`) publish to the matching npm dist-tag - (`alpha`) instead of `latest`. -- `--skip-changelog-check` skips the changelog gate for emergency releases. -- The publish step skips any version already on npm, so re-running a failed - workflow run is safe. +- **Package manager**: bun (not pnpm, not npm for workspace operations) +- **Commit format**: Conventional commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`) +- **TypeScript**: Avoid `any` and `as T` assertions. Prefer type guards and narrowing. +- **Compositions**: HTML files with `data-*` attributes. Clips need `class="clip"`. GSAP timelines must be paused and registered on `window.__timelines`. +- **Frame Adapters**: Animation runtimes plug in via the seek-by-frame adapter pattern. GSAP is the primary adapter. +- **Deterministic rendering**: No `Date.now()`, no unseeded `Math.random()`, no render-time network fetches. -## Skills +## Documentation -Composition authoring (not repo development) is guided by skills installed via `npx skills add heygen-com/hyperframes`. See `skills/` for source. Invoke `/hyperframes`, `/hyperframes-cli`, `/hyperframes-registry`, `/tailwind`, or `/gsap` when authoring compositions. Use `/tailwind` for projects created with `hyperframes init --tailwind` so agents follow the pinned Tailwind v4 browser-runtime contract instead of Studio's Tailwind v3 setup. Use `/animejs`, `/css-animations`, `/lottie`, `/three`, or `/waapi` when a composition uses those first-party runtime adapters. Invoke `/hyperframes-media` for asset preprocessing (TTS narration, audio/video transcription, background removal for transparent overlays) — these commands have their own skill so the CLI skill stays focused on the dev loop. When a user provides a website URL and wants a video, invoke `/website-to-hyperframes` — it runs the full 7-step capture-to-video pipeline. +- Docs: https://hyperframes.heygen.com/introduction +- Catalog (50+ blocks): https://hyperframes.heygen.com/catalog/blocks/data-chart diff --git a/docs/guides/pipeline.mdx b/docs/guides/pipeline.mdx index 0b72c96a2..97c957e54 100644 --- a/docs/guides/pipeline.mdx +++ b/docs/guides/pipeline.mdx @@ -195,7 +195,7 @@ Each artifact is a checkpoint, so you can stop, hand off to a human reviewer, or The pipeline is the recommended structure for: -- Capturing a website with the [/website-to-hyperframes](/guides/prompting) skill, which follows it end-to-end. +- Capturing a website with the [/website-to-video](/guides/website-to-video) skill, which follows it end-to-end. - Shipping a product launch. Most of the [HeyGen launch videos](/launch-videos) use this artifact layout. - Any narrative video with three or more beats, where a storyboard pays for itself. - Learning Hyperframes, because the artifacts leave every creative decision inspectable on disk. diff --git a/docs/guides/prompting.mdx b/docs/guides/prompting.mdx index 69fcd2978..ae390aa9c 100644 --- a/docs/guides/prompting.mdx +++ b/docs/guides/prompting.mdx @@ -21,7 +21,7 @@ In Claude Code, restart the session after installing. Skills register as **slash | `/hyperframes-cli` | Dev-loop CLI — `init`, `lint`, `inspect`, `preview`, `render`, `doctor` | | `/hyperframes-media` | Asset preprocessing — `tts`, `transcribe`, `remove-background` | | `/hyperframes-registry` | Block and component installation via `hyperframes add` | -| `/website-to-hyperframes` | Capture a URL and turn it into a video; runs the full [Hyperframes pipeline](/guides/pipeline) | +| `/website-to-video` | Capture a URL and turn it into a video; runs the full [Hyperframes pipeline](/guides/pipeline) | | `/gsap` | GSAP animation API — timelines, easing, ScrollTrigger, plugins | diff --git a/docs/guides/website-to-video.mdx b/docs/guides/website-to-video.mdx index 7175a2b64..464b86905 100644 --- a/docs/guides/website-to-video.mdx +++ b/docs/guides/website-to-video.mdx @@ -209,7 +209,7 @@ See the [pipeline guide](/guides/pipeline#iterating) for more re-entry patterns. npx skills add heygen-com/hyperframes ``` - Lead your prompt with _"Use the /website-to-hyperframes skill"_ for the most reliable results. Agents also discover it automatically when they see a URL and a video request. + Lead your prompt with _"Use the /website-to-video skill"_ for the most reliable results. Agents also discover it automatically when they see a URL and a video request. diff --git a/docs/packages/cli.mdx b/docs/packages/cli.mdx index db8f5b5c6..551693b5b 100644 --- a/docs/packages/cli.mdx +++ b/docs/packages/cli.mdx @@ -430,7 +430,7 @@ This is suppressed in CI environments, non-TTY shells, and when `HYPERFRAMES_NO_ The capture command extracts everything an AI agent needs to understand a website's visual identity: viewport screenshots at every scroll depth, color palette (pixel-sampled + DOM computed), font files, images with semantic names, SVGs, Lottie animations, video previews, WebGL shaders, visible text, and page structure. - Output is a self-contained directory with a `CLAUDE.md` file that any AI agent can read to understand the captured site. Used by the `/website-to-hyperframes` skill as step 1 of the video production pipeline. + Output is a self-contained directory with a `CLAUDE.md` file that any AI agent can read to understand the captured site. Used by the `/website-to-video` skill as step 1 of the video production pipeline. Set `GEMINI_API_KEY` in a `.env` file for AI-powered image descriptions via Gemini vision (~$0.001/image). See the [Website to Video](/guides/website-to-video#enriching-captures-with-gemini-vision) guide for details. diff --git a/packages/cli/scripts/build-copy.mjs b/packages/cli/scripts/build-copy.mjs index 926493a9d..510d779d3 100644 --- a/packages/cli/scripts/build-copy.mjs +++ b/packages/cli/scripts/build-copy.mjs @@ -16,6 +16,7 @@ const DIST = join(CLI_ROOT, "dist"); const STUDIO_WAIT_TIMEOUT_MS = 30_000; const STUDIO_POLL_INTERVAL_MS = 250; +// fallow-ignore-next-line complexity async function waitForStudioDist(dir) { const deadline = Date.now() + STUDIO_WAIT_TIMEOUT_MS; while (Date.now() < deadline) { @@ -54,6 +55,7 @@ function copyMdFiles(srcDir, destDir) { } } +// fallow-ignore-next-line complexity async function main() { for (const sub of ["studio", "docs", "templates", "skills", "docker"]) { mkdirSync(join(DIST, sub), { recursive: true }); @@ -68,8 +70,16 @@ async function main() { copyDir(join(CLI_ROOT, "src", "templates", tmpl), join(DIST, "templates", tmpl)); } + // Skills bundled into the published CLI. Branches don't all carry the same + // skills/ tree (it gets restructured), so each entry is existsSync-guarded: + // a missing skill dir warns + skips instead of crashing the build. for (const skill of ["hyperframes", "hyperframes-cli", "gsap"]) { - copyDir(join(REPO_ROOT, "skills", skill), join(DIST, "skills", skill)); + const src = join(REPO_ROOT, "skills", skill); + if (!existsSync(src)) { + console.warn(`[build-copy] skill not found, skipping: skills/${skill}`); + continue; + } + copyDir(src, join(DIST, "skills", skill)); } const dockerfile = join(CLI_ROOT, "src", "docker", "Dockerfile.render"); diff --git a/packages/cli/src/capture/agentPromptGenerator.ts b/packages/cli/src/capture/agentPromptGenerator.ts index 8b52953d8..9b6f33745 100644 --- a/packages/cli/src/capture/agentPromptGenerator.ts +++ b/packages/cli/src/capture/agentPromptGenerator.ts @@ -7,7 +7,7 @@ * * This file generates a DATA INVENTORY that tells the AI agent what files * exist and what they contain. The actual workflow lives in the - * website-to-hyperframes skill — this file points agents there. + * website-to-video skill — this file points agents there. */ import { writeFileSync, readdirSync, existsSync } from "node:fs"; @@ -20,6 +20,7 @@ import type { CatalogedAsset } from "./assetCataloger.js"; * Infer a human-readable role hint from a hex color based on luminance and saturation. * Not a substitute for DESIGN.md — just helps orient agents scanning the brand summary. */ +// fallow-ignore-next-line complexity function inferColorRole(hex: string): string { const r = parseInt(hex.slice(1, 3), 16) / 255; const g = parseInt(hex.slice(3, 5), 16) / 255; @@ -56,6 +57,7 @@ export function generateAgentPrompt( writeFileSync(join(outputDir, ".cursorrules"), prompt, "utf-8"); } +// fallow-ignore-next-line complexity function buildPrompt( outputDir: string, url: string, @@ -196,7 +198,7 @@ function buildPrompt( Source: ${url} -To create a video from this capture, use the \`website-to-hyperframes\` skill. +To create a video from this capture, use the \`website-to-video\` skill. ## What's in This Capture diff --git a/packages/cli/src/capture/index.ts b/packages/cli/src/capture/index.ts index 89121c0ea..1903abbcc 100644 --- a/packages/cli/src/capture/index.ts +++ b/packages/cli/src/capture/index.ts @@ -41,6 +41,7 @@ import type { CaptureOptions, CaptureResult } from "./types.js"; export type { CaptureOptions, CaptureResult } from "./types.js"; +// fallow-ignore-next-line complexity export async function captureWebsite( opts: CaptureOptions, onProgress?: (stage: string, detail?: string) => void, @@ -141,9 +142,18 @@ export async function captureWebsite( // Intercept network responses to detect Lottie JSON files const discoveredLotties: DiscoveredLottie[] = []; + // Layer 1 (passive video discovery): every direct-video URL the page fetches + // over the whole session (load / scroll / carousel rotation), independent of + // whether a