Address PR #2082 review feedback:
- Route studio thumbnail + render call sites through preferManagedChrome
so studio renders no longer silently fall back to whatever system
Chrome happens to be installed.
- `hyperframes browser ensure` now resolves through the same
preferManagedChrome path render uses, so it reports what render will
actually pick instead of any system Chrome it happens to find.
- Point the unsupported-Chrome fallback log at `browser ensure --force`
instead of `doctor`, which doesn't check Chrome/drawElement capability.
- Fix stale findFromCache comment: the HF pin is now a Dev-channel build
that can be newer than a user's puppeteer-cache Stable install.
* fix(cli): purge stale/partial browser installs instead of wedging retries
Two independent reports of the same failure: a `chrome-headless-shell`
zip extraction gets interrupted (Windows AV lock, sleep/wake, ctrl-C)
and leaves only the alphabetically-early files (ABOUT/LICENSE) in the
target directory, no executable. Every subsequent `browser ensure` (or
implicit re-download from `findBrowser`/`ensureBrowser`) sees the
directory already exists and hands it straight to @puppeteer/browsers'
install(), which throws "folder exists but the executable is missing"
without re-extracting -- permanently wedging the machine until someone
manually deletes the directory. `--force` didn't help because it was a
phantom flag: `browser.ts` never declared it, so it silently did
nothing (mentioned only in an error-message string).
Root cause: `findFromCache()` already detects this exact case (dir
exists, exe missing) and returns it as `staleHyperframesCachePath`, but
`findBrowser()`/`ensureBrowser()` fed that straight into a re-download
without ever deleting the stale directory first, so install() hit the
same "exists" branch every time.
Fix:
- `findFromCache()` also returns `staleInstallPath` (InstalledBrowser's
`.path` -- the actual install-folder root, not the missing
executablePath) for the stale case.
- Both `findBrowser()` and `ensureBrowser()` now purge that directory
(`rmSync`, inside the existing `withInstallLock` mutex from #1866 so
a purge can't race a concurrent installer) before retrying, so
install() actually re-extracts instead of erroring.
- Wired up a real `--force` flag on `hyperframes browser ensure`: it
purges the whole HF-managed cache (reusing the already-tested
`clearBrowser()`) and skips every cache/system shortcut, so it always
gets a fresh download regardless of what's currently on disk --
matching what the existing (previously false) help text already
claimed it did.
Not fixed here (separate root cause, flagged for later): neither
report's machine had a usable auto-detected system Chrome fallback on
Windows -- `SYSTEM_CHROME_PATHS` only lists macOS/Linux paths, so
`findFromSystem()` can never succeed on win32. Both reporters worked
around this manually via HYPERFRAMES_BROWSER_PATH, which still works
fine; adding real Windows system-Chrome detection is a distinct,
larger change.
Test: extended manager.test.ts's existing stale-cache-redownload test
to include a populated stale install directory and assert it's gone
before the mocked install() is called (was previously only asserting
the redownload happened, not that the fix's purge step ran). Added a
new test for `ensureBrowser({force: true})` purging the cache and
bypassing a healthy cache/system-Chrome shortcut. Also fixed the shared
fs mock's `rmSync` to actually simulate recursive deletion (drop
nested tracked paths too), which the new tests need and the old ones
never exercised. Full CLI suite (1222 tests) passes.
* fix(cli): serialize force browser cache purge
Observability showed `browser` (~75% fail, ~1.3k users/day) and `info` (~60%
fail) failing at high rates with no captured reason — only
`cli_command_result success=false`. citty's `runMain` catches a command's
thrown error and `process.exit(1)`s without re-throwing, so a thrown failure
never reached the existing `cli_error` telemetry (which only fired from the
uncaughtException / unhandledRejection handlers).
Wrap every command's `run()` at the dispatch boundary (cli.ts) so a thrown
failure reports its reason via `cli_error` (kind=command_error) before being
re-thrown unchanged — citty's print + exit-1 behavior is preserved. This
de-blinds every throw-style command at once: `browser ensure` (Chrome
download), `tts`, `inspect`, `render`, etc.
Paths that bypass the wrapper are handled inline:
- `browser` self-exits (`path` download failure, unknown subcommand) — report
inline; the ARM64 `ensure` branch previously swallowed a failed install and
returned success, now reports and exits 1.
- `resolveProject()` self-exits on InvalidProjectError (the dominant `info`
failure — run outside a project) — report inline before exit.
Hardening:
- PII: `trackCliError` now redacts error_message + stack_trace via
redactTelemetryString (matching render_* events) — CLI errors and stacks
carry absolute install paths / cache dirs / user args.
- Race: the wrapper awaits an on-demand telemetry import before re-throwing, so
a command that fails before the lazy telemetry import settles still reports
(a telemetry failure is swallowed and never masks the real error).
Pure helpers in utils/command-failure-tracking.ts with unit tests for the
throw / success / no-run / onFailure-rejection cases, the reporter wiring, and
trackCliError redaction. CommandDef<any> mirrors citty's SubCommandsDef.
Known scope: commands that print + `process.exit(1)` on their own validation
paths (tts/validate/lint argument errors) remain wrapper-blind — follow-up.
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each)
* fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files
* feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson
Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux:
- Detects the platform automatically
- Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM)
- Falls back to clear manual instructions with exact commands
- 'hyperframes browser ensure' guides through the setup interactively
- After setup, all render commands work without any flags
* fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds
Path exclusions are insufficient — Defender re-scans new files created
during bun install before the exclusion takes effect. Disable real-time
monitoring for the entire job duration instead (standard CI practice).
* refactor(studio): split all files >500 LOC + extract useToast, delete allowlist
All 11 large files split into focused modules under 500 LOC.
App.tsx extracted toast logic into useToast hook (493 LOC now).
.filesize-allowlist deleted — no longer needed.
* fix: remove unused imports from split files, extract useToast from App.tsx
App.tsx: 504 → 493 lines (toast logic extracted to useToast hook)
timelineDOM.ts: remove unused imports from re-export pattern
MotionPanel.tsx: remove unused clampStudioCustomEasePoints import
studioMotionOps.ts: remove unused StudioGsapMotionDirection import
* fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs)
* fix(producer): use node --experimental-strip-types instead of tsx for build:fonts
Eliminates the tsx binary dependency that Windows Defender locks during
bun install, causing EPERM errors. Node 22.6+ strips TypeScript types
natively with no external binary.
* chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500)
* fix(ci): disable Windows Defender before checkout to prevent all EPERM races
* fix(producer): skip build:fonts if fontData.generated.ts already exists
The generated file is tracked in git, so CI doesn't need to regenerate
it. This avoids @fontsource/inter node_modules access on Windows which
triggers EPERM from Defender scanning during bun install.
Move per-command examples from the centralized `help.ts` record into
each command file as `export const examples: Example[]`. help.ts now
dynamically imports them at --help time.
This means adding a new command and its examples happens in one file
instead of two, reducing the chance of forgetting examples.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): non-interactive by default, --human-friendly for UI
Following ElevenLabs CLI pattern: default mode is agent-friendly
(flag-driven, plain text output, fail fast on missing args).
Interactive clack UI is opt-in via --human-friendly.
Init command:
- --template required in default mode (errors with example if missing)
- --video / --audio flags for media input
- --skip-skills / --skip-transcribe to control optional steps
- --human-friendly enables the existing interactive prompts
- --help shows examples for every flag combination
- Transcription runs automatically in default mode (unless --skip-transcribe)
- Plain console.log output, process.exit(1) on errors
Skills command:
- Added --human-friendly flag
- Added examples to --help output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): improve --help documentation and add --yes/--check to upgrade
- upgrade: add --yes and --check flags to skip interactive prompt
- benchmark: clarify description — preset fps/quality/worker configs
- browser: describe each subcommand (ensure/path/clear) in help
- docs: list available topics inline in --help output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract shared isDevMode() to utils/env.ts (was duplicated in dev.ts and client.ts)
- Use ui/colors.ts instead of raw ANSI escapes in telemetry notice (respects NO_COLOR)
- Derive known commands from subCommands object instead of maintaining duplicate set
- Skip telemetry on --help/--version and unknown commands
- Gate incrementCommandCount() behind shouldTrack() (no disk writes in CI)
- Add flushSync() for process.exit() paths (beforeExit doesn't fire on explicit exit)
- Remove dead trackBrowserInstall(success) param (failure path never called it)
- Remove redundant isEnabled/anonymousId caching in client.ts (config.ts cache suffices)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add anonymous usage telemetry to help improve the CLI. Uses PostHog's
HTTP batch API directly (zero new dependencies) with a 5-second timeout
and fail-silent behavior — telemetry never breaks the CLI.
What's collected: command names, render performance (duration, fps,
quality), template choices, OS/arch/Node version/CLI version.
What's NOT collected: file paths, project names, video content, or
any personally identifiable information.
Telemetry is:
- Disabled in dev mode (running via tsx)
- Disabled in CI (CI=true) or via HYPERFRAMES_NO_TELEMETRY=1
- Disabled when API key is placeholder (safe to merge before key is set)
- Controllable via `hyperframes telemetry [enable|disable|status]`
- Disclosed on first run with clear opt-out instructions
Config stored at ~/.hyperframes/config.json (0600 permissions).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: initial code port from hyperframes-internal
Port all OSS-ready packages from the internal monorepo:
- @hyperframes/core — shared types, HTML generation, GSAP utilities, runtime
- @hyperframes/cli — CLI for creating, previewing, and rendering compositions
- @hyperframes/engine — framework-agnostic rendering engine (BeginFrame + FFmpeg)
- @hyperframes/producer — video rendering pipeline (Puppeteer + FFmpeg)
- @hyperframes/ui-player — browser-based video player component
- @hyperframes/studio — composition editor (React frontend + Hono backend)
Includes regression test suite with Docker-based test harness.
All HeyGen-internal references, deployment infrastructure, and
proprietary assets have been removed. Package names migrated
from @app/* to @hyperframes/*.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: scrub internal codenames and stale references from OSS port
- Replace static.heygen.ai runtime URLs in test fixtures
- Remove internal CDN publish script (publish-hyperframe-runtime.ts)
- Replace sandbox-studio, sandbox-interceptor, __magicEditRuntime
with neutral names (studio, hyperframe-runtime, __hyperframeRuntime)
- Fix stale Vault API / localhost references in docs
- Remove broken deprecated_studio link
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove remaining internal codenames and stale references
- Delete stale producer README.md and PIPELINE.md (referenced nonexistent files)
- Replace "Cerberus" codename with "HyperFrames" in test design reviews
- Replace magic-edit postMessage identifiers with hf-preview/hf-parent
- Rename debug-magic-edit-timeline.ts to debug-timeline.ts
- Replace "Motion Cut" with "HyperFrames" in Timeline comments
- Fix studio/CLI references to nonexistent archive package
(use local data/projects/ dir, stub render proxy)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>