## 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)
The CLI bundle uses tsup to inline @hyperframes/producer, but the
deterministicFonts module used require.resolve('@fontsource/*/package.json')
at runtime to find woff2 files on disk. When installed via npx, these
@fontsource packages don't exist, causing "Cannot find module" errors.
Replace runtime filesystem lookups with a build-time generator that reads
all @fontsource woff2 files and produces a TypeScript module with base64
data URIs. The generator runs before both producer and CLI builds, making
the bundle fully self-contained with zero @fontsource runtime dependencies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- rewrite workspace protocol dependencies to publish-safe semver ranges before the npm publish workflow runs
- keep workspace protocol references in source manifests for normal monorepo development
- ensure the published `@hyperframes/producer` manifest no longer ships unresolved `workspace:` deps
## Why
The internal repo hit a Docker build failure because the published `@hyperframes/producer` metadata still contained `workspace:^` dependencies for `@hyperframes/core` and `@hyperframes/engine`. `npm install` cannot resolve those outside the monorepo, so the published package itself was the root cause.
## Validation
- `bun install --frozen-lockfile`
- `bun run build:producer`
- `bun run prepare:publish-manifests`
- `npm pack --workspace packages/core`
- `npm pack --workspace packages/engine`
- `npm pack --workspace packages/producer`
- installed the three tarballs together in a clean temp project with `npm install --ignore-scripts`
- extracted the producer tarball and verified its `package.json` contains `^0.1.3` for `@hyperframes/core` and `@hyperframes/engine`, not `workspace:^`
## Release v0.1.3
Bumps all packages to v0.1.3.
### Changes since v0.1.2
- fix(producer): resolve manifest from sibling dist/ directory
- fix(producer): add margin reset when wrapping HTML fragments
- fix(producer): exclude test files from tsc build
- feat(cli): add opt-out anonymous telemetry via PostHog
- feat: add HyperFrames skills for AI coding tools
- fix(cli): resolve npx hyperframes from inside monorepo
- fix(ci): publish workflow fixes (remove provenance, idempotent steps, remove prepublishOnly)
After merging, the release tag is created automatically, which triggers npm publish.
* 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>
* 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>
* 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>
* 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>
* 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>
Replace markdown issue templates with GitHub YAML form templates for
structured bug reports and feature requests. Add config.yml to disable
blank issues and enforce template usage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- When `index.html` is a bare fragment (just a `<div>` without `<!DOCTYPE>`), `ensureFullDocument()` wraps it in a minimal HTML document
- The wrapper was missing a CSS reset, so Chrome applied its default `body { margin: 8px }` — creating visible white lines at the top and left edges of rendered video
- Added `* { margin:0; padding:0; box-sizing:border-box }` and `body { overflow:hidden; background:#000 }` to the fragment wrapper
## Test plan
- [x] Render a composition that starts with a bare `<div>` (no `<!DOCTYPE html>`)
- [x] Verify no white lines appear at the edges of the rendered MP4
The producer's tsconfig.json included all src/**/* which caused tsc
to fail on test files that import vitest (not a producer dependency).
Exclude *.test.ts from the declaration build — vitest handles its own
tsconfig for running tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies:
- env var override takes priority
- sibling path is first candidate (catches the npm package bug)
- monorepo-relative fallback works in dev
- path construction invariant holds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The build script already copies hyperframe.manifest.json into the
producer's dist/ directory, but the runtime loader never checked there.
This caused `window.__hf not ready` timeouts when rendering via the
published npm package outside the monorepo, since the only candidate
paths were monorepo-relative.
Add a sibling-directory lookup (same dir as the bundled module) as the
first candidate, so the manifest is found whether running from source,
a bundled dist/, or an npm install.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- flushSync: use detached spawn + unref() instead of execFileSync,
so process.exit() paths don't block up to 5s on slow networks
- showTelemetryNotice: persist notice flag BEFORE printing/tracking,
so users are never tracked without having seen the disclosure
- Config dir: set mode 0o700 on ~/.hyperframes/ directory (was umask default)
- $ip: null comment: clarify this is belt-and-suspenders with server-side discard
- shouldTrack: update comment — phc_ prefix check is a safety net, not dead code
- env.ts: add comment explaining try/catch fail-safe defaults to production
- init.ts: consistently call trackInitTemplate after scaffoldProject in both paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Run oxfmt on cli.ts and client.ts
- Replace literal placeholder comparison with phc_ prefix check
(TS2367: comparing two different string literals has no overlap)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PostHog's $ip: null property tells the server to not associate the
request IP with the event. Combined with the "Discard client IP data"
project setting for server-side enforcement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract shared isDevMode() to utils/env.ts (was duplicated in dev.ts and client.ts)
- Use ui/colors.ts instead of raw ANSI escapes in telemetry notice (respects NO_COLOR)
- Derive known commands from subCommands object instead of maintaining duplicate set
- Skip telemetry on --help/--version and unknown commands
- Gate incrementCommandCount() behind shouldTrack() (no disk writes in CI)
- Add flushSync() for process.exit() paths (beforeExit doesn't fire on explicit exit)
- Remove dead trackBrowserInstall(success) param (failure path never called it)
- Remove redundant isEnabled/anonymousId caching in client.ts (config.ts cache suffices)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add anonymous usage telemetry to help improve the CLI. Uses PostHog's
HTTP batch API directly (zero new dependencies) with a 5-second timeout
and fail-silent behavior — telemetry never breaks the CLI.
What's collected: command names, render performance (duration, fps,
quality), template choices, OS/arch/Node version/CLI version.
What's NOT collected: file paths, project names, video content, or
any personally identifiable information.
Telemetry is:
- Disabled in dev mode (running via tsx)
- Disabled in CI (CI=true) or via HYPERFRAMES_NO_TELEMETRY=1
- Disabled when API key is placeholder (safe to merge before key is set)
- Controllable via `hyperframes telemetry [enable|disable|status]`
- Disclosed on first run with clear opt-out instructions
Config stored at ~/.hyperframes/config.json (0600 permissions).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Already-published versions cause npm to return E403. With
continue-on-error, the workflow skips published packages and
continues to publish the remaining ones. Safe to re-run.
The publish workflow already runs `bun run build` before publishing.
The prepublishOnly scripts tried to run pnpm/bun which may not be
available during `npm publish`. Replace with no-op to prevent failures.
npm provenance requires a public GitHub repo. The repo is currently
set to "internal" visibility, which causes E422 on publish.
Remove --provenance until the repo is made public.
- Remove pnpm/action-setup (no pnpm-lock.yaml exists after bun migration)
- Remove cache: pnpm from setup-node (caused "lockfile not found" error)
- Use bun for install/build, npm for publish (npm comes with node)
- Pass NODE_AUTH_TOKEN per publish step
## What
Enhanced base64 media detection to identify fabricated data and fixed CSS scoping to preserve @import rules.
## Why
The linter was flagging all base64 media as prohibited, but the real issue is fabricated/fake base64 data that won't actually play. Additionally, CSS @import rules were being corrupted during composition scoping, breaking font imports and other external stylesheets.
## How
- Updated base64 media linting to detect fabricated data by checking for repetitive patterns and suspicious characteristics
- Changed error code from `base64_media_prohibited` to `fabricated_inline_media` with severity based on suspicion level
- Fixed `scopeCssToComposition()` to extract @import rules before applying selector scoping, then prepend them to the final output
- Added minimum length threshold (100 chars) for base64 detection to focus on substantial media files
## Test plan
- [x] Added regression test `css-import-scoping` to verify @import rules survive CSS scoping and render correctly
- [x] Updated linter logic to distinguish between legitimate and fabricated base64 media
- [x] Verified CSS scoping preserves @import statements while properly scoping selectors
scopeCssToComposition corrupted @import url() rules because they have
no {} block. The selector regex ([^{}@]+)\{ treated the text after @
as a selector, producing invalid CSS like:
@[data-composition-id="x"] import url('...')
This broke font loading, CSS variable resolution, and all composition
styling in rendered output.
Fix: extract @import rules before running the scoping regex, then
prepend them back unmodified.
Also adds:
- Regression test fixture (css-import-scoping)
- Common-mistakes docs: autoplay/loop, GSAP TextPlugin, sub-composition
positioning
- Format fix for hyperframeLinter.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Adds playback rate and loop support for media elements
Introduces per-element playback rate control and looping functionality for `<video>` and `<audio>` elements in the runtime media system.
### Playback Rate Control
- Reads `defaultPlaybackRate` property from media elements (set via JavaScript)
- Clamped to [0.1, 5] range for safety
- Multiplied with global transport rate during playback
- Adjusts timeline duration calculation: 10s source at 0.5x rate = 20s on timeline
- Defaults to 1.0 when not specified
### Loop Functionality
- Reads native `loop` attribute from media elements
- When enabled, wraps `relTime` using modulo of source duration
- Restarts from `mediaStart` offset when source reaches end
- Works correctly with partial media clips (respects `data-media-start`)
- Defaults to false when not specified
### Implementation Details
- Extends `RuntimeMediaClip` type with `playbackRate`, `loop`, and `sourceDuration` fields
- Updates `refreshRuntimeMediaCache` to parse new properties from DOM elements
- Modifies `syncRuntimeMedia` to apply per-element rates and handle loop wrapping
- Maintains backward compatibility with existing media clips
### Test Coverage
Adds 8 new unit tests covering:
- Playback rate parsing from DOM elements
- Rate clamping to valid ranges
- Duration adjustment calculations
- Combined per-element and global rate application
- Loop wrapping with and without `mediaStart` offsets
- Non-looping behavior verification
- data-playback-rate: per-element slow-mo/fast-forward (0.1-5x range)
Multiplied with global transport rate. Affects timeline duration
calculation when source duration is used as fallback.
- loop: native HTML loop attribute now works correctly in the runtime.
Wraps media playback from mediaStart when source reaches end.
Enables looping short clips over longer durations.
Both follow the existing data-media-start/data-volume pattern.
## Summary
- Adds `editor-agent-prod` regression test fixture for the producer
- Tests preview/render parity for compositions with sub-compositions, GSAP timelines, overlay elements, and `data-end` attributes
- Adds `styles-g` shard to regression CI workflow to run the new test
- Creates comprehensive Basel Form-styled video composition with intro/outro cards, reveals, persistent overlays, and captions
- Includes biomorphic SVG assets and Inter font integration for typographic consistency
- Tests complex animation timing with staggered entrances, crossfades, and synchronized A-roll movements
## Test plan
- [x] Regression CI passes for editor-agent-prod shard
- [x] Existing regression tests unaffected
- [x] Preview/render output matches expected visual and timing specifications
* 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>
- Lint (oxlint) only runs when code changes are detected
- Format (oxfmt) runs on all PRs including docs-only changes
- Update path filter: pnpm-lock.yaml → bun.lock
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add READMEs for all 5 packages (core, engine, producer, cli, studio)
with install, overview, basic usage, and links to full docs
- Rewrite core README from internal doc to OSS-facing format
- Polish root README: add badges, packages table, docs link, requirements
- Add AI usage policy and BDFL governance statement to CONTRIBUTING.md
- Genericize license references (pending final license decision)
- Docs URL set to hyperframes.heygen.com
Addresses VA-850.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## What
Enhanced the hyperframe linter with new media validation rules and improved error detection for critical HTML issues.
## Why
The linter needed to catch more critical HTML errors that cause compositions to fail at render time, including self-closing media tags, placeholder URLs, and fabricated base64 data. Additionally, the media URL validation needed better concurrency control and more accurate error reporting.
## How
**Linter improvements:**
- Added detection for self-closing `<audio/>` and `<video/>` tags that cause rendering issues
- Added validation for placeholder media URLs ([placehold.co](http://placehold.co), [example.com](http://example.com), etc.) that return 404 errors
- Enhanced fabricated base64 media detection with severity levels (error for suspicious patterns, warning for others)
- Renamed `suspicious_global_gsap_selector` to `unscoped_gsap_selector` for clarity
- Improved error messages and fix hints to be more actionable
**Media URL validation enhancements:**
- Added concurrency control to `lintMediaUrls()` with configurable batch processing (default 15 parallel requests)
- Improved error handling and timeout management for URL accessibility checks
- Enhanced error messages to focus on URL replacement rather than tool-specific suggestions
**Runtime improvements:**
- Added null checks in picker module to prevent errors with missing DOM nodes and attributes
- Added TypeScript configuration for the runtime module with strict type checking
**Configuration cleanup:**
- Removed unused render seek configuration options from engine config
- Updated file server to support both inline scripts and external script URLs in head injection
- Fixed runtime script marker references for proper script stripping
## Test plan
- [x] Unit tests added for new linter rules (self-closing media, placeholder URLs, fabricated base64)
- [x] Test coverage for renamed linter code verification
- [x] Validation of fabricated vs non-suspicious base64 media detection
- [x] Manual testing of media URL accessibility checking with concurrency limits
- [x] Testing of picker module null safety improvements