* refactor: make @hyperframes/lint depend only on parsers, not core
Relocates the leaf utilities lint pulled from core — URL/asset-path helpers,
font aliases, and the slideshow manifest parser — into the standalone
@hyperframes/parsers base, and drops @hyperframes/core from lint's
dependencies. Core keeps back-compat re-export stubs at the old paths, so
producer/studio/cli are unchanged.
Why: lint was the lightweight validator from #1749, but depending on core
transitively pulled studio-server (hono) and bpm-detective — irrelevant to
linting. Now installing @hyperframes/lint pulls only parsers + postcss, and
the core<->lint dependency cycle is gone.
- parsers main entry stays browser-safe (pure utils only); the node:path
asset helpers live behind the new @hyperframes/parsers/asset-paths subpath
- slideshow parser exposed via @hyperframes/parsers/slideshow
* feat(lint): add browser entry; harden CSS url() regex (ReDoS)
@hyperframes/lint/browser — a fully client-side rule engine (lintHyperframeHtml,
lintMediaUrls, shouldBlockRender) with zero node: builtins, so browser-only
editors can validate compositions with no Node.js and no server round-trip.
Closes the browser-validation ask on #1749.
- shouldBlockRender extracted from the fs-bound project.ts into its own pure
module so the browser entry stays node-free
- pure composition primitives (data types, font aliases, URL helper) exposed via
a new recast-free @hyperframes/parsers/composition subpath, so the browser
bundle tree-shakes out the GSAP/recast machinery (verified: esbuild
platform=browser bundles with 0 node builtins)
- lint built with a platform:browser tsup pass — compile-time guarantee the
browser entry never pulls a node builtin
- harden CSS_URL_RE against polynomial ReDoS (CodeQL js/polynomial-redos);
behavior-preserving, verified against existing tests + an old/new parity check
- parsers/lint marked sideEffects:false
New docs pages for the three packages extracted from core (#1755, #1756,
#1757), wired into the Packages nav after @hyperframes/core. Each covers
when-to-use, exports, and API with cross-links. Core's parser/lint sections
now point at the dedicated packages and its Related Packages lists all three.
* docs: list all 19 skills in README + add CLAUDE.md maintenance reminder
Agents discover skills via the README, so silently-out-of-date entries
kill discovery. This change:
- Adds a `## Skills` section to the README listing all 19 skills,
grouped Router / Creation workflows / Domain skills, with a one-line
"use when" blurb for each (sourced from each skill's SKILL.md
frontmatter `description:`).
- Updates the existing CLAUDE.md `## Skills` section to cover all 19
skills (was missing the domain skills, `/media-use`, `/slideshow`,
and `/music-to-video`), mirroring the README's Router / Creation /
Domain grouping.
- Adds a "Skill catalog maintenance" section to CLAUDE.md so future
skill additions / renames update both surfaces and the
`/hyperframes` router skill in lockstep.
Docs-only — no source or test changes.
— Jerrai (https://claude.com/claude-code)
* docs(mintlify): add skills catalog page + extend maintenance reminder
Per follow-up on HF#1722: the Mintlify docs at
hyperframes.heygen.com also need the skills catalog so agent
discoverability is consistent across README and docs site.
- New: docs/guides/skills.mdx (3-group catalog — router / creation
workflows / domain skills — mirrors README structure, sourced from
the same SKILL.md frontmatter)
- Update: docs/quickstart.mdx — completes the workflow-skills list
(was missing /music-to-video, /slideshow, /general-video) and
cross-links the new page
- Update: docs/introduction.mdx — adds a skills-catalog card to the
hero CardGroup and the Next Steps section
- Update: docs/docs.json — adds /guides/skills to the Guides nav
- Update: CLAUDE.md "Skill catalog maintenance" — adds
docs/guides/skills.mdx as the third sync target alongside README
and skills/hyperframes/SKILL.md, and notes the count drift surface
(README + CLAUDE.md mention "19 AI agent skills" in their intros;
the new docs page deliberately omits a count to avoid drift)
Docs-only — no source, packages, or test changes.
— Jerrai (https://claude.com/claude-code)
* docs(readme): oxfmt table column-alignment fix
Pure whitespace — oxfmt's table-column alignment caught README.md
after the previous commit. No content change.
— Jerrai (https://claude.com/claude-code)
* docs(skills): reconcile install-command contract across README/CLAUDE/Mintlify
Per Magi's review on HF#1722: the new README/CLAUDE/skills.mdx pages
described bare `npx skills add heygen-com/hyperframes` as installing all
19 skills, while existing quickstart/prompting docs said the bare command
opens a picker and `--all` installs everything.
Verified actual CLI behavior with `npx skills add --help` and a clean-dir
run: bare command opens an interactive picker for human users (the CLI
help documents `--all` as "Shorthand for --skill '*' --agent '*' -y" —
the picker-skipping form). Inside an agent the bare command auto-installs
all non-interactively, but that's an agent-detection UX shortcut, not the
public contract — documenting the picker is correct for human readers.
All touched docs now use the consistent contract:
- `npx skills add heygen-com/hyperframes` -> interactive picker
- `npx skills add heygen-com/hyperframes --all` -> install all 19 (skips picker)
- `npx skills add heygen-com/hyperframes --skill <name>` -> install just one
Files updated: README.md, CLAUDE.md, docs/guides/skills.mdx. Existing
docs/quickstart.mdx and docs/guides/prompting.mdx already used this
contract and are unchanged.
— Jerrai (https://claude.com/claude-code)
Add formatSrt/formatVtt/wordsToCues to normalize.ts (the inverse of the
existing parseSrt/parseVtt) and a 'hyperframes transcribe <transcript> --to
srt|vtt' export mode. Word-level whisper transcripts group into cues on
sentence boundaries with maxChars/maxGap guards; imported phrase-level cues
pass through unchanged. Default transcribe behavior is unchanged and no new
dependencies are added.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
* fix: handle caption skin workflow
* docs(skills): simplify the finalize step across video workflows
- Drop --strict-layout; all skills use plain `hyperframes inspect`
- Add the caption text_box_overflow false-positive note to faceless-explainer
- On a failed check, the orchestrator makes the cheapest safe edit itself
(no worker re-dispatch / Step 3 backtrack language)
- Snapshot: glance at the stitched contact-sheet.jpg and move on
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(auth): onboarding-first `auth status` + shared TTS/BGM preflight
When no HeyGen credential is configured, `hyperframes auth status` now
prints registration-first guidance instead of a terse error:
- Interactive / agent-driven sessions get sign-in guidance led by
`hyperframes auth login` (the OAuth step that also creates an account
and is shared with heygen-cli), and never steer users to a per-repo
`.env`. CI / non-interactive runs get a terse note. Exit 1 is kept so
the "am I logged in?" `$?` contract still holds.
- It probes which local engine voice/music will fall back to (Kokoro /
MusicGen, mirroring the skill resolution order) and whether their
Python deps are installed, with a pip hint when missing. `--json`
exposes `recommended_action` + `offline_engines` for skills to branch.
- `doctor` gains matching "TTS (Kokoro)" / "BGM (MusicGen)" checks via
the same shared probe (findPython/hasPythonModules extracted to
tts/python.ts; provider resolution in audio/providers.ts).
Every TTS/BGM workflow now relays this at Step 0 (setup) instead of
improvising its own "missing key" prompt: pr-to-video, product-launch-
video, faceless-explainer, website-to-video, music-to-video. The
canonical behavior + key-priority table live once in hyperframes-media.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pr-to-video): scale recommended video length to PR change size
Step 0 led with a fixed ~60-90s length default. Now the recommended
length is derived from the PR's diff stat (lines added+deleted, nudged
by file count) on a tier scale (trivial ~20-40s → large ~110-180s, hard
cap ~3 min), reusing the same PR peek already done to infer the angle.
The agent states the basis when proposing it, and a huge PR with one
headline change still stays tight. User can always override.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(captions): embed brand fonts whose files use separators
brandFontFaces() matched font files by stripping only whitespace, so an
underscore/hyphen-named file (TT_Norms_Pro_Bold.woff2) never matched the
family key "ttnormspro" — captions shipped with no @font-face, the
font_family_without_font_face bug. Now both family and filename normalize
away all non-alphanumerics; families match longest-key-first so a parent
family can't swallow a more specific one's files (TT Norms Pro vs Mono);
each file is claimed once; "demibold" ranks before "bold"; and when
nothing matches it warns loudly at build time instead of returning "".
Also: parseFonts() falls back to h1/h2/title/hero display roles, and the
frame-worker + caption authoring docs spell out that only shipped font
files render — no system CJK/Devanagari families on the headless renderer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(hyperframes-media): enforce sign-in preflight on standalone BGM/TTS
A one-off "generate me a BGM" request went straight to local MusicGen
without recommending sign-in: bgm.md/tts.md framed the no-credential path
as an automatic fallback, so the generation path bypassed the Preflight
stop, and the preflight used a bare `hyperframes auth status` that isn't
on PATH in a fresh `npx skills` project.
- Preflight now applies to one-off generation as well as workflows, uses
`npx hyperframes auth status`, and says: if the CLI can't run, still
recommend signing in and STOP — never treat "no credential" as a silent
green light for local generation.
- bgm.md and tts.md point at the Preflight before generating, reframing
local generation as the fallback the user opts into, not a default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(auth): add Authentication & API keys guide
Document signing in, the keys each capability (voice, music, capture)
uses, their resolution priority, and the fully local fallback. Add the
guide to the nav and cross-link it from the cloud deploy note and the
CLI env-var reference.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(lint): strip HTML comments in a fixpoint loop (CodeQL)
Single-pass <!-- --> removal can re-form a complete comment from
adjacent markers (e.g. `<<!-- -->!-- ... -->`), letting a decoy
<template> survive and hijack the template-boundary match. Loop to a
fixpoint, mirroring the captions.mjs precedent; add a regression test
that fails on single-pass (2 root findings) and passes on the loop.
Also wrap the build-frame.mjs node:fs imports to satisfy oxfmt — the
new copyFileSync import pushed the line past the width limit, which
was the sole cause of the Format / Preflight CI failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(lint): strip HTML comments with a linear scan (CodeQL ReDoS)
The fixpoint loop still ran a /<!--[\s\S]*?-->/ regex per pass, which
backtracks O(n^2) on inputs with many unterminated "<!--" — CodeQL
js/polynomial-redos (high). Looping the same regex (the prescribed
fix) never addressed this; only the regex itself does.
Replace it with an indexOf-based linear strip in utils.ts
(stripHtmlComments), kept in a fixpoint loop so markers that re-form
when a comment is removed are still stripped. 200k unterminated
"<!--" now strips in ~3ms instead of quadratic time; behavior is
otherwise unchanged — unterminated comments are kept verbatim, as the
old regex left them. The re-forming regression test still guards it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(auth): make TTS/BGM sign-in guidance accurate and runnable
From team review of the not-signed-in onboarding:
- OAuth is a `hyperframes auth login` feature only. The separate `heygen`
CLI is API-key-only — `heygen auth login` stores a pasted key, it is not
OAuth and does not create an account. Stop presenting the two CLIs as the
same OAuth/sign-up step.
- Use `npx hyperframes` in every imperative and runtime hint. Bare
`hyperframes` is not on PATH on a fresh machine (command not found); only
`npx hyperframes` is guaranteed. Also updates the JSON recommended_action.
- Drop `heygen auth login` from the terminal/skill onboarding: it needs its
own install and there is no `npx heygen`, so it was a command-not-found
trap. The shared-credential fact stays in the reference docs.
Covers the `auth status` guidance + tests, the Authentication docs, the
shared hyperframes-media preflight (SKILL, requirements, tts, error hints),
and the `npx hyperframes auth status` preflight in every TTS/BGM workflow
(pr-to-video, product-launch-video, faceless-explainer, website-to-video,
music-to-video).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`hyperframes capture <url>` (no -o) used to dump into `./captures/<hostname>/`,
which buries the project two levels deep and silently merges re-runs into the
previous dir — file-by-file, so leftover screenshots / assets from the prior
run stay mixed in and any later `glob` sees both.
Switch the default to `./capture/`. When it already exists, auto-suffix to
`./capture-2/`, `./capture-3/`, … (up to -99). Each capture is its own clean
directory — no crud, no friction, no clobber. The CLI prints a one-line note
when the suffix kicks in so the user sees which dir actually got written.
Explicit `-o <name>` is unaffected (still overwrite-tolerant).
* docs(cloud): add managed cloud rendering guide + fix flag reference
Add a dedicated guide for the managed `hyperframes cloud render` path
(HeyGen-hosted, zero-infra) at docs/deploy/cloud.mdx, covering auth/setup,
the zip→upload→render→download flow, templates via --variables, webhooks /
fire-and-forget, render management, and idempotent retries. Register it at
the top of the Deploy nav group and link it from the local Rendering guide.
Also fix a stale flag reference in the CLI docs: the `cloud render`
`--resolution` row listed the local-render presets (landscape/portrait/...)
but the cloud command only accepts `1080p`/`4k`, and `--aspect-ratio` was
missing. Verified against `hyperframes cloud render --help`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(cloud): correct aspect-ratio wording and flow-diagram status
Two accuracy fixes from review:
- `--aspect-ratio` is only auto-detected for a local project dir; for
`--asset-id`/`--url` there is no local composition, so detection is
skipped and the server defaults to 16:9. Reword both the guide and the
CLI-reference rows to say so.
- The flow diagram showed status `done`, which is not a real value
(HyperframesRenderStatus is queued | rendering | completed | failed).
Use `completed` and re-align the box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(core): add param-substitution utility for GSAP timeline inlining
U1: clone + shadow-aware identifier substitution over acorn ESTree, plus
provenance tagging and a GsapProvenance type. Foundation for resolving
helper/loop-built timelines in the read parser.
* feat(core): inline helper-built and bounded-loop GSAP timelines
U2: expansion pre-pass that rewrites the analysis AST so a helper called N
times, a literal-bounds for-loop, a for-of, or a forEach over an inline array
each become concrete per-call/per-iteration tl.* statements with substituted
positions and provenance tags. Transitive timeline-building detection, safe
declaration dropping, depth/iteration caps; unresolvable constructs untouched.
* feat(core): resolve computed GSAP timelines in the read parser
U3: parseGsapScriptAcorn runs the inlining pre-pass before analysis, so
helper-built and bounded-loop timelines resolve at true positions with
motionPath arcs recognized; each tween carries provenance. Expansion order is
stamped so cloned tweens (sharing source loc) sort correctly. Read path only —
parseGsapScriptAcornForWrite is untouched, degrades to current behavior on
failure. The add-to-basket addCycle case now yields 7 resolved animations.
* feat(studio): runtime-authoritative keyframes for dynamic timelines
Phase 2 (U4-U6): the live-runtime scanner returns tween-relative keyframes
with per-tween timing and converts them to clip-relative when given clip dims,
fixing the timeline-vs-clip-relative bug; it extracts motionPath into arcPath
(shared buildArcPath) so the Arc Motion panel activates for data-driven arcs;
the cache leaves statically-unresolvable tweens to the runtime scan. Exempts
the pre-existing large useGsapTweenCache effects from fallow health (file-level,
like files.ts) rather than suppression comments.
* feat(studio): surface keyframe editability from provenance
U9: editabilityForProvenance(provenance) -> direct|unroll|override (core,
re-exported from the acorn subpath). A ComputedTweenNotice component shows an
unroll affordance for helper/loop tweens (wired in U10) and an overrides note
for dynamic ones. Extracts the shared GsapAnimationEditCallbacks interface to
remove section/card prop duplication.
* feat(core): lint understands computed timelines (acorn parser)
U7: the GSAP lint rule now loads parseGsapScriptAcorn (which inlines helpers
and bounded loops) instead of the recast parser, so overlapping_gsap_tweens and
related findings reflect true resolved positions for computed timelines — and
keeps recast out of the lint graph entirely. Literal compositions are
unchanged (parity), all 182 lint tests pass.
* docs: document the computed-timeline keyframe editing model
U8: keyframes.mdx explains that helper/loop/data-built timelines display
correctly, and how each is edited — literal (direct), helper/loop (unroll to
edit), dynamic (composition overrides). Nothing is permanently locked.
* feat: unroll computed timelines into literal tweens (U10)
Adds unrollComputedTimeline (core): serializes a parsed timeline's resolved
animations back to literal tl.* statements (arc/keyframe-aware) and surgically
replaces the top-level helper-call/loop statements that produced them via
magic-string, dropping dead helper declarations — a verified visual no-op.
Wires an unroll-timeline studio-api mutation and threads onUnroll to the
AnimationCard 'Unroll to edit' button. Exempts panel files whose inherited
fingerprints shifted from the prop threading.
* feat(runtime): declarative keyframe override layer for dynamic tweens (U11)
Adds applyKeyframeOverrides: fetches a gsap-overrides.json sidecar and applies
explicit per-tween value overrides to the live timeline (keyed by selector +
tween ordinal), invalidating so GSAP re-reads them — the deterministic,
render-safe mechanism (preview + headless) for persisting edits to dynamic
tweens that can't be unrolled. Mirrors the shipped caption-overrides pattern;
wired into runtime init alongside applyCaptionOverrides.
* refactor: drop the keyframe override layer; rely on unroll + source
Removes the gsap-overrides.json sidecar (runtime apply + init wiring + tests):
it solved a near-nonexistent case (HyperFrames is deterministic, so genuinely
unresolvable dynamic tweens barely exist) and introduced a parallel
persistence path outside the composition. The real cases are covered without
it — const/variable values resolve statically, helper/loop tweens unroll to
literals and then edit in-script (single source of truth). Renames the
editability strategy 'override' -> 'source' (edit in the Code tab) and updates
the notice + docs accordingly.
* fix(studio): drag outside tween range creates new keyframe, picks nearest tween
Fixes the GSAP drag intercept to pick the position tween closest to the
playhead (not the one with the most keyframes), and when dragging outside all
tweens' ranges, creates a brand-new keyframed tween instead of destructively
extending/replacing the nearest one. Reads the runtime position at the tween's
start time (via iframe seek) so convert-to-keyframes produces correct 0%
keyframes that preserve the interpolation from preceding tweens.
* fix(studio): drag outside tween range creates new keyframe, picks nearest tween
Also reverts all fallow health.ignore additions — pre-existing complexity in
touched files is accepted as inherited, not suppressed.
Add a "Video Components" overview to the docs site — an entry point to the
50+ catalog blocks and components: what they are, how to install and wire
them, and how to contribute a new one. Wire it into the Guides sidebar.