580 Commits
Author SHA1 Message Date
Miguel ÁngelandClaude Opus 4.6 d52ea88df5 fix(cli): make blank template a full HTML document
The blank template was a bare `<div>` fragment without `<!DOCTYPE>`,
`<head>`, or `<body>`. This caused:
- Blank preview (bundler/runtime can't initialize from a fragment)
- "Failed to run lint" (parsing errors on the malformed document)

All other templates (swiss-grid, vignelli, warm-grain, play-mode) are
proper HTML documents — blank was the only outlier.

Also improves lint error reporting to show the actual error message
instead of the generic "Failed to run lint."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:50:53 -04:00
JamesandClaude Opus 4.6 89beb95a87 fix(cli): exit on Ctrl+C during hyperframes dev
The SIGINT handlers that were meant to do graceful cleanup (close server,
remove symlinks) override Node's default exit behavior. If the cleanup
hangs (e.g. server.close() blocked by open connections), the process is
stuck and Ctrl+C does nothing.

Fix: don't intercept SIGINT at all. Node's default behavior exits the
process immediately on Ctrl+C. The OS reclaims the port and file handles.
Use process.on("exit") for best-effort symlink cleanup instead.

Also fixes running the CLI via `tsx` in dev mode — __CLI_VERSION__ is a
tsup build-time define that crashes at runtime without a fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 06:35:41 +00:00
github-actions[bot] 55ff721552 chore: release v0.1.6 2026-03-27 06:04:18 +00:00
James Russo 2915b68633 fix(cli): handle port collisions in dev server with auto-increment (#85)
## Summary
- Fix silent failure when `hyperframes dev` port (default 3002) is already in use (e.g., by Cursor IDE)
- Replace TOCTOU-prone `isPortAvailable()` probe with `serveWithPortFallback()` that binds the real server directly
- Auto-increment to next available port with a visible yellow warning, or show a clear error when all ports (range of 10) are exhausted

## What changed
The old approach used a throwaway `net.createServer()` to test port availability, closed it, then opened the real Hono server — a classic TOCTOU race. The fallback also silently returned the original port when all 10 were taken.

Now we use `createAdaptorServer()` (creates the Hono HTTP server without binding) and manually call `.listen(port)`, catching `EADDRINUSE` to try the next port. This eliminates the race entirely.

**Before:** `hyperframes dev` says "Studio running at http://localhost:3002" even when port 3002 belongs to another process.

**After:**
- Port available: works as before
- Port taken: `Port 3002 is in use, using 3003 instead` (yellow warning)
- All ports taken: `Ports 3002–3011 are all in use. Use --port to specify a different port.` (error + exit)

## Testing
- Verified TypeScript compiles cleanly (`tsc --noEmit`)
- All pre-commit hooks (lint, format, commitlint) pass
- Manual test: run another server on 3002, then `hyperframes dev` → confirms auto-increment message appears

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-26 23:02:50 -07:00
JamesandClaude Opus 4.6 f3964f3e45 fix(cli): handle port collisions in dev server with auto-increment
The embedded dev server (hyperframes dev) silently reported success even
when the requested port was already in use (e.g., by Cursor IDE on 3002).
This happened because the old isPortAvailable() probe had a TOCTOU race
and findAvailablePort() silently fell back to the original port on exhaustion.

Replace with serveWithPortFallback() that binds the real Hono server
directly via createAdaptorServer + manual listen(), retrying on EADDRINUSE.
Shows a yellow warning when auto-incrementing and a clear error when all
ports are exhausted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 05:15:30 +00:00
James Russo e0ee983e19 Merge pull request #81 from heygen-com/feat/webm-transparency
feat(render): WebM output with VP9 alpha transparency
2026-03-26 22:07:57 -07:00
JamesandClaude Opus 4.6 83d11ac595 refactor: simplify review fixes for WebM PR
- Use static import for copyFileSync (was unnecessary dynamic import)
- Shallow-copy config before mutating forceScreenshot (prevents
  caller-provided config from being permanently modified)
- Consolidate isWebm/isWebmRender/outputFormat into single early
  declaration in renderOrchestrator
- Fix debug output extension for WebM (was hardcoded .mp4)
- Log unexpected audio extraction errors instead of silently swallowing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 03:40:35 +00:00
Miguel Ángel dc0d69a21b feat(producer): support entryFile for rendering individual compositions (#42)
## Changes

- Add optional `entryFile` parameter to render API endpoints (`/v1/render` and `/v1/render-stream`)
- Enable rendering individual sub-compositions by extracting them from index.html context when the entry file is a `<template>` wrapper
- Change base64 audio/video linting from detecting "fabricated" media to prohibiting all inline base64 media
- Add manifest path resolution for bundled producer deployments

## API Changes

- `RenderConfig` — new optional `entryFile` field for specifying HTML file to render
- `server.ts` — parses `entryFile` from request body, validates file exists in project directory
- `executeRenderJob` — uses `entryFile` instead of hardcoded `"index.html"`

## Template Extraction

- `extractStandaloneEntryFromIndex` — extracts sub-composition hosts from index.html and creates standalone render context
- Handles `<template>` entry files by finding matching `data-composition-src` in index.html and isolating that host
- Resets `data-start` to 0 for standalone rendering

## Linting Updates

- Change rule #3.7 from detecting "fabricated" base64 media to prohibiting all inline base64 audio/video
- Lower detection threshold from 100+ to 20+ base64 characters
- All base64 media now triggers error severity with clearer messaging about file size bloat

## Usage

```json
POST /v1/render-stream
{ "projectDir": "/path/to/project", "entryFile": "compositions/intro.html" }
```

Omit `entryFile` for default behavior (renders `index.html`).
2026-03-27 04:28:51 +01:00
James Russo 244a9cbe60 Merge pull request #80 from heygen-com/docs/update-for-recent-features
docs: update CLI docs for dev server, version checks, and agent output
2026-03-26 20:13:15 -07:00
JamesandClaude Opus 4.6 684b103ba4 docs: add WebM transparency docs to engine, producer, and CLI examples
- Engine: document getEncoderPreset() for MP4/WebM, VP9 alpha flags,
  Opus audio in mux step
- Producer: document format field in RenderConfig, WebM usage example,
  pipeline steps updated for WebM
- CLI: add render command examples in --help (including WebM overlay)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:45:09 +00:00
JamesandClaude Opus 4.6 1ac9d27b45 docs: add WebM transparency documentation and examples
Document the --format webm flag, VP9 alpha output, overlay workflow
with FFmpeg, and transparent background requirement for compositions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:42:49 +00:00
JamesandClaude Opus 4.6 8d47665149 fix(render): update WebM golden baseline with working alpha
The VP9 alpha encoding works correctly — verified by overlaying on a
green background with explicit VP9 decoder. FFmpeg's default decoder
doesn't expose VP9 alpha through ffprobe, but browsers and VP9-aware
decoders read it correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:42:16 +00:00
JamesandClaude Opus 4.6 efc7b82755 fix(render): fix WebM transparency — use screenshot mode + proper CDP setup
- Force screenshot capture mode for WebM (beginFrame doesn't support
  alpha channel in chrome-headless-shell)
- Set Emulation.setDefaultBackgroundColorOverride once during session
  creation (matching experiment-framework's approach)
- Fix acquireBrowser to pass executable path in screenshot mode on
  Linux (was setting undefined, causing puppeteer-core to fail)
- Add Page.captureScreenshot params: fromSurface, captureBeyondViewport,
  optimizeForSpeed (matching experiment-framework)
- Fix regression harness extractMonoPcm16 for videos without audio
- Add getEncoderPreset unit tests
- Add webm-transparency regression test with golden baseline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:35:03 +00:00
James Russo b02017f8e2 Merge pull request #79 from heygen-com/feat/version-check-system
feat(cli): version check system with agent-friendly output
2026-03-26 19:25:39 -07:00
JamesandClaude Opus 4.6 a146c9e527 test(render): add WebM regression test and getEncoderPreset unit tests
- New regression test `webm-transparency`: minimal transparent composition
  rendered to WebM, validates VP9 codec and visual quality at 100
  checkpoints against golden baseline
- Extend regression harness: `renderConfig.format` field ("mp4" | "webm"),
  format-aware output paths and snapshot filenames
- Fix `extractMonoPcm16` to gracefully handle videos without audio
  streams (WebM without audio was throwing instead of returning empty)
- Unit tests for `getEncoderPreset()`: VP9/yuva420p for WebM,
  h264/yuv420p for MP4, preset mapping, quality preservation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:19:43 +00:00
JamesandClaude Opus 4.6 15f6b86ac0 feat(render): add WebM output with VP9 alpha transparency
Support rendering compositions with transparent backgrounds via
`--format webm`. VP9+alpha is the standard format for overlayable
video (captions, lower thirds, overlays).

Changes by layer:
- CLI: `--format mp4|webm` flag on render command
- Producer: threads format through RenderConfig, switches to PNG
  capture and VP9 encoding when webm
- Engine: getEncoderPreset() returns VP9 config with yuva420p;
  transparent page background via CDP when capturing PNG;
  mux uses Opus audio for WebM; VP9 flags from production:
  -row-mt 1, -auto-alt-ref 0, alpha_mode=1 metadata
- Frame capture: Emulation.setDefaultBackgroundColorOverride a=0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:12:39 +00:00
Miguel Ángel 9262d01109 fix(producer): fall back to data-duration when GSAP timeline is empty (#78)
## What

When a composition has an empty GSAP timeline (no animations), `window.__hf.duration` was always 0, causing `hyperframes render` to time out after 45 seconds waiting for `duration > 0`.

## Why

The HF bridge script reads duration from `window.__player.getDuration()`, which returns the GSAP timeline duration. An empty timeline has duration 0. The render engine waits for `window.__hf.duration > 0` to confirm the runtime is ready — so compositions with no animations would always deadlock.

This hits the `--template blank` scaffold immediately: it generates an empty GSAP timeline and relies solely on `data-duration="10"` for composition timing.

## How

One-line change to the bridge script: when `getDuration()` returns 0, fall back to reading `data-duration` from the root `[data-composition-id]` element. This is the same value the static compiler already extracted — so we get a correct duration without any extra browser round-trips.

```js
get duration() {
  var d = p.getDuration();
  return d > 0 ? d : getDeclaredDuration(); // reads data-duration from root element
}
```

## Test plan

- [x] `hyperframes init my-video --template blank && hyperframes render my-video` completes successfully
- [x] Output: `output.mp4` — 10s, 1920×1080, 30fps ✓  
- [x] Compositions with actual GSAP animations unaffected (fallback only triggers when timeline duration is 0)
- [x] Build passes, lint/format clean
2026-03-27 02:56:52 +01:00
James c463f8c699 ci: retrigger checks 2026-03-27 01:51:51 +00:00
JamesandClaude Opus 4.6 31aa45ba3a docs: update CLI docs for dev server, version checks, and --port flag
- Document the three dev server modes (embedded/local studio/monorepo)
- Add --port flag to dev command
- Document _meta envelope on all --json commands
- Document upgrade --check --json for agent consumption
- Document passive update notices and HYPERFRAMES_NO_UPDATE_CHECK
- Update doctor output example with Version check row
- Fix README default port from 3000 to 3002

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:44:28 +00:00
JamesandClaude Opus 4.6 cd15c68a2e feat(cli): add version check system with agent-friendly output
- New `updateCheck.ts` utility: cached npm registry check (24h TTL),
  sync `getUpdateMeta()` for _meta envelope, `printUpdateNotice()` for
  passive stderr banner
- `upgrade --check --json`: machine-readable version check for AI agents
  Returns { current, latest, updateAvailable }
- `_meta` envelope on all --json commands (info, lint, benchmark,
  compositions): includes version, latestVersion, updateAvailable
- `doctor` shows version check as first row
- Passive update notice on stderr after command completes (skipped in
  CI, non-TTY, --json, --quiet)
- Background check fires on startup (non-blocking, populates cache)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:41:34 +00:00
github-actions[bot]andgithub-actions[bot] ffd0fb678f chore: release v0.1.5 (#77)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
v0.1.5
2026-03-27 01:50:05 +01:00
James Russo 101ff633ca Merge pull request #76 from heygen-com/fix/cli-version-port-docker-hint
fix(cli): inject version from package.json, fix docker hint, add --port to dev
2026-03-26 17:46:44 -07:00
Miguel ÁngelandClaude Sonnet 4.6 9d2362990b fix(cli): inject version from package.json, fix docker hint, add --port to dev
- version.ts: replace hardcoded "0.1.0" with __CLI_VERSION__ injected by
  tsup at build time from package.json — fixes version mismatch where
  `hyperframes --version` reported 0.1.0 while package was 0.1.4
- tsup.config.ts: add define.__CLI_VERSION__ using package.json version
- render.ts: renderDocker error handler showed "Try --docker" even when
  already using --docker — changed to "Check Docker is running: docker info"
- dev.ts: add missing --port arg to embedded mode; findAvailablePort now
  starts from the user-supplied port instead of hardcoded 3002

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:45:36 +00:00
James Russo fd9d56f7e9 Merge pull request #75 from heygen-com/feat/embedded-dev-server
feat(cli): implement embedded dev server for hyperframes dev
2026-03-26 17:28:15 -07:00
JamesandClaude Opus 4.6 c34cbedbc7 fix(cli): resolve typecheck errors in studioServer
Add nullish coalescing for regex match groups that TypeScript flags as
possibly undefined.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:56:14 +00:00
JamesandClaude Opus 4.6 80cc643ac5 feat(cli): add render endpoint to embedded studio server
Wire up the "Export MP4" button in the studio UI. The render runs
async in the same process using @hyperframes/producer's executeRenderJob,
with SSE progress streaming and MP4 download on completion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:53:29 +00:00
JamesandClaude Opus 4.6 5ace89d634 feat(cli): implement embedded dev server for hyperframes dev
When installed via npx, `hyperframes dev` now starts a standalone
Hono HTTP server that serves the pre-built studio SPA and implements
the project API (file listing, read/write, preview bundling,
sub-composition rendering, runtime serving, SSE file watching).

Three modes are auto-detected:
1. Monorepo dev (running from .ts source) → spawn Vite (existing)
2. Local @hyperframes/studio installed → spawn Vite via package (new)
3. Default → embedded Hono server (new, zero extra deps needed)

Also patches the studio SPA to use EventSource SSE fallback when
Vite HMR is unavailable (production/embedded builds).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:34:00 +00:00
Miguel Ángel e554710646 fix(producer): propagate data-start to inner composition element during compilation (#73)
## Summary
- copy data-start from the host sub-composition node to the inlined inner composition root
- preserve the correct runtime offset lookup for nested compositions after producer compilation
- avoid nested GSAP timelines snapping to their end state when the host starts later than t=0
2026-03-27 00:20:16 +01:00
James Russo 09fdc31e33 Merge pull request #72 from heygen-com/fix/cli-publish-name
fix(ci): rename CLI package to match npm and make publish idempotent
v0.1.4
2026-03-26 15:44:51 -07:00
JamesandClaude Opus 4.6 bac66c2152 fix(ci): keep @hyperframes/cli name, rewrite to hyperframes at publish time
Reverting the package rename — Vance needs @hyperframes/cli for local
dev workflow. Instead, rewrite the name to "hyperframes" in the publish
workflow just before npm publish, so the monorepo name stays intact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:36:06 +00:00
JamesandClaude Opus 4.6 1e2875ab56 chore: update lockfile for CLI package rename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:27:03 +00:00
JamesandClaude Opus 4.6 70e9dfca30 fix(ci): rename CLI package to match npm and make publish idempotent
The CLI is published to npm as unscoped `hyperframes` but the
package.json had `@hyperframes/cli`, causing ENEEDAUTH on publish
(wrong scope for the npm token).

Also replace per-step continue-on-error with a single publish script
that skips already-published versions and fails on real errors, making
re-runs safe.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:25:36 +00:00
James Russo 30f9ec4927 Merge pull request #70 from heygen-com/release/v0.1.4
chore: release v0.1.4
2026-03-26 15:10:58 -07:00
github-actions[bot] 87fc3e3d8f chore: release v0.1.4 2026-03-26 22:09:12 +00:00
James Russo 81e6cb92f0 Merge pull request #69 from heygen-com/fix/embed-fonts-at-build-time
fix(cli): make npx hyperframes render work outside monorepo
2026-03-26 15:08:22 -07:00
JamesandClaude Opus 4.6 e746debb1a fix(ci): generate font data in Docker build instead of committing
The generated font data is a pure function of the generator script +
@fontsource package versions — no reason to store 566KB of base64
blobs in git. Generate it during the Docker test image build instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:34:25 +00:00
JamesandClaude Opus 4.6 879a1a1ae5 fix(ci): commit generated font data for regression tests
The regression CI runs the producer source directly via tsx (not the
bundled CLI), so it needs fontData.generated.ts to exist at import
time. Remove from .gitignore and commit the generated file.

Mark as linguist-generated in .gitattributes so GitHub collapses it
in PR diffs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:22:32 +00:00
JamesandClaude Opus 4.6 c926985b63 fix(cli): copy pre-built runtime artifacts instead of regenerating
The previous approach called loadHyperframeRuntimeSource() to regenerate
the runtime IIFE, but its output lacked the trailing newline present in
the pre-built artifact, causing a SHA256 checksum mismatch with the
manifest. Copy the pre-built files from core/dist directly instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:15:56 +00:00
JamesandClaude Opus 4.6 2280273f79 fix(cli): remove empty src attrs from swiss-grid audio elements
Empty src="" on <audio> elements causes the browser to fetch the
current page URL as the audio source, producing 404 errors in the
console during rendering. These are placeholder sound effect slots
whose src should be set dynamically when audio is configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:06:00 +00:00
JamesandClaude Opus 4.6 e4a145a809 fix(cli): pass resolved browser path to engine for rendering
The CLI's ensureBrowser() finds Chrome via env var, cached download,
or system paths (including /Applications/Google Chrome.app on macOS).
But the result was discarded — the engine's acquireBrowser() only
checked the puppeteer cache for chrome-headless-shell, passing
undefined executablePath on macOS without headless-shell installed.

Bridge the two by setting PRODUCER_HEADLESS_SHELL_PATH from the CLI's
resolved browser path before creating the render job, so the engine's
resolveConfig() picks it up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:05:36 +00:00
JamesandClaude Opus 4.6 68f86f66ac fix(cli): ship runtime manifest alongside CLI bundle
The hyperframeRuntimeLoader uses import.meta.url to resolve
hyperframe.manifest.json as a sibling file. When bundled into the CLI
via tsup, import.meta.url points to dist/cli.js, so the sibling lookup
checks dist/hyperframe.manifest.json — which wasn't being shipped.

Copy the manifest and the canonical-named runtime IIFE into dist/ during
the build-runtime step so the loader's sibling-path resolution works in
published npm packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:02:36 +00: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)
v0.1.3
2026-03-26 14:02:30 -07:00
JamesandClaude Opus 4.6 7e059e15f9 fix(producer): embed font data at build time instead of runtime require.resolve
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>
2026-03-26 20:59:11 +00:00
Miguel Ángel e1c1c6fb30 fix: rewrite workspace deps before npm publish (#67)
## 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:^`
2026-03-26 21:14:39 +01:00
James Russo 4b3c45e3f4 Merge pull request #66 from heygen-com/docs/audit-and-fix-all-documentation
docs: audit and fix all documentation against actual codebase
2026-03-26 12:16:18 -07:00
JamesandClaude Opus 4.6 db892f4e8f docs: audit and fix all documentation against actual codebase
Comprehensive audit of every documentation page against the actual source
code, fixing incorrect APIs, wrong CLI flags, nonexistent templates, and
missing public exports. Also documents the new agent-friendly CLI design.

Key fixes:
- Quickstart: `npx create-hyperframe` → `npx hyperframes init`, Node 20→22
- Templates: replaced nonexistent blank/title-card/video-edit with actual
  templates (blank, warm-grain, play-mode, swiss-grid, vignelli)
- CLI: removed nonexistent short flags (-o/-f/-q/-w), added missing
  commands (browser, docs, telemetry, skills), documented agent-friendly
  non-interactive default and --human-friendly flag
- Producer: replaced nonexistent `render()` API with actual
  `createRenderJob()`/`executeRenderJob()`, added server API docs
- Engine: replaced nonexistent `createEngine()` with actual session-based
  API, added HfProtocol, encoding, streaming, parallel rendering docs
- Core: fixed wrong type names (Composition/Clip→TimelineElement), wrong
  function names (parseHyperframeHtml→parseHtml), documented all 4 entry
  points (main, /lint, /compiler, /runtime)
- Studio: added all missing exports (NLELayout, SourceEditor,
  PropertyPanel, FileTree, StudioApp, hooks, Tailwind preset)
- All pages: --output not -o, Node 22+ not 20+

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:23:32 +00:00
Miguel Ángel 36c91cecbb chore: release v0.1.3 (#60)
## 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.
2026-03-26 19:17:09 +01: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