Commit Graph
26 Commits
Author SHA1 Message Date
Vance IngallsandClaude Opus 4.6 a97dc75702 fix(lint): detect GSAP animations targeting clip elements (tab crash) (#114)
* fix(lint): detect GSAP animations targeting clip elements (tab crash)

The runtime manages clip visibility via inline styles. When GSAP also
writes inline styles on the same element, both systems trigger style
recalculations every frame, creating a runaway loop that crashes the
browser tab.

New rule gsap_animates_clip_element (error severity):
- Builds map of all elements with class="clip" (by id and class)
- Checks if any GSAP selector resolves to a clip element
- Nested selectors like "#overlay .title" are correctly ignored
- Merged into existing GSAP script loop (no redundant parsing)

* fix: remove non-null assertions and add missing test coverage

- Replace `!` assertions with optional chaining in lint.ts and tests
- Add shouldBlockRender tests for --strict-all without --strict
- Add clip element test for class-only detection (no id)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use optional chaining for array access in lintProject tests

TypeScript's strict mode flags array indexing as possibly undefined.
Use optional chaining and fallbacks instead of non-null assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:32:41 -07:00
Vance Ingalls 229538c622 fix: add media rendering guardrails to prevent silent failures (#112)
## Summary

- **Lint rules** catch media elements missing `id` (renderer silently skips them), missing `src`, `preload="none"` (blocks renderer), and video nested in timed divs (freezes playback). Upgraded `video_nested_in_timed_element` from warning to error.
- **Compiler** strips `preload="none"` from media during compilation. Runs parallel, cached keyframe interval analysis via ffprobe — warns on sparse keyframes (>2s) that cause seek failures and audio/video desync. Suggested ffmpeg command preserves audio (`-c:a copy`).
- **Pre-render lint** lints `index.html` + all `compositions/*.html` sub-compositions before render via shared `lintProject()` helper. Warns by default; `--strict` blocks on errors, `--strict-all` blocks on errors + warnings.
- **Render orchestrator** logs a hint to retry with `--workers 1` when parallel capture times out on video-heavy compositions.
- **Refactor**: extracted `runFfprobe()` + `parseProbeJson()` helpers to deduplicate ~80 lines of spawn boilerplate across 3 ffprobe functions. Extracted `shouldBlockRender()` so strict flag tests exercise production code. Shared `lintProject()` used by both `lint` and `render` commands.

## Context

Discovered during a real composition build session where:
1. `<audio>` without `id` rendered silently (preview worked fine because runtime queries `[data-start]`, but renderer queries `[id][src]`)
2. `<video>` inside timed `<div>` froze on first frame
3. `preload="none"` caused 45s renderer timeout
4. YouTube clips with sparse keyframes from `yt-dlp --download-sections` caused audio/video desync
5. Parallel workers timed out on video-heavy compositions

## Test plan

- [x] Core: 365/365 tests passing (5 new lint tests)
- [x] Engine: 24/24 tests passing
- [x] CLI: 14/14 tests passing (7 lintProject + 7 shouldBlockRender)
- [x] Lint + format hooks pass
- [ ] Manual: create a composition with `<audio data-start="0" src="test.wav">` (no id) — verify `npx hyperframes lint` catches it
- [ ] Manual: run `npx hyperframes render --strict` with lint errors — verify it blocks
- [ ] Manual: run `npx hyperframes render --strict-all` with lint warnings — verify it blocks
2026-03-30 11:07:19 -07:00
Vance Ingalls f0a8644208 feat(lint): add template_literal_selector rule (#107)
Detects querySelector/querySelectorAll calls that use template literal
variables (e.g. `${compId}`) inside script tags. The HTML bundler's
cheerio/css-what parser crashes on these during compilation, causing
silent fallback to raw HTML without runtime injection.

Severity: error (breaks bundling)
Fix: replace template literal with hardcoded composition ID string
2026-03-27 17:42:28 -07:00
Vance Ingalls 476c20747d feat: add templates (#102)
## What

Adds 4 new composition templates and fixes structural issues across all templates.

### New templates
- **decision-tree** — animated flowchart with branching paths
- **kinetic-type** — bold kinetic typography promo
- **product-promo** — multi-scene product showcase with SVG assets (3 scenes)
- **nyt-graph** — animated data chart in NYT print editorial style

### Fixes across all templates
- GSAP updated from 3.12.2 → 3.14.2 (all templates, including warm-grain, swiss-grid, vignelli, play-mode)
- New templates restructured with proper root wrapper div, `data-duration`, sub-composition refs with `data-composition-id` / `data-width` / `data-height`
- nyt-chart: replaced `${compId}` template literal variables with hardcoded `"nyt-chart"` string — cheerio's css-what parser crashes on template literals during bundling, causing silent fallback to raw HTML without runtime injection
- nyt-chart: added DOM readiness retry for dynamically created SVG elements
- kinetic-type: removed external S3 audio URL
- All templates: GSAP script loaded in `<head>` before any scripts reference it

## Why

The new templates expand the range of content types available via `hyperframes init`. The fixes ensure all templates work correctly in the studio preview (bundler inlines sub-compositions and injects the runtime).

## Test plan

- [x] All 4 new templates render in studio preview
- [x] nyt-graph chart animates bars, line, and labels on playback
- [x] Existing templates unaffected (GSAP version bump is backwards compatible)
- [x] `hyperframes lint` passes on all templates
- [x] `generators.ts` updated with new template IDs
2026-03-27 17:31:28 -07:00
Vance IngallsandClaude Opus 4.6 e3fad3029c feat(skill): add data-in-motion guide and house style refinements (#91)
data-in-motion.md — minimal guide for data/stats in video:
- Visual continuity: related data stays in same visual space
- Numbers need visual weight: pair metrics with fills/shapes
- Avoid web patterns: no pie charts, dashboards, axes, legends

house-style.md refinements from eval iterations:
- Layout approach variety (step 4 in Before Writing HTML)
- Explicit weight contrast requirement
- SVG illustration anti-default
- Overlap anti-default
- Ambient motion variety (not always zoom)

SKILL.md — added reference to data-in-motion.md

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:29:16 -07:00
Vance Ingalls 238962adff fix(skill): house style anti-defaults for SVG, overlap, and zoom (#68)
## Summary

- Anti-default: don't draw real-world objects with SVG paths — they look crude. Geometric shapes and abstract forms only.
- Anti-default: every element needs clear space — overlapping text is always ugly.
- Replaced zoom-in monoculture in choreography — now offers 6 ambient motion options instead of always zooming.
- Added scene pacing: build/breathe/resolve phases.

Follow-up to the house style PR (#59, merged). These fixes came from visual review of 20+ eval compositions.

## Test plan

- [ ] Generate a composition with a sparse prompt and verify no SVG illustrations of real objects
- [ ] Verify elements don't overlap in generated compositions
- [ ] Verify ambient motion varies (not always zoom-in)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-26 14:02:30 -07:00
Vance IngallsandClaude Opus 4.6 f8fad54261 feat(skill): house style guide for compose-video (#59)
* feat(cli): non-interactive by default, --human-friendly for UI

Following ElevenLabs CLI pattern: default mode is agent-friendly
(flag-driven, plain text output, fail fast on missing args).
Interactive clack UI is opt-in via --human-friendly.

Init command:
- --template required in default mode (errors with example if missing)
- --video / --audio flags for media input
- --skip-skills / --skip-transcribe to control optional steps
- --human-friendly enables the existing interactive prompts
- --help shows examples for every flag combination
- Transcription runs automatically in default mode (unless --skip-transcribe)
- Plain console.log output, process.exit(1) on errors

Skills command:
- Added --human-friendly flag
- Added examples to --help output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): improve --help documentation and add --yes/--check to upgrade

- upgrade: add --yes and --check flags to skip interactive prompt
- benchmark: clarify description — preset fps/quality/worker configs
- browser: describe each subcommand (ensure/path/clear) in help
- docs: list available topics inline in --help output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(skill): add house style guide with motion defaults, palettes, and anti-defaults

When no visual-style.md is provided, compose-video now follows house-style.md
for professional output quality. Includes:

- Motion: easing variety, timing, entrance patterns, choreography
- Sizing: text scale contrast, element fill, travel distance
- Visual depth: gradient/shadow/texture guidance
- Typography: weight contrast, tracking, case
- Anti-defaults: table of generic AI patterns to avoid
- 72 curated color palettes across 9 categories
- Content interpretation: generate real content, not prompt text

Eval-validated across 5 iterations with 50+ test compositions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(skill): add container anti-default and typography guidance

- Anti-default: discourage card/container patterns in favor of content
  placed directly on canvas (professional video style vs web UI style)
- Typography section: weight contrast, deliberate case, tracking, one
  typeface at two weights
- Visual depth: softened to avoid templating (content-appropriate, not
  every-composition-the-same)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(skill): add scene pacing guidance to house style

Three-phase composition structure: build (staggered entrances),
breathe (subtle motion to keep holds alive), resolve (fast exits
with intention). Prevents front-loading all animation into the
first second and dead static holds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(skill): replace zoom-in monoculture with ambient motion variety

The viewport scale and end emphasis code examples were being copied
verbatim to every composition. Now offers 6 ambient motion options
(pan, rotation, scale in/out, parallax, color shift, stillness) and
4 ending options instead of always zooming.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:15:16 -07:00
Vance IngallsandClaude Opus 4.6 25f4af428e feat(cli): agent-friendly CLI — non-interactive by default (#57)
* feat(cli): non-interactive by default, --human-friendly for UI

Following ElevenLabs CLI pattern: default mode is agent-friendly
(flag-driven, plain text output, fail fast on missing args).
Interactive clack UI is opt-in via --human-friendly.

Init command:
- --template required in default mode (errors with example if missing)
- --video / --audio flags for media input
- --skip-skills / --skip-transcribe to control optional steps
- --human-friendly enables the existing interactive prompts
- --help shows examples for every flag combination
- Transcription runs automatically in default mode (unless --skip-transcribe)
- Plain console.log output, process.exit(1) on errors

Skills command:
- Added --human-friendly flag
- Added examples to --help output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): improve --help documentation and add --yes/--check to upgrade

- upgrade: add --yes and --check flags to skip interactive prompt
- benchmark: clarify description — preset fps/quality/worker configs
- browser: describe each subcommand (ensure/path/clear) in help
- docs: list available topics inline in --help output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:10:08 -07:00
Vance IngallsandClaude Opus 4.6 f4367d5726 feat(cli): add whisper transcription and template improvements (#53)
* feat(cli): add whisper transcription to init flow

New modules:
- whisper/manager.ts: download/cache whisper.cpp binary + model
  (~/.cache/hyperframes/whisper/)
- whisper/transcribe.ts: extract audio, run whisper, save transcript.json

Init flow changes:
- "Got a video or audio file?" now accepts audio-only files (mp3, wav, m4a)
- "Generate captions from audio?" prompt after file selection
- Transcription produces transcript.json in project root
- Graceful fallback if whisper/ffmpeg unavailable

Supports: macOS ARM64/x86, Linux x86_64. Downloads whisper.cpp v1.7.3
from GitHub releases and ggml-base.en model from Hugging Face.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): use brew/system whisper instead of downloading binaries

whisper.cpp doesn't ship pre-built macOS/Linux CLI binaries.
Use brew install whisper-cpp on macOS (auto-installs if brew available),
system PATH lookup otherwise. Model still downloaded from Hugging Face.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): simplify whisper install — detect or instruct, don't build

Remove build-from-source complexity. If whisper-cpp is found on PATH,
use it. If not, show install instructions instead of blocking:

  "To generate captions, install whisper-cpp: brew install whisper-cpp"

The transcription prompt only appears when whisper is available.
When it's not, the user sees the install command and can re-run init.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): auto-install whisper via brew or build from source

ensureWhisper() now tries 4 strategies in order:
1. System PATH (whisper-cli or whisper already installed)
2. Homebrew (macOS: brew install whisper-cpp)
3. Build from source (git clone + cmake, ~30-60s)
4. Show install instructions as last resort

Init flow always asks "Generate captions?" — whisper is installed
automatically in the background if needed. No user intervention
required on macOS with Xcode CLI tools or any system with git+cmake.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): add window.__timelines guard to all templates

The studio bundler doesn't always initialize window.__timelines
before template scripts run, causing "Cannot set properties of
undefined" errors. Add defensive guard to every template.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): patch template captions with actual transcript data

After scaffolding, if transcript.json exists, replace the hardcoded
word array in the template's captions composition with the real
transcript data. The template's caption animation and styling are
preserved — only the word data changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): show install notice when whisper needs to be installed

When whisper-cpp isn't found, show an info message before the spinner:
"whisper-cpp not found — installing automatically..."
Then the spinner shows "Installing whisper-cpp (this may take a moment)..."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): add muted and playsinline to all template video elements

The framework requires video elements to have muted and playsinline
attributes. All four templates were missing these, causing video
to not play in the studio preview.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): flat asset structure + separate audio tracks in templates

Assets: video, images, fonts all go at project root (not assets/ or
fonts/ subdirectories). The studio preview can't resolve relative
paths from subdirectories due to the /preview URL suffix.

Audio: added <audio> elements alongside muted <video> in all 4
templates so the video's audio plays back. The framework requires
muted video + separate audio element.

Removed assets/ and fonts/ directory creation from scaffoldProject.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(studio): inject base tag for asset resolution in preview

The preview iframe serves bundled HTML from /api/projects/:id/preview
but relative asset paths (video.mp4, font.woff2) resolve to the wrong
URL without a <base> tag. Now injects <base href="/api/projects/:id/preview/">
so relative paths route through the static asset handler.

Also adds proper MIME types for video, audio, image, and font files
served from the preview asset route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(studio): serve HyperFrames runtime in dev mode

The preview runtime script had an empty src — the framework never
loaded, so video playback and clip lifecycle didn't work.

Now auto-detects packages/cli/dist/hyperframe-runtime.js and serves
it at /api/runtime.js. No env var needed in dev mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): filter whisper special tokens from transcript

Use --output-json instead of --output-json-full to avoid special
tokens like [_TT_485] and [BLANK_AUDIO]. Also filter remaining
bracket tokens when building the word array for captions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): use --output-json-full for word-level timestamps

--output-json only produces segment-level timing (no tokens).
--output-json-full is required for word-level timestamps that
the captions template needs. Special tokens are filtered out
by the patchTranscript function.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): patch template durations to match uploaded video

Templates now use __VIDEO_DURATION__ placeholder that gets replaced
with the actual probed video duration. All data-duration values on
the root composition, video, audio, and caption clips are updated.

Without a video, defaults to 10 seconds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): merge punctuation tokens with preceding word

Whisper outputs punctuation (. , ! ?) as separate tokens. These
appeared as standalone words in captions, sometimes in the wrong
group. Now merged with the preceding word during transcript
normalization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): match both TRANSCRIPT and script variable names in templates

Three templates use `const TRANSCRIPT = [...]` while warm-grain uses
`const script = [...]`. The patchTranscript function now matches both.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): security and template fixes

- Replace shell injection risk (execSync rm) with unlinkSync in transcribe.ts
- Add GIT_TERMINAL_PROMPT=0 to whisper buildFromSource git clone
- Fix hardcoded data-duration="18" in warm-grain captions template
- Add data-start="0" to root compositions in swiss-grid, vignelli, warm-grain
- Add data-start="0" to warm-grain grain-overlay composition
- Deduplicate hasFFmpeg: remove from init.ts, import from whisper/manager.ts
- Add my-video/ and packages/studio/data/ to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): format warm-grain captions and fix TS nullability errors

