Files
hyperframes/packages/cli
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
..
2026-10-28 09:30:08 +00:00

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