* 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>
hyperframes
CLI for creating, previewing, and rendering HTML video compositions.
Install
npm install -g hyperframes
Or use directly with npx:
npx hyperframes <command>
Requirements: Node.js >= 22, FFmpeg
Commands
init
Scaffold a new Hyperframes project from a template:
npx hyperframes init my-video
cd my-video
preview
Start the live preview studio in your browser:
npx hyperframes preview
# Studio: http://localhost:3002/#project/my-video
# Server: http://localhost:3002
npx hyperframes preview --port 4567
In an interactive terminal, the preview stays attached until you press
Ctrl+C. In a non-interactive shell such as a coding-agent session, the same
command starts a managed preview that survives after the command returns. Use
--background or --foreground to choose explicitly, and manage persistent
previews with --status, --stop, --list, and --kill-all. Add --json to
managed lifecycle commands for machine-readable output. --foreground --json
prints the ready-session envelope once, then remains attached until stopped.
audio
Measure where sounds land: onsets, the moment each sound peaks, loudness and silence. Give files, or query the film's own clips in film time:
npx hyperframes audio sfx/whoosh.mp3
npx hyperframes audio --at 12 --json
normalize-audio
Measure two local authored audio clips with integrated LUFS and match the target
to the unchanged reference. The command is a dry run unless --write is passed:
npx hyperframes normalize-audio --reference target-audio --target user-audio
npx hyperframes normalize-audio --reference target-audio --target user-audio --write
It updates only the target element's data-volume and refuses unsafe boosts
that exceed Studio's +12 dB ceiling or would clip.
render
Render a composition to MP4. Run from the project directory; the positional
argument is the project directory (not a file), so render the project's
index.html directly, or point at a specific composition file with -c:
npx hyperframes render -o output.mp4
npx hyperframes render -c ./my-composition.html -o output.mp4
A render stops when the process that started it exits. To keep one running after
its launcher exits, set HYPERFRAMES_RENDER_DETACHED=1 and start it in its own
process group, e.g. bash -c 'set -m; HYPERFRAMES_RENDER_DETACHED=1 npx hyperframes render -o out.mp4 < /dev/null > render.log 2>&1 &'.
Piped, non-quiet renders emit a single @hf-diagnostic JSON line when an audio
failure reaches the CLI error handler. Version 1 has operation: "render" and
at most 16 audioFailures: allowlisted stage, reason, owner, retryable, and
optional subprocess exit code, signal, termination reason and OS code. It omits
messages, stderr, source paths/URLs, element IDs and command arguments. Consumers
must ignore unknown versions and fields. This is local CLI output; uploading it
requires the caller's existing consent and privacy checks. Quiet mode (including
batch JSON mode) emits no diagnostic line.
This covers audio preparation and mix failures, including the typed external interruption path. Probe failures retain their existing stage/reason but do not gain subprocess evidence here; browser/FX failures remain classified as internal. Desktop ingestion and live report delivery are separate changes.
publish
Upload a project directory and get a hosted URL that keeps working after the CLI exits. Published projects are private by default:
npx hyperframes publish
npx hyperframes publish ./my-video
npx hyperframes publish --public
npx hyperframes publish --yes
Signed-out publishing returns an authentication-required claim URL; opening it
lets someone sign in and claim the project. Sign in first with
npx hyperframes auth login to publish an owned project you can update. Use
--public to make the claimed project visible to anyone. --yes only skips the
confirmation prompt and does not change visibility.
Signed-in publishers can use --update <url-or-id> to target an existing project
or --space <space-id> to publish into a shared team space. If the requested
project is missing or inaccessible, publishing can create a new project instead;
check the printed URL and status.
See the publish reference for all options, including video proxy settings.
lint
Validate your Hyperframes HTML:
npx hyperframes lint ./my-composition
npx hyperframes lint ./my-composition --json # JSON output for CI/tooling
npx hyperframes lint ./my-composition --verbose # Include info-level findings
By default only errors and warnings are shown. Use --verbose to also display informational findings (e.g., external script dependency notices). Use --json for machine-readable output with errorCount, warningCount, infoCount, and a findings array.
compositions
List compositions found in the current project:
npx hyperframes compositions
benchmark
Run rendering benchmarks:
npx hyperframes benchmark ./my-composition.html
doctor
Check your environment for required dependencies (Chrome, FFmpeg, Node.js):
npx hyperframes doctor
browser
Manage the bundled Chrome/Chromium installation:
npx hyperframes browser
info
Print version and environment info:
npx hyperframes info
docs
Open the documentation in your browser:
npx hyperframes docs
upgrade
Check for updates and show upgrade instructions:
npx hyperframes upgrade
npx hyperframes upgrade --check --json # machine-readable for agents
Documentation
Full documentation: hyperframes.heygen.com/packages/cli
Related packages
@hyperframes/core— types, parsers, frame adapters@hyperframes/engine— rendering engine@hyperframes/producer— render pipeline@hyperframes/studio— composition editor UI