Commit Graph
276 Commits
Author SHA1 Message Date
x da508f70e1 docs(cli): check's --timeout default matches snapshot and render 2026-11-02 17:22:11 -05:00
JerraiandXuanru Li acd5c30e60 feat(cli): read audio stems through your HeyGen account (#6993)
* 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>
2026-11-02 18:42:05 +00:00
Wenbo ZhuandClaude Opus 5.5 7da583ec69 feat(cli): reverse start stops a Free account when the server blocks it (#7017)
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>
2026-11-02 17:27:31 +00:00
Wenbo ZhuandClaude Opus 5.5 7d3c225207 feat(cli): reverse start records a reverse run; cloud render carries its session (#7016)
* 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>
2026-11-02 16:47:05 +00:00
Jerrai 346f0e49fa feat(producer): report browser-computed reference backgrounds (#7007)
* 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
2026-11-02 14:42:18 +00:00
James Russo 7f48a6894c feat!: remove Plan v1 distributed transport (#6970)
* feat!: remove Plan v1 distributed transport

BREAKING CHANGE: Cloud adapters accept only Plan v2; producer v1 exports are removed.

* ci: authorize Plan v1 cleanup deletions

* docs: avoid citing generated smoke result file

* fix(gcp): reject unsupported plan protocols in workflow

* ci: preserve gcp package size reduction

* fix: reject stale Plan v1 SDK options

* chore: remove retired Plan parity scripts
2026-11-02 04:13:57 +00:00
Miguel Ángel de6ec3811e fix: cache paths follow HYPERFRAMES_CACHE_DIR (#6716)
* 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
2026-11-01 22:44:11 +00:00
Vance IngallsandClaude Opus 5.5 34ddced2d0 fix(cli): keep browser sign-in open through checkout (#6879)
* 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>
2026-11-01 12:29:35 -08:00
Miguel Ángel 5ce6b30bdb fix(cli): billing and Desktop handoff read their hosts from settings (#6897)
* 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
2026-10-31 22:49:12 +00:00
Akshar Patel 164d6c7a36 fix(studio-server): download exports larger than 2 GiB (#6602) 2026-10-30 22:49:09 -04:00
WaterrrForeverandMiguel Angel Simon Sierra f2358315e5 feat(skills): re-land reverse any video on the HyperFrames plan, with clearer credit wording (#6833)
* 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>
2026-10-31 00:47:54 +00:00
Akshar Patel 3245b769aa fix(studio-server): keep long file paths from blocking saves (#6611) 2026-10-30 16:03:58 +00:00
Akshar Patel fc3d0a5fce fix(core): load responsive images from their scene (#6399) 2026-10-30 16:03:37 +00:00
Miguel Ángel 5a164fb369 feat(cli): render --start and --end render only that span of the film as its own MP4 (#6834)
* 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
2026-10-30 14:45:43 +00:00
James RussoandClaude Opus 5.5 b45e602faa revert: feat(skills): reverse any video (#6754) (#6840)
* 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>
2026-10-30 14:32:26 +00:00
cb2389cf35 feat(skills): reverse any video on the HyperFrames plan (#6754)
* 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>
2026-10-30 13:24:02 +00:00
Miguel Ángel a373071261 feat(cli): beats analyses an audio or video file given directly (#6812)
* feat(cli): beats analyses an audio or video file given directly

* docs(skills): the beats reference says a file argument is analysed directly
2026-10-30 10:09:07 +00:00
Miguel Ángel 21eefcd006 fix(cli): hyperframes docs motion-blur shows the motion blur guide (#6779)
* fix(cli): hyperframes docs motion-blur shows the motion blur guide

* docs(cli): list the motion-blur topic in the docs command reference
2026-10-30 03:55:29 +00:00
James Russo 3e462f4277 feat(cli): use dedicated HyperFrames OAuth clients (#6687)
* 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
2026-10-29 21:25:20 +00:00
Miguel Ángel 838f8782c4 fix(cli): a project .env only sets the CLI's own settings (#6565)
* 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
2026-10-29 12:41:46 +00:00
Miguel Ángel 5d437d5eec docs(cli): say which audio probes run once per file (#6731) 2026-10-29 08:10:51 +00:00
Miguel Ángel fa97f9d4b7 docs(cli): list check's audio findings with severity, trigger and fix (#6730) 2026-10-29 07:41:09 +00:00
James RussoandClaude Opus 5.5 1c3fcf4d23 feat(telemetry): identify signed-in CLI users by their HyperFrames user id, and say signed-in usage is not anonymous (#6703)
* 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>
2026-10-29 02:57:48 +00:00
James RussoandClaude Opus 5.5 85efaece7e fix(cli): every --json failure answers with one envelope, and --json only where supported (#6622)
* 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>
2026-10-28 10:42:10 +00:00
James RussoandClaude Opus 5.5 e2c9151320 fix(cli): add help [cmd], suggest the closest command or flag, and quiet the nags (#6621)
* 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>
2026-10-28 09:39:06 +00:00
James RussoandClaude Opus 5.5 0bdc128cb5 fix(cli): never prompt or auto-install when nobody is at the terminal (#6617)
* 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>
2026-10-28 07:56:20 +00:00
James RussoandClaude Opus 5.5 22e6da0233 feat(cli): define every command through one factory and test them all as an agent (#6616)
* 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>
2026-10-28 06:28:44 +00:00
Ular Kimsanov 6ae7c7688c docs: links point at hyperframes.studio, the app's new home (#6585)
* 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
2026-10-27 20:08:22 +00:00
Akshar Patel 1d4aadc1fb fix(engine): mux uppercase WebM exports with Opus audio (#6579)
* fix(engine): mux uppercase WebM exports with Opus audio

* test(engine): terminate ffprobe options before input
2026-10-27 18:15:39 +00:00
Miguel Ángel 2a0fa7f4de fix(cli): check fails a full-frame element that covers every other clip, naming it (#6572)
* 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
2026-10-27 17:38:21 +00:00
Xuanru LiandJerrai cf04452c0d fix(cli): audio --read reads music onsets, effect peaks, pauses and voice against music (#6546)
* 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>
2026-10-27 11:08:00 +00:00
Miguel Ángel 5a92fe7e5e fix(cli): beats given a media file points to hyperframes audio instead of a folder error (#6544)
* 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
2026-10-27 10:49:26 +00:00
Xuanru Li 87baff8842 fix(cli): audio --read reports noise beds as ambience, not music (#6536)
* 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
2026-10-27 09:44:40 +00:00
Miguel Ángel eba78b4ad3 fix(cli): audio accepts several files, as its help says (#6535) 2026-10-27 09:08:48 +00:00
Xuanru LiandJerrai a779206430 feat(cli): capture accepts a local HTML file again (#6498)
* 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>
2026-10-27 04:45:32 +00:00
Xuanru LiandJerrai 53d3caeac5 feat(cli): capture can route its traffic through an opt-in upstream proxy (#6499)
* 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>
2026-10-27 02:52:52 +00:00
Xuanru Li 725f069cb9 feat(cli): skip the preview-server scan when HYPERFRAMES_NO_PREVIEW_DISCOVERY=1 (#6503)
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
2026-10-27 02:34:19 +00:00
Miguel Ángel 54d0af57eb feat(engine): render sets a keyframe interval and a peak bitrate cap (#6444)
* 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
2026-10-27 00:29:06 +00:00
Miguel Ángel d83bf997b0 feat(cli): check catches audio the render would lose or play as silence (#6439)
* 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
2026-10-26 17:02:57 +00:00
Miguel Ángel 324671829f feat(cli): hyperframes <dir> opens the project in the app (#6426)
* 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
2026-10-26 16:12:41 +00:00
Akshar Patel d507323ef9 fix(player): keep new controls in sync with playback (#6373) 2026-10-26 14:53:06 +00:00
Xuanru Li 257bf285ff feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review (#6400)
* 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
2026-10-26 07:42:50 +00:00
Ular Kimsanov a988afa00b docs: one Troubleshooting page, four tabs, and the start pages folded (#6380)
* 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.
2026-10-26 07:24:33 +00:00
Ular Kimsanov 0390f3f908 docs(studio): the browser editor in six pages, named the browser editor (#6379)
* docs(studio): the browser editor in six pages, named the browser editor

* docs(studio): mute where the editor has it, and no solo control

The audio pages still described a solo control and a group-row mute that
the editor does not render. Muting is the speaker button on an audio
track (Mute track / Unmute track), the same toggle on a selected clip,
and the Muted switch; a group is silenced through its members' tracks.
The audio reference drops its solo section for the same reason.
2026-10-26 06:24:53 +00:00
Xuanru Li afdad4ea38 feat(cli): opt-in check flag that leaves out contrast measured mid-animation (#6396)
* feat(cli): opt-in check flag that leaves out contrast measured mid-animation

`hyperframes check --skip-contrast-in-motion`, or
HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1, leaves out a contrast failure
measured while a GSAP tween on the text or an ancestor is running, or while
the text's cumulative opacity is below 1. Repeating tweens and tweens of 4 s
or longer (camera drifts) do not count as motion. Text no GSAP tween targets
is reported as before. With neither set, check is unchanged.

The motion state is read at the same contrast sample, with no extra seek or
screenshot.

* fix(cli): read contrast motion on current GSAP clocks

* fix(cli): keep infinite timeline motion ambient
2026-10-26 06:22:46 +00:00
Miguel Ángel a11280eef0 perf(cli): lambda deploy warms the video function so the first render does not wait for its image (#6376)
* perf(cli): lambda deploy warms the video function so the first render does not wait for its image

A freshly deployed container-image function loads its image on first use, which added about 12 s to the first render's plan on some deploys. Deploy now invokes the function's warm action once after the stack is up (default on, --no-warm skips it). A failed warm-up prints a warning and the deploy still succeeds.

* chore(cli): move the deploy warm-up to its own file, regenerate the skills manifest

Regenerates skills-manifest.json for the lambda reference change, keeps runDeploy under the complexity gate by pulling the warm step into a helper, and leaves sam.ts untouched.

* fix(cli): the deploy warm-up asks the aws CLI for JSON output whatever the profile sets

A profile configured for text or yaml output made the reply unparseable, which printed a spurious warm-up warning.

* refactor(cli): split the reserved-concurrency notice and warm-up gate out of runDeploy
2026-10-26 05:32:53 +00:00
Ular Kimsanov a4e6bc3254 docs(guides): media pages titled by the task people search for (#6360)
* docs(guides): media pages titled by the task people search for

* docs(guides): background fixes as a visible section; list color grading as an agreed deletion
2026-10-26 01:45:25 +00:00
James RussoandClaude Opus 5.5 f4a48ad3e0 fix(cli): make snapshot --describe opt-in (#6368)
snapshot uploaded every frame to Gemini whenever GEMINI_API_KEY or
GOOGLE_API_KEY was set, even with no flag. Frames now stay local unless
--describe is passed. A bare --describe uses the default prompt (it used
to send an empty one), --describe "question" asks a custom one, and
--describe false still works.

Fixes #6138

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 01:06:29 +00:00
Abdullah M. Nazif 693447f670 fix(engine): keep capture diagnostics on stderr (#5304)
* fix(engine): keep capture diagnostics on stderr

* chore: prepare conflict resolution against current main

* chore: preserve reviewed fix on current main

* fix(engine): send motion blur diagnostics to stderr

* chore: prepare reviewed capture conflict resolution

* fix(engine): preserve stderr diagnostics across capture updates

* chore: prepare reviewed capture conflict resolution

* fix(engine): preserve stderr for scene callback warnings
2026-10-25 23:04:09 +00:00
Akshar Patel 6a20ec9e9a fix(cli): preserve clip lengths when duplicating media (#6280)
* fix(cli): prevent overlaps when duplicating natural-duration media

* fix(cli): preserve absolute ends when duplicating clips

* fix(cli): keep rippled clip bounds together
2026-10-25 23:01:27 +00:00