- Format warm-grain/compositions/captions.html
- Add optional chaining on token.offsets (may be undefined)
- Use intermediate variable for lastWord to satisfy TS strict checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): add blank template option, smart defaults for video vs audio

- Blank template: minimal scaffolding (root composition, video, audio,
  GSAP timeline) with __VIDEO_SRC__ and __VIDEO_DURATION__ placeholders
- Template defaults: video uploads default to "blank" (user brings
  their own content), audio-only defaults to "warm-grain" (motion
  graphics template since there's no video to show)
- Audio-only projects now tracked with isAudioOnly flag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): address whisper review feedback

- Clean stale builds: if BUILD_DIR exists but no binary, nuke and retry
- Build failures clean up BUILD_DIR so next attempt starts fresh
- patchTranscript regex scoped within <script> blocks to prevent
  matching across block boundaries
- Removed hardcoded model size hint (~148MB)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): add missing rmSync import to whisper manager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove test project and lock file

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): address review items 7-12 — execFileSync, build diagnostics, WAV verification

- manager.ts: replace all execSync with execFileSync to prevent command injection
- manager.ts: capture cmake stderr and include in build failure error message
- transcribe.ts: verify WAV is 16kHz mono via ffprobe before passing to whisper
- init.ts: replace fragile JSON formatting with JSON.stringify(words, null, 2)
- init.ts: fix default duration from "10" to "5" matching DEFAULT_META
- init.ts: add probeAudioDuration() and --audio/--skip-transcribe flags
- init.ts: extract finalizeProject() to reduce code path duplication
- init.ts: wire transcription into non-interactive path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:05:15 -07:00
Vance IngallsandClaude Opus 4.6 5fceab9279 feat(cli): add skills install command and init integration (#48)
* fix(ci): publish CLI from temp copy to avoid workspace mutation

Copy packages/cli to a temp directory before renaming to "hyperframes"
for publish. Avoids corrupting the workspace if the job fails mid-way.

Addresses review feedback on #47.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): resolve leftover conflict markers in publish.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): add skills install command

Adds `hyperframes skills install` to download and install HyperFrames
and GSAP skills globally to ~/.claude/skills/. Also adds
`hyperframes skills list` to show installed skills.

- HyperFrames skills: bundled in CLI dist, copied from dist/skills/
- GSAP skills: cloned from github.com/greensock/gsap-skills
- Cache: ~/.cache/hyperframes/gsap-skills/ (shallow clone, updated on install)
- Handles overwriting existing skills (removes before copy)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(cli): simplify skills to flat command

`hyperframes skills` directly installs + shows summary.
No subcommands needed — list was redundant since install
already prints all installed skills.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): scope skills summary to only HyperFrames and GSAP skills

