* fix(check): the analysis restarts the idle window with every call it makes
* test(check): runAuditGrid's analysis must use the progress-reporting driver
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>
The pack was written to a temporary file before tar read it. tar now
reads the checked bytes on stdin, so nothing downloaded is written to
disk before it is unpacked.
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(cli): measure cuts reads a zoom that speeds up as a move, not a cut
Bug bash (Stripe "pay", 0.9.6): the app window's zoom-in at 0.2 s changes
3, 9, 12, 5 grey levels a frame, and the 12 crossed a cut's level, so
measure cuts listed a cut there and diff failed the rebuild (pass: false at
0.93 similarity) for missing it.
A single-frame cut is now a fast move when the frames either side move too
(30% of its change) and the frame before, zoomed 0.8-1.25x and shifted a few
pixels, comes within 40% of its change of the frame after. A punch-in also
zooms, but out of a still picture, so it stays a cut.
* fix(cli): a zoom is a move only when the picture moves on both sides
Review (hf-review): motion on one side hid a real punch-in out of a still
picture next to a moving shot, and the mirrored punch-out. isMove now needs
both neighbours moving (min, not max). The 33-film check now changes only
Stripe's 0.2 s zoom; league-promo 95.93 s and M1000 5.43 s are cuts again.
* fix(cli): measure picks more than 100 frames on ffmpeg 8
ffmpeg 8 refuses a flat sum of more than 100 terms ("Error while parsing
expression"), and every frame pick in measure, diff and snapshot was one flat
sum of eq(n,k). The ffmpeg HyperFrames downloads is 8.1.2, so measure frames,
diff, face, person, drawn boxes and snapshot all failed past 100 frames.
PostHog: every such cli_error since 0.9.3 came from Windows agents on ffmpeg 8.
sumOf keeps a sum of up to 100 terms flat and nests a longer one in halves.
* fix(cli): sums nest from 51 terms, so ffmpeg 8 takes every pick count
Review (hf-review): 100-term leaves still failed on ffmpeg 8.1.2 once nested,
at 199-201 eq() picks, and snapshot's deeper terms failed at 97-100 times.
ffmpeg 8 limits how deep an expression nests, and a flat sum counts a level
a term. Leaves of 50 pass every count from 1 to 1000 in both shapes, and
1500-5000. Tests decode 199/200/237/401 picks and 97/100/199 snapshot times.
The top measure and diff errors since 0.9.4 (PostHog, CLI project) were
agents' calls, not bad videos:
- A flag from another command: --from/--to on ops that take --start/--end
(extract is the other way round), -o for --out. The unknown-flag error now
names the flag that means the same here.
- measure sync without --roi stopped at citty's bare "Missing required
argument"; diff with one video at "Missing required positional argument:
YOURS". Both now reach their own message with the command to run.
- A Windows path whose backslashes bash dropped (D:videoclip.mp4) was only
"no file at". The fix line now says to quote it or write it with /.
* feat(cli): call HeyGen's v3 media routes through the generated client
The media-use generate routes (image, video, avatar, voice, music), HeyGen
transcription and the public voice list called fetch by hand. They now go
through the generated cloud client, regenerated with the create and status
methods for those routes.
runJob takes a job route (its path, how it starts, how its status is read)
in place of a path and a poll-path function. Keys, retries, timeouts,
polling and error messages are unchanged. Speech answers in the request and
the API takes no Idempotency-Key there, so it has no status to poll and no
key header is sent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(cli): keep v3.ts comment share at the base
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(cli): regenerate the cloud client without API description text
The generated client and types now carry names and types only, with no
JSDoc taken from the API spec's summaries and descriptions. The source
contract test checks that no method carries generated description text,
in place of checking the uploadAsset description.
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(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>
* fix(cli): snapshot stops Chrome decoding videos it already replaces with extracted frames
* fix(cli): a held video keeps its intrinsic size in snapshot, as in render
* fix(cli): shorten a comment line in the held-video hints
* fix(cli): the latest version in _meta is never older than the running CLI
* fix(cli): keep the daily recheck cadence when the cached latest is older than the running CLI
* fix(cli): cloud export re-encodes media whose bytes are not the type its extension claims
* test(cli): generated media in the offload tests carries its container header
* fix(cli): an audio-only MP4 named as video is re-encoded as audio
* test(cli): type the header fixture
* test(cli): give the heavy-media dry-run fixture a real mp4 header
* 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(cli): snapshot reads video frames through any ffprobe and fails on an unreadable one
* fix(cli): snapshot reads video frames through any ffprobe and fails on an unreadable one
* fix(cli): snapshot counts only a failed frame read as unreadable
* fix(cli): snapshot fails when an on-screen video frame cannot be read
* feat(skills): publish reverse-any-video, render it at high quality, and stop on a stuck shot
From Wanbi's Pro test of reverse-any-video (11-01, checked against 0.9.2):
- A standard install never found the skill: it was internal, and /hyperframes did not route to
it. It is now an on-demand skill on every catalog surface, and the routing line names it.
- The cloud render in SKILL.md had no --quality high, so the final rendered at standard.
- A re-shoot that stayed pending had no rule; the run waited four hours. people.md now runs the
command once more after generate's 30-minute stop, then treats the shot as failed.
* feat(cli): generate takes a language for --voice, so a Pro sign-in gets a HeyGen voice
Wanbi's Pro run fell back to Kokoro: --voice needed an id, and the only way to list ids was the
separate heygen CLI and its own login. /v3/voices answers on the HyperFrames sign-in, so:
- --voice <language> picks HeyGen's first public voice in it and names it in the output.
- A missing --voice lists six English voices to pick from instead of a bare error.
- A voice id still goes straight through, with no extra call.
* fix(cli): a voice picked by language stays with its request across re-runs
Rames: `--voice Chinese` re-picked HeyGen's first voice on every run, and the voice is part of the
body and the idempotency key. If the catalog reordered while a job was pending, the same command
could start a second paid job instead of resuming the first.
The pick is now bound to the request (every field, plus the account scope a job bills) in the
project's heygen-jobs.jsonl, or the CLI config dir outside a project, and a re-run reuses it.
Regression: a pending timeout, a reordered catalog, the same command, for voice and avatar; the
second POST keeps the voice, body and key.
* fix(cli): a voice pick waits for a known account instead of binding to none
Rames: when /v3/users/me failed, senderScope() was null and the pick was bound to an empty
scope; once the account came back, the same command missed that binding and could pick another
voice and paid key. pickVoice now stops before picking or posting while the account is unknown,
and never reads or writes a blank-account binding.
Regressions, both directions: unknown then known refuses with no POST, then picks once; known
then unknown refuses the re-run with no second POST, then resumes with the same body and key.
Also README: a non-interactive install gets all 8 skills, not 7.
* fix(studio-server): more than six clips on one media file no longer starve check and preview of connections
Every file server answered `Range: bytes=N-` with the rest of the file. A
media element reads that until its buffer is full, then stops reading but
keeps the response open, so each <video> held one of Chrome's six HTTP/1.1
connections to the host. With seven or more <video> clips on the server's
host, every other request queued behind them and `hyperframes check`
stalled at 0% CPU. A long recording cut into trimmed segments, each its own
<video> on the same file, is enough.
An open-ended range of audio or video now gets at most 4 MiB
(OPEN_MEDIA_RANGE_BYTES), as a normal 206 with an exact Content-Range. The
element asks for the next range as it plays or seeks. Bounded and suffix
ranges, and files that aren't media, are served as before. The helper lives
in studio-server; its preview route and the CLI's static project server and
composition server use it.
Fixes#6029
Claude-Session: https://claude.ai/code/session_014aw1z3VrTNenka3LGDBBLa
* fix(studio-server): carry the range cap's reason in names and tests, not comments
The comment ratchet doesn't let a file's comment share rise: mediaRange.ts
was 48% comments against the package's 7%, and staticProjectServer.ts gained
a line. The cap is now MAX_OPEN_MEDIA_RANGE_BYTES, and the why and the
repro stay in the three tests and #6029.
Claude-Session: https://claude.ai/code/session_014aw1z3VrTNenka3LGDBBLa
* fix(producer): the render file server bounds open-ended media ranges too
parseRangeHeader takes the response's content type and caps an open-ended
`bytes=N-` on audio or video with openRangeEnd, like the CLI and Studio
servers. The producer imports it from a new `media-range` subpath of
@hyperframes/studio-server, as it does `encoder-options`, so a render
doesn't load the rest of the package.
Claude-Session: https://claude.ai/code/session_01DW4V8HZZx5JUgMffb3juVV
* test(cli): eight clips on one file no longer leave the host unanswered
A real Chromium page buffers eight long video clips of one file from the project server and then asks the same host for another file; with open-ended ranges unbounded the request stays unanswered, with the bounded piece it is answered at once.
* docs(producer): range comments name the open-ended form in lowercase
* docs(cli): the range test's comment says what holds now
---------
Co-authored-by: HuntsDesk <hunter@talloak.com>
* fix(cli): measure and diff report why they failed, and reverse reports its runs
Since launch, 97 people saw a bare "Command failed" from measure: runMeasure
and diff called failCommand() without the error, so telemetry kept only the
wrapper. 59 agents ran `measure <file>` with no op and got no hint. Reverse
runs had no skill tag, so nobody could count how many finished.
- runMeasure and diff pass the error to failCommand.
- An unknown word that is an existing file names the commands to put first.
- references/start.md sends skill_invoked and skill_completed beacons.
* fix(cli): the file hint stays out of the root command list
Jerrai: `hyperframes index.html` would list every top-level command. fileHint moves to
didYouMean.ts, answers only inside a command group, and has tests for the root, a missing
file and a group with no commands.
A regenerated shot never lines up with a whip frame by frame, so diff listed every fast move.
A jump now counts only above the original's own motion around it.
fails without the fix: reads a change no stronger than the original's motion as the same fast move
* fix(cli): snapshot shoots a frame again when Chrome drops the screenshot mid-paint
* fix(cli): keep the first screenshot error when the retry fails differently
* 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): say a font or image URL is a download, not a page, when capture is given one
* fix(cli): word the capture download hint as a likely cause, not a fact