feat(media-use): use CLI free HeyGen usage#2027
Conversation
5803392 to
374159d
Compare
vanceingalls
left a comment
There was a problem hiding this comment.
R1 — hyperframes #2027 at 374159db98d0dd27fa8b085d7fc34153167eca26
🟢 LGTM on the CLI side of the CLI-free-usage handshake. Small, single-purpose, and the header contract lines up cleanly with the EF #41448 server-side.
Cross-repo handshake — verified
- CLI sends
X-HeyGen-Source: cli(bothbuildAuthHeadersforauthorization: BearerOAuth ANDx-api-key; media-useheygen.mjsmerges the same header viaHEYGEN_CLI_SOURCE_HEADERS). - EF's
movio/api/decorators/authentication/oauth2.pyaccepts exactly"cli"as a programmatic source and stampsrequest.source = "cli"(via_resolve_programmatic_source, allowlist{"mcp", "cli"}). - Header name + value + case all match. Server's allowlist is exact-string, so
Cli/CLIwould fail — worth ensuring the CLI never mutates the constant.HEYGEN_CLI_SOURCE = "cli"is lowercase in bothclient.tsandheygen.mjs; good.
Registry reorder — the load-bearing product change
Voice provider order flipped from [kokoro.local, heygen.tts] (Kokoro-first) to [heygen.tts, kokoro.local] (HeyGen-first when credentialed). Test voice cascade: HeyGen TTS first, Kokoro remains the local fallback is updated and now asserts the new order + kept the --local-only skip semantics. This means:
- Fresh CLI OAuth user → media-use resolves voice → hits HeyGen TTS → consumes free 10min/month allowance per EF #41448's
TTS_FREE_CREDITpool. If exhausted (or ifcheck_oauth_integration_billingreturnsBLOCK_UPGRADE_REQUIREDbecause the user is already paid), HeyGen TTS returns 402/403 → the registry cascade will fall through to Kokoro. That fallback edge is worth spot-checking at runtime. --local-onlyuser → still Kokoro (network flag skips heygen.tts). ✅- User with API key only → HeyGen TTS runs on normal API billing (server's
oauth_billing_decisionis not MCP_FREE_TIER because it's not OAuth). Doc reflects this correctly.
Non-blocking nit: I can't tell from this diff alone whether media-use's cascade actually falls through to Kokoro on a 402 from heygen.tts (vs. surfacing the error). If heygenTtsGenerate throws on 402, the registry cascade's next provider gets a shot; if it just returns an unusable result, the cascade stops. Worth confirming this is exercised in the runtime e2e.
Docs
Sync'd across SKILL.md, references/requirements.md, references/tts.md, references/operations.md, and scripts/lib/registry.mjs comments. The framing — "OAuth CLI users consume the web-plan free allowance (10 min/month); API keys follow the normal API billing path" — matches the EF-side semantics exactly. skills-manifest.json hash bumped as expected.
What I can and can't verify
- ✅ Code-level e2e header contract, provider order, doc alignment.
- ❌ Live runtime hit against dev/prod (no CLI OAuth + HeyGen account in this session). Runtime leg needs someone with the env warm to confirm: (a)
X-HeyGen-Source: cliactually makes it through any intermediate proxy without being stripped, (b) the media-use TTS cascade falls through gracefully when heygen.tts returns 402, (c) fresh-user TTS_FREE_CREDIT actually grants on first request.
One flag on the pre-commit note
PR body says: "the monorepo typecheck hook failed during dependency/module resolution before change-specific type errors ... so the commit was created with --no-verify". Fine for the commit itself, but worth double-checking that typecheck in CI (which resolves against a clean install) is actually green on the current head before merge — a real type error in the touched files would land here silently and the CI signal is the only thing catching it.
R1 by Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 374159db98d0dd27fa8b085d7fc34153167eca26 as part of the BE/FE pair with experiment-framework#41448.
Static end-to-end trace across both PRs. No blockers on the FE side. Full BE analysis lives in the EF#41448 review — this side is small and clean.
Wire contract with EF#41448 — verified match:
packages/cli/src/auth/client.ts:24-25HEYGEN_CLI_SOURCE_HEADER = "X-HeyGen-Source"/HEYGEN_CLI_SOURCE = "cli"→ EF#41448movio/api/decorators/authentication/oauth2.py:83-88_PROGRAMMATIC_OAUTH_SOURCES = frozenset({"mcp", "cli"}). Header key + value both match. HTTP header lookups are case-insensitive, so the FE'sX-HeyGen-Sourceand BE's exact-case.headers.get(...)don't diverge.skills/media-use/audio/scripts/lib/heygen.mjs:12HEYGEN_CLI_SOURCE_HEADERS = { "X-HeyGen-Source": "cli" }— same wire tag, second producer. Two tests (heygen.test.mjs) cover both API-key and OAuth branches.buildAuthHeadersreturns the source header on both OAuth and API-key credentials. BE only elevates it torequest.sourcewhenis_integration_oauth_app=True(viaOAuth2Authentication.apply), so API-key traffic keeps its normal billing path. Benign, but see the small nit below.
Provider ordering flip (skills/media-use/scripts/lib/registry.mjs:56-59) — HeyGen TTS now precedes kokoro.local. The registry-test rewrite is coherent (heygen.tts first, paid: true, network; kokoro.local second, offline). Under --local-only, the network flag skips heygen.tts cleanly and Kokoro remains reachable — that's the escape hatch for offline/uncredentialed setups. Docs in SKILL.md + references/tts.md + references/requirements.md update in lockstep.
Findings
🟡 X-HeyGen-Source: cli on x-api-key requests is dead metadata. packages/cli/src/auth/client.ts:184-188 returns the header on the API-key branch too; the BE explicitly ignores it for direct API-key traffic (is_integration_oauth_app=False skips source elevation and the free-quota grant), and API-key requests keep the normal TTSAPIDeductStrategy path. Not incorrect — just extra header bytes on every API-key request, and it reads as "cli-source claim on an API-key request" which is contradictory. Consider gating the header emission to credential.type === "oauth" for symmetry with BE's actual use. Same nit applies to skills/media-use/audio/scripts/lib/heygen.mjs:74 where the spread unconditionally adds the header to both cred types. Non-blocking; the tests explicitly assert the current wide behavior, so this would be a deliberate scope-narrow.
🟡 X-HeyGen-Client-Origin not sent yet. EF#41448 wires oauth2.py to read X-HeyGen-Client-Origin (→ request.client_origin) and its tests exercise "cli:claude_code" end-to-end. Neither CLI helper here emits it — the BE tests pass because they set request.client_origin directly. Fine for this PR since BE has a bare-cli fallback, but worth exporting a HEYGEN_CLI_ORIGIN_HEADER constant now so Claude Code (and any future cli:<origin> consumer) can send it without duplicating the string. Coordination-only, no functional impact today.
🟡 paid: true on HeyGen TTS + agent-confirm X4 semantics. registry.mjs:34-37 still tags heygen.tts as P (network + paid). For OAuth CLI users with unspent monthly free quota, the first call is genuinely free — but agent-initiated calls will still hit the "confirm before paid" prompt per the X4 rule in SKILL.md. Product/UX question, not a code one: is a per-call confirmation the right UX for the first 10 free minutes/month? If not, the paid flag semantics may need a tri-state ("quota-first, paid after"). Non-blocking; flagging so the intent is on the record.
🟢 Test coverage on the auth-headers change: client.test.ts covers both branches; heygen.test.mjs (new) covers both credential shapes with a clean env-isolation helper; registry.test.mjs covers the ordering flip and the --local-only semantic. Skills manifest regenerated (skills-manifest.json hash bump for media-use).
Cross-check with recent CLI PRs — clean:
- HF#2008 citty
default: "30"unreachable-fallback trap: no citty arg definitions in this diff. - HF#2035
autoUpdate.tsisSafeVersionguard: this PR doesn't add new consumers of registry data orexec-shaped call sites; nothing to gate.
— Review by Rames D Jusso
374159d to
508f1cd
Compare
This stack of pull requests is managed by Graphite. Learn more about stacking. |
vanceingalls
left a comment
There was a problem hiding this comment.
R2 — hyperframes #2027 at 508f1cd3973e986ef18345327c9f38cf12c52095
🟢 R1 verdict stands on the CLI-free-usage handshake; the scope has grown considerably since 374159d, so this R2 verifies (a) the handshake I signed off on is byte-for-byte preserved, then (b) spot-checks the additive product surfaces that have landed on top.
Handshake preservation — verified byte-for-byte
The CLI header contract I certified at R1 is untouched at 508f1cd:
packages/cli/src/auth/client.ts:24-25—HEYGEN_CLI_SOURCE_HEADER = "X-HeyGen-Source",HEYGEN_CLI_SOURCE = "cli"(identical to374159d).client.ts:180-188—buildAuthHeadersemits the source header on both the OAuthBearerpath AND thex-api-keypath. No change.skills/media-use/audio/scripts/lib/heygen.mjs:12—HEYGEN_CLI_SOURCE_HEADERS = { "X-HeyGen-Source": "cli" }, merged into every credentialed request at:74-75. No change.skills/media-use/scripts/lib/registry.mjs:55-60— voice cascade still[heygen.tts (P), kokoro.local (A)];--local-onlystill skips the network provider.
Note on the raw compare API: gh api /compare/374159d...508f1cd renders a synthesized patch that shows client.ts as if the header emission was newly added. It isn't — direct contents?ref=374159d fetch confirms the constants and the buildAuthHeaders two-arm return were already present. This is the rebased-branches compare caveat — synthesized diff, not a real content delta. Called out so the artifact doesn't spook downstream reviewers.
Scope drift since R1 — additive, non-cross-repo
The following NEW files have landed on top of what I R1-ed. None touches the auth/billing handshake:
scripts/lib/logo-provider.mjs(222) — extracts the svgl → simple-icons → GitHub-org → favicon cascade. All-network, all-free, all fail-safe. Documented with a 54-brand stress-test note.scripts/lib/cube-build.mjs(181) +cube-validate.mjs(198) + tests — local.cubeLUT emission/validation. Zero network, zero auth.scripts/lib/grade-analyzer.mjs(165) + tests — local color-grade analysis. Zero network, zero auth.scripts/lib/lut-preset-provider.mjs(232) + tests — local preset catalog for the newlutregistry type. Zero network.luts/index.json(57) +luts/README.md(28) — the preset catalog data.registry.mjs— two new registry types (grade,lut), both withA(...)(local-only) entries pointing at resolve.mjs-owned handlers (search/generate returnnull; real work happens in the resolve pipeline).
Cross-cut check on the local-only guarantee: the two new registry types are A(...) entries only — no network:true, no paid:true. --local-only continues to hold as a hard offline switch for the whole registry (runProviders at registry.mjs:127-... still skips network entries under ctx.localOnly).
Two things worth naming
- Non-blocking, scope-related: I R1'd a small, single-purpose handshake PR. This landed on top with ~2000 lines of grade/LUT/logo-provider machinery. That's fine — the additions are self-contained and local-only — but the PR title (
use CLI free HeyGen usage) and the R1 mental model no longer describe the shipped scope. If any reviewer downstream is stamping on the title alone, they should know most of the diff is now unrelated color-grading + logo-cascade work. Peer-lens split: I've kept my lens on the handshake; leaving the deep grade/LUT / logo-provider rubric to Rames D Jusso if he chooses to go there. His R1 was already scoped to the BE/FE pair with EF #41448, which is still-and-only about the handshake. - Handshake runtime leg still not verifiable from code: same caveat as R1 —
X-HeyGen-Source: clipropagation through any intermediate proxy, the media-use TTS cascade fallback on a real 402, and the fresh-userTTS_FREE_CREDITgrant remain live-env checks that neither I nor Miga can run from a bot session. Deferring to James / vance if a live-env verify is desired before merge.
R1 observation status
- ✅ Header contract on OAuth AND x-api-key paths — preserved.
- ✅ Registry voice cascade order +
--local-onlysemantics — preserved. - ✅ Docs (
SKILL.md,references/tts.md,references/operations.md) — still align on the "OAuth CLI → free web-plan allowance; API key → normal API billing" framing. - ✅
skills-manifest.jsonhash bumped as expected on content changes.
R2 by Via — handshake preserved; scope-drift called out but non-blocking.
…th, export origin constant
- X-HeyGen-Source is now sent only on OAuth (Bearer) requests, not API-key ones —
the backend ignores it for API-key traffic (normal billing), so it was dead
metadata there. buildAuthHeaders + heygenAuthHeaders + tests updated.
- Export HEYGEN_CLI_ORIGIN_HEADER ("X-HeyGen-Client-Origin") for future cli:<origin>
consumers.
- Document the deliberate paid/X4 confirm-before-call decision on heygen.tts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv
Fallow flagged 5 findings on this PR:
- major: HEYGEN_CLI_ORIGIN_HEADER was exported but never emitted or
imported — speculative dead code ("future consumers"). Remove it; a
real consumer can add the constant when one exists.
- 4x minor duplication in client.test.ts: fold the repeated
`.rejects.toSatisfy(auth-code)` assertion into expectAuthCode(), and the
repeated try/catch scrubbed-message assertion into expectRejectionMessage().
No behavior change; auth/client tests still 17/17.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

Summary
Enables the HeyGen free-usage path for the CLI and media-use. The CLI auth client and media-use's HeyGen TTS helper mark requests as CLI-originated (
X-HeyGen-Source: cli), so OAuth sign-ins draw from the backend's free-usage pools instead of being routed as generic OAuth traffic. media-use now prefers HeyGen TTS when credentials are present, with local Kokoro as the offline fallback.What / why
packages/cli/src/auth/client.ts+skills/media-use/audio/scripts/lib/heygen.mjs: sendX-HeyGen-Source: cli.skills/media-use/scripts/lib/registry.mjs: prefer HeyGen TTS forvoice.Depends on
The experiment-framework backend PR (
fix(api): enable CLI OAuth free usage quotas) — now merged + in prod. Verified end-to-end: a fresh free (OAuth) account generated TTS through media-use on free quota, usingheygenCLI v0.3.0 (the first OAuth-capable release).Tests
node --testmedia-use skills suite +packages/cliauth tests — green.Stack (merge bottom→top): #2027 → #2065 → #2113
🤖 Generated with Claude Code
https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv