hyperframes 0.8.58 → 0.8.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4529 -3220
- package/dist/docs/data-attributes.md +2 -1
- package/dist/fontLocalizeCli.js +96 -16
- package/dist/hyperframe-runtime.js +25 -25
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +25 -25
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/registry/localSemantic.js +548 -0
- package/dist/skills/hyperframes-cli/SKILL.md +2 -2
- package/dist/skills/media-use/SKILL.md +96 -0
- package/dist/skills/media-use/audio/assets/sfx/CREDITS.md +35 -0
- package/dist/skills/media-use/audio/assets/sfx/chime.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/click-soft.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/click.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/error.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/glitch-1.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/glitch-2.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/glitch-3.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/impact-bass-1.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/impact-bass-2.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/key-press.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/manifest.json +97 -0
- package/dist/skills/media-use/audio/assets/sfx/notification.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/ping.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/pop.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/riser.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/sparkle.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/typing.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/whoosh-cinematic.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/whoosh-short.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/whoosh.mp3 +0 -0
- package/dist/skills/media-use/audio/references/bgm.md +74 -0
- package/dist/skills/media-use/audio/references/captions/authoring.md +163 -0
- package/dist/skills/media-use/audio/references/captions/motion.md +87 -0
- package/dist/skills/media-use/audio/references/captions/transcript-handling.md +97 -0
- package/dist/skills/media-use/audio/references/remove-background.md +143 -0
- package/dist/skills/media-use/audio/references/requirements.md +29 -0
- package/dist/skills/media-use/audio/references/sfx.md +42 -0
- package/dist/skills/media-use/audio/references/transcribe.md +52 -0
- package/dist/skills/media-use/audio/references/tts-to-captions.md +26 -0
- package/dist/skills/media-use/audio/references/tts.md +165 -0
- package/dist/skills/media-use/audio/scripts/audio.mjs +295 -0
- package/dist/skills/media-use/audio/scripts/audio.test.mjs +49 -0
- package/dist/skills/media-use/audio/scripts/heygen-tts.mjs +121 -0
- package/dist/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
- package/dist/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
- package/dist/skills/media-use/audio/scripts/lib/bgm.mjs +257 -0
- package/dist/skills/media-use/audio/scripts/lib/bgm.test.mjs +30 -0
- package/dist/skills/media-use/audio/scripts/lib/concurrency.mjs +14 -0
- package/dist/skills/media-use/audio/scripts/lib/concurrency.test.mjs +41 -0
- package/dist/skills/media-use/audio/scripts/lib/heygen.mjs +156 -0
- package/dist/skills/media-use/audio/scripts/lib/heygen.test.mjs +103 -0
- package/dist/skills/media-use/audio/scripts/lib/python.mjs +63 -0
- package/dist/skills/media-use/audio/scripts/lib/python.test.mjs +68 -0
- package/dist/skills/media-use/audio/scripts/lib/sfx.mjs +143 -0
- package/dist/skills/media-use/audio/scripts/lib/sfx.test.mjs +69 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.mjs +379 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.spawn.test.mjs +158 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.test.mjs +157 -0
- package/dist/skills/media-use/audio/scripts/lyria-recipe.py +136 -0
- package/dist/skills/media-use/audio/scripts/wait-bgm.mjs +171 -0
- package/dist/skills/media-use/audio/scripts/wait-bgm.test.mjs +65 -0
- package/dist/skills/media-use/luts/README.md +28 -0
- package/dist/skills/media-use/luts/index.json +57 -0
- package/dist/skills/media-use/references/audio.md +19 -0
- package/dist/skills/media-use/references/grading.md +160 -0
- package/dist/skills/media-use/references/media-treatment-recipes.md +929 -0
- package/dist/skills/media-use/references/media-treatments.md +202 -0
- package/dist/skills/media-use/references/memory.md +36 -0
- package/dist/skills/media-use/references/meta.md +61 -0
- package/dist/skills/media-use/references/operations.md +325 -0
- package/dist/skills/media-use/references/resolve.md +150 -0
- package/dist/skills/media-use/references/setup-providers.md +80 -0
- package/dist/skills/media-use/references/telemetry-dashboard.md +49 -0
- package/dist/skills/media-use/registry/localSemantic.js +548 -0
- package/dist/skills/media-use/scripts/audio-duck.mjs +113 -0
- package/dist/skills/media-use/scripts/compatibility.test.mjs +27 -0
- package/dist/skills/media-use/scripts/dither.mjs +328 -0
- package/dist/skills/media-use/scripts/dither.test.mjs +176 -0
- package/dist/skills/media-use/scripts/eval.mjs +369 -0
- package/dist/skills/media-use/scripts/lib/adopt.mjs +128 -0
- package/dist/skills/media-use/scripts/lib/adopt.test.mjs +95 -0
- package/dist/skills/media-use/scripts/lib/bgm-provider.mjs +20 -0
- package/dist/skills/media-use/scripts/lib/brand-provider.mjs +59 -0
- package/dist/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +119 -0
- package/dist/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +139 -0
- package/dist/skills/media-use/scripts/lib/cache.mjs +150 -0
- package/dist/skills/media-use/scripts/lib/candidates.mjs +91 -0
- package/dist/skills/media-use/scripts/lib/candidates.test.mjs +155 -0
- package/dist/skills/media-use/scripts/lib/codex-provider.mjs +161 -0
- package/dist/skills/media-use/scripts/lib/codex-provider.test.mjs +34 -0
- package/dist/skills/media-use/scripts/lib/coverage.test.mjs +132 -0
- package/dist/skills/media-use/scripts/lib/cube-build.mjs +181 -0
- package/dist/skills/media-use/scripts/lib/cube-build.test.mjs +80 -0
- package/dist/skills/media-use/scripts/lib/cube-validate.mjs +198 -0
- package/dist/skills/media-use/scripts/lib/cube-validate.test.mjs +125 -0
- package/dist/skills/media-use/scripts/lib/cutlist.mjs +184 -0
- package/dist/skills/media-use/scripts/lib/cutlist.test.mjs +162 -0
- package/dist/skills/media-use/scripts/lib/duck.mjs +95 -0
- package/dist/skills/media-use/scripts/lib/duck.test.mjs +153 -0
- package/dist/skills/media-use/scripts/lib/error-diffusion.mjs +230 -0
- package/dist/skills/media-use/scripts/lib/error-diffusion.test.mjs +102 -0
- package/dist/skills/media-use/scripts/lib/freeze.mjs +62 -0
- package/dist/skills/media-use/scripts/lib/freeze.test.mjs +46 -0
- package/dist/skills/media-use/scripts/lib/grade-analyzer.mjs +265 -0
- package/dist/skills/media-use/scripts/lib/grade-analyzer.test.mjs +285 -0
- package/dist/skills/media-use/scripts/lib/heygen-cli.mjs +191 -0
- package/dist/skills/media-use/scripts/lib/heygen-cli.test.mjs +301 -0
- package/dist/skills/media-use/scripts/lib/heygen-search.mjs +45 -0
- package/dist/skills/media-use/scripts/lib/heygen-search.test.mjs +49 -0
- package/dist/skills/media-use/scripts/lib/heygen-video-provider.mjs +138 -0
- package/dist/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +435 -0
- package/dist/skills/media-use/scripts/lib/image-provider.mjs +44 -0
- package/dist/skills/media-use/scripts/lib/index-gen.mjs +63 -0
- package/dist/skills/media-use/scripts/lib/local-media-search.mjs +32 -0
- package/dist/skills/media-use/scripts/lib/local-models.mjs +318 -0
- package/dist/skills/media-use/scripts/lib/local-models.test.mjs +220 -0
- package/dist/skills/media-use/scripts/lib/local-run.mjs +78 -0
- package/dist/skills/media-use/scripts/lib/local-run.test.mjs +121 -0
- package/dist/skills/media-use/scripts/lib/logo-provider.mjs +223 -0
- package/dist/skills/media-use/scripts/lib/logo-provider.test.mjs +154 -0
- package/dist/skills/media-use/scripts/lib/ltx-video-provider.mjs +94 -0
- package/dist/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +316 -0
- package/dist/skills/media-use/scripts/lib/lut-preset-provider.mjs +271 -0
- package/dist/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +199 -0
- package/dist/skills/media-use/scripts/lib/manifest.mjs +229 -0
- package/dist/skills/media-use/scripts/lib/manifest.test.mjs +388 -0
- package/dist/skills/media-use/scripts/lib/match.mjs +47 -0
- package/dist/skills/media-use/scripts/lib/media-fetch.mjs +71 -0
- package/dist/skills/media-use/scripts/lib/media-fetch.test.mjs +169 -0
- package/dist/skills/media-use/scripts/lib/media-search.d.mts +9 -0
- package/dist/skills/media-use/scripts/lib/media-search.mjs +107 -0
- package/dist/skills/media-use/scripts/lib/mflux-provider.mjs +128 -0
- package/dist/skills/media-use/scripts/lib/mflux-provider.test.mjs +174 -0
- package/dist/skills/media-use/scripts/lib/misses.mjs +49 -0
- package/dist/skills/media-use/scripts/lib/misses.test.mjs +78 -0
- package/dist/skills/media-use/scripts/lib/npx-sync.mjs +31 -0
- package/dist/skills/media-use/scripts/lib/npx-sync.test.mjs +50 -0
- package/dist/skills/media-use/scripts/lib/parakeet-words.mjs +27 -0
- package/dist/skills/media-use/scripts/lib/parakeet-words.test.mjs +44 -0
- package/dist/skills/media-use/scripts/lib/prefs-store.mjs +180 -0
- package/dist/skills/media-use/scripts/lib/prefs-store.test.mjs +189 -0
- package/dist/skills/media-use/scripts/lib/probe.mjs +39 -0
- package/dist/skills/media-use/scripts/lib/probe.test.mjs +31 -0
- package/dist/skills/media-use/scripts/lib/providers.mjs +5 -0
- package/dist/skills/media-use/scripts/lib/recipe-store.mjs +367 -0
- package/dist/skills/media-use/scripts/lib/recipe-store.test.mjs +367 -0
- package/dist/skills/media-use/scripts/lib/registry.mjs +218 -0
- package/dist/skills/media-use/scripts/lib/registry.test.mjs +262 -0
- package/dist/skills/media-use/scripts/lib/search.mjs +24 -0
- package/dist/skills/media-use/scripts/lib/search.test.mjs +32 -0
- package/dist/skills/media-use/scripts/lib/sfx-provider.mjs +23 -0
- package/dist/skills/media-use/scripts/lib/specs.mjs +132 -0
- package/dist/skills/media-use/scripts/lib/specs.test.mjs +157 -0
- package/dist/skills/media-use/scripts/lib/stats.mjs +117 -0
- package/dist/skills/media-use/scripts/lib/stats.test.mjs +165 -0
- package/dist/skills/media-use/scripts/lib/telemetry.mjs +193 -0
- package/dist/skills/media-use/scripts/lib/telemetry.test.mjs +373 -0
- package/dist/skills/media-use/scripts/lib/tts-local-provider.mjs +92 -0
- package/dist/skills/media-use/scripts/lib/tts-local-provider.test.mjs +72 -0
- package/dist/skills/media-use/scripts/lib/usage.mjs +57 -0
- package/dist/skills/media-use/scripts/lib/usage.test.mjs +54 -0
- package/dist/skills/media-use/scripts/lib/voice-provider.mjs +57 -0
- package/dist/skills/media-use/scripts/lib/voice-provider.test.mjs +46 -0
- package/dist/skills/media-use/scripts/lib/words.mjs +18 -0
- package/dist/skills/media-use/scripts/prefs.mjs +70 -0
- package/dist/skills/media-use/scripts/recipe.mjs +103 -0
- package/dist/skills/media-use/scripts/resolve.mjs +1287 -0
- package/dist/skills/media-use/scripts/transcribe.mjs +180 -0
- package/dist/skills/media-use/scripts/transcript-cut.mjs +239 -0
- package/dist/skills/registry/localSemantic.js +548 -0
- package/dist/studio/assets/{hyperframes-player-zGyOcwwh.js → hyperframes-player-Di7GyCOP.js} +3 -3
- package/dist/studio/assets/{index-DKJpHYYd.css → index-CBKb_0TI.css} +1 -1
- package/dist/studio/assets/{index-d8ferjRc.js → index-Dpq2lkOm.js} +190 -190
- package/dist/studio/assets/{index-ZJaRCDYV.js → index-Dx_pt9m4.js} +1 -1
- package/dist/studio/assets/{index-BcE2OSVA.js → index-jAjhQTrB.js} +1 -1
- package/dist/studio/index.d.ts +549 -13
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +23133 -21941
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { track } from "./telemetry.mjs";
|
|
3
|
+
|
|
4
|
+
// v0.3.0 is the first CLI that can use an OAuth session; v0.1.x/0.2.x reject it
|
|
5
|
+
// ("heygen-cli can't use OAuth yet"), and OAuth is what the free-usage path
|
|
6
|
+
// needs — so anything below this can't authenticate for free usage at all.
|
|
7
|
+
export const HEYGEN_MIN_VERSION = "0.3.0";
|
|
8
|
+
// Free-usage path is OAuth (`--oauth` → subscription/free credits); `--api-key`
|
|
9
|
+
// bills API credits, so the onboarding steers to OAuth. Keep pipe-to-shell
|
|
10
|
+
// installer text out of the runtime module; the docs are the safer source of
|
|
11
|
+
// truth for platform-specific setup.
|
|
12
|
+
export const HEYGEN_INSTALL_COMMAND =
|
|
13
|
+
"Install the CLI from https://developers.heygen.com/cli, then run: heygen auth login --oauth";
|
|
14
|
+
export const HEYGEN_AUTH_COMMAND = "heygen auth login --oauth";
|
|
15
|
+
export const HEYGEN_UPDATE_COMMAND = "heygen update";
|
|
16
|
+
export const HEYGEN_CLIENT_SOURCE_ARGV = ["--headers", "X-HeyGen-Client-Source: media-use"];
|
|
17
|
+
|
|
18
|
+
export const HEYGEN_NOT_FOUND_MESSAGE = `media-use: heygen CLI not found — it's the free path for bgm/image/voice/avatar-video. ${HEYGEN_INSTALL_COMMAND}`;
|
|
19
|
+
export const HEYGEN_NOT_AUTHENTICATED_MESSAGE = `media-use: heygen CLI not authenticated (free usage) — run: ${HEYGEN_AUTH_COMMAND}`;
|
|
20
|
+
export const HEYGEN_OUTDATED_MESSAGE = `media-use: heygen CLI is outdated — run: ${HEYGEN_UPDATE_COMMAND} (need >= v${HEYGEN_MIN_VERSION})`;
|
|
21
|
+
|
|
22
|
+
const ACTIONABLE_MESSAGES = new Set([
|
|
23
|
+
HEYGEN_NOT_FOUND_MESSAGE,
|
|
24
|
+
HEYGEN_NOT_AUTHENTICATED_MESSAGE,
|
|
25
|
+
HEYGEN_OUTDATED_MESSAGE,
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export function classifyHeygenError(err) {
|
|
29
|
+
return classifyHeygenErrorResult(err).message;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function classifyHeygenErrorCode(err) {
|
|
33
|
+
return classifyHeygenErrorResult(err).code;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function classifyHeygenErrorResult(err) {
|
|
37
|
+
const detail = heygenErrorDetail(err);
|
|
38
|
+
const text = [err?.stderr, err?.stdout, err?.message, detail]
|
|
39
|
+
.map((value) => textOf(value))
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join("\n");
|
|
42
|
+
const lower = text.toLowerCase();
|
|
43
|
+
|
|
44
|
+
// Only ENOENT (spawn of a missing binary) or a shell's "command not found"
|
|
45
|
+
// mean the CLI itself is absent. A bare "not found" would misfire on the CLI's
|
|
46
|
+
// own resource errors (e.g. a stale voiceId → "voice not found"), whose message
|
|
47
|
+
// embeds the `heygen ...` command line — sending users to reinstall a CLI they
|
|
48
|
+
// just ran successfully. Keep this narrow.
|
|
49
|
+
if (err?.code === "ENOENT" || lower.includes("command not found")) {
|
|
50
|
+
return { code: "not_found", message: HEYGEN_NOT_FOUND_MESSAGE };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (
|
|
54
|
+
lower.includes("unauthorized") ||
|
|
55
|
+
lower.includes("unauthenticated") ||
|
|
56
|
+
// \b401\b, not a bare "401" substring — otherwise request IDs (req-401abc),
|
|
57
|
+
// URLs, and retry-after headers would misclassify as an auth failure.
|
|
58
|
+
/\b401\b/.test(lower) ||
|
|
59
|
+
lower.includes("not logged in") ||
|
|
60
|
+
lower.includes("no api key") ||
|
|
61
|
+
lower.includes("missing api key") ||
|
|
62
|
+
lower.includes("invalid api key") ||
|
|
63
|
+
lower.includes("login required") ||
|
|
64
|
+
lower.includes("auth required") ||
|
|
65
|
+
lower.includes("authentication required")
|
|
66
|
+
) {
|
|
67
|
+
return { code: "not_authenticated", message: HEYGEN_NOT_AUTHENTICATED_MESSAGE };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const version = firstSemver(text);
|
|
71
|
+
if (version && versionLessThan(version, HEYGEN_MIN_VERSION)) {
|
|
72
|
+
return { code: "outdated", message: HEYGEN_OUTDATED_MESSAGE };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
lower.includes("rate limit") ||
|
|
77
|
+
lower.includes("quota") ||
|
|
78
|
+
lower.includes("insufficient credit") ||
|
|
79
|
+
lower.includes("too many requests") ||
|
|
80
|
+
lower.includes("throttled") ||
|
|
81
|
+
/\b429\b/.test(lower)
|
|
82
|
+
) {
|
|
83
|
+
return { code: "rate_limited", message: detail };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { code: "other", message: detail };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// reportHeygenFailure's callers (voice-provider.mjs, heygen-search.mjs) are
|
|
90
|
+
// synchronous and several layers below the CLI's process.exit() calls, so
|
|
91
|
+
// they can't await this tracking call themselves. Stash each attempt's
|
|
92
|
+
// promise here so a caller closer to exit (resolve.mjs) can join it first —
|
|
93
|
+
// same "awaited so a short-lived run flushes it" discipline telemetry.mjs's
|
|
94
|
+
// track() already documents, just reachable from a sync call site.
|
|
95
|
+
const pendingFailureTracking = new Set();
|
|
96
|
+
// resolve.mjs is a single-shot CLI (one resolve per process), so one shared
|
|
97
|
+
// consume-once slot is sufficient. If resolve becomes an in-process/concurrent
|
|
98
|
+
// API, move this state into a per-resolve context before reusing that path.
|
|
99
|
+
let pendingRemediation = null;
|
|
100
|
+
|
|
101
|
+
export function consumeHeygenRemediation() {
|
|
102
|
+
const remediation = pendingRemediation;
|
|
103
|
+
pendingRemediation = null;
|
|
104
|
+
return remediation;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function reportHeygenFailure(err, context, trackEvent = track) {
|
|
108
|
+
const { code, message } = classifyHeygenErrorResult(err);
|
|
109
|
+
if (code === "not_found" || code === "outdated") {
|
|
110
|
+
pendingRemediation = { code, message };
|
|
111
|
+
}
|
|
112
|
+
if (ACTIONABLE_MESSAGES.has(message)) {
|
|
113
|
+
console.error(message);
|
|
114
|
+
} else {
|
|
115
|
+
console.error(`media-use: \`${context}\` failed: ${message}`);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const tracked = Promise.resolve(
|
|
119
|
+
trackEvent("media_use_provider_error", { provider: "heygen", reason: code }),
|
|
120
|
+
).catch(() => {});
|
|
121
|
+
pendingFailureTracking.add(tracked);
|
|
122
|
+
void tracked.finally(() => pendingFailureTracking.delete(tracked));
|
|
123
|
+
return tracked;
|
|
124
|
+
} catch {
|
|
125
|
+
// Telemetry must never affect the provider failure path.
|
|
126
|
+
return Promise.resolve();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Awaits every provider-error track fired since the last flush, so a caller
|
|
131
|
+
// about to process.exit() doesn't orphan one mid-request (both are separate,
|
|
132
|
+
// non-keepalive HTTP connections with no ordering guarantee otherwise).
|
|
133
|
+
// Never rejects: each tracked promise already swallows its own failure.
|
|
134
|
+
export async function flushHeygenFailureTracking() {
|
|
135
|
+
if (pendingFailureTracking.size === 0) return;
|
|
136
|
+
await Promise.all(pendingFailureTracking);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Shared discovery/generation helper for the CLI-shelling providers (voice,
|
|
140
|
+
// avatar-video): run a heygen JSON subcommand, report+classify on failure,
|
|
141
|
+
// and hand the caller the classified reason via onError (used by callers that
|
|
142
|
+
// need to distinguish e.g. not_authenticated from other failures).
|
|
143
|
+
export function runHeygenJson(bin, argv, label, onError) {
|
|
144
|
+
let out;
|
|
145
|
+
try {
|
|
146
|
+
out = execFileSync(bin, argv, {
|
|
147
|
+
encoding: "utf8",
|
|
148
|
+
timeout: 120000,
|
|
149
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
reportHeygenFailure(err, `${bin} ${label}`);
|
|
153
|
+
onError?.(classifyHeygenErrorCode(err));
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(out);
|
|
158
|
+
} catch {
|
|
159
|
+
console.error(`media-use: \`${bin} ${label}\` returned non-JSON output`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function firstSemver(text) {
|
|
165
|
+
const match = String(text || "").match(/\bv?(\d+)\.(\d+)\.(\d+)\b/);
|
|
166
|
+
return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function versionLessThan(version, minimum) {
|
|
170
|
+
const left = versionParts(version);
|
|
171
|
+
const right = versionParts(minimum);
|
|
172
|
+
if (!left || !right) return false;
|
|
173
|
+
for (let i = 0; i < 3; i++) {
|
|
174
|
+
if (left[i] < right[i]) return true;
|
|
175
|
+
if (left[i] > right[i]) return false;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function heygenErrorDetail(err) {
|
|
181
|
+
return textOf(err?.stderr) || textOf(err?.stdout) || err?.message || String(err);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function textOf(value) {
|
|
185
|
+
return value == null ? "" : String(value).trim();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function versionParts(version) {
|
|
189
|
+
const match = String(version || "").match(/^v?(\d+)\.(\d+)\.(\d+)$/);
|
|
190
|
+
return match ? match.slice(1).map((part) => Number.parseInt(part, 10)) : null;
|
|
191
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import {
|
|
5
|
+
classifyHeygenError,
|
|
6
|
+
classifyHeygenErrorCode,
|
|
7
|
+
consumeHeygenRemediation,
|
|
8
|
+
flushHeygenFailureTracking,
|
|
9
|
+
HEYGEN_NOT_AUTHENTICATED_MESSAGE,
|
|
10
|
+
HEYGEN_NOT_FOUND_MESSAGE,
|
|
11
|
+
HEYGEN_OUTDATED_MESSAGE,
|
|
12
|
+
reportHeygenFailure,
|
|
13
|
+
} from "./heygen-cli.mjs";
|
|
14
|
+
|
|
15
|
+
function captureFailureReport(err, context, trackEvent) {
|
|
16
|
+
const originalError = console.error;
|
|
17
|
+
const stderrCalls = [];
|
|
18
|
+
console.error = (...args) => stderrCalls.push(args);
|
|
19
|
+
try {
|
|
20
|
+
if (trackEvent) {
|
|
21
|
+
reportHeygenFailure(err, context, trackEvent);
|
|
22
|
+
} else {
|
|
23
|
+
reportHeygenFailure(err, context);
|
|
24
|
+
}
|
|
25
|
+
} finally {
|
|
26
|
+
console.error = originalError;
|
|
27
|
+
}
|
|
28
|
+
return stderrCalls;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("classifies ENOENT-style missing heygen errors with install instructions", () => {
|
|
32
|
+
const message = classifyHeygenError({ code: "ENOENT", message: "spawn heygen ENOENT" });
|
|
33
|
+
|
|
34
|
+
assert.equal(message, HEYGEN_NOT_FOUND_MESSAGE);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("classifies auth failures with login instructions", () => {
|
|
38
|
+
const message = classifyHeygenError({ stderr: Buffer.from("Error: not logged in") });
|
|
39
|
+
|
|
40
|
+
assert.equal(message, HEYGEN_NOT_AUTHENTICATED_MESSAGE);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("classifies a real 401 as auth, but not a bare 401 substring in prose", () => {
|
|
44
|
+
assert.equal(
|
|
45
|
+
classifyHeygenError({ stderr: Buffer.from("HTTP 401 Unauthorized") }),
|
|
46
|
+
HEYGEN_NOT_AUTHENTICATED_MESSAGE,
|
|
47
|
+
);
|
|
48
|
+
// A request id that merely contains "401" must NOT read as an auth failure.
|
|
49
|
+
const noise = classifyHeygenError({ stderr: Buffer.from("upload failed (request req-401abc)") });
|
|
50
|
+
assert.notEqual(noise, HEYGEN_NOT_AUTHENTICATED_MESSAGE);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("classifies old heygen versions with update instructions", () => {
|
|
54
|
+
const message = classifyHeygenError({
|
|
55
|
+
stderr: Buffer.from("heygen v0.1.5 does not support --headers"),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
assert.equal(message, HEYGEN_OUTDATED_MESSAGE);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("does not misclassify a resource 'not found' error as a missing CLI", () => {
|
|
62
|
+
// A stale voiceId makes `heygen voice speech create` fail with "voice not
|
|
63
|
+
// found"; the error message embeds the `heygen ...` command line. This must
|
|
64
|
+
// pass through as detail, not send the user to reinstall a working CLI.
|
|
65
|
+
const message = classifyHeygenError({
|
|
66
|
+
stderr: Buffer.from("Error: voice not found (id: stale-123)"),
|
|
67
|
+
message: "Command failed: heygen voice speech create --voice stale-123",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
assert.notEqual(message, HEYGEN_NOT_FOUND_MESSAGE);
|
|
71
|
+
assert.equal(message, "Error: voice not found (id: stale-123)");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("classifies a shell 'command not found' as a missing CLI", () => {
|
|
75
|
+
const message = classifyHeygenError({ stderr: Buffer.from("bash: heygen: command not found") });
|
|
76
|
+
|
|
77
|
+
assert.equal(message, HEYGEN_NOT_FOUND_MESSAGE);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("passes through unrelated errors", () => {
|
|
81
|
+
const message = classifyHeygenError({
|
|
82
|
+
stderr: Buffer.from("rate limit exceeded"),
|
|
83
|
+
message: "Command failed",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
assert.equal(message, "rate limit exceeded");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("classifies existing HeyGen failures with stable reason codes", () => {
|
|
90
|
+
assert.equal(classifyHeygenErrorCode({ code: "ENOENT" }), "not_found");
|
|
91
|
+
assert.equal(
|
|
92
|
+
classifyHeygenErrorCode({ stderr: Buffer.from("HTTP 401 Unauthorized") }),
|
|
93
|
+
"not_authenticated",
|
|
94
|
+
);
|
|
95
|
+
assert.equal(
|
|
96
|
+
classifyHeygenErrorCode({ stderr: Buffer.from("heygen v0.1.5 is unsupported") }),
|
|
97
|
+
"outdated",
|
|
98
|
+
);
|
|
99
|
+
assert.equal(classifyHeygenErrorCode({ stderr: Buffer.from("provider unavailable") }), "other");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("classifies rate-limit text case-insensitively", () => {
|
|
103
|
+
assert.equal(
|
|
104
|
+
classifyHeygenErrorCode({ stderr: Buffer.from("RATE LIMIT exceeded") }),
|
|
105
|
+
"rate_limited",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("classifies quota and insufficient-credit errors as rate limited", () => {
|
|
110
|
+
for (const detail of ["Quota exhausted", "INSUFFICIENT CREDIT remaining"]) {
|
|
111
|
+
assert.equal(classifyHeygenErrorCode({ stderr: Buffer.from(detail) }), "rate_limited");
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("classifies the literal 429 reason phrase and throttling language as rate limited", () => {
|
|
116
|
+
for (const detail of ["Too Many Requests", "Error: throttled by upstream, retry later"]) {
|
|
117
|
+
assert.equal(classifyHeygenErrorCode({ stderr: Buffer.from(detail) }), "rate_limited");
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("does not misclassify unrelated errors that share a word with the new phrasing", () => {
|
|
122
|
+
// Shares "too many" with "too many requests" but is a distinct failure (fd
|
|
123
|
+
// exhaustion, not a rate limit) — the match must require the full phrase.
|
|
124
|
+
assert.equal(
|
|
125
|
+
classifyHeygenErrorCode({ stderr: Buffer.from("Too many open file descriptors") }),
|
|
126
|
+
"other",
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("classifies a bare 429 as rate limited without matching request IDs", () => {
|
|
131
|
+
assert.equal(
|
|
132
|
+
classifyHeygenErrorCode({ stderr: Buffer.from("HTTP 429 Too Many Requests") }),
|
|
133
|
+
"rate_limited",
|
|
134
|
+
);
|
|
135
|
+
assert.equal(
|
|
136
|
+
classifyHeygenErrorCode({ stderr: Buffer.from("request req-429abc failed") }),
|
|
137
|
+
"other",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("tracks not-found failures without changing actionable output", () => {
|
|
142
|
+
const trackingCalls = [];
|
|
143
|
+
const stderrCalls = captureFailureReport({ code: "ENOENT" }, "heygen asset search", (...args) =>
|
|
144
|
+
trackingCalls.push(args),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
assert.deepEqual(stderrCalls, [[HEYGEN_NOT_FOUND_MESSAGE]]);
|
|
148
|
+
assert.deepEqual(trackingCalls, [
|
|
149
|
+
["media_use_provider_error", { provider: "heygen", reason: "not_found" }],
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("records missing and outdated CLI remediation once", () => {
|
|
154
|
+
consumeHeygenRemediation();
|
|
155
|
+
captureFailureReport({ code: "ENOENT" }, "heygen audio sounds list", () => {});
|
|
156
|
+
assert.deepEqual(consumeHeygenRemediation(), {
|
|
157
|
+
code: "not_found",
|
|
158
|
+
message: HEYGEN_NOT_FOUND_MESSAGE,
|
|
159
|
+
});
|
|
160
|
+
assert.equal(consumeHeygenRemediation(), null);
|
|
161
|
+
|
|
162
|
+
captureFailureReport(
|
|
163
|
+
{ stderr: Buffer.from("heygen v0.1.5 does not support --headers") },
|
|
164
|
+
"heygen audio sounds list",
|
|
165
|
+
() => {},
|
|
166
|
+
);
|
|
167
|
+
assert.deepEqual(consumeHeygenRemediation(), {
|
|
168
|
+
code: "outdated",
|
|
169
|
+
message: HEYGEN_OUTDATED_MESSAGE,
|
|
170
|
+
});
|
|
171
|
+
assert.equal(consumeHeygenRemediation(), null);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("does not record non-install remediation", () => {
|
|
175
|
+
consumeHeygenRemediation();
|
|
176
|
+
for (const error of [
|
|
177
|
+
{ stderr: Buffer.from("HTTP 401 Unauthorized") },
|
|
178
|
+
{ stderr: Buffer.from("quota exhausted") },
|
|
179
|
+
{ stderr: Buffer.from("provider unavailable") },
|
|
180
|
+
]) {
|
|
181
|
+
captureFailureReport(error, "heygen audio sounds list", () => {});
|
|
182
|
+
assert.equal(consumeHeygenRemediation(), null);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("tracks generic failures without including raw detail", () => {
|
|
187
|
+
const trackingCalls = [];
|
|
188
|
+
const stderrCalls = captureFailureReport(
|
|
189
|
+
{ stderr: Buffer.from("private provider detail") },
|
|
190
|
+
"heygen asset search",
|
|
191
|
+
(...args) => trackingCalls.push(args),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
assert.deepEqual(stderrCalls, [
|
|
195
|
+
["media-use: `heygen asset search` failed: private provider detail"],
|
|
196
|
+
]);
|
|
197
|
+
assert.deepEqual(trackingCalls, [
|
|
198
|
+
["media_use_provider_error", { provider: "heygen", reason: "other" }],
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("keeps failure output observable when telemetry is opted out", () => {
|
|
203
|
+
const previousOptOut = process.env.HYPERFRAMES_NO_TELEMETRY;
|
|
204
|
+
process.env.HYPERFRAMES_NO_TELEMETRY = "1";
|
|
205
|
+
try {
|
|
206
|
+
const stderrCalls = captureFailureReport({ code: "ENOENT" }, "heygen asset search");
|
|
207
|
+
|
|
208
|
+
assert.deepEqual(stderrCalls, [[HEYGEN_NOT_FOUND_MESSAGE]]);
|
|
209
|
+
} finally {
|
|
210
|
+
if (previousOptOut === undefined) {
|
|
211
|
+
delete process.env.HYPERFRAMES_NO_TELEMETRY;
|
|
212
|
+
} else {
|
|
213
|
+
process.env.HYPERFRAMES_NO_TELEMETRY = previousOptOut;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("keeps failure output observable when tracking throws synchronously", () => {
|
|
219
|
+
const originalError = console.error;
|
|
220
|
+
const stderrCalls = [];
|
|
221
|
+
let thrown;
|
|
222
|
+
console.error = (...args) => stderrCalls.push(args);
|
|
223
|
+
try {
|
|
224
|
+
try {
|
|
225
|
+
reportHeygenFailure({ code: "ENOENT" }, "heygen asset search", () => {
|
|
226
|
+
throw new Error("tracking failed");
|
|
227
|
+
});
|
|
228
|
+
} catch (err) {
|
|
229
|
+
thrown = err;
|
|
230
|
+
}
|
|
231
|
+
} finally {
|
|
232
|
+
console.error = originalError;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
assert.deepEqual(stderrCalls, [[HEYGEN_NOT_FOUND_MESSAGE]]);
|
|
236
|
+
assert.equal(thrown, undefined);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("does not leave rejected tracking promises unhandled", () => {
|
|
240
|
+
const moduleUrl = new URL("./heygen-cli.mjs", import.meta.url).href;
|
|
241
|
+
const script = `
|
|
242
|
+
import { reportHeygenFailure } from ${JSON.stringify(moduleUrl)};
|
|
243
|
+
reportHeygenFailure(
|
|
244
|
+
{ stderr: "provider unavailable" },
|
|
245
|
+
"heygen asset search",
|
|
246
|
+
() => Promise.reject(new Error("tracking failed")),
|
|
247
|
+
);
|
|
248
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
249
|
+
`;
|
|
250
|
+
const child = spawnSync(
|
|
251
|
+
process.execPath,
|
|
252
|
+
["--unhandled-rejections=strict", "--input-type=module", "--eval", script],
|
|
253
|
+
{ encoding: "utf8", timeout: 5000 },
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
assert.equal(child.error, undefined);
|
|
257
|
+
assert.equal(child.signal, null);
|
|
258
|
+
assert.equal(child.status, 0, child.stderr);
|
|
259
|
+
assert.equal(child.stderr, "media-use: `heygen asset search` failed: provider unavailable\n");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("flushHeygenFailureTracking waits for a pending report before resolving", async () => {
|
|
263
|
+
const events = [];
|
|
264
|
+
let releaseTrack;
|
|
265
|
+
const gate = new Promise((resolve) => {
|
|
266
|
+
releaseTrack = resolve;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Mirrors the real call sites (voice-provider.mjs, heygen-search.mjs):
|
|
270
|
+
// fire-and-forget, the return value is never awaited by the caller.
|
|
271
|
+
reportHeygenFailure({ code: "ENOENT" }, "heygen voice speech", () =>
|
|
272
|
+
gate.then(() => {
|
|
273
|
+
events.push("track-settled");
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const flushed = flushHeygenFailureTracking().then(() => {
|
|
278
|
+
events.push("flush-resolved");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Let several pending microtasks drain before releasing the gate, so this
|
|
282
|
+
// proves flush is genuinely still waiting on the tracked promise -- not
|
|
283
|
+
// merely that it hasn't had a tick yet.
|
|
284
|
+
await Promise.resolve();
|
|
285
|
+
await Promise.resolve();
|
|
286
|
+
await Promise.resolve();
|
|
287
|
+
assert.deepEqual(events, [], "flush must not resolve while the tracked promise is still pending");
|
|
288
|
+
|
|
289
|
+
releaseTrack();
|
|
290
|
+
await flushed;
|
|
291
|
+
|
|
292
|
+
assert.deepEqual(
|
|
293
|
+
events,
|
|
294
|
+
["track-settled", "flush-resolved"],
|
|
295
|
+
"flush must resolve only after the pending track settles, in that order",
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("flushHeygenFailureTracking resolves immediately when nothing is pending", async () => {
|
|
300
|
+
await flushHeygenFailureTracking();
|
|
301
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { HEYGEN_CLIENT_SOURCE_ARGV, reportHeygenFailure } from "./heygen-cli.mjs";
|
|
3
|
+
|
|
4
|
+
export function heygenSearch(subcommand, query, { type, limit = 5, minScore } = {}) {
|
|
5
|
+
// execFileSync with an argv array (no shell), so query/type/etc. are passed as
|
|
6
|
+
// literal arguments — no quoting tricks, no command injection. subcommand is a
|
|
7
|
+
// hardcoded multi-word string (e.g. "audio sounds list"), split into tokens.
|
|
8
|
+
// Tag the caller via the CLI's allowlisted attribution header (heygen >= v0.3.0).
|
|
9
|
+
const args = [...HEYGEN_CLIENT_SOURCE_ARGV, ...subcommand.split(" "), "--query", query];
|
|
10
|
+
if (type) args.push("--type", type);
|
|
11
|
+
args.push("--limit", String(limit));
|
|
12
|
+
// Server-side score floor. Honored by `audio sounds list`; the `asset search`
|
|
13
|
+
// backend rejects it, so only audio providers pass minScore (see image-provider).
|
|
14
|
+
if (minScore != null) args.push("--min-score", String(minScore));
|
|
15
|
+
|
|
16
|
+
let out;
|
|
17
|
+
try {
|
|
18
|
+
out = execFileSync("heygen", args, {
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
timeout: 15000,
|
|
21
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
22
|
+
});
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// Don't swallow a broken command / auth failure as "no results" — that turns
|
|
25
|
+
// a typo or expired key into a silent dead end. Surface it, then give up.
|
|
26
|
+
reportHeygenFailure(err, `heygen ${subcommand}`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(out);
|
|
33
|
+
} catch {
|
|
34
|
+
console.error(`media-use: \`heygen ${subcommand}\` returned non-JSON output`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (parsed?.error) {
|
|
38
|
+
const e = parsed.error;
|
|
39
|
+
console.error(`media-use: \`heygen ${subcommand}\` error: ${e.message ?? JSON.stringify(e)}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = parsed?.data;
|
|
44
|
+
return Array.isArray(data) && data.length > 0 ? data : null;
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { HEYGEN_CLIENT_SOURCE_HEADERS } from "../../audio/scripts/lib/heygen.mjs";
|
|
7
|
+
import { HEYGEN_CLIENT_SOURCE_ARGV } from "./heygen-cli.mjs";
|
|
8
|
+
import { heygenSearch } from "./heygen-search.mjs";
|
|
9
|
+
|
|
10
|
+
test("tags HeyGen searches with the shared media-use client source", () => {
|
|
11
|
+
const dir = mkdtempSync(join(tmpdir(), "media-use-heygen-search-"));
|
|
12
|
+
const capturePath = join(dir, "argv.log");
|
|
13
|
+
const heygenPath = join(dir, "heygen");
|
|
14
|
+
const previousPath = process.env.PATH;
|
|
15
|
+
const previousCapturePath = process.env.HEYGEN_CAPTURE_PATH;
|
|
16
|
+
|
|
17
|
+
writeFileSync(
|
|
18
|
+
heygenPath,
|
|
19
|
+
`#!/bin/sh
|
|
20
|
+
printf '%s\\n' "$*" >> "$HEYGEN_CAPTURE_PATH"
|
|
21
|
+
printf '%s\\n' '{"data":[{"id":"x"}]}'
|
|
22
|
+
`,
|
|
23
|
+
);
|
|
24
|
+
chmodSync(heygenPath, 0o755);
|
|
25
|
+
process.env.PATH = `${dir}:${previousPath ?? ""}`;
|
|
26
|
+
process.env.HEYGEN_CAPTURE_PATH = capturePath;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const result = heygenSearch("audio sounds list", "ocean", { limit: 1 });
|
|
30
|
+
const argv = readFileSync(capturePath, "utf8").trim();
|
|
31
|
+
|
|
32
|
+
assert.deepEqual(result, [{ id: "x" }]);
|
|
33
|
+
assert.match(argv, /X-HeyGen-Client-Source: media-use/);
|
|
34
|
+
} finally {
|
|
35
|
+
if (previousPath === undefined) delete process.env.PATH;
|
|
36
|
+
else process.env.PATH = previousPath;
|
|
37
|
+
if (previousCapturePath === undefined) delete process.env.HEYGEN_CAPTURE_PATH;
|
|
38
|
+
else process.env.HEYGEN_CAPTURE_PATH = previousCapturePath;
|
|
39
|
+
rmSync(dir, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("keeps CLI and REST media-use client source headers in lockstep", () => {
|
|
44
|
+
const [entry] = Object.entries(HEYGEN_CLIENT_SOURCE_HEADERS);
|
|
45
|
+
assert.ok(entry);
|
|
46
|
+
const [key, value] = entry;
|
|
47
|
+
|
|
48
|
+
assert.equal(HEYGEN_CLIENT_SOURCE_ARGV[1], `${key}: ${value}`);
|
|
49
|
+
});
|