The summary now only lists skills installed by this command, grouped
by source (HyperFrames vs GSAP), instead of listing everything in
~/.claude/skills/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): correct dev path for bundled skills directory

Path needed 4 levels up from cli/src/commands/ to reach repo root,
not 3. Was resolving to packages/.claude/skills/ instead of
.claude/skills/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): support multiple AI coding tools for skills install

Install to Claude Code, Gemini CLI, and Codex CLI by default.
Use flags to target specific tools:

  hyperframes skills              # claude + gemini + codex
  hyperframes skills --cursor     # cursor only (project-level)
  hyperframes skills --claude     # claude only

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): move skills to repo root, support multi-CLI install

- Move skills from .claude/skills/ to skills/ (tool-agnostic location)
- Install to Claude Code, Gemini CLI, Codex CLI by default
- Add --claude, --gemini, --codex, --cursor flags for targeting specific tools
- Update build script to copy from skills/ instead of .claude/skills/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add infographics skill for data visualization compositions

Professional infographic design and animation patterns:
- Typography hierarchy (hero stat, label, context)
- Layout rules (grid-aligned, generous whitespace, 2-color max)
- 5 infographic types: single stat, comparison, bar chart, progress, steps
- Animation patterns: count-up, bar growth, entrance choreography, exits
- Narration sync (stat appears when narrator says the number)
- Design constraints (no gradients, no shadows, no clip art)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add evals for infographics skill

