Skip to content

feat(skills): reverse any video on the HyperFrames plan - #6754

Merged
jrusso1020 merged 19 commits into
mainfrom
miao/reverse-skill
Oct 30, 2026
Merged

jrusso1020 merged 19 commits into
mainfrom
miao/reverse-skill

Conversation

@WaterrrForever

@WaterrrForever WaterrrForever commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

Reverse Any Video, on the HyperFrames plan. A person types Reverse this video: clip.mp4 in Claude Code. Reverse rebuilds the video as an editable HyperFrames project, and HeyGen re-shoots the people in it. It is a Pro feature: a Free account is told so and given the upgrade step.

Why

This is the hero skill for Launch 1, and the first skill that spends HyperFrames plan credits from the CLI. It follows Wenbo's decisions of Oct 29 (HF CLI & Skills × Subscription):

  • Reverse is Pro only, gated at the prompt level.
  • Upgrades go to /pricing.
  • Money comes up only when a step doesn't fit.
  • Spend is capped only when the person names a limit.

How

Skills

  • reverse-any-video (new): the facts agents get wrong (size, frame rate, cuts, sound), how to treat each layer, the people recipe, and one check at the end.
    • Signed out: explains why HeyGen, then asks for ! npx hyperframes auth login.
    • Signed in on Free: says Reverse is part of Pro, gives ! npx hyperframes billing subscribe, and starts nothing.
    • Signed in on Pro: says one line, then works. Resuming re-checks sign-in the same way.
  • media-use is the one gate for HeyGen calls: sign-in first, no prices or balances while working, and when a step won't fit it says so and asks before dropping or shrinking it. A credit limit the person names goes on every call as --max-credits.
  • remotion-to-hyperframes compares with hyperframes diff instead of its own two shell scripts.

CLI

  • media-use generate: makes an image, video shot, avatar, voice or music on HeyGen v3.
    • --plan prices the call and runs the same checks as the real call, without sending it.
    • --max-credits refuses, before sending, any call over the limit.
    • A motion guide with a cut inside is refused; a returned clip reports cuts_inside.
    • The same call again picks up the same job, so nobody pays twice. A failed or refused job is sent as a new one, for the account that sent it.
  • measure (probe, cuts, events, frames, extract, curve, energy, look, face, sync) and diff: what Reverse rebuilds from and checks against.
  • auth status: shows the HyperFrames plan and credits, read from /v1/payment/hyperframes/subscription on api2.
  • billing subscribe / billing top-up: open the plans or credits page, then wait until the account is on Pro or the balance covers the step.
  • check no longer copies VP9 and AV1 videos before it starts. Chrome plays them, alpha included, and the copy took minutes on projects with cutouts.

Test plan

  • Unit tests added/updated.
  • Manual testing performed: journeys J1–J6 on a dev Pro test workspace, all passing at 1278ae2e7. The retry check was run again live at e5156dd85, the final commit, because the commits after 1278ae2e7 changed only comments and the retry code.
  • Documentation updated: the skill files and docs/packages/cli.mdx.
Journey Result
J1 sign in Pass: asks for ! npx hyperframes auth login. Used linked test credentials, so the HeyGen sign-in page itself was not checked
J2 Free hits Pro Pass: gives billing subscribe, starts nothing
J3 Pro makes the video Pass as far as dev allows: dev HeyGen has no video nodes, so the agent retries once, then stops and asks
J4 credits run out Pass: says the step needs about 782 and nothing was charged, then gives billing top-up --needed 782
J5 credit limit Pass: refuses a step over a limit of 100; --max-credits on every paid call
J6 coming back Pass: asks whether the credit limit still applies, with no numbers
Paid retries Pass at e5156dd85: a failed job sent again gets a new job; a completed job sent again returns the same job, and the balance moves once

Not in this PR

  • /pricing and /account/plan on hyperframes.studio: both still 404 today, so billing subscribe and billing top-up open a missing page until they ship.
  • Dev HeyGen has no video nodes, so a real re-shoot has not run on dev. It ran in the bench against production HeyGen.

Base automatically changed from feat/cli-hyperframes-oauth to main October 29, 2026 21:48
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 102 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (1)

  • seqplainkeys-none-pct-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@mintlify

mintlify Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 30, 2026, 11:16 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

Reverse rebuilds a video as an editable HyperFrames project and re-shoots its
people with HeyGen. It runs for a person signed in on HyperFrames Pro; Free is
told it is a Pro feature and given the upgrade step.

- skills: reverse-any-video; media-use is the one gate for HeyGen calls
  (sign-in, credits, refusals)
- cli: media-use generate (image, video shot, avatar, voice, music) with --plan
  pricing and --max-credits; measure, diff and analyze for the rebuild; the
  HyperFrames plan in auth status; billing subscribe and top-up wait on the
  plans or credits page
- remotion-to-hyperframes compares with `hyperframes diff` instead of its own
  shell scripts

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Comment thread packages/cli/src/heygen/generate.ts Dismissed
- docs: list diff, measure, analyze and billing in "Find your command" in
  docs/packages/cli.mdx, each with its own section.
- skills: bring media-use/SKILL.md under its 5120 B budget by pointing setup,
  direct-recipe and treatment detail at the references that already hold it;
  the sign-in, credits, --max-credits and billing rules stay in SKILL.md.
- comments: register transcript.json and REVERSE.md as project files for the
  citation checker, and drop a history note from face.test.ts.
- comments: cut restating prose or move it into names so every changed file is
  at or under its comment share. Allowlist the two deleted remotion scripts in
  check-no-main-deletions.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
probeVideo passed the input path straight after its options, so a path
starting with "-" would be read as a flag; the producer's ffprobe argv
contract test failed on it. runFfmpeg's fixed flags move to a constant
so the contract no longer mistakes that ffmpeg wrapper for a probe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@jrusso1020

