* fix(producer): a stalled capture resumes at the first unwritten frame instead of frame 0
* fix(producer): a page that keeps stalling falls back after three resumes instead of resuming forever
* docs(skills): reverse-any-video runs sign-in and payment itself and opens the result
From Wenbo's end-to-end reverse test (F1, F2, F8, F9, F12, F13):
- the person types nothing: the agent runs auth login, billing subscribe and
billing top-up itself and gives each page as a clickable link
- never sign in with an API key to get past Pro
- reverse start's plan is final; auth status is read only for a null plan,
and its hyperframes field, never user.subscription
- an old CLI without reverse start: run this run as npx -y hyperframes@latest
- hand over: side by side from diff --sbs, Studio preview, Framey line
- animated shots: cut the guide inside a dissolve, retime each generated shot
* docs(skills): reverse sign-in names --device, reads auth status --json, trims dissolve frames
Rames's three wording notes on #7101, and the old-CLI subscribe line now
applies only to a plugin's pinned CLI, beside main's null upgrade_url rule.
* docs(skills): a plugin whose CLI can't reverse stops before payment
Jerrai on #7101: the plugin fallback sent the person to subscribe when the
pinned CLI has no reverse start, so the run could not go on after paying.
It now stops and says the plugin needs updating.
* fix(check): the analysis restarts the idle window with every call it makes
* test(check): runAuditGrid's analysis must use the progress-reporting driver
* fix(studio): a film that cuts one file into many clips plays smoothly while their durations load
* test(studio): the metadata test counts one store write per file
reverse/start answers upgrade_url: null when no plan can be bought right
now. The CLI treated that like a missing field and built its own plans
link, so a Free account under gate "block" was sent to a page where it
can't buy. Now null means no link anywhere: the JSON prints
upgrade_url: null and subscribe_command: null (billing subscribe would
open that same plans page), the refusal says a plan can't be purchased
right now instead of naming the subscribe command, and the skill runs
nothing and passes on no link. Only an absent field (an older server)
falls back to the plans page; a string is used as is.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The pack was written to a temporary file before tar read it. tar now
reads the checked bytes on stdin, so nothing downloaded is written to
disk before it is unpacked.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(studio-server): a render that ran over 5 minutes can still be read after it finishes
* fix(studio-server): keep the finished-jobs helper's reasoning in its names
* feat(engine): a script-load failure names where the script came from and why it failed
* fix(engine): keep the script-load cause helpers private and within the engine size budget
* fix(cli): no auto-update across 0.x minors; install skills at the CLI's release tag
- autoUpdate: before 1.0 a minor bump counts as a major one, so 0.8.x -> 0.9.x
is a notice, not a silent background install. From 1.0 on, the major alone.
- skills: `skills update` / bare `skills` / `init` install from
heygen-com/hyperframes#v<VERSION>, and `skills check` plus the background
freshness nudge read the manifest at the same tag. A build the repo has no
tag for (dev build) falls back to main, as before.
- skills: run the upstream installer as `npx skills@1.7.4` instead of an
unpinned `npx skills`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): keep the comment share of the touched files from rising
Comment ratchet: trim the comments added in the previous commit. No code change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): pass the CLI version into the skills check instead of importing it
scripts/clawhub-unlist-retired.mjs loads skillsManifest.ts from source under
plain node, which cannot resolve a relative `../version.js` import. Callers now
pass `cliVersion` (and skillsSourceRef takes it as an argument), so the module
keeps importing only node builtins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): read skills at the CLI's tag without a probe; pin skills@1.5.13
- A release build reads the manifest at its own tag directly (publish pushes
the tag before npm publish). Only a 404 for that manifest falls back to main;
a timeout, missing git or proxy error now surfaces instead of quietly
comparing against main, so the background nudge can't cache a false
"outdated" count.
- checkSkills returns the ref it read, and skills update installs from that
same ref, so check and install can't disagree within one run.
- Pin skills@1.5.13 (it parses `#<ref>` too) so the pin matches the version
agentDirs.generated.ts and the lock-path notes were checked against.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(cli): split the tag and main manifest fetches
Fallow flagged fetchRemoteManifest's CRAP score (31.6 > 30). The tag fetch
(404 is the only fallback to main) and the SHA-pinned main fetch are now their
own functions. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): never auto-upgrade a Homebrew install
`brew upgrade hyperframes` installs whatever formula the tap holds, so a
background run scheduled for an allowed patch could still cross a 0.x minor.
Homebrew installs now only get the update notice naming `brew upgrade`.
A new test drives the scheduler through the real installer detection and
installer script: no spawn on Homebrew, a versioned npm install for a patch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test(parsers): a stamped document needs no parse to keep its ids
* perf(parsers): ensureHfIds skips the parse when every element already has its id
* fix(parsers): a fragment checks every tag, a stray body tag included
* fix(parsers): keep the parse for a page not opening head then body
* refactor(parsers): split the head check out of the page shape check
* fix(parsers): find the head end tag without stripping comments
* fix(core): Safari audio that sits frozen after its cut is pulled onto the playhead in 0.4 s, not 1 s
* fix(core): shorten the frozen-audio comment
* fix(core): leave a WebKit audio element that is still filling data out of the frozen rule
* 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.
* feat(skills): creator templates on the HyperFrames plan
A Pro account asks for a video in a creator's format and the agent pulls that
template on demand. The packs sit in the private bucket behind the plan-gated
bundle route, so a Free account gets a 402 and the upgrade step. A pull costs
no credits; making the film spends them as Reverse does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): make creator-remix the one template skill
SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): the creator-remix skill and its packs
SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(skills): word creator-remix's sign-in steps the way media-use does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): a creator-remix presenter runs on the plan by default
The person's own HeyGen avatar is recommended, not required: without
one, 小Lin's presenter is made with HeyGen Video 1 from their photo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): pin template packs whose voice bills the plan first
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): offload media named inside template tags
References in a composition's <template> were never rewritten, because the DOM
query stops at the tag and template.content does not serialize, so the file stayed
in the zip. A stem matched as a plain word and a same-named file elsewhere counted
as a mention too. A 5-minute presented film went from 196 MB to 71 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): lipsync, and avatars that speak a finished voice
Video 1 takes audio as a reference and re-paces it, so a shot's lips follow its
own track and drift up to a second from the voice. generate --type lipsync redoes
the mouth to the exact voice. generate --type avatar takes --audio, and --avatar
for the person's own look, so the lips land on the voice from one job.
Also moves the plan rows to the 2026-10-30 price book, estimates a voice's credits
so --max-credits can hold it, logs jobs from a folder with no project, and describes
the resolve flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): presenters go avatar first, and a voice can be stock
The person's own avatar, then Avatar IV from their photo, then Video 1 with
lipsync. With no clone or recording, a stock voice named in the credits. Pins the
five rebuilt packs. The archive test keeps its ../ under GNU tar with -P.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): fix avatar price and sfx provider tests, trim comments
A wav gives the avatar price test an exact length on every platform. The
sfx tests now expect HeyGen first. Comment share is back under the base.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): read a font shorthand whose size nests parentheses
`font: calc(var(--size) * 1px)/1 "X"` cut at the first closing parenthesis, so a
bad family name failed the render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): one ledger row per job, and search skips a template's own pictures
A job done at once logs one row, transcribe logs to the project, and resolve no
longer offers a component's file for a search. --reuse and --provider get help text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): pin the tested template packs, and a board needs no voice
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): read the transcribe ledger row as JSON, for Windows paths
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Big Questions pack without the film length check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): call them style templates, and the docs example pulls a real slug
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the cleaned template packs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): drop the stock-presenter ban and the no-spend-before-storyboard rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Paper Trail and Long Story Short packs without the stock-presenter ban
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): rename creator-remix to style-remix, and the five templates to names that say the look
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): style-remix lists the five templates when none is named
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Finance Explainer pack that varies its motion graphics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): media-use generate --type cutout takes a shot out of its background on the plan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Finance Explainer pack without the provisional variety rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the five style packs with variety rules measured on real films
Animated Science gets a worlds-and-boards budget, 3D True Crime a main board and a
brief corner note, 3D Brand caps by measured rate, Finance and Collage set pieces and
more than one look.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): media-use names the on-plan cutout and lipsync commands, not the heygen CLI ones
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): cutout retries dedupe on request_id, uploads carry their real type; style-remix is public
The background-removal route dedupes on the body's request_id and ignores the Idempotency-Key
header, so a retry after a lost answer started a second job: runJob now also sends the key in the
body for such a route. Uploads typed every non-MP4 file as audio, so WebM and MOV inputs were
refused: they now take their type from the extension. Fails without the fix: the new cutout test
in generate.test.ts sees audio/mpeg for WebM and MOV.
style-remix drops metadata.internal and is listed with the other published skills.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): keep the comment share of the touched files at main's
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: count style-remix in the README and CLAUDE.md skill lists
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): rename style-remix to style-sampling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): diff --offset compares the original at t with yours at t + offset
Every per-frame measure (SSIM per frame and per cell, frames.tsv, cuts,
missing content) reads yours offset seconds later, and a project folder is
captured at the shifted times. The window keeps to where both sides have
frames and says so in warnings; an offset with no overlap, or one that is
not a number, is refused with a fix. Sound keeps its own offset reading,
duration is reported as not measured, and the result echoes offset.
* refactor(cli): trim diff offset comments under the file's comment share
* fix(cli): rebase full-length diff offsets and reject overflow
Keep offset streams on a zero-based clock even when every frame is selected. Cover an untrimmed delayed render and reject non-finite offsets before file or project processing.
— Jerrai
* refactor(cli): remove redundant offset filter comment
— Jerrai
* fix(cli): measure cuts reads a zoom that speeds up as a move, not a cut
Bug bash (Stripe "pay", 0.9.6): the app window's zoom-in at 0.2 s changes
3, 9, 12, 5 grey levels a frame, and the 12 crossed a cut's level, so
measure cuts listed a cut there and diff failed the rebuild (pass: false at
0.93 similarity) for missing it.
A single-frame cut is now a fast move when the frames either side move too
(30% of its change) and the frame before, zoomed 0.8-1.25x and shifted a few
pixels, comes within 40% of its change of the frame after. A punch-in also
zooms, but out of a still picture, so it stays a cut.
* fix(cli): a zoom is a move only when the picture moves on both sides
Review (hf-review): motion on one side hid a real punch-in out of a still
picture next to a moving shot, and the mirrored punch-out. isMove now needs
both neighbours moving (min, not max). The 33-film check now changes only
Stripe's 0.2 s zoom; league-promo 95.93 s and M1000 5.43 s are cuts again.
* fix(cli): measure picks more than 100 frames on ffmpeg 8
ffmpeg 8 refuses a flat sum of more than 100 terms ("Error while parsing
expression"), and every frame pick in measure, diff and snapshot was one flat
sum of eq(n,k). The ffmpeg HyperFrames downloads is 8.1.2, so measure frames,
diff, face, person, drawn boxes and snapshot all failed past 100 frames.
PostHog: every such cli_error since 0.9.3 came from Windows agents on ffmpeg 8.
sumOf keeps a sum of up to 100 terms flat and nests a longer one in halves.
* fix(cli): sums nest from 51 terms, so ffmpeg 8 takes every pick count
Review (hf-review): 100-term leaves still failed on ffmpeg 8.1.2 once nested,
at 199-201 eq() picks, and snapshot's deeper terms failed at 97-100 times.
ffmpeg 8 limits how deep an expression nests, and a flat sum counts a level
a term. Leaves of 50 pass every count from 1 to 1000 in both shapes, and
1500-5000. Tests decode 199/200/237/401 picks and 97/100/199 snapshot times.
* fix(core): keep overflowing content and SVG clip paths in motion-blurred frames
A blurred mover was copied only within its own layout box, so text wider
than the box (a one-line wordmark in a fixed-width container) was cut in
blurred frames. The movers-alone capture also hid every non-mover, including
the SVG clipPath a mover references, which empties the clip and makes the
mover vanish. Both showed as flicker during playback and in full renders.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): keep the motion-blur fix within each file's comment share
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): measure overflowing text with a Range, ignore rects inside the mover
A block child is as wide as its mover however long its no-wrap text, so
reading child boxes missed the common overflow layout. Read the text's own
rects through a Range. On a rotated mover each child's upright client rect
covers the mover's corners, which doubled the box at 45 degrees and could
pull a neighbouring mover into the trail; only rects that leave the
mover's own client rect now grow it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): measure a rotated mover's overflow unrotated
The upright client rect of a rotated mover has empty corners, so overflow
that landed there passed the "stays inside" check and was still cropped.
Measure a rotated mover with its own transform off and map the rects back
through its layout pose, then restore its style.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): keep a rotated mover the containing block while measured
With only transform: none, a mover with no position stops being the
containing block of its absolute and fixed children, so they jump to an
outer ancestor while measured and their real overflow is cropped. Add
will-change: transform during the swap, and skip the swap when the mover
has no transform of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The top measure and diff errors since 0.9.4 (PostHog, CLI project) were
agents' calls, not bad videos:
- A flag from another command: --from/--to on ops that take --start/--end
(extract is the other way round), -o for --out. The unknown-flag error now
names the flag that means the same here.
- measure sync without --roi stopped at citty's bare "Missing required
argument"; diff with one video at "Missing required positional argument:
YOURS". Both now reach their own message with the command to run.
- A Windows path whose backslashes bash dropped (D:videoclip.mp4) was only
"no file at". The fix line now says to quote it or write it with /.
Bug bash (Miguel's #2): given a link, the agent downloaded the video before
reverse start could tell it the account was Free. The skill now runs auth
status first. To stay in budget, the no-reverse-start fallback lives only in
start.md (it was in both), and two lines are shorter.
* fix(studio-server): a project's signature no longer freezes the host while it walks the files
* style(studio-server): format the signature changes
* fix(studio-server): keep the adapter for the preview's early-error script
* feat(cli): call HeyGen's v3 media routes through the generated client
The media-use generate routes (image, video, avatar, voice, music), HeyGen
transcription and the public voice list called fetch by hand. They now go
through the generated cloud client, regenerated with the create and status
methods for those routes.
runJob takes a job route (its path, how it starts, how its status is read)
in place of a path and a poll-path function. Keys, retries, timeouts,
polling and error messages are unchanged. Speech answers in the request and
the API takes no Idempotency-Key there, so it has no status to poll and no
key header is sent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(cli): keep v3.ts comment share at the base
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(cli): regenerate the cloud client without API description text
The generated client and types now carry names and types only, with no
JSDoc taken from the API spec's summaries and descriptions. The source
contract test checks that no method carries generated description text,
in place of checking the uploadAsset description.
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(studio-server): the preview records the kind and origin of each early error
* fix(studio-server): write preview script values with U+2028/2029 and < escaped
* feat(producer): a render request can force screenshot capture for that render
A retried render can ask for the slower, safer capture mode without changing the pod's environment.
* fix(producer): drop the comment line the comment ratchet flags
* fix(skills): reverse on Free opens the upgrade page for the person
* docs(skills): reverse on Free opens the upgrade page with a quoted link
* fix(cli): reverse on Free runs billing subscribe credited to the run
* fix(cli): trim the reverse skill and drop a doc comment for lint budgets
* fix(cli): drop --page-motion-blur from cloud render
The flag sent page_motion_blur to the hosted render API, which never
accepted that field and rejects unknown fields. Motion blur is set in
the composition with data-motion-blur, and the runtime turns it off on
its own when it cannot render correctly, so the cloud flag is removed
with its request plumbing, test and docs row.
Local render keeps its own --page-motion-blur flag.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(ci): allow deleting the cloud render page motion blur test
The deletion guard blocks removed files unless they are listed. The
test file only covered the cloud render --page-motion-blur flag that
this branch removes.
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(core): preview playback lands where a seek does when a set interrupts a tween
* test(core): the preview overlap fix holds under a nested time scale and fires each callback once
* style(core): shorten the overlap comment
* refactor(core): the overlap span names its fields and carries no comments
* fix(core): motion blur notices a WebGL canvas the Studio page touched first
When a same-origin host page (Studio) reaches a preview element before the
scene does, for example through a MutationObserver record, Chrome gives that
element the host's prototype. The scene's getContext("webgl") then ran the
host's unpatched getContext, so the canvas never reached the accelerated
canvas set, capture motion blur stayed on, and the WebGL canvas froze during
preview playback.
The tracker now records a canvas in the set of the canvas's own window, and
installs itself in a same-origin parent too, compiled with the parent's own
Function. Cross-origin parents are skipped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(studio): track accelerated canvases in Studio's window, not the embedder's
The preview runtime no longer installs the canvas tracker in its parent
window. That ran on every preview boot, so any same-origin page embedding
the player got its getContext wrapped, and a host CSP without
unsafe-eval reported a violation on each load. Studio now installs the
tracker in its own window when its Player module loads, and core
exposes it at @hyperframes/core/page-motion-blur.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: keep the comment share of the touched files from rising
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(skills): reverse asks to top up once for the run's total
* docs(skills): reverse builds the project with init --video
* test(skills): pin reverse's init --video and top-up-once wording
* docs(skills): a refusal with no amount runs a plain top-up
* fix(cli): prefer OAuth session over HEYGEN_API_KEY
A signed-in `hyperframes auth login` session now wins over an API key in
the environment, so a paid plan on the login is used even when the shell
also exports HEYGEN_API_KEY.
New order: HEYGEN_ACCESS_TOKEN (host app), the stored login (refreshed
when expired), HEYGEN_API_KEY, HYPERFRAMES_API_KEY, the saved API key.
The env key still comes first when HEYGEN_API_URL or HEYGEN_API_BASE
names a host app's gateway, and takes over for the rest of the process
when the login cannot be refreshed. The media-use audio helper follows
the same order.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): fall back to the env key only when HeyGen refuses the refresh
A 503, a network error or an overlapping refresh no longer switches the
process to HEYGEN_API_KEY. The fallback now needs a refused grant or an
expired login with no refresh token. A login another process stored since
is re-read first, and refreshes that overlap in one process share one
request. The media-use helper no longer treats HeyGen's own API base as a
gateway.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): keep the login when the refresh reply can't be read
Only a 400/401 from the token endpoint marks the grant as refused and
switches to the env key. A 200 whose body can't be parsed now throws and
leaves the login in place, so the next call refreshes again.
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(studio-server): two exports in the same second get separate jobs
* fix(studio-server): name the same-second job id helper instead of commenting it
* fix(cli): snapshot stops Chrome decoding videos it already replaces with extracted frames
* fix(cli): a held video keeps its intrinsic size in snapshot, as in render
* fix(cli): shorten a comment line in the held-video hints
* fix(cli): the latest version in _meta is never older than the running CLI
* fix(cli): keep the daily recheck cadence when the cached latest is older than the running CLI
* feat(preview): an unplayable video shows its own frame while its copy is made
* fix(preview): drop the comment on the copy's light flag
* fix(preview): shorten the still helper's doc comment
* test(preview): each proxy-wait test starts with no copy report left over
* fix(preview): cached still frames are swept with the proxy copies
* docs(changelog): weekly digest 2026-10-26-2026-11-02
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(changelog): embed the Oct 26-Nov 2 weekly video
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(changelog): tighten two claims from review
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>