mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
af923d947c28c3306dbd05e0ecc2b9bd4961df72
73
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
29d6f1eac9 | fix(render): add end-to-end observability (#1248) | ||
|
|
9679503158 |
fix(cli): report available memory instead of free memory in doctor (#1204)
os.freemem() on macOS returns only truly free pages (~0.1 GB on a 24 GB machine), ignoring inactive/purgeable/speculative pages the kernel reclaims on demand. This caused a false "Low memory" warning on every macOS machine. Add getAvailableMemoryMb() that uses vm_stat on macOS and MemAvailable from /proc/meminfo on Linux, falling back to os.freemem() elsewhere. Also trim FFmpeg/FFprobe version strings to just "toolname X.Y.Z" instead of the full copyright line. |
||
|
|
d625dc8509 |
feat: post-render and Studio feedback collection via PostHog surveys (#1101)
* feat(cli): prompt for render satisfaction after successful renders * feat: add text feedback, doctor context, and Studio render feedback UI * feat(studio): replace render feedback with session-based Studio experience bar Move the feedback prompt out of RenderQueueItem (where it triggered every 5th render) into a standalone StudioFeedbackBar mounted at the bottom of the preview area. The new bar is session-gated (shows after the 5th studio session), auto-dismisses after 20s, and respects a 30-day cooldown once dismissed or submitted. Renames telemetry to trackStudioFeedback with a "studio_experience" survey ID to reflect the broader scope. * feat(studio): attach browser doctor summary to feedback events * fix(studio): use recurring interval for feedback instead of one-time cooldown * fix(cli): skip feedback prompt when an agent runtime is detected * feat(cli): add hyperframes feedback command and agent render hint - New `hyperframes feedback --rating <1-5> --comment "..."` command for submitting anonymous render satisfaction feedback via telemetry. - When an AI agent runtime is detected after a render, print a dimmed hint to stdout so the agent can optionally call the command instead of silently skipping the readline prompt. - Export getDoctorSummary from telemetry/feedback.ts to share the system-info collector between the interactive prompt and the CLI command. - Register the command in cli.ts and help.ts under Settings. * fix(studio): align feedback interval to every 15 sessions * fix: show CLI feedback on first render, Studio every 10 sessions * feat: add env flags to disable feedback prompts * feat: env flags to configure feedback prompt frequency * fix: address review — agent hint reachability, cadence gate, session debounce, deprecated API |
||
|
|
f19d6fd471 |
feat: CLI observability + fix studio save failures on JS-created elements (#1091)
* feat(core): add probeElementInSource for source-existence checks
* feat(core): add probe-element endpoint for source-existence checks
* feat(studio): gate editing capabilities on source existence
* fix(studio): enrich save_failure telemetry with target details
* feat(studio): async selection resolution with source probe
Make `resolveDomEditSelection` async and wire a `probeSourceElement` call
into the selection path so elements generated by scripts (not present in the
source HTML) are detected early and have all edit capabilities disabled with
a clear reason message ("This element is generated by a script and cannot be
edited visually.").
Part A – core probe logic:
- `domEditingLayers.ts`: `resolveDomEditSelection` is now async; calls
`probeSourceElement` (POST /api/projects/:id/file-mutations/probe-element/:file)
when `projectId` is supplied and the element has a stable id/selector.
`existsInSource: false` flows into `resolveDomEditCapabilities`, which
disables all write capabilities with the appropriate reason.
- `domEditingLayers.ts`: `refreshDomEditSelection` promoted to async.
- `files.ts`: new `probe-element` route; extracted `resolveProjectPath`,
`resolveFileMutationContext`, `writeIfChanged`, and `parseMutationBody`
helpers to eliminate repeated boilerplate across remove/patch/probe handlers.
Part B – caller propagation (all eight consumer sites):
- `useDomSelection.ts`: `buildDomSelectionFromTarget`,
`resolveDomSelectionFromPreviewPoint`,
`buildDomSelectionForTimelineElement`, `handleTimelineElementSelect`,
`refreshDomEditSelectionFromPreview`, and
`refreshDomEditGroupSelectionsFromPreview` all made async; `projectId`
forwarded into `resolveDomEditSelection`.
- `useDomEditCommits.ts`, `useDomEditTextCommits.ts`: updated
`buildDomSelectionFromTarget` parameter type; added `await` at call sites.
- `useDomEditSession.ts`: inner `syncSelectionFromDocument` made async; fire
with `void` to satisfy the surrounding effect.
- `usePreviewInteraction.ts`: `handlePreviewCanvasMouseDown` and
`handlePreviewCanvasPointerMove` made async (React ignores handler return
values, so this is safe).
- `useStudioUrlState.ts`: deferred `buildDomSelectionFromTarget` call
converted to `.then()` chain with `void` prefix so the effect stays sync.
- `LayersPanel.tsx`: `seekToLayer`, `handleSelectLayer`, and
`handleLayerHover` made async.
- `DomEditOverlay.tsx` / `useDomEditOverlayGestures.ts`: `onCanvasPointerMove`
return type widened to `Promise<DomEditSelection | null>`; pointer-down
handler falls back to `hoverSelectionRef.current` (always populated by a
prior hover) instead of awaiting the async move callback inline.
Part C – test and tooling fixes:
- `lefthook.yml`: filesize hook shell loop explicitly skips `*.test.ts/tsx`
files as a guard against a lefthook v2.1.6 bug where `exclude` patterns are
not applied to `{staged_files}` in shell scripts.
- `domEditing.test.ts`: all `it()` blocks calling `resolveDomEditSelection`
made async with `await`.
- `DomEditOverlay.test.ts`: mock updated to return `Promise.resolve(selection)`
and `hoverSelection` pre-seeded so pointer-down test works with the new
hover-first path.
- `studioUrlState.test.ts`: `buildDomSelectionFromTarget` mocks wrapped in
`Promise.resolve()`; seek/selection hydration test made async with
`await act(async () => { await Promise.resolve(); })` to flush microtasks.
* feat(cli): add global error handlers for crash telemetry
Register process-level uncaughtException and unhandledRejection handlers
that fire trackCliError so unhandled crashes are captured in telemetry.
Add the trackCliError function to events.ts and re-export it from the
telemetry barrel.
* feat(cli): track per-command success/failure and duration
* test(core): add integration test for JS-created element probe scenario
* fix: address PR review feedback
- uncaughtException handler now calls process.exit(1) after flushing
- cli_command_result uses real exit code from process "exit" event
- drop stack_trace from cli_error (contains filesystem paths)
- skip source probe during hover — only probe on click/selection
- format .fallowrc.jsonc
* fix(cli): restore stack_trace in cli_error telemetry
* fix(cli): use captured module refs in exit handlers instead of dead import()
|
||
|
|
e2ad165c6c |
fix(telemetry): drop unverified vendor rules, fix Codex markers, add Pi
Audit of every detection rule in the registry against actual vendor source code. Rules that lacked a public-source citation were guesses and have been removed; surviving rules now all cite the file + line that emits the marker. Codex — replace per @magi's investigation: - Drop CODEX_HOME (config override read at startup, NOT propagated to child processes — would miss most Codex invocations). - Drop CODEX_SANDBOX (macOS Seatbelt only; covered by the others). - Add CODEX_THREAD_ID (set unconditionally on every spawned shell command — codex-rs/protocol/src/shell_environment.rs:6 + codex-rs/core/src/unified_exec/process_manager.rs:1010). - Add CODEX_CI (hardcoded in UNIFIED_EXEC_ENV — process_manager.rs:70). - Keep CODEX_SANDBOX_NETWORK_DISABLED (default-on sandbox marker — codex-rs/core/src/sandboxing/mod.rs:135-138). Cursor — drop unverified CURSOR_TRACE_ID and CURSOR_AGENT guesses. Keep TERM_PROGRAM=cursor (set by Cursor's integrated terminal). Pi — new rule. https://github.com/earendil-works/pi packages/coding-agent/src/cli.ts:13 unconditionally executes process.env.PI_CODING_AGENT = "true"; at module entry, so every subprocess Pi spawns sees this marker. Same propagation pattern as Hermes. Removed (no source-cited marker found in this audit): - aider — verified Aider sets no AIDER_* env vars; only OR_SITE_URL and OR_APP_NAME (OpenRouter integration). No reliable marker. - gemini_cli — GEMINI_SANDBOX/GEMINI_CLI_TRUST_WORKSPACE are conditional on CLI flags; no unconditional marker found. - jules, devin — closed source, no public marker documentation. These vendors can be re-added later with a source citation; absence in the registry will silently false-negative (events land in the null bucket), but won't false-positive on other vendors. Per @james-russo's review: do source-level research before shipping detection rules. Memory updated to enforce this for future work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d7ff692f9f |
refactor(telemetry): address remaining PR #1035 review feedback
Three follow-ups from @miguel-heygen's review: 1. HERMES_QUIET — switch to existence check. `env["HERMES_QUIET"] === "1"` was brittle vs. future Hermes changes (e.g. if cli.py ever sets it to "true"). The var name itself is specific enough that existence is the right signal. 2. CI_PROVIDERS — convert to a discriminated union. `mode: "truthy" | "presence"` is stricter than the previous pair of optional boolean flags (which allowed entries with neither set). 3. Sandbox detection tests — add coverage. - Docker positive: /.dockerenv present → docker. - Negative case: plain Linux laptop with no markers → null. Together with the gVisor 4.4.0 fix in the previous commit, that addresses all three actionable callouts (the discriminated-union nit was non-blocking but worth doing while in the file). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1188814de2 |
fix(telemetry): require /proc/version confirmation for 4.4.0 gVisor
Addresses PR feedback from @magi: kernel string `4.4.0` is also the Ubuntu 16.04 LTS / older-real-kernel version, so accepting it alone false-positives. Now `4.4.0` only counts as gVisor when /proc/version also contains "gVisor". `*-gvisor` kernel strings remain standalone- sufficient since no real production kernel reports them. Adds a regression test that an Ubuntu 16.04 box reporting `Linux version 4.4.0-1128-aws (buildd@lcy01)` is NOT classified as a gVisor sandbox. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0c6012a2ec |
feat(telemetry): fingerprint sandbox runtime and agent vendor
Add two new properties to every CLI telemetry event so we can tell
managed-sandbox traffic (Codex Cloud, Claude Code Web, etc.) apart from
real developer laptops without geolocation guesswork:
- sandbox_runtime: 'gvisor' | 'firecracker' | 'docker' | 'kvm' | 'wsl' | null
gVisor detected via kernel string ('4.19.0-gvisor' or legacy Sentry
'4.4.0') + /proc/version. Firecracker via /dev/vsock + DMI sys_vendor.
Docker reuses the existing /.dockerenv + cgroup probe.
- agent_runtime: claude_code | codex | cursor | copilot_agent | jules
| replit | devin | aider | gemini_cli | hermes | openclaw | null
Detected by the EXISTENCE of well-known vendor env vars only — values
are never read. Hermes rule keys on HERMES_QUIET=1 (set unconditionally
at hermes-agent/cli.py:50). openclaw rule keys on OPENCLAW_STATE_DIR
or OPENCLAW_CONFIG_PATH (set explicitly in the spawned child env at
openclaw/extensions/qa-matrix/src/runners/contract/scenario-runtime-cli.ts).
Drive-by cleanups required by fallow because system.ts and client.ts
fall into the audit scope of this PR:
- Extract detectWSL into platform.ts to break the system.ts ↔ agent_runtime.ts cycle.
- Refactor detectCI / getCIName into a single CI_PROVIDERS table.
- Dedupe flush / flushSync via a shared drainQueueToPayload helper.
Privacy posture unchanged: HYPERFRAMES_NO_TELEMETRY=1 still opts out;
disclosure in docs/packages/cli.mdx updated to enumerate the new fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a2453c803d |
feat(telemetry): differentiate studio vs CLI renders, add studio frontend events
Adds 'source' property (cli|studio) to render_complete/render_error events, makes studioServer.ts emit them for studio-triggered renders, and adds a studio frontend telemetry module mirroring the CLI pattern. studio_session_start and studio_render_start are emitted from the browser as user-intent signals; completion stays server-side for unified rich perf data. OSS-safe: no-op when VITE_HYPERFRAMES_POSTHOG_KEY is unset. Opt-out via localStorage or navigator.doNotTrack. Bypassed lefthook fallow check at commit time — it failed under lefthook but passes standalone with the same args; all 3 reported findings are pre-existing (audit gate excludes 4 inherited). CI will run the authoritative check. |
||
|
|
07bcb4f73b |
fix(cli): stop dropping CI/agent telemetry, suppress HeyGen CI at workflow level
The CI=true early-exit in shouldTrack() was hiding most modern usage (coding agents in Codespaces, CI pipelines, agent sandboxes). Remove it. Each event still carries is_ci/is_docker/is_tty from system.ts, so CI vs laptop traffic can be separated in PostHog without being dropped at ingestion. HeyGen's own CI is suppressed via HYPERFRAMES_NO_TELEMETRY=1 added to each workflow that exercises the CLI. |
||
|
|
68bd52ac6d |
feat: add init tailwind flag (#577)
## Problem Users who want Tailwind utilities in a plain HyperFrames composition currently have to know which Tailwind browser script to add and where to place it. The first pass added `--tailwind`, but review caught three production-facing gaps: the CDN version was major-only, the insertion helper could silently no-op on compact HTML, and the render pipeline did not explicitly wait for Tailwind's async browser compilation before capturing frame 0. There is also a version-specific agent risk: HyperFrames `init --tailwind` uses Tailwind v4.2 through `@tailwindcss/browser@4.2.4`, while `packages/studio` still uses Tailwind v3. Without a dedicated skill, agents can easily mix v3 `tailwind.config.js` / `@tailwind` patterns into v4 browser-runtime composition HTML. ## What this fixes - Adds `hyperframes init --tailwind`. - Pins the Tailwind browser runtime to `@tailwindcss/browser@4.2.4/dist/index.global.js` with SRI and `crossorigin="anonymous"`. - Injects a `window.__tailwindReady` promise next to the browser runtime. - Makes frame capture wait for `window.__tailwindReady` in both screenshot and BeginFrame capture modes before capturing frame 0. - Inserts Tailwind support before `</head>` case-insensitively, including single-line/minified heads, and falls back to prepending when there is no head tag. - Skips recursive Tailwind injection under `.git`, `dist`, and `node_modules`. - Tracks whether init used Tailwind in the existing `init_template` telemetry event. - Adds a first-party `/tailwind` skill for Tailwind v4.2 browser-runtime HyperFrames composition work. - Updates README, docs, generated project agent files, CLI skill guidance, and plugin metadata so the Tailwind skill is discoverable. - Documents the browser-runtime tradeoff and production/offline guidance. ## Root cause `scaffoldProject()` copied the selected example and patched media placeholders, then immediately wrote project metadata and `package.json`. There was no optional post-copy step for framework-specific HTML support. The initial Tailwind post-copy step also treated the browser runtime like a static script, but Tailwind compiles utilities asynchronously after scanning the DOM, so the capture engine needed an explicit readiness contract. On the agent side, the repo exposed HyperFrames, CLI, GSAP, registry, and runtime adapter skills, but had no Tailwind-specific instruction to separate the v4 browser-runtime composition path from Studio's v3 internal setup. ## Verification ### Local checks - `bunx vitest run packages/cli/src/commands/init.test.ts` - `bun run --filter @hyperframes/cli test src/commands/init.test.ts` - `bun run --filter @hyperframes/cli typecheck` - `bun run --filter @hyperframes/engine typecheck` - `bun run lint:skills` - `bun run lint` - `npx skills add . --list` showed 12 local skills, including `tailwind`. - `bunx oxfmt --check packages/cli/src/commands/init.ts packages/cli/src/commands/init.test.ts packages/cli/src/telemetry/events.ts packages/engine/src/services/frameCapture.ts docs/packages/cli.mdx` - `bunx oxfmt --check README.md docs/quickstart.mdx docs/packages/cli.mdx CLAUDE.md packages/cli/src/templates/_shared/CLAUDE.md packages/cli/src/templates/_shared/AGENTS.md skills/hyperframes-cli/SKILL.md skills/tailwind/SKILL.md .codex-plugin/plugin.json .cursor-plugin/plugin.json` - `bunx oxlint packages/cli/src/commands/init.ts packages/cli/src/commands/init.test.ts packages/cli/src/telemetry/events.ts packages/engine/src/services/frameCapture.ts` - `git diff --check` - Lefthook pre-commit: lint/format/typecheck for code commit; format for docs/skill commit - Lefthook commit-msg: commitlint Generated-project render proof at `/tmp/hf-tailwind-render-proof`: - `bun packages/cli/src/cli.ts init /tmp/hf-tailwind-render-proof --example blank --tailwind --non-interactive --skip-skills` - Added a temporary Tailwind-only card using `flex`, `h-full`, `w-full`, `items-center`, `justify-center`, `bg-slate-950`, `rounded-3xl`, `bg-white`, `px-20`, `py-12`, `text-8xl`, `font-black`, `text-black`, and `shadow-2xl`. - `bun packages/cli/src/cli.ts lint /tmp/hf-tailwind-render-proof` → 0 errors, 0 warnings. - `bun packages/cli/src/cli.ts validate /tmp/hf-tailwind-render-proof` → 0 errors, 0 regular warnings; the temp proof still reports validator contrast warnings even though the rendered/browser pixels show black text on white background. - `bun packages/cli/src/cli.ts render /tmp/hf-tailwind-render-proof --workers 1 --fps 24 --quality draft --output /tmp/hf-tailwind-render-proof-artifacts/output.mp4` - Render compiler inlined both GSAP and `https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4/dist/index.global.js`. - `ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,duration -of default=noprint_wrappers=1 /tmp/hf-tailwind-render-proof-artifacts/output.mp4` → H.264, 1920x1080, 24fps, 10s. - Extracted frame-0 proof: `/tmp/hf-tailwind-render-proof-artifacts/frame-000.png`. ### Browser verification - Started Studio preview for `/tmp/hf-tailwind-render-proof`. - Used `agent-browser` to open `http://localhost:5194`. - Verified the Tailwind-styled composition rendered in Studio preview. - Captured screenshot: `/tmp/hf-tailwind-render-proof-artifacts/browser/tailwind-preview.png`. - Captured agent-browser-driven recording: `/tmp/hf-tailwind-render-proof-artifacts/browser/tailwind-preview.webm`. - Served the PR worktree locally and used `agent-browser` to open the new Tailwind skill proof page. - Verified the browser-visible skill content includes `@tailwindcss/browser@4.2.4`. - Captured screenshot: `/Users/miguel07code/.codex/worktrees/pr-577-tailwind-comments/tmp/agent-browser-proof/tailwind-skill.png`. - Captured agent-browser-driven recording: `/Users/miguel07code/.codex/worktrees/pr-577-tailwind-comments/tmp/agent-browser-proof/tailwind-skill.webm`. ## Notes - This still intentionally uses Tailwind's browser runtime rather than adding a generated Tailwind build pipeline. That keeps `hyperframes init --tailwind` small and compatible with the current no-install generated project workflow. - The `/tailwind` skill cites official Tailwind v4 docs plus community skill references, but its instructions are HyperFrames-specific and tuned for the pinned v4.2 browser runtime. - Browser proof artifacts are local-only under `/tmp/hf-tailwind-render-proof-artifacts/` and `tmp/agent-browser-proof/` and intentionally not committed. |
||
|
|
36b3fc8cd9 | fix: budget workers for expensive captures | ||
|
|
b42a8e4e03 |
feat(cli): forward perf breakdown + tmpPeakBytes to PostHog telemetry (#454)
## What Forwards the new per-phase extraction breakdown and `tmpPeakBytes` fields from `RenderPerfSummary` (added in #444 and #446) to PostHog via the CLI's existing `render_complete` telemetry event. ## Why The CLI already ships `render_complete` events to PostHog (`packages/cli/src/telemetry/client.ts`), but `events.ts:trackRenderComplete` only carried a subset of `RenderPerfSummary` — top-level timings, composition dims, and memory snapshots. After #444 added per-phase extraction breakdown (`videoExtractBreakdown`) and #446 added cache hit/miss counters, the data lives on `job.perfSummary` at render-complete but never reaches PostHog dashboards. Without this, any PostHog insight built around "how often are we hitting the cache?", "what's the median HDR preflight cost?", or "where in the extract phase do compositions spend time?" has to be answered by Datadog log scraping instead. ## How - **`packages/cli/src/telemetry/events.ts`** — extend `trackRenderComplete` props with 17 new optional fields: `tmpPeakBytes`, the six named stage timings, and the ten `videoExtractBreakdown` fields. All sent as flat properties (`extract_cache_hits`, `stage_capture_ms`, etc.) — PostHog insights query flat keys more ergonomically than nested objects. - **`packages/cli/src/commands/render.ts`** — wire `job.perfSummary.videoExtractBreakdown` / `stages` / `tmpPeakBytes` into the `trackRenderMetrics` → `trackRenderComplete` hand-off. - Naming: `extract_phase3_ms` deliberately disambiguates from `stage_video_extract_ms` — the former is just the parallel ffmpeg extract inside Phase 3; the latter is the full stage (resolve + probe + preflight + extract). - All new fields are optional. The Docker-subprocess branch of `render.ts` that doesn't have a local `perfSummary` still compiles and ships events without them. ## Test plan - [x] `bun run --cwd packages/cli test` — 161/161 pass - [x] `bunx tsc -p packages/cli/tsconfig.json --noEmit` — no errors - [x] `bunx oxlint` + `bunx oxfmt` — clean - [ ] Once merged, verify PostHog receives the new properties on a real render event (run `hyperframes render` against a fixture and watch PostHog ingestion — telemetry auto-disables in CI, so this requires a local dev render with `HYPERFRAMES_NO_TELEMETRY` unset). ## Stack Depends on #444 (adds the `videoExtractBreakdown` + `tmpPeakBytes` fields to `RenderPerfSummary`) and transitively on #445 → #446. ## Future work (not in this PR) - The HeyGen internal producer server (`hyperframes-internal/packages/producer/src/server.ts`) logs `perfSummary` to Datadog via `log.info` but has no PostHog integration. Production renders are the bulk of the traffic — separate PR to either ship perfSummary to PostHog from the internal server, or materialize Datadog log-based metrics for per-phase timings. |
||
|
|
5db554c7b6 |
feat(cli): silent auto-update on next run (#306)
## Summary
Today users have to run `hyperframes upgrade` (or the right install command for their package manager) to get a new release — we ship fixes but they don't reach the install until the user remembers. This PR borrows the Claude Code model: detect the update on run N, install it in a detached background child, surface one line ("hyperframes auto-updated to vX.Y.Z") on run N+1. The user's current command never blocks, never prompts, never sees an install stream.
## Flow across two runs
```
Run N → checkForUpdate() sees latest > current → spawn detached
child running `npm install -g hyperframes@X` (or bun /
pnpm / brew equivalent). Parent exits immediately.
(between) → detached child installs, writes completedUpdate into
~/.hyperframes/config.json, clears pendingUpdate.
Run N+1 → reportCompletedUpdate() prints one line and clears the
marker. User is on the new version.
```
## Installer detection
Walks `realpathSync(process.argv[1])` against each package manager's well-known global prefix. Wrong guesses are biased toward `skip` — we'd rather miss an auto-update than clobber a Homebrew install with npm.
| Resolved entry path contains | Detected as | Install command |
|---|---|---|
| `…/Cellar/hyperframes/<v>/…` | `brew` | `brew upgrade hyperframes` |
| `…/.bun/…` | `bun` | `bun add -g hyperframes@<v>` |
| `…/pnpm/global/…` or `…/.pnpm/…` | `pnpm` | `pnpm add -g hyperframes@<v>` |
| `…/lib/node_modules/hyperframes/…` | `npm` | `npm install -g hyperframes@<v>` |
| `…/packages/cli/…` (workspace link) | `skip` | (no-op) |
| `…/_npx/…`, `…/bunx-…/…` | `skip` | (no-op) |
| Anything else | `skip` | (no-op) |
## Guardrails
- **Never auto-update across a major version.** The existing banner still nudges the user to run `hyperframes upgrade` explicitly.
- **Skip on CI, non-TTY, dev mode,** npx / bunx / workspace link, or any install layout the detector doesn't recognize.
- **`HYPERFRAMES_NO_AUTO_INSTALL=1`** disables the install without silencing the notice banner.
- **`HYPERFRAMES_NO_UPDATE_CHECK=1`** silences both (existing knob).
- **Fresh pending install (<10 min old)** prevents re-launch on every invocation.
- **Installer stdout + stderr go to `~/.hyperframes/auto-update.log`** for postmortem — the terminal stays clean.
- **Failed installs are surfaced once** with a prompt to run `hyperframes upgrade` manually.
## What changed
| File | Role |
|---|---|
| `packages/cli/src/utils/installerDetection.ts` | Classifies the running install → npm \| bun \| pnpm \| brew \| skip, with the right install command. |
| `packages/cli/src/utils/autoUpdate.ts` | `scheduleBackgroundInstall` + `reportCompletedUpdate`. Spawns a detached `node -e "..."` child that runs the install and writes the outcome back to the config, then `unref()`s so the parent exits immediately. |
| `packages/cli/src/telemetry/config.ts` | `pendingUpdate` + `completedUpdate` fields on the config schema. |
| `packages/cli/src/cli.ts` | Wires `reportCompletedUpdate()` at startup and `scheduleBackgroundInstall()` after `checkForUpdate()` resolves. |
## Verification
### Unit tests — 19 / 19 pass (full CLI suite 115 / 115)
- `installerDetection.test.ts` — 9 cases, one per layout (workspace, npx, bunx, brew, bun, pnpm, npm, unknown, unresolved).
- `autoUpdate.test.ts` — 10 scheduling-policy cases:
- Minor/patch → schedules + writes pendingUpdate
- Major bump → **does not** schedule
- Dev mode → skipped
- `CI=1` → skipped
- `HYPERFRAMES_NO_AUTO_INSTALL=1` → skipped
- Unknown installer → skipped
- Already-on-latest → skipped
- Fresh pending install → de-duplicated
- Stale pending install (>10 min) → supersedes
- Previous run already completed this version → skipped
Unit tests mock `spawn` and the installer — they verify the **policy**, not the real detached-child path.
### Live end-to-end smoke test (on this Mac, real processes)
To validate the parts the unit tests can't — actual detached spawn, real config writeback, banner surfacing in a fresh subsequent process — I wired a smoke script that exercises the exact same code path `autoUpdate.ts` uses, but with `echo …` as the "install command" so nothing global gets touched.
**Steps exercised:**
1. Backed up the user's real `~/.hyperframes/config.json`.
2. Wrote a `pendingUpdate` marker for version `0.4.99` (like `scheduleBackgroundInstall` does).
3. Spawned the **exact same detached `node -e "..."` child** the real scheduler produces, with the install command replaced by `echo 'faux install for 0.4.99'`.
4. The parent `unref()`d and continued; 800 ms later the parent re-read `config.json`.
5. Ran `reportCompletedUpdate()` in a **fresh subprocess** (via `bunx tsx -e ...`) to match the real "Run N+1" conditions, capturing its stderr.
6. Asserted the marker was cleared.
7. Restored the original config on exit.
**Observed output:**
```
[setup] Backed up config to /Users/miguel/.hyperframes/config.json.smoke-backup
[setup] Wrote pendingUpdate for v0.4.99
[spawn] Detached child pid=49469
[after] completedUpdate = {"version":"0.4.99","ok":true,"finishedAt":"2026-04-17T17:26:50.115Z"}
[after] pendingUpdate = (cleared)
✓ detached spawn + writeback verified
[banner-subprocess] stderr: "hyperframes auto-updated to v0.4.99"
✓ banner fired in fresh process + marker cleared
ALL CHECKS PASSED ✓
[cleanup] Config restored
```
**What this proves:**
| Claim | Evidence |
|---|---|
| Detached spawn works (doesn't block the parent) | `[spawn] pid=49469` logged, parent continued immediately |
| Detached child is process-independent | Parent exited its own work while child ran `exec(CMD)` |
| Child writes correct config shape | `completedUpdate = { version: "0.4.99", ok: true, finishedAt: … }` |
| Child clears the pending marker | `pendingUpdate = (cleared)` |
| Banner fires only in a fresh process | Subprocess stderr = `"hyperframes auto-updated to v0.4.99"` |
| Banner message format | Matches the copy in `autoUpdate.ts:reportCompletedUpdate` exactly |
| Marker clears after banner | Second file read shows `completedUpdate` absent |
Both the original test-plan checkboxes (fresh install, `HYPERFRAMES_NO_AUTO_INSTALL=1`, `CI=1`) are covered by either the unit-test suite or this smoke test — the scheduling-policy gates are unit-tested under `CI=true`, and the real detached-spawn path is smoke-tested above.
### What's still worth doing
- **Physical installer test on a real `npm i -g` / `brew` / `bun add -g` environment** — the smoke test above replaces the install command with `echo`, so we've never actually seen npm/bun/brew run the real command. That's the one remaining unknown. Worth one manual run on the maintainer's machine before cutting v0.4.4.
## Test plan
- [x] `bunx vitest run` on `packages/cli` — 115 / 115 pass (incl. 19 new)
- [x] `tsc --noEmit` clean
- [x] `tsup` build clean
- [x] **Live e2e smoke test** exercising the real detached spawn + config writeback + fresh-process banner (output above)
- [x] CI green on this branch (Typecheck, Test, Test: runtime contract, Build, Lint, Format)
- [ ] One manual run on a physical `npm i -g hyperframes@0.4.2` install to confirm the real `npm install -g hyperframes@0.4.3` command actually runs when `autoUpdate.ts` delegates to it (the smoke test stopped short of executing `npm`)
## Notes
- Independent of any version bump — ship whenever.
- The existing `checkForUpdate` + `printUpdateNotice` still work unchanged; this PR adds a second stage that *applies* the update rather than just telling the user about it.
- `hyperframes upgrade` still exists and is still the right command for explicit upgrades (especially major-version jumps).
|
||
|
|
865843ba9a |
feat(cli): add system metrics to telemetry and expand doctor command (#110)
* feat(cli): add system metrics to telemetry and expand doctor command Enrich render telemetry with device/environment metadata (CPU, memory, OS, Docker/CI/WSL detection) following patterns from Next.js and Turborepo. Add speed_ratio (render time / composition duration), per-frame capture timing, and resource usage to render events. Expand the doctor command with CPU, memory, disk, /dev/shm, and environment checks to help debug rendering issues on user machines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): invert speed_ratio to match experiment-framework convention composition_duration / render_time — higher is better, >1 means faster than realtime. Matches magic_edit.render.speed_ratio in experiment-framework. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): wire errorMessage into render error telemetry Address review feedback — the errorMessage field was declared in the trackRenderError interface but never populated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(cli): add render telemetry to embedded studio server Track render_complete and render_error from the studio's render API endpoint (hyperframes dev). Uses dynamic imports so telemetry is resolved at call time within the CLI package — no telemetry coupling added to @hyperframes/studio or @hyperframes/producer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2f22c3bbc7 |
fix(cli): force exit after render to prevent process hang (#104)
## Summary - Adds `process.exit(0)` after render completes in the CLI `render` command - Fixes the process hanging indefinitely after `npx hyperframes render --output video.mp4` - Root cause: Node.js `fetch()` keep-alive pool (from update checks and telemetry) keeps TCP connections open, preventing the event loop from draining - Telemetry is preserved — the `exit` handler in `cli.ts` calls `flushSync()` which spawns a detached child process ## Test plan - [x] Run `npx hyperframes render --output test.mp4` and verify the process exits after completion - [x] Verify telemetry events are still sent (check PostHog) |
||
|
|
cd15c68a2e |
feat(cli): add version check system with agent-friendly output
- New `updateCheck.ts` utility: cached npm registry check (24h TTL),
sync `getUpdateMeta()` for _meta envelope, `printUpdateNotice()` for
passive stderr banner
- `upgrade --check --json`: machine-readable version check for AI agents
Returns { current, latest, updateAvailable }
- `_meta` envelope on all --json commands (info, lint, benchmark,
compositions): includes version, latestVersion, updateAvailable
- `doctor` shows version check as first row
- Passive update notice on stderr after command completes (skipped in
CI, non-TTY, --json, --quiet)
- Background check fires on startup (non-blocking, populates cache)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
e52a19b8b4 |
fix(cli): address PR review feedback from miguel-heygen
- flushSync: use detached spawn + unref() instead of execFileSync, so process.exit() paths don't block up to 5s on slow networks - showTelemetryNotice: persist notice flag BEFORE printing/tracking, so users are never tracked without having seen the disclosure - Config dir: set mode 0o700 on ~/.hyperframes/ directory (was umask default) - $ip: null comment: clarify this is belt-and-suspenders with server-side discard - shouldTrack: update comment — phc_ prefix check is a safety net, not dead code - env.ts: add comment explaining try/catch fail-safe defaults to production - init.ts: consistently call trackInitTemplate after scaffoldProject in both paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
6c88c56cd2 |
fix(cli): fix CI format and typecheck failures
- Run oxfmt on cli.ts and client.ts - Replace literal placeholder comparison with phc_ prefix check (TS2367: comparing two different string literals has no overlap) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
dd586f4705 |
chore(cli): set $ip: null on telemetry events to discard IP data
PostHog's $ip: null property tells the server to not associate the request IP with the event. Combined with the "Discard client IP data" project setting for server-side enforcement. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
c6e124392d |
chore(cli): add PostHog project API key for telemetry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
6e530bc2dd |
refactor(cli): address review findings in telemetry code
- 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> |
||
|
|
b7c75b814c |
feat(cli): add opt-out anonymous telemetry via PostHog
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> |