* feat(cli): billing top-up becomes add-credits, top-up stays as an alias
* fix(cli): keep the skills manifest and core-skills test in step with add-credits
* revert: leave the core-skills test to its own PR
* feat(cli): billing shows the price and pays from the terminal on Enter
* fix(cli): the top-up page opens at the credit packs
* test(cli): billing tests cover the checkout offer with no terminal
* fix(cli): the purchase wait runs while Press Enter is open
reverse/start answers upgrade_url: null when no plan can be bought right
now. The CLI treated that like a missing field and built its own plans
link, so a Free account under gate "block" was sent to a page where it
can't buy. Now null means no link anywhere: the JSON prints
upgrade_url: null and subscribe_command: null (billing subscribe would
open that same plans page), the refusal says a plan can't be purchased
right now instead of naming the subscribe command, and the skill runs
nothing and passes on no link. Only an absent field (an older server)
falls back to the plans page; a string is used as is.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): no auto-update across 0.x minors; install skills at the CLI's release tag
- autoUpdate: before 1.0 a minor bump counts as a major one, so 0.8.x -> 0.9.x
is a notice, not a silent background install. From 1.0 on, the major alone.
- skills: `skills update` / bare `skills` / `init` install from
heygen-com/hyperframes#v<VERSION>, and `skills check` plus the background
freshness nudge read the manifest at the same tag. A build the repo has no
tag for (dev build) falls back to main, as before.
- skills: run the upstream installer as `npx skills@1.7.4` instead of an
unpinned `npx skills`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): keep the comment share of the touched files from rising
Comment ratchet: trim the comments added in the previous commit. No code change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): pass the CLI version into the skills check instead of importing it
scripts/clawhub-unlist-retired.mjs loads skillsManifest.ts from source under
plain node, which cannot resolve a relative `../version.js` import. Callers now
pass `cliVersion` (and skillsSourceRef takes it as an argument), so the module
keeps importing only node builtins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): read skills at the CLI's tag without a probe; pin skills@1.5.13
- A release build reads the manifest at its own tag directly (publish pushes
the tag before npm publish). Only a 404 for that manifest falls back to main;
a timeout, missing git or proxy error now surfaces instead of quietly
comparing against main, so the background nudge can't cache a false
"outdated" count.
- checkSkills returns the ref it read, and skills update installs from that
same ref, so check and install can't disagree within one run.
- Pin skills@1.5.13 (it parses `#<ref>` too) so the pin matches the version
agentDirs.generated.ts and the lock-path notes were checked against.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(cli): split the tag and main manifest fetches
Fallow flagged fetchRemoteManifest's CRAP score (31.6 > 30). The tag fetch
(404 is the only fallback to main) and the SHA-pinned main fetch are now their
own functions. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): never auto-upgrade a Homebrew install
`brew upgrade hyperframes` installs whatever formula the tap holds, so a
background run scheduled for an allowed patch could still cross a 0.x minor.
Homebrew installs now only get the update notice naming `brew upgrade`.
A new test drives the scheduler through the real installer detection and
installer script: no spawn on Homebrew, a versioned npm install for a patch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli,skills): say a step's credits are its total, and keep cost out of retries
An out-of-credits refusal named what the step needs, and an agent read
that as the shortfall. It now says the number is the step's total.
After its last try, an agent told the person what was spent and what
another try would cost. The HeyGen reference now says not to.
* fix(cli): plan avatar credits from the HyperFrames price rows
An avatar's credits came from its old API price, about five times what
the plan charges, so --max-credits refused calls that fit and a top-up
asked for too much. They now use the Avatar IV photo row plus the
general avatar video row, 950 credits a minute.
* fix(cli): pin the avatar price test and keep pricing under the comment share
* fix(cli): plan avatar seconds on the Avatar IV row alone
The general avatar row is only checked at admission and billed on
seconds left after Avatar IV, so adding it to every second made a 15 s
script plan at 238 instead of 200 and a 220 limit refuse a call that fits.
* feat(skills): creator templates on the HyperFrames plan
A Pro account asks for a video in a creator's format and the agent pulls that
template on demand. The packs sit in the private bucket behind the plan-gated
bundle route, so a Free account gets a 402 and the upgrade step. A pull costs
no credits; making the film spends them as Reverse does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): make creator-remix the one template skill
SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): the creator-remix skill and its packs
SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(skills): word creator-remix's sign-in steps the way media-use does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): a creator-remix presenter runs on the plan by default
The person's own HeyGen avatar is recommended, not required: without
one, 小Lin's presenter is made with HeyGen Video 1 from their photo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): pin template packs whose voice bills the plan first
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): offload media named inside template tags
References in a composition's <template> were never rewritten, because the DOM
query stops at the tag and template.content does not serialize, so the file stayed
in the zip. A stem matched as a plain word and a same-named file elsewhere counted
as a mention too. A 5-minute presented film went from 196 MB to 71 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): lipsync, and avatars that speak a finished voice
Video 1 takes audio as a reference and re-paces it, so a shot's lips follow its
own track and drift up to a second from the voice. generate --type lipsync redoes
the mouth to the exact voice. generate --type avatar takes --audio, and --avatar
for the person's own look, so the lips land on the voice from one job.
Also moves the plan rows to the 2026-10-30 price book, estimates a voice's credits
so --max-credits can hold it, logs jobs from a folder with no project, and describes
the resolve flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): presenters go avatar first, and a voice can be stock
The person's own avatar, then Avatar IV from their photo, then Video 1 with
lipsync. With no clone or recording, a stock voice named in the credits. Pins the
five rebuilt packs. The archive test keeps its ../ under GNU tar with -P.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): fix avatar price and sfx provider tests, trim comments
A wav gives the avatar price test an exact length on every platform. The
sfx tests now expect HeyGen first. Comment share is back under the base.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): read a font shorthand whose size nests parentheses
`font: calc(var(--size) * 1px)/1 "X"` cut at the first closing parenthesis, so a
bad family name failed the render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): one ledger row per job, and search skips a template's own pictures
A job done at once logs one row, transcribe logs to the project, and resolve no
longer offers a component's file for a search. --reuse and --provider get help text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): pin the tested template packs, and a board needs no voice
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): read the transcribe ledger row as JSON, for Windows paths
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Big Questions pack without the film length check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): call them style templates, and the docs example pulls a real slug
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the cleaned template packs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): drop the stock-presenter ban and the no-spend-before-storyboard rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Paper Trail and Long Story Short packs without the stock-presenter ban
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): rename creator-remix to style-remix, and the five templates to names that say the look
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): style-remix lists the five templates when none is named
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Finance Explainer pack that varies its motion graphics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): media-use generate --type cutout takes a shot out of its background on the plan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Finance Explainer pack without the provisional variety rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the five style packs with variety rules measured on real films
Animated Science gets a worlds-and-boards budget, 3D True Crime a main board and a
brief corner note, 3D Brand caps by measured rate, Finance and Collage set pieces and
more than one look.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): media-use names the on-plan cutout and lipsync commands, not the heygen CLI ones
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): cutout retries dedupe on request_id, uploads carry their real type; style-remix is public
The background-removal route dedupes on the body's request_id and ignores the Idempotency-Key
header, so a retry after a lost answer started a second job: runJob now also sends the key in the
body for such a route. Uploads typed every non-MP4 file as audio, so WebM and MOV inputs were
refused: they now take their type from the extension. Fails without the fix: the new cutout test
in generate.test.ts sees audio/mpeg for WebM and MOV.
style-remix drops metadata.internal and is listed with the other published skills.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): keep the comment share of the touched files at main's
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: count style-remix in the README and CLAUDE.md skill lists
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): rename style-remix to style-sampling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): diff --offset compares the original at t with yours at t + offset
Every per-frame measure (SSIM per frame and per cell, frames.tsv, cuts,
missing content) reads yours offset seconds later, and a project folder is
captured at the shifted times. The window keeps to where both sides have
frames and says so in warnings; an offset with no overlap, or one that is
not a number, is refused with a fix. Sound keeps its own offset reading,
duration is reported as not measured, and the result echoes offset.
* refactor(cli): trim diff offset comments under the file's comment share
* fix(cli): rebase full-length diff offsets and reject overflow
Keep offset streams on a zero-based clock even when every frame is selected. Cover an untrimmed delayed render and reject non-finite offsets before file or project processing.
— Jerrai
* refactor(cli): remove redundant offset filter comment
— Jerrai
* fix(skills): reverse on Free opens the upgrade page for the person
* docs(skills): reverse on Free opens the upgrade page with a quoted link
* fix(cli): reverse on Free runs billing subscribe credited to the run
* fix(cli): trim the reverse skill and drop a doc comment for lint budgets
* fix(cli): drop --page-motion-blur from cloud render
The flag sent page_motion_blur to the hosted render API, which never
accepted that field and rejects unknown fields. Motion blur is set in
the composition with data-motion-blur, and the runtime turns it off on
its own when it cannot render correctly, so the cloud flag is removed
with its request plumbing, test and docs row.
Local render keeps its own --page-motion-blur flag.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(ci): allow deleting the cloud render page motion blur test
The deletion guard blocks removed files unless they are listed. The
test file only covered the cloud render --page-motion-blur flag that
this branch removes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): prefer OAuth session over HEYGEN_API_KEY
A signed-in `hyperframes auth login` session now wins over an API key in
the environment, so a paid plan on the login is used even when the shell
also exports HEYGEN_API_KEY.
New order: HEYGEN_ACCESS_TOKEN (host app), the stored login (refreshed
when expired), HEYGEN_API_KEY, HYPERFRAMES_API_KEY, the saved API key.
The env key still comes first when HEYGEN_API_URL or HEYGEN_API_BASE
names a host app's gateway, and takes over for the rest of the process
when the login cannot be refreshed. The media-use audio helper follows
the same order.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): fall back to the env key only when HeyGen refuses the refresh
A 503, a network error or an overlapping refresh no longer switches the
process to HEYGEN_API_KEY. The fallback now needs a refused grant or an
expired login with no refresh token. A login another process stored since
is re-read first, and refreshes that overlap in one process share one
request. The media-use helper no longer treats HeyGen's own API base as a
gateway.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): keep the login when the refresh reply can't be read
Only a 400/401 from the token endpoint marks the grant as refused and
switches to the env key. A 200 whose body can't be parsed now throws and
leaves the login in place, so the next call refreshes again.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(cli): audio --read organises the reading by stem
Measures where each stem is heard, leaves the drums' recurring hits to the
music while it plays, names the stem of each remaining effect, writes a
voice-free mix next to the stems, and describes each audible stem and the
whole recording with Gemini.
* feat(cli): audio --read reads stems as vocals, drums, bass and melodic
Piano, guitar and other are mixed into melodic.wav next to the stems; levels,
spans, descriptions, effects and pieces are read per group, and a group heard
for under two seconds or 5% of the recording is left out of the reading.
* feat(cli): audio --read describes the six stems and writes a BGM prompt from them
Stems are read one by one again; a stem heard under two seconds or 5% of the
recording is still left out. Each stem's description gives the times of its
changes and names the effects it hears. The overall and the BGM prompt are
written from those descriptions and the measured spans, not by listening to
the mix again; the BGM prompt orders the arrangement without seconds.
* fix(cli): audio --read writes no BGM prompt for a recording without music
Foley and effects in a rhythm are not music; the reading says so instead of a
prompt that would make a remix generate a track the reference never had.
* fix(cli): audio --read prompts name no example sounds
* fix(cli): describe audio through HeyGen and keep effects measured
* docs(cli): document measured effect events without sound kinds
* fix(cli): clear audio description review checks
Use portable stem path assertions and simplify response validation.
Keep auth-derived URLs out of cache hashes and trim redundant comments.
— Jerrai
---------
Co-authored-by: Xuanru Li <157947275+xuanruli@users.noreply.github.com>
When /v1/hyperframes/reverse/start answers gate "block" for plan "free", `reverse start`
saves the session, then exits non-zero. The error and its `fix` carry the upgrade link that
credits the purchase to this run: the server's `upgrade_url`, else the plans page with
`feature=reverse&rs=<session>`. Pro, unknown plans, `record`, and every error still go on.
The reverse-any-video skill tells the agent to pass that exact link on and stop.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): reverse start notes a reverse run with HeyGen and cloud render carries its session
`npx hyperframes reverse start <video> [--dir <project>]` probes the video (the same JSON
as `measure probe`), then, when signed in, calls POST /v1/hyperframes/reverse/start and saves
the returned session id in the project's hyperframes.json. It never stops the run: signed
out, offline, a 404 from a server without the route, or a malformed answer all continue
with the probe and no session. `cloud render` sends `reverse_session_id` only when the
project holds one, which only a successful /reverse/start writes.
`init` now treats a folder holding only hyperframes.json as empty, so `reverse start --dir x`
followed by `init x` works and keeps the session. The reverse-any-video skill opens with
`reverse start` instead of `auth status` + `measure probe`, with a fallback for older CLIs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): keep the comment share of touched files from rising
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): reverse start keeps unknown-plan sessions, prints the attributed upgrade link, takes only rvs ids
- A `plan: "unknown"` answer is a valid session: saved, `recorded: true`, printed as
`plan: null`; it never blocks and never falls back to the client-side plan read.
- On Free, `reverse.upgrade_url` is printed: the server's link, else the plans page with
`feature=reverse&rs=<session>` (the fields checkout reads). The skill passes that exact
link on instead of `billing subscribe`.
- Session ids must match the render endpoint's pattern (`rvs_<lowercase uuid>`). A saved
value that does not is ignored: not sent on `cloud render`, and `reverse start` opens a
fresh session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): name no CLI version for reverse start's fallback
0.9.4 shipped without `reverse start` and skills go live from main before the CLI release,
so the fallback is described as "a CLI without it" instead of a version number.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(producer): report browser-computed reference backgrounds
Preserve one initial root/page reference across local, streamed and distributed
render results without guessing from film pixels.
— Jerrai
* fix(producer): preserve plan compatibility and optional inspection
Keep reference metadata outside the v2 manifest hash and read it from the
verified execution-plan artifact. Static planning uses a non-gating page
inspection instead of requiring render-worker capabilities.
— Jerrai
* fix(producer): poll readiness independently of animation frames
Use timed polling while the render clock parks animation frames. Cover readiness
that changes after the first check with a short real-browser regression.
— Jerrai
* fix(producer): budget measured reference metadata runtime growth
Producer adds 2,228 compressed bytes over current main. Tests, fixtures and source
maps are already absent from its dist-only tarball. Allow 7,766 bytes of headroom
above the verified 837,234-byte payload while retaining the unpacked limit.
— Jerrai
* fix: cache paths follow HYPERFRAMES_CACHE_DIR
Every place HyperFrames keeps downloaded or built files now takes its
folder from one helper, hyperframesCacheDir() in @hyperframes/core. The
default stays ~/.cache/hyperframes; HYPERFRAMES_CACHE_DIR moves it.
* fix: register the cache-dir subpath for bun and node, and build inspector from core source
* test: the cache-dir override test uses a folder that is absolute on every platform
* fix: the opencv cache follows HYPERFRAMES_CACHE_DIR
* fix(cli): npm installs run in their staging dir, so no cache path reaches a command line
* fix(cli): whisper.cpp is cloned inside its build dir, so no cache path is a git argument
* test(cli): the whisper clone test finds no system whisper on Windows either
* fix(cli): keep browser sign-in open through checkout
The loopback listener timed out after 5 minutes, but plan checkout runs
inside the OAuth flow. A slower purchase ended on ERR_CONNECTION_REFUSED.
Raise the default to 60 minutes, matching pacific's plan-checkout handoff.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): show the sign-in limit and how to recover
The waiting line now states the 60-minute limit and Ctrl+C. After a
timed-out browser login the CLI says to run auth login again.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): drop unverified purchase claim, harden timer test, document limit
Review fixes: the timeout hint no longer says purchases stay on the account
(the CLI cannot observe billing); the no-timer-left test now asserts before
any explicit close() and uses node:http so undici timers cannot pollute the
count; docs mention the 60-minute window and Ctrl+C.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): close the sign-in listener on every path and record Ctrl+C
Ctrl+C is now the advertised exit, but events only flush on exit, which a
default SIGINT skips, so an abandoned login vanished from telemetry. Record
it as aborted and exit 130 through requestCliExit. Close the listener on any
throw, so a failure before the wait can't hold the process for the full
window. Derive the waiting line and timeout message from the listener's own
limit, and name the constant for why it is long, which clears the comment
ratchet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): billing and Desktop handoff read their hosts from settings
* fix(cli): OAuth client and API hosts read from settings
* test(cli): host and client settings tests run the same on any machine
* chore(cli): format and refresh the skills manifest
* chore(cli): trim a comment
* chore(cli): keep the stylesheet as it was
* fix(cli,skills): say a step's credits are its total, and keep cost out of retries
An out-of-credits refusal named what the step needs, and an agent read
that as the shortfall. It now says the number is the step's total.
After its last try, an agent told the person what was spent and what
another try would cost. The HeyGen reference now says not to.
* fix(cli): plan avatar credits from the HyperFrames price rows
An avatar's credits came from its old API price, about five times what
the plan charges, so --max-credits refused calls that fit and a top-up
asked for too much. They now use the Avatar IV photo row plus the
general avatar video row, 950 credits a minute.
* fix(cli): pin the avatar price test and keep pricing under the comment share
* fix(cli): plan avatar seconds on the Avatar IV row alone
The general avatar row is only checked at admission and billed on
seconds left after Avatar IV, so adding it to every second made a 15 s
script plan at 238 instead of 200 and a 220 limit refuse a call that fits.
* fix(cli): the credits link opens the web plan page, which is live
* test(cli): pin the production credits link too
* chore(skills): regenerate the skills manifest after merging main
* docs(cli): keep the person measure's doc comments to four lines
* docs(cli): keep the measure doc comments to four lines
* docs(cli): keep the measure comment lines to 120 characters
---------
Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
* feat(cli): render --start and --end render only that span of the film as its own MP4
The span reuses the chunked render path, so frames are captured at their absolute times and sub-composition timing matches a full render.
* chore(producer): shorten the renderSpan comment
* fix(producer): a cancelled span render stops, and its real-render test runs in the integration lane
renderSpan checks the abort signal before each chunk part and before the stitch. The real-render tests move to their own file listed in the integration lane.
* chore(producer): put the renderSpan doc comment back above renderSpan
* revert: feat(skills): reverse any video (#6754)
Keep the reverse-any-video skill and its CLI billing commands out of main until
web subscriptions are live (/pricing and /account/plan return 404 today).
Re-land together with #6833 once those pages ship.
This reverts commit cb2389cf35.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(scripts): allow the #6754 revert deletions
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(skills): reverse any video on the HyperFrames plan
Reverse rebuilds a video as an editable HyperFrames project and re-shoots its
people with HeyGen. It runs for a person signed in on HyperFrames Pro; Free is
told it is a Pro feature and given the upgrade step.
- skills: reverse-any-video; media-use is the one gate for HeyGen calls
(sign-in, credits, refusals)
- cli: media-use generate (image, video shot, avatar, voice, music) with --plan
pricing and --max-credits; measure, diff and analyze for the rebuild; the
HyperFrames plan in auth status; billing subscribe and top-up wait on the
plans or credits page
- remotion-to-hyperframes compares with `hyperframes diff` instead of its own
shell scripts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(skills): satisfy CI gates for reverse-any-video
- docs: list diff, measure, analyze and billing in "Find your command" in
docs/packages/cli.mdx, each with its own section.
- skills: bring media-use/SKILL.md under its 5120 B budget by pointing setup,
direct-recipe and treatment detail at the references that already hold it;
the sign-in, credits, --max-credits and billing rules stay in SKILL.md.
- comments: register transcript.json and REVERSE.md as project files for the
citation checker, and drop a history note from face.test.ts.
- comments: cut restating prose or move it into names so every changed file is
at or under its comment share. Allowlist the two deleted remotion scripts in
check-no-main-deletions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): terminate measure's ffprobe options with --
probeVideo passed the input path straight after its options, so a path
starting with "-" would be read as a flag; the producer's ffprobe argv
contract test failed on it. runFfmpeg's fixed flags move to a constant
so the contract no longer mistakes that ffmpeg wrapper for a probe.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): split diff's video tests into three files under CI's per-file limit
The file took ~131 s on CI against the flake gate's 120 s, and its heaviest test (four
comparisons) hit the 20 s per-test limit. The video tests now live in three files sharing
diff.test-helpers.ts, and that test is two tests of two comparisons each.
analyze reads each part once and checks the bytes it read, instead of a stat then a read
(CodeQL js/file-system-race).
* fix(skills,cli): one credits rule; ask before dropping a step that won't fit
On dev with a 100-credit limit the agent priced a re-shoot with --plan and dropped it without
asking (J5), and in J6 it told the person a price. SKILL.md's Credits rule now covers both:
--plan is for the agent, and when a step won't fit (a --plan over what is left, or a refused
call) the agent says what doesn't fit and asks before dropping or shrinking it.
setup-providers.md's older confirm-before-paying rule points to it.
--plan now runs the same checks as the real call (checkRequest), so it no longer prices a
request that would be refused. The re-shoot recipe in people.md gains the --prompt it needs.
heygen.md: a sign-in also makes images, and --plan's usd is the API charge rounded to $1/60
while its credits come from the list price.
* fix(cli): auth status drops 'balance unavailable' above the plan's credits; sign-in page title
* test(cli): keep diff's video tests inside the flake gate's limits
Under the gate's CPU contention every diff test needing face detection or
optical flow paid opencv.js's 11 MB download and WebAssembly start-up, and
every diffVideos call wrote four worst-moment pictures (about 28 ffmpeg runs)
no assertion read. Tests ran 21-28 s against the 20 s limit.
- loadOpenCvFirst() stages opencv.js in a beforeAll (from this machine's
cache, a temp copy kept between runs, else the network).
- diffVerdicts() runs diffVideos with worst: 0 for tests that only read
verdicts; the worst-moment tests keep the pictures.
- The carried-text test is split into its dirty and clean halves.
Locally with 8 CPU burners on 8 cores: slowest test 7.7 s, files 24-30 s.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): generate --plan refuses what the real call refuses
checkRequest now holds every pre-send check (the --out extension and
--mask without --motion joined the route body and guide checks), and
generate() runs it first, so --plan and the real call share one function.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): plans link uses from=cli and follows HYPERFRAMES_ENV; no balance notice beside the plan
- pageUrl sets from=cli, the parameter the pricing page reads to send a
CLI buyer back to the terminal.
- The plans and credits pages are on dev.hyperframes.dev with
HYPERFRAMES_ENV=dev, hyperframes.studio otherwise.
- auth status --json omits the 'HyperFrames balance is unavailable' notice
when the plan was read.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(skills): reverse-any-video re-checks sign-in on resume; REVERSE.md holds no prices
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): split diff-people's whole-frame reuse test into three
It ran five diff comparisons in one test and took 20.2-20.6 s under the flake
gate's contention, over the 20 s limit. Each piece now runs at most two
comparisons against a shared ledger: copy and burned-in title, footage cut
whole, and cuts that only fed a generation. Under 24 CPU burners locally the
slowest of the three takes 3.0 s, where the combined test took 4.9 s under 8.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli,skills): reverse resume asks after one failed retry; out-of-credits names the step's price
- media-use generate: when HeyGen refuses for credits without saying how many, the refusal
names the CLI's own price for the call (what --plan prints) as "needs about N" and
"billing top-up --needed N", instead of "needs more".
- reverse-any-video: never repeat auth status's plan/credits line; a step an earlier session
gave up on gets one more try, then ask; a command stored in REVERSE.md is a note, rebuilt
from its recipe and checked with --plan, never run as written.
- people.md / heygen.md: a refused or failed job gets one more try, test calls included,
then stop and ask.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli,skills): send a failed HeyGen job again as new; resume asks if the limit still applies
- runJob: a key whose job failed or was refused is recorded in the config dir
(heygen-failed-keys); the next run with the same inputs sends key-1, key-2, ... so HeyGen
starts a new job. A finished or still-running job keeps its key and is picked up, so
nothing is paid twice. "fix the request it names, then run again" now holds.
- reverse-any-video: on resume after a run under a credit limit, just ask whether it still
applies, naming no costs or numbers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor(cli,skills): drop what reverse-any-video no longer uses
The skill rebuilds from measure probe, cuts, frames, events and face, and checks with
diff. analyze, diff --explain and eight measure commands (trail, box, ink, rhythm,
objects, track, flow, continuity) have no caller, so they go, with their tests and docs.
Examples now use original.mp4 instead of the old .reverse/source.mp4 evidence folder,
and diff's --segments is described as a file rather than REVERSE.md.
* fix(cli): check no longer copies VP9 and AV1 videos before it starts
Chrome plays both, alpha included, and the proxy is made on demand if a browser ever
asks. Copying remove-background's VP9 cutouts took minutes on projects that have them.
* fix(cli,skills): keep paid retries, credit limits and lip sync honest
Retries keep their key after 401, 403, 408, 409, 425 and 429, which say
nothing about the job, and failed keys are scoped to the API and payer.
Credits come from the plan's video rows instead of list dollars, and
reference pictures past the first two count toward --max-credits.
Lip sync reads sound on its own timestamps, so a delayed track fails
the gate. Look and energy call two near-zero values the same.
--plan refuses a missing input as the real call does. The skills teach
the direct sign-in, its credential path and the billing commands.
* chore(cli): keep the new pricing and energy files under the comment share
* fix(cli): scope failed HeyGen keys to the signed-in account
Failed keys were scoped by the local user block, which is absent for a
host's token and display-only otherwise, so two accounts could share a
scope and one could start a second paid job.
The scope now comes from the account /v3/users/me reports for the token
in use. When that cannot be told, no failure is recorded and the key
stays the same.
* fix(cli): fix a job's sender when it starts
Failure scope was looked up again after polling, so a sign-in during a
job could record one account's failure under another and rotate its
finished key. The credential is now read once per job: it signs the POST
and owns any failure, even if someone else signs in meanwhile.
Prefer username to email, since /v3/users/me returns no user id.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: James <james.russo@heygen.com>
* feat(cli): use dedicated HyperFrames OAuth clients
* docs(cli): shorten credential store contract comment
* fix(cli): respect Windows modes in OAuth media test
* fix(cli): keep media workflows on HyperFrames OAuth
* test(cli): scope billing status assertions to wallet rows
* test(cli): align telemetry fixtures and CI auth inputs
* fix(cli): a project .env only sets the CLI's own settings
The .env in the working directory could set any environment variable for every
command except capture. It now sets only the keys the CLI reads from a project:
the HeyGen, ElevenLabs and Figma credentials and the vision keys, models, Vertex
settings and timeout. Other keys are ignored, and one stderr line names them.
* test(cli): the startup test and comment budget follow the single project .env key list
* fix(cli): a project .env can still turn telemetry off
* docs(telemetry): say signed-in usage is linked to your account, not anonymous
After hyperframes auth login the CLI sends a PostHog $identify with the
account email (username as fallback) and merges the install's earlier
anonymous usage; media-use does the same when it finds a signed-in
account. Several places still called all telemetry anonymous. Say it is
anonymous until you sign in, in the CLI reference (media-use, --skill,
feedback, events, telemetry), the hyperframes-cli skill references, and
the CLI help and notice strings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(telemetry): identify signed-in CLI users by their HyperFrames user id
The CLI and media-use identified a signed-in install by account email, so
CLI usage landed on a different PostHog person than the same account in
the HyperFrames web app, which keys on toUUID(username). Identify by that
same sha256-derived id instead, and $set email plus heygen_username on the
person. heygen_username is the cross-project join column for the Snowflake
event table (HeyGen's own project stores it as `name`). An account with no
username still falls back to the lowercased email.
Notices and docs now say usage is keyed on an id derived from the username
with email and username stored on it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(telemetry): trim comments to the package's comment share
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(telemetry): put heygen_username on every event while signed in
The Snowflake event sync may not carry PostHog person properties, so a
$set alone does not let an event row join across projects. The CLI now
adds the signed-in HeyGen username (raw, from ~/.heygen/credentials) to
every event and drops it once the credentials hold no user; media-use
does the same on each event it sends.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: count auth/paths.ts as an edit-accuracy bench input
Telemetry now imports it to read the signed-in username.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(telemetry): stat and read the credentials through one file handle
CodeQL flagged the stat-then-read as a file system race.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(telemetry): key signed-in events on the account id; fix stale anonymity copy
Genesis review on #6703:
- telemetry --help listed 'Any personally identifiable information' as
never collected, and media-use's meta.md still said anonymous.
- Installs already identified by email under an older CLI never merge
into the new person, so send signed-in events with distinct_id = the
account id (back to the install id after logout), in the CLI and in
media-use, and stop claiming earlier usage always merges.
- A user block with no credential no longer counts as signed in.
- A UUID-shaped username passes through unhashed, as on the web.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): every --json failure answers with one envelope, and --json only where supported
Under --json, failures came back as a bare string (`"error": "Command failed"`
from lint), a command-specific shape, or nothing at all on stdout (info,
compositions, render, snapshot, cloud get). Now every failure is
`{"ok": false, "error": {"code", "message", "hint"?}}`:
- jsonFailure() in utils/jsonError.ts builds it. The commands that printed
`{ok: false, error: "<text>"}` use it, keeping their extra fields (lint's
findings, validate's errors). A refused timeline edit now writes it to stdout
instead of stderr, and keeps its `reason` and `fix` fields.
- When a --json run fails without writing anything to stdout, cli.ts writes the
envelope itself: usage errors carry their own code (unknown_flag,
unknown_command, json_unsupported, confirmation_required), anything else is
runtime_error with the message the command printed on stderr.
--json is no longer accepted by every command. A command supports it by
declaring a `json` arg; the others reject it with exit 2 and the code
json_unsupported, instead of accepting the flag and printing prose.
The contract test now checks every command's --json path, and packages/cli
gets an AGENTS.md with the rules: stdout is data, stderr is messages, never
prompt without a TTY, exit 2 for usage errors, --json always returns an
envelope.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): put the real failure in the --json envelope, never the generic text or a notice
lint --json and check --json in a directory with no project answered
"message": "Command failed". The project check reports the problem, then
throws a presented "Command failed" with the real error as its cause, and the
command's own catch serialized that generic text. failureMessage() now returns
the cause, or else what the command reported through errorBox, and every
command catch that writes a --json failure uses it (lint, check, validate,
layout, snapshot, compare, grade-compare, media-treatment, tts,
remove-background, add, skills, lambda policies). compare, grade-compare and
add share failCaught() for that catch.
The boundary envelope follows the same order: the error's own message unless
it is the generic one, then its cause, then the errorBox title and hint, and
only then the stderr text. Notices (the telemetry disclosure, deprecation
lines, diag progress) are no longer captured, so validate, layout and inspect
no longer put "is deprecated" in the error message.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): render --json keeps human output off stdout so a failure answers with the envelope
Under --json a single render printed its header, lint findings and progress on
stdout, so a later failure such as `render --variables '{bad' --json` ended
with no JSON envelope. --json now silences that output the way --batch --json
already did, and --variables is parsed before the browser check so a bad value
fails without a Chrome download.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): a --strict lint abort says why, even under --json
Under --json the render is quiet, so a --strict lint abort printed nothing and
the envelope said only "Command failed". The abort is now an error box on
stderr in every mode, naming the error and warning counts and the first
finding, and the --json envelope carries the same message. Lint now runs
before the browser check, so the abort never waits on a Chrome download.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): time the render setup cancel from the wrapper's exit
The 1 s budget started when the command did. With lint now running before the
browser check, its subprocess (about 250 ms here) sat inside that budget, and
the test failed once under load. The budget now starts when the wrapper exits,
which is the latency the test is about.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): add help [cmd], suggest the closest command or flag, and quiet the nags
`hyperframes help` printed "Unknown command help" with raw ANSI codes and no
suggestion. It is now an alias: `help [cmd...]` is `[cmd...] --help`.
An unknown command (at the root or inside a group like `cloud`) prints
"Unknown command: rendr", "Did you mean 'hyperframes render'?" and the group's
usage, all on stderr, and exits 2. An unknown flag suggests the flag it most
likely means ("Did you mean --output?"). Usage text printed for a usage error
now goes to stderr instead of stdout, and citty's colors are stripped from
help whenever this CLI's own colors are off.
No update, skills or app-history notice is appended to a usage error. The
skills notice shows at most once a day per project (timestamps in
~/.hyperframes/skills-notice.json) and, in an unattended run, only when a core
skill is missing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): reject an unknown top-level command without loading every command
citty looks for an alias on every registered command before it reports an
unknown name, which loads all of them. From a source checkout without the
studio packages built, that meant `hyperframes rendr` failed in preview's
build check with exit 1 instead of suggesting `render`. The name is now
checked against the registry first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): help with a mistyped subcommand reports it like the command itself
`hyperframes help cloud rendr` printed cloud's help and exited 0, while
`hyperframes cloud rendr` exits 2 with a suggestion. Both now exit 2 with the
suggestion. A group that also takes positional arguments (timeline takes a
project directory) still shows its help for a word it does not know.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): never prompt or auto-install when nobody is at the terminal
publish and upgrade called the confirmation prompt with no terminal check.
With stdin from /dev/null, publish exited 13; with an open pipe it hung.
Unattended and without --yes, both now exit 2 before doing any work, and
stderr names --yes (upgrade also names --check).
All prompts and spinners now go through ui/prompts.ts, the only module allowed
to import @clack/prompts (a lint rule and a contract test enforce it). Its
confirm, select, text and password refuse to prompt in an unattended run, and
its spinner prints only the final line, on stderr. init, feedback, catalog and
cloud delete decide whether to ask with isUnattended() instead of a TTY check
of their own.
The background auto-install no longer starts in an unattended run, and the
update notice ("Run: npm install -g ...") is not shown while an install for
that version is pending. CI now counts as set for any value but "false" or "0".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): neutralize agent detection in the catalog offer tests through isAgentDriven
Attendance now reads isAgentDriven(), so that is what the catalog tests stub to
stay independent of the agent running the suite.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(cli): define every command through one factory and test them all as an agent
Every command now comes from defineCommand in utils/defineCommand.ts. A lint
rule bans importing defineCommand from citty anywhere else, and a new contract
test walks the command registry in cli.ts, checks each command was built by the
factory, and runs it the way a coding agent does: no terminal, stdin closed,
CI=1, CLAUDECODE=1 and a home folder of its own.
Usage errors now exit 2: unknown flags, extra positionals, and citty's own
parse errors (missing argument, bad value, unknown or missing subcommand).
An unknown flag names the help command instead of printing nothing else.
isUnattended() in utils/attendedTerminal.ts is the one answer to "can a person
respond here". It is true without a TTY on stdin or stdout, under CI (any value
except "false" or "0"), when the existing agent detection finds an agent, or
with HYPERFRAMES_AGENT=1. Color is off when it is true.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): spawn preview in the agent-mode test only when the studio packages are built
From source, preview refuses to load until the studio workspace packages are
built, and the CLI test jobs do not build them. preview is still checked for
being defined with the shared factory.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): count only agent-run shells as unattended, and exit 2 for missing arguments
isUnattended() used the telemetry fingerprint detectAgentRuntime(), which also
matches a person typing in Cursor's or Windsurf's terminal (TERM_PROGRAM) and
any Replit shell (REPL_ID, REPLIT_USER). It now uses isAgentDriven(), which
keeps only markers an agent sets on the shells it runs: CURSOR_AGENT counts,
TERM_PROGRAM=cursor, TERM_PROGRAM=windsurf and the Replit variables do not.
The hand-written argument checks in lambda, cloudrun, lambda policies,
capture, remove-background, init (--resolution), tts (--lang) and cloud render
(--idempotency-key, --dry-run) exited 1. They are usage errors and now exit 2.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: links point at hyperframes.studio, the app's new home
* docs: the registry guide publishes to hyperframes.studio
* chore: format README table, regenerate the skills manifest
* docs: leave the browser editor's card wording for its own change
* fix(cli): check fails a full-frame element that covers every other clip, naming it
* test(cli): shorten the cover scene helper's comment
* chore(skills): refresh the manifest for the check reference
* fix(cli): full-frame b-roll over a backdrop is not a covered canvas
* fix(cli): audio --read starts music where its sound is sustained
A run of the bed is split where sound fills less than half of the surrounding
2 s, so a short tone and scattered clicks that bridge into the music no longer
pull its start earlier. Sparse stretches are left to effects.
* fix(cli): audio --read finds effects in each stem and levels them from the mix
A sound ends where the next one starts, so a run of kicks is not one long
riser. Effects are searched in the drums and in the bed outside music, each on
its own, so a click inside a tone is still found. Each effect's peak is read
from the decoded mix in dBFS, and the music reference is taken next to the
effect rather than under it, where its own leakage sits.
* fix(cli): audio --read measures the recording through each pause
Pauses carry the whole recording's level so the read can say what keeps
playing while the voice stops.
* feat(cli): audio --read prints peaks, what plays in pauses, voice vs music
Effect lines print the absolute peak and name the reference of the relative
figure; pause lines keep the pause kind and list the effects, music and
ambience under them; sentences over music print voice and music loudness and
which is louder. Read schema version 4.
* fix(cli): audio --read finds bed effects on the whole bed, not a blanked one
Blanking the bed wherever music or ambience played made every blanked
stretch that ended while sound was still audible look like a sound rising
out of silence. Fades and quiet bridges then printed as dings, hits and
whooshes. The bed is now searched whole, and each source carries the
spans it plays music or ambience in. A sound whose onset-to-peak overlaps
one of those spans belongs to it, so it is not an effect. The drums and
the bare mix carry no spans.
— Jerrai
---------
Co-authored-by: Jerrai <jerrai@heygen.com>
* fix(cli): beats given a media file points to hyperframes audio instead of a folder error
* fix(cli): beats names the folder for a composition file and syncs the skills manifest
* fix(cli): audio --read reports noise beds as ambience, not music
A stretch of the non-voice stems counted as music whenever it was above
-55 dB, so room tone and engine rumble separated into "other" printed as
a music piece. A stretch is now music only with a beat, clear tonal chroma
or more than one leading instrument; otherwise it is ambience with its
level in dBFS, called quiet under -45 dBFS, and breaks lie only between
two runs of music. The header lists only stems that carry sound, with
their level, instead of the separator's six fixed names.
* refactor(cli): audio --read gate carries its meaning in names, not comments
* fix(cli): keep ambience out of downstream music measurements
* fix(cli): measure music beat evidence within each stretch
* feat(cli): capture accepts a local HTML file again
Adds an explicit --local-file mode. The file's directory (or --local-root) is served
on a loopback port that only Chrome may reach outside the capture proxy; all other
traffic keeps the public-address policy. The URL argument stays public HTTP(S) only.
* fix(cli): local capture output names the file, not the loopback server
In --local-file mode, meta.json, AGENTS.md, extracted files and the returned result
used the temporary 127.0.0.1 origin. One rewriter maps that origin to file:// URLs
under the root, and meta.json records the file as its source. Root and served paths
now resolve with the same native realpath, which Windows needs.
* fix(cli): local capture reads its files from disk and resolves real paths natively
The asset downloader read the local page server with Node's fetch, which an environment proxy
(NODE_USE_ENV_PROXY with HTTP_PROXY) intercepts, so local images and fonts were dropped. It now
reads the file from disk under the server's own checks.
Root and served paths both use the native realpath. The JS realpath kept Windows 8.3 short names
while fs/promises expanded them, so every file answered 404 on Windows.
The default root is the directory the file was named in, so a symlinked entry can no longer widen
it to wherever the link points.
The local-file docs move below the capture policy text so their heading does not swallow it.
* refactor(cli): capture settles a watchdog outcome in one place
* fix(cli): escape every RegExp metacharacter in the local origin rewriter
---------
Co-authored-by: Jerrai <jerrai@heygen.com>
* feat(cli): capture can route its traffic through an opt-in upstream proxy
HYPERFRAMES_CAPTURE_PROXY=http://host:port sends the capture browser proxy's
forwarded requests and CONNECT tunnels, and every publicRequest download,
to that proxy by hostname instead of dialing a checked public IPv4. Unset,
behaviour is unchanged.
* fix(cli): keep capture comment share flat and close the new CodeQL findings
Test fixtures record what they receive instead of echoing request data, and
capture --video strips line breaks from a failed download's message before
logging it.
* fix(cli): proxied HTTP downloads send no URL fragment, and a malformed proxy credential is refused
The absolute-form request to the upstream used the URL's href, so a fragment such as
sprite.svg#icon went on the request line, which the pinned path never sends and strict proxies
refuse. A malformed percent-escape in the proxy credentials threw a bare URIError instead of the
usual "not a valid proxy URL" error. The test servers label their echoes text/plain.
* fix(cli): strip line breaks from capture --video errors in the form CodeQL recognizes
* fix(cli): capture --video logs errors through a recognized newline sanitizer
---------
Co-authored-by: Jerrai <jerrai@heygen.com>
render, check and snapshot open project history, which looks for a running
preview by probing 100 loopback ports and 5190. Behind an egress fence every
probe is refused and reported. Setting HYPERFRAMES_NO_PREVIEW_DISCOVERY=1 makes
historyDeps.findServer return null without a request, so history opens directly.
Refs #6204
* feat(engine): encoders cap peak bitrate and place keyframes on a set interval
* style(engine): shorten the rate-cap comment
* style(engine): drop the doc lines the comment ratchet flagged
* feat(producer): mp4 renders honour a keyframe interval and a peak bitrate cap on every encoder path
* style(producer): shorten the delivery encoder file headers
* style(producer): drop a comment line from the segment manifest
* style(producer): shorten the integration test header
* test: delivery encoder tests follow the ffprobe argv contract and run in the integration lane
* feat: the render request, server and Lambda plan carry the keyframe interval and peak bitrate cap
* fix(studio): the Studio render route passes the keyframe interval and bitrate cap to the encoder
* chore(studio-server): drop a comment the helper's name already says
* feat(cli): render and lambda render take --keyframe-interval, --max-bitrate and --buffer-size
* feat(cli): check catches audio the render would lose or play as silence
check gains an Audio section, read from the media list the render mixes:
- error: a clip's source has no audio the render can read (its mix fails,
the film ships without sound)
- warning: a clip starts past the end of its file, or the part it plays
is silent
- info: the film has no sound at all
The bundle and media list are now built once and shared with the HDR
inspection; the audio pass runs while the browser pass does.
Fixes#2775
* fix(cli): check reports an unreadable audio source the way render handles it
A source with no audio track is refused by render's own media-type rule, now exported from the producer and called by check; a source that does not decode as audio fails the mix and stops the render.
* perf(cli): check samples a long clip at a few spots before reading all of it for silence
Sound at any spot ends the decode; a clip is only called silent after its whole played part is read.
* chore(cli): drop a comment the shared finding helper's name already says
* feat(cli): hyperframes <dir> opens the project in the app
* refactor(cli): one command-word lookup and one rewritten command line for the folder shortcut
* refactor(cli): shorten the folder shortcut comments to fit the comment ratchet
* feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review
The --skip-contrast-in-motion flag and HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION
env give way to a check-review capability in HF_TOOL_CONTEXT, so only a harness
turns it on. The skill text and manifest go back to main.
* fix(cli): satisfy comment ratchet for check capability
* docs: one Troubleshooting page, four tabs, and the start pages folded
* docs: keep how Studio app updates arrive on the help page
Folding the Studio app troubleshooting page into /help dropped its
"How updates arrive" section: the update button, Settings > Updates
and the Mac menu route, the Automatic updates toggle, and what to do
when an update stalls or cannot check. It is back word for word; every
label is still in the app.