Commit Graph
257 Commits
Author SHA1 Message Date
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
Abdullah M. Nazif fc625ac971 docs(aws-lambda): explain long-video planning limits (#5263)
* docs(aws-lambda): explain long-video planning limits

* docs(aws-lambda): prepare planning guidance for main update

* docs(aws-lambda): align planning limits with chunk extraction

* chore: prepare conflict resolution against current main

* docs(aws-lambda): align long-video planning guidance with current extraction limits

* docs(aws-lambda): identify the timeout example construct
2026-10-25 22:58:43 +00:00
Abdullah M. Nazif abb06b47b7 fix(engine): verify WebGPU device creation before capture (#6343) 2026-10-25 22:54:30 +00:00
Miguel Ángel 26e3045e3b fix(producer): --workers N is a ceiling, drawElement renders keep their single worker (#6314)
* fix(producer): --workers N is a ceiling, drawElement renders keep their single worker

* docs(producer): keep the inversion predicate's doc as short as before
2026-10-25 15:33:05 +00:00
Miguel Ángel ada9932d4e fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled (#6307)
* fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled

* perf(core): a video copy's size box follows the stage within a fifth, not a half

* docs(cli): shorten two comments to the line limit

* fix(cli): the copy heartbeat drops a finished copy; size budgets follow the stall and progress code

* chore: size budgets rise by this change's measured bytes
2026-10-25 13:53:03 +00:00
Akshar Patel 32d2d3cba0 fix(core): keep cache-busted images visible in bundles (#6264) 2026-10-25 04:15:02 +00:00
James RussoandClaude Opus 5.5 7f19f2335a feat(studio): load GSAP from the CLI's bundled copy by default (#6256)
* feat(studio): load GSAP from the CLI's bundled copy by default

Studio preview now loads the GSAP, CustomEase and MotionPathPlugin scripts
it adds from files the CLI ships and serves same-origin at /api/vendor/npm,
instead of jsDelivr. HYPERFRAMES_GSAP_BASE_URL still wins when set, and a
composition on another GSAP version gets its plugin from jsDelivr.

Renders now honor HYPERFRAMES_GSAP_BASE_URL when they replace a missing
local GSAP script, and the setting accepts plain http only for loopback
hosts.

Closes #6205

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(studio): keep comment share flat for the GSAP local root

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): find the installed gsap with node resolution when run from source

The Windows CI lane installs with the hoisted linker, so gsap is not under
packages/core/node_modules there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(producer): load a nested composition's missing GSAP from the mirror

A sub-composition's missing local gsap/dist script was hoisted into the
render unchanged, so HYPERFRAMES_GSAP_BASE_URL (and the jsDelivr fallback)
only reached the entry document. Run the same rewrite on every
sub-composition before it is compiled, resolving its path the way the
inliner will hoist it.

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-25 03:08:42 +00:00
Akshar Patel e2129c3915 fix(lint): allow scoped descendant zoom (#6254)
* fix(lint): allow scoped descendant zoom

* chore(lint): account for scoped zoom bundle size

* fix(lint): retain canvas zoom guard for runtime identity

* fix(lint): preserve runtime identity in selector functions

* fix(lint): retain identity across uncertain selector branches
2026-10-25 01:53:04 +00:00
Akshar Patel d44f67ac1d fix(core): preserve repeated stylesheet imports (#6250)
* fix(core): preserve repeated stylesheet imports

* docs(core): note repeated import bundle growth
2026-10-25 01:53:03 +00:00
Akshar Patel 5b752168c1 fix(parsers): keep each keyframe array on its own timing (#6240) 2026-10-25 01:26:22 +00:00
Akshar Patel 17332cf0df fix(engine): concat resolves relative paths without dropping clips (#6238)
* fix(engine): concat resolves relative paths without dropping clips

* test(engine): terminate ffprobe options in concat regression
2026-10-25 00:29:23 +00:00
Akshar Patel f21c3e3e7e fix(lint): accept GLSL script blocks without JavaScript syntax errors (#6227) 2026-10-25 00:28:34 +00:00
Abdullah M. Nazif 0c3387b8be fix(producer): report unavailable frame checks in debug logs (#6200)
* fix(producer): report unavailable frame checks in debug logs

* chore: prepare conflict resolution against current main

* fix(producer): retain scoped diagnostics with current artifact handling
2026-10-25 00:28:25 +00:00
Abdullah M. Nazif f9d72a3f73 fix(cli): preserve distinct font URLs during capture (#5312)
* fix(cli): preserve distinct font URLs during capture

* fix(cli): preserve distinct font URLs during capture
2026-10-25 00:21:53 +00:00
Abdullah M. Nazif 1c14fbb542 fix(studio): allow a local GSAP dependency mirror (#6247) 2026-10-24 22:13:28 +00:00
Akshar Patel 73dda7191c fix(core): preserve images with commas in srcset URLs (#6192) 2026-10-24 08:16:02 +00:00
Akshar Patel 3596a7f22b fix(cli): report failed tag installations to scripts and agents (#6086)
* fix(cli): report failed tag installations to scripts and agents

* fix(cli): restore status markers in tag install summaries
2026-10-24 07:18:42 +00:00
Miguel Ángel 0d104e6fca fix(cli): init never touches installed skills and no command removes one (#6168)
* fix(cli): init and skills update only touch HyperFrames skills; --skip-skills works

* fix(cli): init never touches installed skills and no command removes one

* fix(cli): shorten the init skills comment

* fix(cli): shorten the init skills comment

* fix(cli): the stale-skills notice reaches agents that run without a terminal

* test(cli): a run without a terminal now checks skills in the background

* fix(cli): the stale-skills notice also prints on --json runs
2026-10-24 04:08:29 +00:00
Jerrai 7a34d6fbe4 docs: align render, fps and transcribe docs with the CLI (#6166)
* docs: align render, fps and transcribe docs with the CLI

- docs/guides/rendering.mdx: default render quality is `looks` (CRF 16),
  not `standard`.
- docs/packages/cli.mdx, render --help (render.ts): default --output is the
  timestamped `renders/<project>_<YYYY-MM-DD>_<HH-MM-SS>.<ext>`.
- skills/hyperframes-cli/references/preview-render.md: local --fps accepts
  1-240 or a rational and defaults to the root data-fps, else 30; only
  lambda and cloudrun are limited to 24, 30, 60.
- skills/hyperframes-cli/references/lint-validate-inspect.md: snapshot
  --range is documented as START,END like every other reference.
- skills/media-use/references/audio.md, skills/media-use/audio/references/
  transcribe.md, skills/hyperframes/references/captions.md: name the entry
  point for each transcription default (transcribe.mjs prefers Parakeet;
  `hyperframes transcribe` uses Parakeet when installed, else the small.en
  whisper model).
- transcribe --model help text and docs/packages/cli.mdx: list `small` and
  `medium`, which the CLI already accepts.
- skills-manifest.json: regenerated hashes for the three skills touched.

— Jerrai

* docs: lambda and cloud run also accept 25 fps

Distributed renders accept 24, 25, 30 and 60 fps (DISTRIBUTED_FPS in
packages/core/src/distributedFps.ts).

- skills/hyperframes-cli/references/preview-render.md: the --fps note
  lists 24, 25, 30, 60 for lambda and cloudrun.
- skills/hyperframes-cli/references/lambda.md, cloudrun.md: --fps lists
  include 25.
- packages/cli/src/docs/rendering.md (`hyperframes docs rendering`): --fps
  said 24, 30 or 60 and --quality defaulted to standard; local render takes
  1-240 or a rational, default root data-fps else 30, and the default
  quality is looks.
- skills-manifest.json: regenerated hyperframes-cli hash.

— Jerrai

* chore(skills): regenerate skills manifest after formatting

The format hook rewrote skill markdown after the manifest hook had hashed
it, leaving the hyperframes-cli hash stale.

— Jerrai
2026-10-24 00:29:56 +00:00
Meghna VasudevaandClaude Opus 5.5 fb8aadad17 docs(cli): apply review notes from #5442 to open, timeline and history (#6110)
Note that the desktop hand-off in `open` is macOS-only for now, that
`timeline apply --plan` prints only a count without --json, and what
`history undo --who` reverts.

Refs #5386

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 00:27:59 +00:00