Copy link
Copy Markdown
Collaborator

@WaterrrForever I rebased this onto main (now includes #6687) and pushed three commits on top of yours. James asked me to restack and test it.

Pushed:

  • d016247e makes the CI gates pass:
    • the deletion guard now allowlists the two remotion scripts
    • diff, measure, analyze and billing are added to docs/packages/cli.mdx
    • media-use/SKILL.md is cut from 6142 to 5096 B, mostly by pointing at references that already held the detail; the sign-in, credits, --max-credits and billing rules stay in SKILL.md
    • comment citations and the ratchet are fixed
  • 0909ef6d adds -- before the path in probeVideo's ffprobe call. A path starting with - would have been read as a flag, and the producer's ffprobe argv contract test caught it.

Your call:

  1. src/measure/diff.test.ts is too slow for CI. The test at line 396 hit the 20 s per-test limit at 20.5 s; two others take 17 s and 20 s. The flake gate timed the whole file out at 120 s in both runs, and it takes ~131 s on CI. Options: split the file into 2–3 files, split the heavy tests, or use smaller clips. Per-test timeouts won't clear the gate's per-file limit.
  2. Two CodeQL alerts, both look like false positives:
  3. Conflicting cost rules. skills/media-use/references/setup-providers.md still has "Cost rule (X4)": confirm before a paid call. That conflicts with the new Credits rule in SKILL.md: plan for the best result and never mention prices.
  4. Windows engine-cli lane hits its 16-minute deadline on main too. This PR adds ~134 s to it, which makes it worse.

End-to-end runs of J1–J6 on dev are in progress; results to follow.

— Rames

@jrusso1020

Copy link
Copy Markdown
Collaborator

@WaterrrForever End-to-end results on dev at d016247e. Runs were on James's Mac in a clean project (project skills only), signed in as james.russo on his own workspace, using claude -p.

Journey Result
J1 sign in Pass. HF-branded authorize page, one command, no prices, plan read first
J2 Free hits Pro Pass. Says it's Pro, gives billing subscribe, starts nothing, 0 spent
J3 Pro makes the video Pass as far as dev allows. The re-shoot 422s (no video nodes on dev). The no-re-shoot path built, checked, rendered and diffed (SSIM 0.991). An image edit charged exactly 67
J4 credits run out Blocked. The dev plans tool can't leave a workspace at ~50 credits
J5 credit limit (100) Fail. It priced the image (67) and the shot (782), then dropped the person re-shoot without asking. people.md step 3 says to ask first. It never made a paid call, so --max-credits never ran
J6 coming back Pass, but it volunteered "782 credits… about 24.9k on Pro", which breaks the no-prices rule

Bugs

  1. generate --plan accepts a request the real call then refuses. The --prompt check at heygen/generate.ts:137 only runs when the request is built for sending.
  2. The re-shoot recipe in reverse-any-video/references/people.md:12 leaves out --prompt, so it always fails as written.
  3. Nothing tells the agent to ask after pricing with --plan. media-use only says to ask after a refusal. This is what caused the J5 failure.
  4. J6: price and balance mentioned unasked.
  5. billing subscribe opens prod hyperframes.studio/pricing (404) even on dev (heygen/plan.ts:52-54).
  6. media-use/references/heygen.md:12 says images need an API key, but a sign-in image edit worked and was charged. heygen.md:50 says $1/60 rounding, while pricing.ts has none.
  7. auth status prints "HyperFrames balance unavailable here." (commands/auth/status.ts:301) right above the line that shows the credits.
  8. Minor: the success page says "Signed in to HeyGen".

Caveat: -p mode may push the agent to finish rather than stop and ask, which matters for J5. Worth rechecking J5 interactively.

— Rames

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Walked this on dev: Free status shows and reverse is refused with the subscribe command, as intended. Three things before merge:

  1. The subscribe link uses ?source=cli; the pricing page reads ?from=cli to bring someone back to their terminal after paying (the agreed paywall contract). With source= a CLI buyer never gets back. Could it be from=cli?
  2. plansUrl() in cli/src/heygen/plan.ts falls back to the production pricing page even with HYPERFRAMES_ENV=dev, so a dev walk of subscribe lands on prod. Could it follow the env like the API host does?
  3. CI is red at 0909ef6: Test (cli), Flake gate, Test, CodeQL.

…ile limit

The file took ~131 s on CI against the flake gate's 120 s, and its heaviest test (four
comparisons) hit the 20 s per-test limit. The video tests now live in three files sharing
diff.test-helpers.ts, and that test is two tests of two comparisons each.

analyze reads each part once and checks the bytes it read, instead of a stat then a read
(CodeQL js/file-system-race).
…n't fit

On dev with a 100-credit limit the agent priced a re-shoot with --plan and dropped it without
asking (J5), and in J6 it told the person a price. SKILL.md's Credits rule now covers both:
--plan is for the agent, and when a step won't fit (a --plan over what is left, or a refused
call) the agent says what doesn't fit and asks before dropping or shrinking it.
setup-providers.md's older confirm-before-paying rule points to it.

--plan now runs the same checks as the real call (checkRequest), so it no longer prices a
request that would be refused. The re-shoot recipe in people.md gains the --prompt it needs.
heygen.md: a sign-in also makes images, and --plan's usd is the API charge rounded to $1/60
while its credits come from the list price.
@jrusso1020

Copy link
Copy Markdown
Collaborator

