* 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
* 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>
* 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>
* 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>
* 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>
* 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>
* 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
* 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
* 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>
* 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
* 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
* 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>
* 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>
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
* 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
* 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
* 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
* 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
* 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.
* 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.
* 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
* 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
* 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
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>
* 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
* 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
* 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
* 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>
* 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
* 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
* 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
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>