mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
* fix(cli): invalidate the skills nudge cache after a successful install/update/check The passive "N skills out of date or missing" nudge reads a 24h config cache that only the background check (on non-skills commands) ever wrote. The skills commands themselves are excluded from the nudge pipeline, so a successful `skills update`/install/check never refreshed or dropped the cached verdict — the pre-install count kept printing on every other command for up to 24h. Reconcile commands now drop the cached verdict (counts + timestamp) so the next command's background check re-runs for real. The offline presence-only path deliberately keeps the cache: that run learned nothing about freshness. * fix(skills): win32-safe npx spawns in media-use + accurate whisper wording The Whisper transcribe fallback and the Kokoro local-TTS delegation both spawned a bare "npx" via execFileSync — on Windows npx is npx.cmd, which spawn cannot exec, so both paths died with `spawnSync npx ENOENT`. Route them through the skill's existing resolveSpawnCommand (node + npx-cli.js on win32, no shell:true), same as the audio engine's TTS spawns. Also corrects the "bundled with the hyperframes CLI" claim about whisper.cpp: it is resolved from PATH / installed via Homebrew / built from source with git+cmake on first use, and models download from HuggingFace — nothing whisper is shipped in the package. * feat(skills): canonical fully-silent marker + auth status exit-code docs product-launch's Step 3.1 gate said "or the project is marked silent" but nothing defined how to mark one, and audio.mjs unconditionally retrieved BGM. Define the canonical marker — `music: none` in the storyboard's top YAML block, plus no SCRIPT.md — and honor it: audio generate produces nothing (removing stale audio_meta.json, since absence is what assemble treats as silent), and `music: none` with narration keeps TTS while turning BGM off. Also documents the `auth status` exit-code contract (exit 1 while signed out is the normal offline state, not a failure) in the product-launch Step 0 note and the CLI skill's cloud reference. * fix(skills): transient-init retry for standalone animation-map and contrast-report The standalone helpers called initializeSession exactly once, so a valid modular project — whose sub-composition timelines register asynchronously — could hit the readiness deadline and die with the transient "zero duration / Runtime ready: false" diagnostic the render pipeline retries (probeStage). Add initializeSessionWithRetry to the shared package-loader (both byte-identical copies): close the crashed session and retry once with a fresh browser, gated by the engine's canonical isTransientBrowserError — now re-exported from @hyperframes/producer, with a frozen fallback pattern list for older published packages. The "Runtime ready: true" fast-fail (a genuine authoring bug) still fails without a retry. * feat(skills): extend the fully-silent marker to faceless-explainer and pr-to-video Both workflows reuse product-launch's audio model — their Step 3.1 gates carried the same undefined "marked silent" phrase, and their (intentionally identical) audio.mjs copies had the same unconditional BGM retrieve. Port the `music: none` marker handling into both copies, define the marker in their SKILL.md Step 3.1 and story-design references, and turn the copies' "intentionally identical" header claim into a byte-identity pin test so the next fix can't silently miss one of them. * test(cli): reset the prune mock explicitly instead of relying on restoreAllMocks The converge test's toHaveBeenCalledTimes(1) held only because vitest 3's vi.restoreAllMocks() clears vi.fn() call state; vitest 4 restores spies only, so the count would accumulate across tests and fail. Reset pruneOrphanedLockEntries in beforeEach like the other manifest mocks — passes under both vitest 3.2.4 (pinned) and vitest 4. * test(skills): close review findings — package-loader pin, whisper win32 parity, quoted-none Review follow-ups on #2476: - package-loader.mjs byte-identity pin (the elevated concern): the two copies now carry initializeSessionWithRetry + FALLBACK_TRANSIENT_PATTERNS, exactly the shared-logic shape a future fix could land in one copy and miss in the other — same enforcement as the audio.mjs pin. - whisper win32 call-site parity: runWhisper's npx resolution lifted into lib/npx-sync.mjs (resolveNpxInvocation, injectable params matching the localTtsGenerate idiom) with the same three-branch coverage as the Kokoro site — plus the hard-fail contract (throws actionably, since the whisper fallback has no next provider to fall through to). - quoted music: "none" pin: the vendored storyboard parser strips matching quotes at parse time (stripQuotes), so the silent marker already accepts the quoted spelling — pinned so that stays true.
129 lines
8.4 KiB
Markdown
129 lines
8.4 KiB
Markdown
# cloud — HeyGen-hosted rendering (zero-infra)
|
||
|
||
`hyperframes cloud render` renders a composition on HeyGen's managed cloud. The CLI zips the project, uploads it, runs the render on HeyGen's infrastructure (Chromium + FFmpeg), and downloads the finished video. Nothing to deploy, and no Chrome/FFmpeg/AWS to manage; you pay per credit.
|
||
|
||
```bash
|
||
npx hyperframes auth login # one-time sign-in
|
||
npx hyperframes cloud render # zip, upload, render, download
|
||
```
|
||
|
||
## When to use managed cloud, Lambda, Cloud Run, or local
|
||
|
||
- **`hyperframes render`** (local): fastest iteration loop, use while authoring.
|
||
- **`hyperframes cloud render`**: zero-infra. HeyGen runs the render and you pay per credit. This is the default answer to "render in the cloud" when you don't want to manage Chrome/FFmpeg/AWS.
|
||
- **`hyperframes lambda render`**: bring-your-own-AWS distributed rendering with chunked parallelism. Only worth it when you've already invested in AWS (see `lambda.md`).
|
||
- **`hyperframes cloudrun render`**: bring-your-own-GCP distributed rendering through Cloud Run and Workflows. Use only when GCP ownership is explicit (see `cloudrun.md`).
|
||
|
||
## Authentication
|
||
|
||
Cloud rendering needs a HeyGen credential, stored at `~/.heygen/credentials` (`0600`) and shared with the [`heygen` CLI](https://github.com/heygen-com/heygen-cli): sign in with one and the other picks up the session.
|
||
|
||
```bash
|
||
npx hyperframes auth login # OAuth 2.0 + PKCE, opens the browser
|
||
npx hyperframes auth login --api-key # CI/headless: hidden prompt, or pipe: echo "$HEYGEN_API_KEY" | ... --api-key
|
||
npx hyperframes auth status # active credential source, identity, billing snapshot
|
||
# exit 0 = signed in and verified; exit 1 = not signed in,
|
||
# or the credential was rejected — signed-out exit 1 is the
|
||
# normal offline state (scripts: `auth status || echo offline`),
|
||
# not a command failure
|
||
npx hyperframes auth refresh # force-refresh an OAuth token before a long job
|
||
npx hyperframes auth logout # clear the stored credential
|
||
```
|
||
|
||
Credential resolution order (first match wins): `HEYGEN_API_KEY`, then `HYPERFRAMES_API_KEY`, then `~/.heygen/credentials`. Point at a different backend with `HEYGEN_API_URL` (default `https://api.heygen.com`).
|
||
|
||
## The render pipeline
|
||
|
||
`cloud render` runs end-to-end:
|
||
|
||
1. **Resolve the project**: a local directory (default `.`), or skip the upload with `--asset-id` / `--url`.
|
||
2. **Auto-detect aspect ratio** from the entry HTML's `data-width`/`data-height`.
|
||
3. **Zip** the project (same ignore set as `hyperframes publish`, so it excludes `.git`, `node_modules`, `dist`, and so on).
|
||
4. **Upload** the zip to `POST /v3/assets`, yielding an `asset_id`.
|
||
5. **Submit** the render to `POST /v3/hyperframes/renders`, yielding a `render_id`.
|
||
6. **Poll** `GET /v3/hyperframes/renders/{id}` until it completes or fails (skip with `--no-wait`).
|
||
7. **Download** the signed video URL to disk.
|
||
|
||
## Render options
|
||
|
||
| Flag | Default | Meaning |
|
||
| ---------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||
| `--fps` | `30` | Frames per second, 1–240. |
|
||
| `--quality` | `standard` | `draft`, `standard`, or `high`. |
|
||
| `--format` | `mp4` | `mp4`, `webm`, or `mov` (webm/mov carry alpha). |
|
||
| `--resolution` | `1080p` | `1080p` or `4k` (4k billed at 1.5×). |
|
||
| `--aspect-ratio` | auto | `16:9`, `9:16`, or `1:1`. Auto from a local project's `data-width`/`data-height`; defaults to `16:9` for `--asset-id`/`--url`. |
|
||
| `--composition` / `-c` | `index.html` | Entry HTML file inside the zip. |
|
||
| `--output` / `-o` | `renders/<render_id>.<ext>` | Local download destination. |
|
||
|
||
```bash
|
||
npx hyperframes cloud render . \
|
||
--composition compositions/intro.html \
|
||
--output ./renders/intro.mp4
|
||
|
||
npx hyperframes cloud render --quality high --fps 60
|
||
```
|
||
|
||
`--resolution 4k` cannot combine with `--format webm`/`mov`: the 4k supersampling path has no alpha channel. Render 4k as mp4, or render alpha at native resolution.
|
||
|
||
## Templates and variables
|
||
|
||
Cloud rendering supports [composition variables](../../hyperframes-core/references/variables-and-media.md#variables): declare `data-composition-variables` on the composition, then fill them at render time.
|
||
|
||
```bash
|
||
npx hyperframes cloud render --variables '{"title":"Q4 Recap","theme":"dark"}'
|
||
npx hyperframes cloud render --variables-file ./vars.json
|
||
npx hyperframes cloud render --variables '{"title":"Q4 Recap"}' --strict-variables
|
||
```
|
||
|
||
For a **local project** the CLI validates `--variables` against the declared schema _before_ uploading. For `--asset-id`/`--url` the schema lives server-side, so mismatches surface as a `hyperframes_project_invalid` API error.
|
||
|
||
**Upload once, re-render many** is the idiomatic template loop: render a local project to get its `asset_id`, then re-submit against that asset with new values (no re-zip, no re-upload).
|
||
|
||
```bash
|
||
npx hyperframes cloud render ./card-template # note the asset_id printed on upload
|
||
npx hyperframes cloud render --asset-id asst_abc123 --variables '{"name":"Ada"}'
|
||
npx hyperframes cloud render --asset-id asst_abc123 --variables '{"name":"Linus"}'
|
||
```
|
||
|
||
For high-volume personalized batches, both self-managed paths provide JSONL fan-out: AWS Lambda (`lambda.md`) and Google Cloud Run (`cloudrun.md`). The full variables schema (types, declarative bindings, sub-composition overrides, precedence) lives in the `hyperframes-core` skill.
|
||
|
||
## Fire-and-forget and webhooks
|
||
|
||
By default the CLI blocks, polls, and downloads. Combine `--no-wait` (submit and exit with just the `render_id`) with `--callback-url` (HTTPS webhook on terminal status) for true fire-and-forget:
|
||
|
||
```bash
|
||
npx hyperframes cloud render --callback-url https://example.com/hf-hook --no-wait
|
||
# Poll later with: hyperframes cloud get hfr_def456
|
||
```
|
||
|
||
| Flag | Meaning |
|
||
| ----------------- | --------------------------------------------------- |
|
||
| `--no-wait` | Submit and exit immediately; print the `render_id`. |
|
||
| `--callback-url` | HTTPS webhook fired when the render terminates. |
|
||
| `--callback-id` | Opaque tracking ID echoed in webhook payloads. |
|
||
| `--poll-interval` | Poll cadence in seconds (default `10`). |
|
||
| `--max-wait` | Max poll duration in minutes (default `60`). |
|
||
|
||
## Managing renders
|
||
|
||
```bash
|
||
npx hyperframes cloud list # recent renders (--limit, --token, --all)
|
||
npx hyperframes cloud get hfr_def456 # full detail + short-lived signed video_url
|
||
npx hyperframes cloud delete hfr_def456 # soft-delete (--no-confirm to skip the prompt)
|
||
```
|
||
|
||
`video_url` and `thumbnail_url` are short-lived presigned URLs, so re-fetch with `cloud get` rather than caching them.
|
||
|
||
## Safe retries
|
||
|
||
The CLI transparently retries a `401` by force-refreshing the OAuth token and replaying. That's harmless for reads, but the zip upload (`POST /v3/assets`) is **not** idempotent: a blind retry creates a duplicate asset and bills twice. Pass `--idempotency-key` so retries are safe:
|
||
|
||
```bash
|
||
npx hyperframes cloud render . --idempotency-key "$(uuidgen)"
|
||
```
|
||
|
||
The key is forwarded to both upload and submit (the server scopes idempotency per-endpoint, so reusing one value is safe). Use any opaque string in `[A-Za-z0-9_:.-]`, 1–255 chars.
|
||
|
||
Full flag reference: docs `/deploy/cloud` and `/packages/cli#hyperframes-cloud`.
|