Rerun at 23dda073e (James's Mac, dev, SSH only)

Check Result
auth status Pass. No "balance unavailable" line; ends HyperFrames: Free · Upgrade to Pro: …/pricing?source=cli
J2 Free hits Pro Pass. "Reverse is part of HyperFrames Pro … I haven't started on clip.mp4 yet … ! npx hyperframes billing subscribe". No prices, nothing created, credits unchanged
--plan without --prompt Pass. Errors --prompt is required for this type, same as the real call
J6 Coming back Fail. No re-sign-in and it found rebuild/, but both runs said "one 8 s shot costs 782 credits and your cap was 100"
J3, J5 Not run yet: need the dev workspace on Pro

CodeQL #1271 dismissed as a false positive (intended download to --out).

Still open

  1. J6 price leak. On resume the agent never loads reverse-any-video, so the new credits rule and the auth-status check don't run. It repeats the price the earlier run wrote into REVERSE.md (now stale: --plan says 400 for the same shot) and reuses the REVERSE.md re-shoot command, which has no --prompt. Suggest: reverse-any-video/SKILL.md says REVERSE.md holds no prices or credit numbers, and says to re-check auth status and the start rules when resuming.
  2. --plan misses two checks. checkRequest (packages/cli/src/heygen/generate.ts:301) skips the --out extension check (:331) and mask-without-motion (:337). --plan with --mask but no --motion, or --out a.png for a video, returns ok: true, credits: 400; the real call refuses both.
  3. Nit: auth status --json still sets billing_notice: "HyperFrames balance is unavailable here…" when the plan is shown (packages/cli/src/commands/auth/status.ts:216).

— Rames

jrusso1020 and others added 4 commits October 30, 2026 08:38
Under the gate's CPU contention every diff test needing face detection or
optical flow paid opencv.js's 11 MB download and WebAssembly start-up, and
every diffVideos call wrote four worst-moment pictures (about 28 ffmpeg runs)
no assertion read. Tests ran 21-28 s against the 20 s limit.

- loadOpenCvFirst() stages opencv.js in a beforeAll (from this machine's
  cache, a temp copy kept between runs, else the network).
- diffVerdicts() runs diffVideos with worst: 0 for tests that only read
  verdicts; the worst-moment tests keep the pictures.
- The carried-text test is split into its dirty and clean halves.

Locally with 8 CPU burners on 8 cores: slowest test 7.7 s, files 24-30 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
checkRequest now holds every pre-send check (the --out extension and
--mask without --motion joined the route body and guide checks), and
generate() runs it first, so --plan and the real call share one function.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…lance notice beside the plan

- pageUrl sets from=cli, the parameter the pricing page reads to send a
  CLI buyer back to the terminal.
- The plans and credits pages are on dev.hyperframes.dev with
  HYPERFRAMES_ENV=dev, hyperframes.studio otherwise.
- auth status --json omits the 'HyperFrames balance is unavailable' notice
  when the plan was read.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…d holds no prices

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@jrusso1020

Copy link
Copy Markdown
Collaborator

Fixes pushed (signed): d6d30a790 flake-gate diff tests, 76c14e766 --plan refuses what the real call refuses, 55e08df8c from=cli + pricing link follows HYPERFRAMES_ENV + no balance notice beside the plan, 245aea8b7 resume re-checks sign-in and REVERSE.md holds no prices. Flake gate is still red on one test (diff-people.test.ts "whole frame played…", 20.6 s vs 20 s); fixing that next.

E2E at 245aea8b7 on a separate dev Pro test workspace (not James's), James's Mac over SSH:

Journey Result
J3 Pro makes the video Pass. Re-shoot carried --prompt; dev 422'd 3 times, agent stopped and asked; "use original footage" rendered (SSIM 0.993). No prices
J4 Credits run out (50) Pass. "That step needs 782 credits and you have 50 … ! npx hyperframes billing top-up --needed 782". No price talk before the refusal; declining top-up paused without dropping anything
J5 Credit limit (100) Pass, nit. Priced with --plan, asked before spending; --max-credits on every paid call. Nit: the ask included a price table with the optional 67-credit image edit
J6 Coming back (new session) Fail. Loaded the skill, re-ran auth status, REVERSE.md had no prices, but said "the balance is still 25,000", and made 5 more video tries without asking
J6 resume of an old project Pass, gap. Removed "782 credits" from REVERSE.md itself and asked before the re-shoot; the re-shoot command left in REVERSE.md still has no --prompt

New findings

  1. Balance quoted on resume despite start.md:43 and media-use SKILL.md:38: the agent echoes auth status's "pro plan, 25,000 credits" line. Suggest saying explicitly not to repeat the auth status balance.
  2. Retry loop: 5 generate calls in one turn vs people.md:21 / heygen.md:19 (regenerate a failing shot once).
  3. packages/cli/src/heygen/v3.ts:47 prints "this step needs more" when the server sends no needed_credits (dev never does); the agent filled in 782 from its own --plan. CLI could fall back to its own estimate.
  4. Top-up link plan.ts:53 → /account/plan still 404 (fix(studio-server): a copy another process holds without progress ends in a named error #6918).
  5. An old re-shoot command in REVERSE.md isn't repaired on resume; nothing says the stored command must run as written.

Dev HeyGen video generation 422s every request (generation_failed), as before.

— Rames

jrusso1020 and others added 2 commits October 30, 2026 09:04
It ran five diff comparisons in one test and took 20.2-20.6 s under the flake
gate's contention, over the 20 s limit. Each piece now runs at most two
comparisons against a shared ledger: copy and burned-in title, footage cut
whole, and cuts that only fed a generation. Under 24 CPU burners locally the
slowest of the three takes 3.0 s, where the combined test took 4.9 s under 8.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…redits names the step's price

- media-use generate: when HeyGen refuses for credits without saying how many, the refusal
  names the CLI's own price for the call (what --plan prints) as "needs about N" and
  "billing top-up --needed N", instead of "needs more".
- reverse-any-video: never repeat auth status's plan/credits line; a step an earlier session
  gave up on gets one more try, then ask; a command stored in REVERSE.md is a note, rebuilt
  from its recipe and checked with --plan, never run as written.
- people.md / heygen.md: a refused or failed job gets one more try, test calls included,
  then stop and ask.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@jrusso1020

Copy link
Copy Markdown
Collaborator

J6 fixes pushed (signed): 1d219429b. All checks green.

  • packages/cli/src/heygen/v3.ts:32-46: when the server refuses for credits without needed_credits, the CLI uses its own --plan estimate ("this step needs about N", billing top-up --needed N). The server's number still wins when sent. Tests in v3.test.ts, generate.test.ts.
  • reverse-any-video/start.md:44: auth status is for sign-in only; never repeat its plan or credits line.
  • reverse-any-video/SKILL.md:14: on resume, a step the last session gave up on gets one more try, then ask.
  • reverse-any-video/SKILL.md:38: a command stored in REVERSE.md is a note. Rebuild it, check it with --plan, never run it as written.
  • people.md:21, heygen.md:19: a failed or refused job gets one more try, then stop and ask.
  • Not changed: media-use SKILL.md, which is at its 5,120-byte cap.

J6 rerun at 1d219429b (dev Pro test workspace, not James's):

Case Result
J6a new session resuming the J3 project Pass. Rebuilt the stored command, checked it with --plan, one retry, then asked ("Keep the original footage for now…"). No balance quoted
J6b resume of the old project (stale re-shoot command) Pass on the command: rebuilt it with --prompt, checked with --plan, spent nothing. Open: REVERSE.md said a credit limit was set without the number, so the agent asked for one and listed step costs ("67… 782… 849 credits in total")

Open for you

  1. J6b: is listing step costs OK when asking for a credit limit? If not, the resume path could ask whether a limit still applies without naming numbers.
  2. CLI: rerunning a failed generate with the same inputs returns the same failed job (same job id), while the error says "fix the request it names, then run again". So a plain retry never reaches HeyGen.

No credits spent (still 25,000).

— Rames

jrusso1020 and others added 2 commits October 30, 2026 10:49
…f the limit still applies

- runJob: a key whose job failed or was refused is recorded in the config dir
  (heygen-failed-keys); the next run with the same inputs sends key-1, key-2, ... so HeyGen
  starts a new job. A finished or still-running job keeps its key and is picked up, so
  nothing is paid twice. "fix the request it names, then run again" now holds.
- reverse-any-video: on resume after a run under a credit limit, just ask whether it still
  applies, naming no costs or numbers.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The skill rebuilds from measure probe, cuts, frames, events and face, and checks with
diff. analyze, diff --explain and eight measure commands (trail, box, ink, rhythm,
objects, track, flow, continuity) have no caller, so they go, with their tests and docs.
Examples now use original.mp4 instead of the old .reverse/source.mp4 evidence folder,
and diff's --segments is described as a file rather than REVERSE.md.
Chrome plays both, alpha included, and the proxy is made on demand if a browser ever
asks. Copying remove-background's VP9 cutouts took minutes on projects that have them.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 6c608fa1519d32a60cd655fb9f9d7b1b49cbfcc2. COMMENT-only as requested; the Important items below need fixes before a stamp.

Need: yes · Size: large but separable by subsystem · Reuse: existing credential resolver, authoritative HF price rows, and timestamp-preserving audio decode · Simpler: keep fixes inside the existing helpers · Structure: measure/diff.ts mixes capture, scoring and reporting in 1,391 lines; extract those existing groups in a follow-up
Risk: high — new paid retry identity and credit-limit enforcement.

Audited: generation/request checks, pricing, v3 retries and failed-key persistence, plan/billing/auth integration, changed skills/references and removed-command references; a fresh independent pass also audited measurement/diff orchestration, decoding, scoring and browser prewarm. Limits: no real paid calls, OAuth journey, live price-book lookup, browser/OpenCV walk or full monorepo build. Backend comparisons use source pinned to EF prod 5fb56d1de90cc3298e93576dbf22770311214b13, not a claim about running pods. Existing peer comments and the two CodeQL inlines were read; the findings here are additional.

Important findings (details, fixes and regression criteria inline):

  1. heygen/v3.ts:191: accepted POST with a lost response → retry receives 401 → re-login rotates to a new key and creates a second job. Authentication refusal does not prove the old job failed. Preserve that replay identity and scope stored failures by payer/API origin.
  2. heygen/pricing.ts:57: a valid 5-second reference shot with nine images estimates 250 credits and passes --max-credits 250; the pinned approved HF rows calculate 260. API list USD ×1,000 is also no longer the current HF video rate. Include billable reference images and calculate HF credits separately from API dollar pricing.
  3. measure/sync.ts:20: decoding raw audio drops its leading timestamps. An unchanged soundtrack remuxed 200 ms later reports +1 ms, high confidence; the aligned control reports +1.8 ms. Preserve the picture-clock offset before computing the lip-sync gate.
  4. measure/look.ts:65: identical grayscale footage returns saturation_ratio: 0 and same_look: false. Handle zero/zero as equivalence, including the sibling zero-motion ratios in measure/energy.ts; test zero versus nonzero separately.

Nit — finish the direct-auth instruction migration: reverse-any-video/references/people.md:3 still tells a signed-in Pro person without an API key to crop the original instead; media-use/SKILL.md:36 requires the separate heygen executable even for the new direct path; media-use/references/heygen.md:7 describes a shared ~/.heygen/credentials store, whereas auth/paths.ts isolates HyperFrames credentials. Remove the keyless crop fallback, narrow the executable requirement to helper-backed operations, correct the store path, and pin these concepts. No live agent-behavior claim.

Nit — sibling billing recovery: skills/media-use/audio/scripts/lib/heygen.mjs:219–240 sends both new billing refusals to the production homepage by default, including in dev. Print the new billing commands or reuse the environment-aware destination contract, and test without URL overrides.

Verification on this head: bun run --cwd packages/cli test --no-cache src/heygen/ src/commands/auth/{status,status-user,login}.test.ts 72 passed; ... test --no-cache src/measure/ src/utils/checkBrowser.test.ts 123 passed. Own parsers/lint/studio-server/core outputs were built using borrowed remaining dependencies; initial unbuilt-export collection errors were resolved before these counts. Two additional actual-module controls reproduce auth rotation and the 250-credit admission (and verify a 249 cap refuses); four isolated retry controls distinguish direct replay from terminal-failure rotation. Real FFmpeg fixtures reproduce timestamp loss and grayscale self-comparison, with aligned/identical controls. Skill checks: 5 core tests and 19 audio-helper tests pass. The removed analyze, diff --explain and eight retired measure commands have no remaining executable references in the audited skills/docs. --plan still accepts missing image/audio paths that real generation refuses before HTTP; add local-media validation to checkRequest as a nonblocking completeness fix. Standalone --drawn is explicitly advisory, so its overall-pass behavior is not reported as a defect.

Verdict: COMMENT
Reasoning: The paid retry, credit-cap and two measurement boundary cases above reproduce despite the passing focused suites. Fixes and concrete regression criteria are in the inlines; no approval or merge was performed.

— hf-review

Comment thread packages/cli/src/heygen/v3.ts Outdated
try {
job = await call("POST", route, body, sent, opts.credits);
} catch (err) {
if (err instanceof Refused) markFailed(sent);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (P2): preserve the key across an authentication refusal. A POST can create a paid job and lose its response. The documented same-command retry then receives 401; this catch appends its key to heygen-failed-keys. After sign-in, liveKey sends key-1, creating another job rather than retrieving the accepted one. My actual-module offline witness observed keys [k,k,k-1] and 2 created jobs; direct replay without the 401 creates only 1. The machine-global failure set also lets account B's refusal rotate account A's previously successful key.

Please carry refusal status/kind and leave replay identity intact for 401/403 and other outcomes that cannot establish terminal failure. Scope persisted failure entries by API origin and stable payer/account identity, and rotate only a confirmed terminal failed job or a rejection known to be memoized. Regression criterion: accepted/lost response → 401 → re-auth returns the original job and creates exactly one; another account's refusal cannot change that result; a genuinely failed job can still get a fresh key. These are offline actual-module witnesses using a stand-in backend, not a live duplicate-charge claim.

Comment thread packages/cli/src/heygen/pricing.ts Outdated
if (r.audio || r.references?.length || r.motion) {
const guide = r.motion ? Math.min(GUIDE_MAX_S, probeVideo(r.motion).pictureDuration) : 0;
return at(
(seconds + guide) * VIDEO_REF_768P_PER_S,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (P2): the reference-image count can exceed the person's credit cap. generate.ts sends all references (the backend accepts nine), but this estimate charges only seconds. A 5-second shot with nine images returns 250 credits; checkLimit(250,250) permits it. Pinned EF prod 5fb56d1de90cc3298e93576dbf22770311214b13 creates both billable-second and extra-image HF work lines; its approved Oct-28 rows charge 100/3 HF credits per second and 40/3 per image after the first two: ceil(5*100/3 + 7*40/3) = 260.

Please count billable images and compute HF credits from the HF rates separately from API USD (creditsOf's list-USD ×1,000 is stale for these video rows). Keep both calculations in the existing pricing helper; add 1/2/3/9-reference fixtures, a 250-cap refusal for this nine-image request, and a sufficient-cap control. Also include the extra images in API USD pricing. Source evidence: HF work construction, price rows. No paid request was sent.

/** Mono samples of the window at RATE Hz. */
function samples(info: VideoInfo, start: number, seconds: number): Float32Array {
const raw = runFfmpeg([
...["-ss", String(start), "-i", info.path, "-t", String(seconds), "-vn"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (P2): retain audio timestamps when building the correlation curve. Raw f32 output here begins at the first decoded audio sample; its leading timestamp is discarded. I generated aligned picture/audio bursts, then remuxed the same audio with -itsoffset 0.2 and stream copy. probeVideo.audioStart becomes 0.2, yet avSync reports +1 ms / high confidence, versus +1.8 ms for the aligned control; the expected picture-versus-audio lag is about −200 ms. person.ts uses that high-confidence result as its lip-sync gate, so this can pass an actually delayed soundtrack.

Decode on the picture/window clock, padding leading silence and preserving the seek origin. The timestamp-preserving aresample=async=1:first_pts=0 approach already used by diff.ts:onsetCurve is a starting point; account for nonzero requested windows too. Add remuxed positive/negative-offset fixtures plus aligned/windowed controls; the 200-ms case must report roughly −200 ms and fail the broadcast tolerance. A local loader-only control adding timestamp padding changes the delayed result to −198.8 ms, while the aligned control stays +1.8 ms; production source was not edited.

Comment thread packages/cli/src/measure/look.ts Outdated

/** Yours over the reference's: ratios near 1 and differences near 0 are the same look. */
export function compareLook(ref: Look, yours: Look): LookComparison {
const ratio = (k: "luma" | "contrast" | "saturation") => round(yours[k] / (ref[k] || 1e-9), 2);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (P2): matching zero values must compare equal. An actual grayscale fixture compared with itself has saturation 0 on both sides. This fallback calculates 0/1e-9 = 0, so sameLook returns false despite identical frames (the luma/contrast ratios are 1). It is user-visible as measure look --compare <same file> → same_look:false, and it feeds the no-face regenerated-shot verdict.

Handle both-near-zero values as a match (ratio 1, or an explicit equality result) and distinguish one-zero/one-nonzero values. Apply the same policy to zero luma/contrast and the zero-motion ratios in measure/energy.ts:37–38, which otherwise reject two matching static clips. Add identical grayscale/black/static controls and a real color/motion difference control. No live generated-film claim; this reproduces with local decoded fixtures.

Retries keep their key after 401, 403, 408, 409, 425 and 429, which say
nothing about the job, and failed keys are scoped to the API and payer.

Credits come from the plan's video rows instead of list dollars, and
reference pictures past the first two count toward --max-credits.

Lip sync reads sound on its own timestamps, so a delayed track fails
the gate. Look and energy call two near-zero values the same.

--plan refuses a missing input as the real call does. The skills teach
the direct sign-in, its credential path and the billing commands.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at e4ec18a250a4a5938853badf32034844b814ef5d, delta from my COMMENT 5712181341 at 6c608fa1519d32a60cd655fb9f9d7b1b49cbfcc2.

Need: yes · Size: focused repair delta · Reuse: existing credential resolver and authoritative HF price rows · Simpler: retain the existing helpers · Structure: fixes stay in their owning helpers; earlier diff.ts decomposition remains a follow-up
Risk: high — a remaining cross-payer replay identity case can create another paid job.

Verdict: COMMENT. Three original Important findings are fixed; the retry finding is fixed for 401 replay and API-key identities, but remains reproducible for the supported host OAuth path. The concrete remaining fix and approval criterion are inline at heygen/v3.ts:45–48.

Verified repairs:

  • 401 replay: accepted POST with a lost response → 401 → reauthentication now sends the same key three times and creates one job in my actual-module offline witness. The six transient/auth refusal statuses preserve their key; confirmed terminal failure still rotates.
  • Credit cap: a five-second reference shot with nine images now estimates 260 HF credits, refuses cap 250, and accepts cap 260. One/two/three/nine images yield 167/167/180/260. I compared active rows against both EF prod 5fb56d1de90cc3298e93576dbf22770311214b13 and current EF master 08ecb80fb57fff8a989a8edd2dd415bd418d3f46: short 768p text video is 12 credits/second, reference video 100/3 per billable second, and each image after the first two 40/3. This is source parity, not a live rate-book or charge claim.
  • Sync clock: actual FFmpeg aligned control is +1.8 ms/pass; audio timestamp delayed 200 ms is −198.8 ms/fail; video delayed 200 ms is +201.2 ms/fail. Nonzero-window controls agree.
  • Zero comparisons: decoded identical grayscale/black now compare equal; static/near-zero motion ratios are 1. Gray versus color, black versus gray, and zero versus real motion remain unequal in both directions.
  • The three auth/skill documentation nits, both helper billing-refusal commands, and missing-local-input validation are corrected. Positive concept checks cover the changed skill contract.

Remaining Important — same original retry-scope class: the real credential resolver supports HEYGEN_ACCESS_TOKEN without a credential file. payerScope nevertheless reads optional local display metadata for OAuth. With no metadata, host A and host B share an empty payer identity. Actual-module offline sequence A completes → B receives 402 → A resumes sends [A:k, B:k, A:k-1] and creates a second A job. A stale local user block also is not bound to the host bearer. Scope persisted failures to a principal bound to the actual credential; do not rotate based on missing/unrelated display metadata. I will approve when the absent/stale-metadata A/B/A witness replays A's first job, while the existing 401 and terminal-failure controls still hold.

Evidence and limits: at 1278ae2e79a640bfbea80a850998cac9ff1bd1c6, focused HeyGen/auth suites plus my original two actual-module witnesses: 82 passed; a separate real-resolver host OAuth witness: 1 passed, asserting the remaining defect; measurement suites: 128 passed; core-skill/audio-helper checks: 25 passed. Fresh independent code and measurement passes and 14 additional look/energy assertions agree. Built own parsers/lint/studio-server/core outputs with borrowed remaining dependencies; resolved initial missing workspace-export setup before final counts. The successor delta was read: comment removal and a constant rename only; affected estimator/comparison and own regression tests were rerun there: 20 passed (bun run --cwd packages/cli test --no-cache src/heygen/{generate,review6754,reviewScope6754}.test.ts src/measure/compare.test.ts). Earlier commands: ... test --no-cache src/heygen/ src/commands/auth/{status,status-user,login}.test.ts, ... test --no-cache src/measure/ src/utils/checkBrowser.test.ts, and node --test scripts/check-core-skills.test.mjs skills/media-use/audio/scripts/lib/heygen.test.mjs. No live paid request, OAuth journey, J1–J6 rerun, browser/OpenCV acceptance, full clean-install build, merge or queue action. Home owns J1–J6 and publication follow-through.

— hf-review

Comment thread packages/cli/src/heygen/v3.ts Outdated
async function payerScope(): Promise<string> {
const credential = await resolveCredential().catch(() => undefined);
const user = credential?.type === "oauth" ? (await readStore()).credentials.user : undefined;
const who = credential?.type === "api_key" ? credential.key : (user?.email ?? user?.username);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (P2), remaining instance of the original retry-scope finding: HEYGEN_ACCESS_TOKEN is a supported host-managed OAuth credential without any local credential file (auth/resolver.ts). For that path, readStore().credentials.user is absent, so every host bearer gets the same API + "\0" scope. A local user block, when present, is display-only and may belong to a different account than the environment token.

I reran an offline actual-module witness using the real resolver/store/header builder: host A completes key k, host B receives 402 for k, then A resumes. Wire keys are [A:k, B:k, A:k-1] and two A jobs are created. The 401 lost-response witness is fixed; this distinct payer-switch case still breaks replay.

Please bind OAuth failure scope to the actual resolved bearer's principal (reuse authenticated user identity rather than local display metadata), and never persist rotation under an empty/unrelated payer identity. Add A/B/A controls with absent and stale local user metadata; A must receive its original job with one creation. Keep the 401/same-key and confirmed-terminal-failure/rotate controls. With that change and those witnesses passing, I will approve. No live translation/generation or charge was made.

Failed keys were scoped by the local user block, which is absent for a
host's token and display-only otherwise, so two accounts could share a
scope and one could start a second paid job.

The scope now comes from the account /v3/users/me reports for the token
in use. When that cannot be told, no failure is recorded and the key
stays the same.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at a1a9028b9a7f3e129cb2d37f4d65e4798f1ceacd; narrow delta from e4ec18a250a4a5938853badf32034844b814ef5d and COMMENT 5712427712.

Need: yes · Size: focused · Reuse: AuthClient, credential resolver and refresh helper · Simpler: capture one sender context and retain it through failure recording · Structure: helpers remain small; avoid repeated resolution of one operation's owner
Risk: high — relabeling a failed paid operation can rotate another account's successful replay key.

The original host-token A success → B 402 → A resume witness is fixed with both absent and stale local display metadata: A sends its original key and creates one job. AuthClient now authenticates the actual OAuth credential instead of trusting a local user block.

Important (P2), same retry-identity class, new in this repair: heygen/v3.ts:67–71 resolves the payer again in markFailed, including after a long poll at line 242. That current account may differ from the account that submitted the job. My actual-module offline witness writes a real credential file, as another auth login process can:

  1. B completes key k.
  2. A submits its own k; the job is pending.
  3. While A's failure poll is in flight, credentials change to B; the response says A's job failed.
  4. markFailed stores A's failed key under B. B's resume sends k-1 and creates another B job.

Observed POST keys: [B:k, A:k, B:k-1], two B creations. The no-switch control replays B:k and keeps one B job. The fix and approval criterion are inline: capture the sending credential/scope once, use it for POST headers/key selection, and retain it to record that job's failure. Do not resolve the newly signed-in account to label an earlier operation. This is a bounded repair in the existing helpers.

Other requested checks:

  • payerScope and call use the same resolver priority and refresh helper, including environment tokens, but separate resolutions do not bind one operation to one credential. A file change between lookup and POST is the same consistency issue; the proposed captured sender context closes both sites.
  • A /users/me failure alone does behave conservatively: after a known failed job, the old key is resent, the same failed result returns, and creations stay one. This was replayed with actual auth modules and a stand-in transport; it is distinct from a successful lookup of a different principal after polling.
  • EF master 08ecb80fb57fff8a989a8edd2dd415bd418d3f46 /v3/users/me delegates to _build_user_info_response, which returns username and email, without a separate user ID. Nit: prefer username ahead of email for the captured scope, subject to its stability contract; no separate reproduced email-reassignment blocker is claimed.

Evidence: bun run --cwd packages/cli test --config review.vitest.config.ts --no-cache src/heygen/v3.test.ts 17 passed. The scratch config substitutes FFmpeg path discovery only and uses borrowed dependencies. A broader v3+generate attempt had 29 pass / 1 fail because this isolated worktree lacks built @hyperframes/core/env-proxy for an existing generate test; no full build/typecheck claimed. Independent fresh adversarial review reproduced the file-switch defect; I replayed its no-switch and lookup-outage controls plus my actual-module absent/stale/file-switch witnesses. Only offline stand-in HTTP was used: no live OAuth, generation, charges or J1–J6 acceptance. Earlier pricing/measurement repairs are unchanged in this three-file delta. No approval, merge, queue or Slack post.

Verdict: COMMENT
Reasoning: The original metadata repro is resolved, but re-resolving the owner at failure time introduces the demonstrated cross-account replay rotation above. I will approve when the file-switch witness preserves B's completed job and records A's failure under A, while absent/stale-metadata, lookup-outage, 401 and terminal-failure controls continue to pass.

— hf-review

Comment thread packages/cli/src/heygen/v3.ts Outdated
}

async function markFailed(key: string): Promise<void> {
const scope = await payerScope();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (P2): retain the submitting account when recording this failure. This new lookup can see a different credential after the POST or long poll. Offline actual-auth-module witness with a real credential file: B completes k; A submits k; another sign-in rewrites the file to B while A's poll is in flight; that response says A failed. This lookup records k under B, so B's resume sends k-1 and creates a second B job. A no-switch control keeps one B job. Original absent/stale-metadata A/B/A now passes; /users/me outage alone only replays the old failed result.

Concrete fix: resolve/refresh the sender once at job start, derive its authenticated scope and POST headers from that same credential, and pass the captured scope into both liveKey and markFailed. Retain that owner through polling; never resolve the newly signed-in account to label a previous operation's failure. If identity is unknown, preserve the original key without recording a shared failure. This also closes the lookup-versus-POST credential-change window.

Approval test: B's completed key stays untouched after A fails across a credential-file switch; A's failure belongs to A. Keep the absent/stale host-token, 401/same-key, lookup-outage and genuine failed-job rotation controls. No live generation or charge was made.

Failure scope was looked up again after polling, so a sign-in during a
job could record one account's failure under another and rotate its
finished key. The credential is now read once per job: it signs the POST
and owns any failure, even if someone else signs in meanwhile.

Prefer username to email, since /v3/users/me returns no user id.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at e5156dd850d3868bac5723aed504a76e037300ff; narrow delta from a1a9028b9a7f3e129cb2d37f4d65e4798f1ceacd and COMMENT 5712544175.

Need: yes · Size: focused sender repair · Reuse: credential resolver, refresh helper and AuthClient · Simpler: one captured sender per job · Structure: fine
Risk: medium — paid replay identity crosses credential refresh and account changes.

The remaining retry-owner finding is fixed. heygen/v3.ts:55–67 resolves and refreshes one credential, uses it for the authenticated identity lookup and POST headers, and retains its scope for both refusal and terminal-job failure recording. liveKey and markFailed no longer resolve the current account. Scope now prefers username over email; an unknown identity neither rotates nor records a failed key.

I reran the actual-module credential-file witness: B completes k; A starts k; the file switches to B during A's failing poll; B resumes with k, keeping one B job. Wire POST keys are [B:k, A:k, B:k]. The original host-token A-success/B-402/A-resume cases also preserve A's key with missing and stale local user metadata.

A fresh independent adversarial pass and my replay additionally cover:

  • A sign-in between identity lookup and POST: the POST still sends captured A.
  • OAuth expiry during polling: the actual refresh helper obtains A2 and the GET sends A2; the failed job is recorded under captured A, while B still resumes its original job. Failure recording does not use the stale sender token or perform another identity lookup.
  • Identity lookup outage: the old failed result replays under its original key and no failure record is added.

Evidence: bun run --cwd packages/cli test --config review.vitest.config.ts --no-cache src/heygen/v3.test.ts src/heygen/transcribe.test.ts → 21 passed. The authored suite retains transient/auth-refusal same-key and genuine failed-job rotation controls. bun review-artifacts/oss6754-r4/root-probe.ts passes the three real-resolver/store cases above; independent switch/refresh/lookup/outage scripts use the actual resolver, AuthClient, OAuth and credential store, with stand-in HTTP. I replayed refresh and lookup controls. Borrowed dependencies and a scratch FFmpeg-discovery-only alias were used for the focused suite; independent transpiled controls substitute only error/link presentation. No clean-install/full build, live OAuth, generation/charge, or J1–J6 acceptance claim. Pricing, measurement and skill repairs from prior rounds are unchanged in this three-file delta. Home owns live acceptance and publication follow-through; no merge or queue action taken.

Verdict: APPROVE
Reasoning: The captured sender closes both lookup-to-POST and failure-time account-switch windows, while same-key retry, unknown-identity and refreshed-poll behavior remain intact. No new blocker found in the delta or independent adversarial pass.

— hf-review

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE · 0 blocker · 1 important · 0 nits · e5156dd85

As hf-review already covered, the retry key, the reference-image credits and the audio-clock fixes are in. I checked them on this head and found one more thing to fix, inline: the avatar estimate is about 5× the HF price book.

Scope and evidence

Audited: heygen/v3.ts and pricing.ts end to end; generate.ts request and price paths; measure/sync.ts, look.ts, energy.ts and media.ts ratioOf; the skill and helper deltas since 6c608fa15.
Trusting: the rest of measure/ and diff.ts and the J1–J6 journeys, which earlier rounds and the PR body already cover.

  • v3: I checked this against EF master. model_videos.py stores only success, or a charged pending submission, under the key. A JSON 5xx never reaches refusal(), because call() turns any status ≥500 into a same-key Fix, so a charged pending start is resumed rather than re-keyed (an offline witness confirmed it). A 402 hf_subscription_required rotates harmlessly, since nothing was started.
  • Video pricing: matches EF rules.json v2 rows (12 per s, 100/3 per s, 40/3 per image after two) and hf_video_works / allocate_reference_video_seconds, which bill fractional guide seconds. gpt-image reference counting matches images_v3._hf_works.
  • Local tests: bun run test src/heygen/ src/measure/{sync,compare}.test.ts src/commands/auth/ gives 94 passed, using locally built core/parsers/lint/studio-server/engine.
  • CI: CI, Comments, Docs, CodeQL and Windows are green on e5156dd85; a second CI run was still going when I posted.

const words = (r.script ?? "").split(/\s+/).filter(Boolean).length;
const seconds = Math.max(1, Math.floor(words / SCRIPT_WORDS_PER_S));
return at(
Math.ceil(seconds * AVATAR_IV_PER_MIN) / 60,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: the avatar estimate is about 5× what the HF price book charges. $4.83 a minute × 1,000 is 4,830 credits a minute. That is the retired pre-10-28 avatar_iv / digital_twin row. --type avatar sends type: "image", and EF avatar_video_hf_charge.admit_hf_avatar_video bills that as hf_avatar_iv_video {avatar_iv, photo_avatar, standard}, which is 800/60 a second since 2026-10-28 (hf-avatar-iv-video-iv-photo-avatar-standard-v2), plus the general hf_avatar_video leg.

Example: a 30-word script (15 s) plans at 1,217 credits, while the book charges about 200 plus the general leg. As a result:

  • --max-credits 500 refuses a call that fits.
  • EF sends no needed_credits, so an out-of-credits refusal falls back to this estimate. It prints billing top-up --needed 1217, and billing.ts:38 then waits until the balance reaches 1,217, not the real need.

Fix: give the avatar its own credits argument from the v2 rows, as the video branch does (800/60 a second plus the general leg's per-second row), and add a pin beside the 260-credit test.

@jrusso1020
jrusso1020 added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit cb2389c Oct 30, 2026
132 checks passed
@jrusso1020
jrusso1020 deleted the miao/reverse-skill branch October 30, 2026 13:42
jrusso1020 added a commit that referenced this pull request Oct 30, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@heygengenesis heygengenesis Bot mentioned this pull request Oct 30, 2026
meefs pushed a commit to meefs/hyperframes that referenced this pull request Oct 30, 2026
…#6840)

* revert: feat(skills): reverse any video (heygen-com#6754)

Keep the reverse-any-video skill and its CLI billing commands out of main until
web subscriptions are live (/pricing and /account/plan return 404 today).
Re-land together with heygen-com#6833 once those pages ship.

This reverts commit cb2389c.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* chore(scripts): allow the heygen-com#6754 revert deletions

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants