Files
hyperframes/skills/media-use/scripts/lib/heygen-cli.test.mjs
T
Miguel ÁngelandClaude Opus 4.8 cdb8d736f1 feat(media-use): fast heygen CLI onboarding — actionable diagnostics, --doctor, free-usage framing (#2065)
* feat(media-use): fast heygen CLI onboarding — actionable diagnostics, --doctor, free-usage framing

media-use resolves bgm/sfx/image/icon (catalog), voice (TTS), and avatar video
through the heygen CLI — the free-usage path. Agents hit a dead end when it's
missing/unauthed. This guides them to install it fast, at the point of need.

- Centralized actionable diagnostics (lib/heygen-cli.mjs): every heygen-backed
  resolve, on failure, prints the exact fix on stderr — not-installed (curl
  install one-liner), not-authenticated (heygen auth login), outdated (heygen
  update). Routed through heygen-search + voice-provider. stdout stays clean JSON.
- resolve --doctor preflight (human + --json): checks heygen present/version/
  auth, ffmpeg, ffprobe, node, a fix per gap. Exit 0 unless ffmpeg missing.
- SKILL reframe: install-first callout; heygen as the free-usage gateway for
  bgm/image/voice/avatar-video; removed the false "degrades gracefully" claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

* fix(media-use): address #2065 review — classifier blocker, doctor contract, telemetry

- Blocker: classifyHeygenError no longer treats a bare "not found" as CLI-missing
  (a stale voiceId → "voice not found" was sending users to reinstall a working
  CLI); keep only ENOENT + "command not found". Regression test added.
- 401 now matches \b401\b, not any "401" substring (request IDs no longer misread).
- --doctor: top-level ok requires ffmpeg AND ffprobe (matches SKILL.md); emits
  media_use_doctor_run telemetry; auth status queried with --json + JSON-only
  parse; auth timeout softened (network issue, not a false "unauthenticated");
  node version gated on >= 18; version-without-semver labeled, not silently green.
- Nits: install cmd uses && ; dropped the runResolveStatus alias.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

* fix(media-use): require OAuth-capable heygen CLI (v0.3.0), fix auth-status probe

E2E against the live free-usage backend surfaced three issues:
- HEYGEN_MIN_VERSION was 0.1.6, but that CLI can't use OAuth ("heygen-cli can't
  use OAuth yet") — free usage needs >= v0.3.0. Bumped the floor; --doctor now
  also nudges `heygen update` when a newer stable exists (always-latest).
- Onboarding pointed at `heygen auth login --key` (API credits / billing); the
  free path is `--oauth` (subscription/free credits). Fixed install + auth
  guidance and SKILL.md accordingly.
- `heygen auth status --json` is an unknown flag on v0.3.0 (JSON is the default
  output) — the added --json broke auth detection. Dropped it; verified
  --doctor reports authenticated on a real free (OAuth) account.

Tests assert against the exported message constants instead of brittle literals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

* fix(media-use): address #2065 review nits — one root cause on old CLI, floor policy

- --doctor skips the auth check when the version check fails (below v0.3.0): an
  old CLI's auth probe fails for the same root cause, so users no longer see two
  errors ("outdated" + "not authenticated") — one root cause, one fix.
- Comment links the auth-status probe's JSON-default assumption to
  HEYGEN_MIN_VERSION >= 0.3.0 so the floor isn't silently lowered later.
- SKILL.md states the uniform v0.3.0 requirement (nudged even for API-key use).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv

* fix(media-use): doctor prints one heygen row per fact

The 'heygen on PATH' and 'heygen version' checks both rendered their
detail as `heygen v0.3.0`, so --doctor printed two byte-identical green
lines. Make the PATH row report presence ("heygen found on PATH") and let
the version row own the version string — one row per fact, no duplicate.

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>
2026-07-09 18:30:53 -04:00

67 lines
2.4 KiB
JavaScript

import { strict as assert } from "node:assert";
import { test } from "node:test";
import {
classifyHeygenError,
HEYGEN_NOT_AUTHENTICATED_MESSAGE,
HEYGEN_NOT_FOUND_MESSAGE,
HEYGEN_OUTDATED_MESSAGE,
} from "./heygen-cli.mjs";
test("classifies ENOENT-style missing heygen errors with install instructions", () => {
const message = classifyHeygenError({ code: "ENOENT", message: "spawn heygen ENOENT" });
assert.equal(message, HEYGEN_NOT_FOUND_MESSAGE);
});
test("classifies auth failures with login instructions", () => {
const message = classifyHeygenError({ stderr: Buffer.from("Error: not logged in") });
assert.equal(message, HEYGEN_NOT_AUTHENTICATED_MESSAGE);
});
test("classifies a real 401 as auth, but not a bare 401 substring in prose", () => {
assert.equal(
classifyHeygenError({ stderr: Buffer.from("HTTP 401 Unauthorized") }),
HEYGEN_NOT_AUTHENTICATED_MESSAGE,
);
// A request id that merely contains "401" must NOT read as an auth failure.
const noise = classifyHeygenError({ stderr: Buffer.from("upload failed (request req-401abc)") });
assert.notEqual(noise, HEYGEN_NOT_AUTHENTICATED_MESSAGE);
});
test("classifies old heygen versions with update instructions", () => {
const message = classifyHeygenError({
stderr: Buffer.from("heygen v0.1.5 does not support --headers"),
});
assert.equal(message, HEYGEN_OUTDATED_MESSAGE);
});
test("does not misclassify a resource 'not found' error as a missing CLI", () => {
// A stale voiceId makes `heygen voice speech create` fail with "voice not
// found"; the error message embeds the `heygen ...` command line. This must
// pass through as detail, not send the user to reinstall a working CLI.
const message = classifyHeygenError({
stderr: Buffer.from("Error: voice not found (id: stale-123)"),
message: "Command failed: heygen voice speech create --voice stale-123",
});
assert.notEqual(message, HEYGEN_NOT_FOUND_MESSAGE);
assert.equal(message, "Error: voice not found (id: stale-123)");
});
test("classifies a shell 'command not found' as a missing CLI", () => {
const message = classifyHeygenError({ stderr: Buffer.from("bash: heygen: command not found") });
assert.equal(message, HEYGEN_NOT_FOUND_MESSAGE);
});
test("passes through unrelated errors", () => {
const message = classifyHeygenError({
stderr: Buffer.from("rate limit exceeded"),
message: "Command failed",
});
assert.equal(message, "rate limit exceeded");
});