mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
feat(media-use): use CLI free HeyGen usage (#2027)
* feat(media-use): use CLI free HeyGen usage * fix(media-use): address #2027 R1 nits — gate cli-source header to OAuth, 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 * refactor(cli): drop unused origin-header export, dedup auth-client tests 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 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1614dd3e5a
commit
3b93f516b4
@@ -12,13 +12,13 @@ Run `npx hyperframes auth status` to see what's configured and which engines a w
|
||||
| **Kokoro** (TTS, no key) | always — final voice fallback | `pip install kokoro-onnx soundfile` |
|
||||
| **MusicGen** (BGM, no key) | always — final music fallback | `pip install transformers torch soundfile numpy` |
|
||||
|
||||
`hyperframes auth login` (browser OAuth) is the recommended setup: one sign-in, every project, no per-repo `.env`. An OAuth login is sent as `Authorization: Bearer`; an API key as `X-Api-Key`. With no HeyGen credential, voice/BGM run fully locally (Kokoro / MusicGen) — `hyperframes auth status` and `hyperframes doctor` both report whether those local deps are installed.
|
||||
`hyperframes auth login` (browser OAuth) is the recommended setup: one sign-in, every project, no per-repo `.env`. An OAuth login is sent as `Authorization: Bearer`; an API key as `X-Api-Key`; both are tagged with `X-HeyGen-Source: cli`. OAuth CLI users can consume the web-plan free allowance for HeyGen TTS (10 min/month); API keys follow the normal API billing path. With no HeyGen credential, voice/BGM run fully locally (Kokoro / MusicGen) — `hyperframes auth status` and `hyperframes doctor` both report whether those local deps are installed.
|
||||
|
||||
## Model caches & system dependencies
|
||||
|
||||
Each command downloads its own model on first run and caches it under `~/.cache/hyperframes/`:
|
||||
|
||||
- **TTS (HeyGen)** — no local deps; needs a HeyGen credential + `ffmpeg` on PATH (to transcode the mp3 response to `.wav`). Credential resolves like the CLI: `$HEYGEN_API_KEY` → `$HYPERFRAMES_API_KEY` → `~/.heygen/credentials` (shared with heygen-cli; run `npx hyperframes auth login`). An OAuth login is sent as `Authorization: Bearer`; an API key as `X-Api-Key`.
|
||||
- **TTS (HeyGen)** — no local deps; needs a HeyGen credential + `ffmpeg` on PATH (to transcode the mp3 response to `.wav`). Credential resolves like the CLI: `$HEYGEN_API_KEY` → `$HYPERFRAMES_API_KEY` → `~/.heygen/credentials` (shared with heygen-cli; run `npx hyperframes auth login`). An OAuth login is sent as `Authorization: Bearer`; an API key as `X-Api-Key`; both include `X-HeyGen-Source: cli` so the backend can apply CLI OAuth free usage.
|
||||
- **TTS (ElevenLabs)** — same as HeyGen: API key + `ffmpeg`.
|
||||
- **TTS (Kokoro)** — Kokoro-82M (~311 MB) + voices (~27 MB) in `tts/`. Requires Python 3.8+ with `kokoro-onnx` and `soundfile` (`pip install kokoro-onnx soundfile`). Non-English text also needs `espeak-ng` system-wide.
|
||||
- **BGM (Lyria)** — needs `$GEMINI_API_KEY` or `$GOOGLE_API_KEY` + `pip install google-genai`. No local model cache.
|
||||
|
||||
@@ -36,8 +36,11 @@ The script resolves a HeyGen credential the same way the CLI does — first sour
|
||||
wins: `$HEYGEN_API_KEY` → `$HYPERFRAMES_API_KEY` → a project `.env` (auto-loaded,
|
||||
walks up ≤5 dirs) → `~/.heygen/credentials` (shared with heygen-cli;
|
||||
`$HEYGEN_CONFIG_DIR` overrides the dir). An OAuth login is sent as
|
||||
`Authorization: Bearer`; an API key as `X-Api-Key`. If the only credential is an
|
||||
expired OAuth token it stops with a hint to run `npx hyperframes auth refresh`.
|
||||
`Authorization: Bearer`; an API key as `X-Api-Key`; both include
|
||||
`X-HeyGen-Source: cli`. OAuth CLI users can consume the web-plan free allowance
|
||||
(10 min/month) before paid usage; API keys follow normal API billing. If the
|
||||
only credential is an expired OAuth token it stops with a hint to run
|
||||
`npx hyperframes auth refresh`.
|
||||
|
||||
```bash
|
||||
# Only needed if you haven't run `npx hyperframes auth login`:
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//
|
||||
// Flags: -o/--output (.wav → ffmpeg transcode; .mp3 → raw bytes), --words,
|
||||
// --voice (starfish id), --speed, --lang, --list.
|
||||
// Requires: $HEYGEN_API_KEY (or ~/.heygen) and ffmpeg for .wav output.
|
||||
// Requires: $HEYGEN_API_KEY / OAuth ~/.heygen credentials and ffmpeg for .wav output.
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
@@ -9,6 +9,7 @@ import { homedir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
|
||||
export const HEYGEN_BASE = "https://api.heygen.com/v3";
|
||||
export const HEYGEN_CLI_SOURCE_HEADERS = { "X-HeyGen-Source": "cli" };
|
||||
|
||||
// Walk up ≤5 dirs from startDir; load the first .env (shell env always wins).
|
||||
export function loadEnvFromDir(startDir) {
|
||||
@@ -71,7 +72,13 @@ export function heygenCredential() {
|
||||
// → auth headers object, or throw with a fix hint.
|
||||
export function heygenAuthHeaders() {
|
||||
const cred = heygenCredential();
|
||||
if (cred?.headers) return cred.headers;
|
||||
if (cred?.headers) {
|
||||
// Only tag OAuth (Bearer) traffic as cli-source — the backend uses it to
|
||||
// grant the free allowance for OAuth requests and ignores it for API-key
|
||||
// (X-Api-Key) traffic, where it's dead metadata.
|
||||
const isOauth = "Authorization" in cred.headers;
|
||||
return isOauth ? { ...cred.headers, ...HEYGEN_CLI_SOURCE_HEADERS } : { ...cred.headers };
|
||||
}
|
||||
if (cred?.expired)
|
||||
throw new Error(
|
||||
"HeyGen OAuth token expired — run `npx hyperframes auth refresh` (or `npx hyperframes auth login`)",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { heygenAuthHeaders } from "./heygen.mjs";
|
||||
|
||||
function withCleanHeygenEnv(fn) {
|
||||
const previousApiKey = process.env.HEYGEN_API_KEY;
|
||||
const previousHyperframesApiKey = process.env.HYPERFRAMES_API_KEY;
|
||||
const previousConfigDir = process.env.HEYGEN_CONFIG_DIR;
|
||||
try {
|
||||
delete process.env.HEYGEN_API_KEY;
|
||||
delete process.env.HYPERFRAMES_API_KEY;
|
||||
delete process.env.HEYGEN_CONFIG_DIR;
|
||||
return fn();
|
||||
} finally {
|
||||
if (previousApiKey === undefined) delete process.env.HEYGEN_API_KEY;
|
||||
else process.env.HEYGEN_API_KEY = previousApiKey;
|
||||
if (previousHyperframesApiKey === undefined) delete process.env.HYPERFRAMES_API_KEY;
|
||||
else process.env.HYPERFRAMES_API_KEY = previousHyperframesApiKey;
|
||||
if (previousConfigDir === undefined) delete process.env.HEYGEN_CONFIG_DIR;
|
||||
else process.env.HEYGEN_CONFIG_DIR = previousConfigDir;
|
||||
}
|
||||
}
|
||||
|
||||
test("heygenAuthHeaders does not tag API-key requests as CLI traffic", () => {
|
||||
withCleanHeygenEnv(() => {
|
||||
process.env.HEYGEN_API_KEY = "hg_test";
|
||||
// API-key requests use normal billing; the backend ignores the cli-source
|
||||
// header for them, so it's not sent.
|
||||
assert.deepEqual(heygenAuthHeaders(), {
|
||||
"X-Api-Key": "hg_test",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("heygenAuthHeaders tags OAuth requests as CLI traffic", () => {
|
||||
withCleanHeygenEnv(() => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "heygen-cred-"));
|
||||
try {
|
||||
process.env.HEYGEN_CONFIG_DIR = dir;
|
||||
writeFileSync(
|
||||
join(dir, "credentials"),
|
||||
JSON.stringify({
|
||||
oauth: {
|
||||
access_token: "at_test",
|
||||
expires_at: "2099-01-01T00:00:00Z",
|
||||
},
|
||||
}),
|
||||
);
|
||||
assert.deepEqual(heygenAuthHeaders(), {
|
||||
Authorization: "Bearer at_test",
|
||||
"X-HeyGen-Source": "cli",
|
||||
});
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user