Three eval scenarios testing design quality and animation correctness:
1. Single stat — count-up animation synced to narration
2. Comparison — before/after reveal with visual hierarchy
3. Process steps — sequential reveal with dimming

Each eval has PASS/FAIL criteria covering: composition structure,
design rules (typography, layout, color), animation patterns
(timing, easing, choreography), and anti-patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: remove infographics and transitions skills, add asset-management

Removed skills that don't add value beyond model knowledge:
- infographics: model already produces equivalent output without it
- transitions: patterns are derivable from compose-video constraints

Added:
- asset-management: organize user-uploaded files into assets/ and fonts/
- Typography section in compose-video: min font sizes, font loading
  (Google Fonts + local @font-face), weight pairing, font-display:block
- Assets section in compose-video: project structure with assets/ and
  fonts/ directories, path rules, CORS, "check before creating" rule

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: consolidate skills to 3, split compose-video under 500 lines

Removed skills that don't add value beyond model knowledge:
- media: duplicates compose-video
- social-media: platform safe areas are the only unique content
- infographics: model produces equivalent output without it
- transitions: patterns derivable from compose-video

Remaining skills (3):
- compose-video: core framework contract (452 lines, under 500 limit)
  - patterns.md: PiP, title card, slideshow examples (loaded on demand)
  - typography-and-assets.md: font loading, sizes, asset paths (on demand)
- captions: tone-adaptive caption styling from script analysis
- asset-management: organize uploaded files into project directories

Rewrote captions skill to focus on style detection from transcript
content (per-word styling, tone mapping) rather than mechanical rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): create assets/ and fonts/ directories on init

- scaffoldProject now creates assets/ and fonts/ directories
- Video files are placed in assets/ instead of project root
- Template __VIDEO_SRC__ placeholders resolve to assets/filename

Aligns with the compose-video skill's project structure convention
where user-provided media goes in assets/ and fonts in fonts/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(cli): fetch skills from GitHub repos instead of bundling

Skills are now fetched directly from their source repos at runtime:
- HyperFrames skills: github.com/heygen-com/hyperframes (skills/ dir)
- GSAP skills: github.com/greensock/gsap-skills (skills/ dir)

Both cached in ~/.cache/hyperframes/ and updated on each run.
Removed skills bundling from build:copy step.

Requires the hyperframes repo to be public for HyperFrames skills
to install. GSAP skills work immediately (public repo).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): install skills automatically during init

After scaffolding a new project, `hyperframes init` now runs
`hyperframes skills` to install HyperFrames and GSAP skills.
Best-effort — if skill installation fails (no git, no network),
project creation still succeeds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): show skills install feedback during init

Added installAllSkills() export for programmatic use by init.
Init now shows a spinner and result message:
  "11 AI skills installed (Claude Code, Gemini CLI, Codex CLI)"

Falls back gracefully if git or network unavailable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): let users select which AI tools to install skills for

Interactive init now shows a multi-select prompt:
  "Install AI coding skills for: Claude Code, Gemini CLI, Codex CLI, Cursor"
Users can deselect tools they don't use or add Cursor (off by default).
Non-interactive mode still installs to all default targets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): prevent git credential prompt from hanging skills install

Set GIT_TERMINAL_PROMPT=0 so git clone/pull fails immediately on
private repos instead of hanging for username/password input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): show skipped skill sources and accurate counts

Skills install now reports which sources failed (e.g., private repo)
and only counts skills that actually installed. Prompt text simplified
to "Install skills for:".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): clean up skills command output

- Skipped sources shown as dim text, not error with full command
- Summary shows "Skipped: HyperFrames (repo not accessible)"
- Outro says "ready" not "installed"
- Shows "No skills installed" if everything failed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): clarify partial skill install in outro message

When some sources fail, the outro now says which skills are ready
and which are unavailable, instead of a misleading total count.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove planning docs and test scaffolding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove eval projects and test examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove remaining test project data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): use generic source names in skills install outro message

Replace hardcoded "GSAP skills ready. HyperFrames skills unavailable."
with a dynamic message listing which sources succeeded and which were
skipped, so the message stays correct as sources change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): address review feedback on skills install

- Lazy process.cwd() for Cursor target (getter, not module-load)
- Track overwritten skills (logged in install output)
- Add --skip-skills flag to init for agent-friendly non-interactive use
- Guard both interactive and non-interactive paths with skipSkills

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): remove assets/ and fonts/ directories from init

Video files go to project root, not assets/. Removes assetsDir,
fonts/ directory creation, and assets/ prefix from video path.
Flat project root convention consistent with compose-video skill.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): address review items 5, 6, 13 — execFileSync, resilient cache, clear counting

- Replace all execSync with execFileSync for git commands (prevent injection)
- On git pull failure, reuse stale cache if skills dir exists instead of nuking
- Extract gitClone() helper for consistent clone calls
- Use explicit counted flag instead of confusing target === targets[0]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:01:27 -07:00
Vance IngallsandClaude Opus 4.6 71e00b4533 fix(cli): resolve npx hyperframes from inside monorepo (#47)
* fix(cli): rename workspace package so npx resolves from registry

Rename packages/cli from "hyperframes" to "@hyperframes/cli" so that
npm/npx stops resolving it as a local workspace package when run from
inside the monorepo. The publish workflow sets the name back to
"hyperframes" before publishing so the npm package name is unchanged.

Root cause: npm sees workspaces in root package.json, finds packages/cli
named "hyperframes", assumes it's local, but bun manages node_modules
so there's no bin symlink — npx fails with "command not found" instead
of falling back to the registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): update lockfile for workspace package rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): publish CLI from temp copy to avoid workspace mutation

Copy packages/cli to a temp directory before renaming to "hyperframes"
for publish. Avoids corrupting the workspace if the job fails mid-way.

Addresses review feedback on #47.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): resolve leftover conflict markers in publish.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:25:19 -07:00
Vance Ingalls 0ce9dd8496 feat: add HyperFrames skills for AI coding tools (#46)
## Summary

- 2 skills in `skills/` directory (tool-agnostic, installed via `hyperframes skills`)
  - **compose-video** (112 lines): Core HyperFrames composition authoring contract — data attributes, timeline, video/audio rules, GSAP constraints, editing consistency
    - `patterns.md`: PiP, title card, slideshow examples (loaded on demand)
  - **captions** (138 lines): Tone-adaptive caption styling from transcript analysis — per-word styling, script-to-style mapping, whisper.cpp format reference

## Test plan

- [ ] Skills load in Claude Code
- [ ] compose-video under 500 lines, no `assets/` or `fonts/` references
- [ ] Captions skill triggers on tone detection keywords

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-26 10:05:05 -07:00
Vance IngallsandClaude Opus 4.6 61c5257402 fix(ci): update publish workflow to use bun install (#36)
* fix(ci): update publish workflow to use bun install

pnpm-lock.yaml was removed in the bun migration but publish.yml
still referenced it. Use bun for install/build, keep pnpm for
publish (publishConfig overrides + --provenance).

* docs: update stale pnpm references to bun across docs and scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 08:46:58 -07:00
Vance Ingalls 94e25443ae build: migrate from pnpm to bun as package manager (#28)
## Summary
- Replace pnpm with bun for dependency installation, script running, and ad-hoc execution
- Keep pnpm for publish workflow only (`publishConfig` overrides + `--provenance`)
- `bun install` replaces `pnpm install` (~4-5x faster cold installs)
- `bun run` replaces `pnpm run` (~28x less startup overhead)
- `bunx` replaces `npx` in lefthook hooks
- CI workflows updated (`oven-sh/setup-bun@v2` + `actions/setup-node@v4`)
- `pnpm-lock.yaml` removed, `bun.lock` generated
- `pnpm-workspace.yaml` kept for publish compatibility
- CLI source code (`packages/cli/src/`) unchanged — shipped to end users who may not have bun

Part 5/5 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] `bun run lint` — 0 errors
- [x] `bun run format:check` — all files pass
- [x] `bun run build` — all 5 packages build
- [x] 330 core tests pass
- [x] 18 engine tests pass
- [x] `publish.yml` unchanged (pnpm stays for npm publishing)
- [x] No `bunx`/`bun run` references in shipped source code (`packages/*/src/`)
2026-03-23 19:50:57 -07:00
Vance Ingalls a6c5e08abb ci: add lint and format check job, update CONTRIBUTING.md (#26)
## Summary
- Add `lint-and-format` job to CI workflow (`pnpm lint` + `pnpm format:check`)
- Fix lefthook commands to use `npx` prefix (bare binaries not on PATH)
- Update CONTRIBUTING.md: document new tooling, commit conventions, and lefthook hooks

Part 4/4 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] CI job matches existing pattern (pnpm 10, node 22, frozen lockfile)
- [x] Git hooks work end-to-end (bad messages rejected, valid commits pass)
- [x] CONTRIBUTING.md accurately reflects new tooling
2026-03-23 18:49:06 -07:00
Vance Ingalls 20be2ea1c2 style: apply oxfmt baseline formatting across all source files (#25)
## Summary
- Run `oxfmt .` across the entire codebase to establish formatted baseline
- 299 files changed — mechanical formatting only, no logic changes
- Double quotes, semicolons, 2-space indent, trailing commas, 100 print width

Part 3/4 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] `pnpm format:check` — all 426 files pass
- [x] `pnpm -r typecheck` — all packages pass
- [x] `pnpm build` — all packages build
- [x] All 348 tests pass
2026-03-23 17:15:14 -07:00
Vance Ingalls 323ff8f860 fix: resolve oxlint errors across codebase (#24)
## Summary
- Remove 5 unused `beforeEach` imports from test files
- Remove unused imports (`existsSync`, `TimelineCompositionElement`)
- Remove unused destructured variables (`options`, `width`, `height`, `goldenEl`)
- Remove dead `formatDuration` function
- Fix unused catch parameters (`catch (err)` → `catch`)
- Prefix unused `renderError` state with `_`
- Add `eslint-disable-next-line` for 2 React exhaustive-deps false positives (stable ref + zustand setter)

Part 2/4 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] `pnpm lint` — 0 errors on 193 files
- [x] All 348 tests pass (core + engine)
2026-03-23 16:41:41 -07:00
Vance Ingalls 17e90f0671 build: add oxlint, oxfmt, commitlint, lefthook, knip, and editorconfig configs (#23)
## Summary
- Install oxlint, oxfmt, commitlint, lefthook, knip as dev dependencies
- Add `.oxlintrc.json` (correctness rules + React plugin)
- Add `.oxfmtrc.json` (double quotes, semicolons, 2-space indent, trailing commas)
- Add `commitlint.config.js` (conventional commits)
- Add `lefthook.yml` (pre-commit lint+format, commit-msg commitlint)
- Add `.editorconfig` and `knip.config.ts`
- Add scripts: `pnpm lint`, `pnpm format`, `pnpm format:check`, `pnpm knip`

Part 1/4 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] `pnpm lint` runs (reports pre-existing errors, expected)
- [x] `pnpm format:check` runs (reports pre-existing diffs, expected)
- [x] `commitlint` validates and rejects messages correctly
- [x] lefthook hooks install via `pnpm run prepare`
- [x] `pnpm knip` runs
2026-03-23 16:05:47 -07:00
Vance IngallsandClaude Opus 4.6 0276255b76 fix: rename root package to avoid shadowing published CLI (#17)
`npx hyperframes init` failed with "could not determine executable to run"
because the monorepo root package.json shared the same name as the published
npm package. npx resolved the local root (which has no bin field) instead of
the published package. Renaming to "hyperframes-monorepo" follows the same
convention as remotion-monorepo.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:47:18 -07:00
Vance IngallsandClaude Opus 4.6 1a713b37b9 fix: change workspace:* to workspace:^ for npm publish compatibility (#12)
pnpm converts workspace:^ to ^X.Y.Z during publish, but workspace:*
was left unconverted in the registry. This caused install failures
for external consumers.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:15:54 -07:00
Vance IngallsandClaude Opus 4.6 00909c8258 fix: revert parity harness to puppeteer-core with channel: chrome
The puppeteer import caused protocol timeout issues. Revert to
puppeteer-core but add channel: "chrome" to auto-discover the
Chrome binary installed by 'puppeteer browsers install'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:31:33 -07:00
Vance IngallsandClaude Opus 4.6 b4023ae249 fix: update parity fixture to use local runtime path
The fixture HTML had an empty src after the static.heygen.ai URL was
scrubbed. Point to the local hyperframe.runtime.iife.js which will be
copied into the fixtures dir by CI before the test runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:23:52 -07:00
Vance IngallsandClaude Opus 4.6 8866fc5791 fix: use puppeteer instead of puppeteer-core in parity harness
puppeteer-core requires an explicit executablePath or channel.
puppeteer auto-discovers Chrome installed by 'puppeteer browsers install'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:20:11 -07:00
Vance IngallsandClaude Opus 4.6 6ad12df1b6 fix: commit regression test golden baselines
- Update .gitignore to allow packages/producer/tests/*/output/
- Commit compiled.html snapshots and output.mp4 golden baselines
  (MP4s tracked via Git LFS)

These were excluded by the blanket output/ gitignore rule but are
needed for regression tests to pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:56:52 -07:00
Vance IngallsandClaude Opus 4.6 fbfb6c6156 fix: resolve TypeScript strict null errors in producer
- server.ts: default token param to empty string to satisfy string type
- renderOrchestrator.ts: capture fileServer in local const before closure
  to preserve TypeScript null narrowing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:50:19 -07:00
Vance IngallsandClaude Opus 4.6 9f8e5ba5a1 initial code (#2)
* feat: initial code port from hyperframes-internal

Port all OSS-ready packages from the internal monorepo:
- @hyperframes/core — shared types, HTML generation, GSAP utilities, runtime
- @hyperframes/cli — CLI for creating, previewing, and rendering compositions
- @hyperframes/engine — framework-agnostic rendering engine (BeginFrame + FFmpeg)
- @hyperframes/producer — video rendering pipeline (Puppeteer + FFmpeg)
- @hyperframes/ui-player — browser-based video player component
- @hyperframes/studio — composition editor (React frontend + Hono backend)

Includes regression test suite with Docker-based test harness.

All HeyGen-internal references, deployment infrastructure, and
proprietary assets have been removed. Package names migrated
from @app/* to @hyperframes/*.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scrub internal codenames and stale references from OSS port

- Replace static.heygen.ai runtime URLs in test fixtures
- Remove internal CDN publish script (publish-hyperframe-runtime.ts)
- Replace sandbox-studio, sandbox-interceptor, __magicEditRuntime
  with neutral names (studio, hyperframe-runtime, __hyperframeRuntime)
- Fix stale Vault API / localhost references in docs
- Remove broken deprecated_studio link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove remaining internal codenames and stale references

- Delete stale producer README.md and PIPELINE.md (referenced nonexistent files)
- Replace "Cerberus" codename with "HyperFrames" in test design reviews
- Replace magic-edit postMessage identifiers with hf-preview/hf-parent
- Rename debug-magic-edit-timeline.ts to debug-timeline.ts
- Replace "Motion Cut" with "HyperFrames" in Timeline comments
- Fix studio/CLI references to nonexistent archive package
  (use local data/projects/ dir, stub render proxy)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:43:56 -07:00