mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
145c71e837f208d382462f0dfe5d8840f3c33414
640
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6be46813a2 |
fix(render): pre-flight aspect-ratio / alpha preset mismatch with actionable guidance (#1843)
Users pick an --resolution preset whose orientation/aspect ratio (or alpha/HDR mode) conflicts with the composition; the render fails deep in the compiler with a cryptic message. ~8K err / ~1K users. - New shared pure helper checkOutputResolutionCompatibility in @hyperframes/parsers — single source of truth for aspect/alpha/HDR/downsample/non-integer-scale constraints; suggests the matching-orientation, tier-preserving preset. - CLI render pre-flight aborts early (before browser/ffmpeg) with an actionable, fix-suggesting message; resolveDeviceScaleFactor delegates to the same helper for identical defense-in-depth messages. - Suggest (not auto-select); defers when dims can't be determined rather than guessing. - suggestMatchingPreset keys tier off the -4k suffix so square-family swaps (square + landscape-4k -> square-4k) aren't downgraded to HD. - render.js DOM polyfill made a lazy import; render.test cold-import beforeAll hooks given a 30s timeout to absorb CI contention. Render-reliability workstream P1-3. Success measured on PostHog dashboard 1783183. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
180f368af1 |
fix(cli): detect missing Chrome libs & ffmpeg on Linux/WSL in doctor (#1841)
WSL first-render success (34.7%) is dominated by a downloaded chrome-headless-shell that launches into `libnss3.so: cannot open shared object file` — doctor/preflight only checked the binary exists, never that it can load its libraries. - New linuxDeps.ts: /etc/os-release distro detection (Debian/Fedora/Arch/Alpine) + WSL detection, per-distro Chrome dep set, ldd-based shared-lib probe. - preflight.checkChrome downgrades a found-but-unlaunchable Chrome to a render-blocking error with the exact per-distro install command. - Distro-aware ffmpeg hints; launch failures converted to actionable guidance pointing at `hyperframes doctor` (skipped on ARM64). - Detect + print remediation (no auto-install). Render-reliability workstream P1-4. Success measured on PostHog dashboard 1783183. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1d4c5d5ec3 | chore: release v0.7.24 | ||
|
|
5149394b00 | chore: release v0.7.23 | ||
|
|
cf573f7f3f |
fix(core,producer,cli): pre-flight validation for empty/malformed sub-compositions (#1831)
* fix(core,producer,cli): pre-flight validation for empty/malformed sub-compositions The #1 render failure bucket in production telemetry (PostHog project 356858, dashboard 1783183 "HyperFrames — Bottom-Line & Activation"; ~65-69K occurrences / ~27-28K affected users over 30 days, ~80% via AI-agent authoring flows) is a `data-composition-src` reference pointing at a scene file that is empty, malformed, or missing. Root cause, traced end-to-end: - The literal error "Composition HTML is empty or could not be parsed: <path>" is real (not a PostHog paraphrase) — thrown by a since-reverted guard in packages/core/src/compiler/inlineSubCompositions.ts (#1364), then changed to a silent skip in #1678 to avoid aborting renders on partial content during authoring. #1629 added per-assembler guards for 3 skill workflows (product-launch-video, faceless-explainer, pr-to-video), but general-video and hand-authored flows — where the dominant filename `scene-title.html` (40K+/68K of the bucket) originates — have no assembler and thus no guard. #1678 assumed the assembler guards from #1629 covered this pre-render; they only covered 3 of the many authoring flows. - On current `main`, an empty/malformed data-composition-src file no longer crashes or throws during render — it's silently dropped by the tolerant inliner. Reproduced locally: `hyperframes render` on a project with an empty scene-title.html "succeeds" after ~93s (two 45s pollSubCompositionTimelines timeouts) with the scene silently missing from the output video. `hyperframes validate` also reports "No console errors" for the same broken project. - The raw `Cannot destructure property 'firstElementChild' of 'documentElement' as it is null` crash reproduces directly against linkedom (the DOMParser polyfill packages/cli/src/utils/dom.ts installs in the real CLI runtime) for empty and non-HTML input — confirmed with a standalone repro script, not just inferred. jsdom/happy-dom (used in this repo's own test environment) are spec-compliant and never produce a null documentElement, which is why this needed a linkedom-specific test file. Fix: - New shared helper `checkSubCompositionUsability` (packages/core/src/compiler/subCompositionValidity.ts) is the single source of truth for "is this data-composition-src file usable" — mirrors the inliner's own parse/template/body logic so all callers agree. - `inlineSubCompositions.ts` (preview/studio bundling) now uses the shared helper internally but keeps its #1678 tolerant skip-and-continue behavior unchanged — mid-authoring iteration on a partial project must keep working. `onMissingComposition` now also receives a human-readable reason. - New render-only pre-flight (`assertSubCompositionsUsable` in packages/producer/src/services/htmlCompiler.ts) walks every data-composition-src reference (including nested ones, root-relative, matching parseSubCompositions' own resolution) before any compilation work starts, and throws naming every offending file at once. This is unconditional — not gated behind --strict — because a render that silently drops a scene is strictly worse than one that refuses to start. Confirmed locally: render now fails in ~0.4s with an actionable message instead of "succeeding" after 93s with a missing scene. - New `hyperframes lint` rule `missing_or_empty_sub_composition` (packages/cli/src/utils/lintProject.ts) surfaces the same check as a file-scoped, actionable lint error (already unconditional — lint exits 1 on any error). - `hyperframes validate` now also runs this check before launching a browser, so it no longer reports "No console errors" for a project with a broken sub-composition. - `packages/core/src/parsers/htmlParser.ts`: guarded every `documentElement`-may-be-null access (parseHtml, updateElementInHtml, addElementToHtml, removeElementFromHtml, extractCompositionMetadata, validateCompositionHtml) with a new typed `CompositionHtmlParseError` (or, for validateCompositionHtml's collect-and-report contract, a typed validation failure) instead of a raw crash. Tests: empty file, whitespace-only, malformed/non-HTML, missing file, nested sub-compositions (both happy path and broken-grandchild), and the happy path — at the shared-helper, lint, and render pre-flight layers. Not changed: the AI-agent authoring skills (skills/*). general-video and hand-authored flows have no assemble-index.mjs equivalent to guard, so the fix is at the CLI/render layer instead — flow-agnostic, covers every authoring path, and the skills' existing "run lint/validate and stop on failure" guidance now actually catches this class of mistake once run. Not run in this environment: the producer package's full regression-harness test suite (`bun test` in packages/producer) — it performs heavy real rendering (S3 asset downloads, Google Fonts fetches, full video encodes) and did not complete in a reasonable time in this sandbox. Verified instead via the targeted test file for all touched code (76/76 passing), whole-repo typecheck/build/oxlint, `fallow audit` (complexity/duplication/dead-code gate, clean), and manual end-to-end CLI runs (render/lint/validate) against reproduction projects, including a nested sub-composition scenario. CI should run the full producer suite before merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * refactor(parsers,lint): port empty-composition pre-flight to extracted packages Rebased onto main, which extracted @hyperframes/lint from core (lint depends only on parsers, not core). Relocate checkSubCompositionUsability from core to @hyperframes/parsers so both core (inliner) and lint can consume it without a core<->lint cycle; core keeps a @deprecated re-export shim. Correctness fixes from code review: - checkSubCompositionUsability now returns "no-composition-root" when the <template>/<body> content has no [data-composition-id] element (previously a marker-free placeholder body passed both guards). - lint's missing/empty sub-composition rule now only checks files reachable via data-composition-src from the root (matching render pre-flight), instead of a raw filesystem walk that false-positived on orphaned files. - drop `as string` cast in inlineSubCompositions in favor of an explicit null guard (per CLAUDE.md). Review-comment items: - move EmptyCompositionError JSDoc above the class (was above the adapter fn). - correct stale circular-ref comment to match actual silent-skip behavior. - rewrite self-contradicting lint message ("silently drop") to describe the new loud render-pre-flight abort. - add the __PLACEHOLDER__ (/^__[A-Z_]+__$/) skip to the render pre-flight so it agrees with lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9c4d9e50a0 |
feat(telemetry): unify CLI and Studio PostHog identity (Layer 1) (#1829)
* feat(telemetry): unify CLI and Studio PostHog identity (Layer 1) Seed the CLI's anonymous distinct_id into Studio at launch so a developer's CLI and their Studio browser session resolve to the same PostHog person. Also unifies Studio's two previously-independent anonymous ids into one source of truth. Uses only the existing anonymous machine id (no new PII). - cli: inject window.__HF_CLI_DISTINCT_ID into the served index.html <head> (mirrors the existing __HF_STUDIO_ENV__ injection) + add a fallback GET /api/telemetry-identity endpoint. Only seeds when CLI telemetry is enabled; empty/no-op otherwise. - studio: new telemetry/distinctId.ts single source of truth; adopts the CLI-seeded id when present, else falls back to the existing per-browser localStorage id. Both Studio clients (studio:* and studio_*/render) now share this one id. * fix(telemetry): keep Studio distinct_id resolver fail-silent on getItem resolveStudioDistinctId read localStorage.getItem() outside a try/catch while every other external access in the module is guarded. In a storage-restricted context where the localStorage reference resolves but getItem throws, the resolver threw — breaking the module's fail-silent contract (telemetry must never break Studio). Guard the reads and treat a throw as "no id". Also drop an unnecessary `as` cast in the test per the repo CLAUDE.md convention (the optional global is already declared). * refactor(telemetry): address review feedback on identity unification - dedup safeLocalStorage/safeSessionStorage into utils/safeStorage.ts, used by both telemetry/config.ts and telemetry/distinctId.ts (Miga #6) - replace redundant `??=` with `=` in the no-storage branch; cachedId is guaranteed null there (Miga #2) - extract buildStudioHeadScripts() so the "identity script before env script" head-injection ordering is a pure, tested invariant (Miga #5) - add tests: head-script ordering + telemetry-off passthrough, and a Studio memoization test proving an adopted CLI id survives a later window.__HF_CLI_DISTINCT_ID reassignment (Rames) - clarify the XSS-escaping comment (both < and / escaped so no </script> sequence can form) (Miga #1) |
||
|
|
8694424807 |
Merge pull request #1827 from heygen-com/feat/capture-component-extraction
feat(capture): extract chips/stat-cells/tabs, detect icon fonts, transparent grounds |
||
|
|
6cc87312d4 |
feat(capture): extract chips/stat-cells/tabs, detect icon fonts, transparent grounds
designStyleExtractor now also extracts chip/pill/badge/tag, stat/metric cells, and tab components — by class-substring selector plus a shape fallback (small + fully rounded + short text) so hashed/utility class names (Tailwind, CSS-modules) are still caught. It also emits a "transparent" sentinel for fully-transparent (rgba(...,0)) grounds instead of collapsing them to #000000, so a transparent chip/tab/stat on a light-ground site no longer reads as solid black. fontMetadataExtractor now flags icon fonts (isIcon) by glyph coverage: a font is an icon font only when it BOTH lacks a real Latin alphabet (<26 of A-Za-z) AND is mostly (>50%) Private-Use-Area glyphs. The Latin gate matters — some text fonts pack thousands of PUA glyphs yet are plainly text (Apple SF Pro is ~81% PUA but ships a full alphabet; Descript's Booton ~50%); flagging by PUA ratio alone would strip a brand's real typeface. Measured icon fonts: "hushly" 63% PUA / 7 letters, Font Awesome 95% / 0 letters. Names alone can't identify icon fonts ("hushly", "swiper-icons"), hence the glyph-based test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0d202ea779 |
fix(cli): keep doctor resilient to a corrupt browser cache (#1822)
* fix(cli): keep doctor resilient to a corrupt browser cache A partial or corrupt browser cache (a stub file where a version directory is expected, a missing executable, or malformed metadata) makes getInstalledBrowsers throw ENOTDIR. That throw propagated up through findBrowser -> checkChrome -> runEnvironmentChecks, and since doctor.run calls runEnvironmentChecks before any try/catch or the --json output, the command crashed with exit 1. doctor --json is documented to exit 0 even when checks fail, so it must report a corrupt cache as "Chrome not found", not crash on it. - checkChrome now catches any error from findBrowser and converts it to the existing ok:false "Chrome not found" outcome with the browser ensure hint, so runEnvironmentChecks never throws for a missing or corrupt browser. - findFromCache treats a throwing getInstalledBrowsers as "no cached browser", letting resolution fall through to system/download instead of crashing every caller (render included), not just doctor. A healthy browser still reports ok:true. Adds a preflight test asserting an ok:false Chrome outcome when discovery throws, instead of propagating. * fix(cli): warn on corrupt browser cache fallback |
||
|
|
a7d0ab2d61 |
fix(cli): exclude clip-path-hidden text from inspect layout and contrast audits (#1821)
* fix(cli): exclude clip-path-hidden text from inspect layout and contrast audits A clip-path can shrink an element's painted region to nothing (a typewriter span pre-reveal at clip-path: inset(0 100% 0 0), or circle(0px)) while its layout box, opacity, visibility and display all still read as present. Such an element paints zero pixels, so the layout audit flagged the visible block beneath it as a content_overlap, and the contrast auditor measured it as a meaningless background-on-background ratio (~1:1) and reported a WCAG failure. Both auditors already filtered opacity:0, visibility:hidden and display:none, but neither accounted for clip-path. Add a shared check: when a non-none clip-path is in effect on the element or an ancestor, probe a grid of points across the element's box with elementFromPoint; if none resolve to the element or a descendant, it is clipped to nothing and is skipped. The probe runs only when a clip-path is present, so a genuinely occluded (but unclipped) element is still measured and still flagged. Wired at the in-page collection chokepoint so it covers content_overlap, text_occluded and the contrast auditor consistently. Genuine overlaps between visible elements remain flagged; data-layout-allow-overlap and data-layout-ignore are honored unchanged. The two audit scripts and the layout-audit test are added to the fallow ignore lists: their pre-existing IIFE-level complexity and per-rule test scaffold re-flag under the line-shift fingerprint when the small probe helpers are inserted. * test(cli): cover clip-path audit edge cases * fix(cli): satisfy clip audit test types |
||
|
|
602590b44d |
Merge pull request #1820 from heygen-com/fix/snapshot-remote-video-frames
fix(cli): snapshot renders remote http(s) <video> frames (not just local files) |
||
|
|
1a36b2abb4 |
fix(cli): don't run unbounded ffprobe on remote snapshot inputs
VP9-alpha detection (shouldUseVp9AlphaDecoder -> extractMediaMetadata) spawns ffprobe with no timeout. For the new remote http(s) fallback that ran before the bounded extractVideoFrameToBuffer, so a stalled remote host could wedge `hyperframes snapshot` in ffprobe before the 30s extract timer ever started. Probe only local files; for remote URLs skip it (pass false). Local alpha behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
36e01f41d2 | chore: release v0.7.22 | ||
|
|
1f377f732b |
fix(cli): extract snapshot video frames from remote http(s) srcs
`hyperframes snapshot` works around Chrome-headless's inability to seek `<video>` elements by extracting a frame via FFmpeg and injecting it as an overlay. That path only resolved `<video src>` to a project-LOCAL file and skipped everything else — so a composition whose embedded `<video>` points at a remote http(s) URL (e.g. an S3-hosted clip embedded by an upstream agent) rendered as a blank box in every snapshot, while `render` (which plays the element in-browser) showed it fine. Add a remote fallback: when the src doesn't resolve to a project-local file but is an http(s) URL, pass the absolute URL straight to FFmpeg (it reads http(s) input directly). Local-first is preserved (fast, sandboxed); the existing 30s extract timeout bounds remote fetches. Verified on a real composition: remote-src snapshot was blank, local-src rendered; `ffmpeg -ss N -i <https-url> -frames:v 1` extracts in ~0.5s. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
856bb0980f |
feat(cli): add --public flag to publish (#1815)
Opt-in --public flag on `hyperframes publish` sends is_public to the publish endpoints (staged complete body and direct multipart form) so a claimed project's studio session can be created public instead of the default private. Absent the flag the request shape is unchanged. |
||
|
|
010df6a0a4 |
feat(cli): file a GitHub issue with a published repro from feedback (#1816)
Add an opt-in --file-issue flag to hyperframes feedback. When set, after sending the usual feedback the CLI publishes a minimal repro of the project to a public URL (consent-gated, mirroring publish --yes) and opens a pre-filled GitHub bug issue draft containing the rating, comment, public repro link, and environment summary. The user reviews and submits the issue under their own account; there is no token, backend, or gh invocation. New --dir selects the project to publish; --yes skips the consent prompt for scripts. URL/body building is extracted into pure, unit-tested helpers. |
||
|
|
a5c2636e8c |
fix(cli): never print "[object Object]" from validate/inspect errors (#1810)
* fix(cli): use normalizeErrorMessage so validate/inspect never print "[object Object]" The validate and inspect (layout) commands formatted thrown values with `err instanceof Error ? err.message : String(err)`. When a browser/CDP/ Puppeteer protocol error or a structured page error reaches the formatter as a plain object without a string `message`, `String(obj)` yields the useless literal "[object Object]", hiding the real cause. Route those paths through the existing shared `normalizeErrorMessage` helper, which returns an Error's message, a string as-is, an object's `.message` when present, or a compact JSON serialization otherwise (with a key-list and String fallback for circular/opaque objects). Also fold the duplicated local `errorMessage` helpers in batchRender and preview into the same shared helper. Covered by added assertions in errorMessage.test.ts for the no-message object and Puppeteer-style protocol-error object cases. * fix(cli): route remaining browser/process error sites through normalizeErrorMessage The validate/inspect fix routed only those two commands through the shared normalizeErrorMessage helper. The same err instanceof Error ? err.message : String(err) pattern survived in the other commands that drive a headless browser or an external process (ffmpeg, Docker, CDP) or surface a network API error, so a thrown structured object without a string message would still render as the useless literal [object Object]. Route those sites through the shared helper: snapshot.ts (the closest sibling to validate/inspect, same bug class), render.ts (Chrome launch + Docker build), capture/index.ts and commands/capture.ts (page-driven extraction), auth/browser.ts, browser/manager.ts (Puppeteer browser resolution), and the cloud/lambda paths (cloud/render.ts, cloudrun.ts, lambda/render-batch.ts, lambda/policies.ts, cloud/detectAspectRatio.ts) that surface API/network error objects. Only the message-deriving expression changes; control flow and error propagation are untouched. capture/index.ts keeps appending the stack for real Errors and only routes the non-Error branch. Adds a helper test for a structured CDP-style error object (code + nested data, no message). |
||
|
|
23adfdc496 |
fix(cli): skip AI skills install when git is unavailable (#1803)
* fix(cli): skip AI skills install when git is unavailable init and `skills update` route through installAllSkills, which shells out to `npx skills add`. That CLI clones the repo with git, so on a machine without git the clone aborts mid-run and dumps a noisy multi-line `spawn git ENOENT` / "Installation failed" / "Canceled" block. init still exited 0 and scaffolded the project, but the output read like a hard failure (and surfaced as exit 1 on some platforms). Detect git up front alongside the existing npx check via a small table-driven preflight: best-effort callers (init) print one calm line and continue; strict callers (`skills update`) throw so the check-or-update recovery contract still fails loudly. The skills freshness check already degrades gracefully without git, so the happy path is unchanged. * feat(cli): record a diagnostic event when a skills install is skipped for a missing prerequisite When init's best-effort skills install bails because git (or npx) is absent from PATH, the skip was silent, so the rare boxes that hit it (fresh Windows without git) were invisible. Emit one low-cardinality event (reason: git_missing / npx_missing) on the best-effort skip path only, never on the happy path or the strict throw. Reuses the existing typed-event pattern, and trackEvent's opt-out gate already applies. |
||
|
|
db61509ddc |
fix(cli): omit render duration when feedback command has none (#1797)
The standalone `feedback` command runs separately from `render`, so it has no access to the prior render's elapsed time, yet it always passed renderDurationMs: 0 to the feedback analytics event. Since that path is the one used in practice (the auto-prompt returns early for agent and non-interactive runtimes), nearly every feedback event recorded a render duration of exactly 0, which is misleading rather than absent. Make renderDurationMs optional and only include render_duration_ms in the event when a real value is supplied. The standalone command no longer passes a duration; the auto-prompt path still forwards the real elapsed time. |
||
|
|
e7939ccd53 |
fix(cli): show output video length in render summary, not render time (#1812)
The render-complete summary printed `<fileSize> · <time> · completed` where `<time>` was the wall-clock render duration. Presented as a bare middle value, users read it as the video length and compared it to ffprobe, repeatedly reporting a "wrong duration". Show the actual output video length (from the perf summary's compositionDurationSeconds, which equals the rendered frame span) as the primary figure and label the render time explicitly: `<fileSize> · <videoLength> video · rendered in <renderTime>`. png-sequence (directory) output has no single muxed video, so it shows a frame count instead; when neither is known the summary falls back to render time only. Docker renders run the producer in a child process with no perf summary threaded back, so they show render time only rather than a misleading number. |
||
|
|
466ee08ffa |
fix(cli): serve project media with HTTP Range so validate reads WAV duration (#1811)
* fix(cli): serve project media with HTTP Range so validate reads WAV duration
The local static server used by validate/snapshot/layout answered every
asset request with a plain 200 and no Accept-Ranges header. Chromium treats
such resources as non-seekable, and for WAV that makes the media element
report `.duration` as Infinity no matter how long it buffers (readyState
reaches HAVE_ENOUGH_DATA but duration never resolves). The duration audit in
validate then emitted a spurious "Could not read the duration of N media
element(s) within the validate timeout" warning for a perfectly valid local
WAV, and a longer --timeout never helped because the value is never going to
arrive. MP3/MP4 carry duration in their container metadata so they were
unaffected.
Serve files with Range support (206 + Content-Range, plus Accept-Ranges on
the full 200) so the element is seekable. WAV duration now resolves, the
false warning is gone, and the genuine "media shorter than its slot" check
works for WAV for the first time.
* perf(cli): stream Range responses instead of buffering the whole file
serveFileWithRange read the entire asset with readFileSync and then sliced
it, so a 1KB Range of a 50MB MP4 still allocated the full 50MB per request.
Switch to statSync for the total size and createReadStream(filePath, { start,
end }) piped to the response, reading only the requested window. Behavior is
unchanged: 206 + Content-Range + Content-Length for a satisfiable range, 416
for an unsatisfiable one, Accept-Ranges advertised on every response, and a
plain 200 full-body stream when there is no Range header. writeHead is
deferred to the stream's open event so a failed open still answers 500, and
the fd closes on end/error. This benefits MP4 seek too, not just WAV duration.
Extend staticProjectServer.test.ts with an 8MB-file case that pulls a 4-byte
slice from deep inside and asserts the streamed bytes, Content-Range, and
Content-Length are correct.
|
||
|
|
a4303137cb |
fix: storyboard-angle review follow-ups (M1 bg-on-clip, B3 slideshow, parser guard, CLI fixes) (#1791)
* fix(skills): storyboard review — bg-on-clip rule, slideshow output, parser parity guard Addresses the storyboard-angle review (jrusso1020): - M1 (invisible text): frame-worker.md (x3) + SKILL.md Step 5 (x3) now require a frame's full-bleed background on a class=clip layer, never the #root / data-composition-id element (the root is clip-gated to its scene window, so a background on it is not a dependable ground and dark text can land on the black host body). The assembler already paints frame.md's canvas onto index #root as the base ground; the per-frame clip rides on top. - B3 (slideshow truncates to slide 1): slideshow/SKILL.md gains an Output section (decks render via 'present'; 'render index.html' captures only the first composition; linear main-line MP4 export is deferred). - Parser drift: vendoredParity.test.ts guards the three vendored storyboard.mjs copies (byte-identical + parse-parity with @hyperframes/core). - skills-manifest.json regenerated for the edited SKILL.md files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): storyboard review — lint, validate help, snapshot, inspect, capture, render Addresses the CLI findings from the storyboard-angle review (jrusso1020): - lint (@hyperframes/lint): accept vendor-prefixed system-font keywords -apple-system / BlinkMacSystemFont so a system stack with a generic fallback no longer trips font_family_without_font_face (+ test). - help: list 'validate' under Project in 'hyperframes --help' (was runnable but undocumented). - snapshot: honor -o/--output (the flag did not exist; output was hardcoded to snapshots/). The dir is resolved once and threaded through capture + contact sheet + Gemini. - snapshot: split font status into loaded / error / unused with a one-line summary; only a real 'error' is reported as FAILED (an unrequested @font-face is 'unused', not a contradiction with 'loaded'). - inspect: suppress text_occluded across a scene-to-scene crossfade (occluder in a different data-composition-id mount while a scene is mid-fade); a same-scene or two-settled-scenes overlap still flags. - inspect: suppress content_overlap between in-flow siblings governed by the same flex/grid container (tight stacks / number lockups are layout slop). - capture: record source resolution (videoWidth/Height) in video-manifest.json alongside the DOM display box; consumers size off the source dims. - render: warn when the target carries a slideshow island (render captures only the first scene, so the MP4 is truncated to slide 1; use 'present'). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
812a5d4246 | chore: release v0.7.21 | ||
|
|
faea9f2267 | chore: release v0.7.20 | ||
|
|
f3177872a1 | chore: release v0.7.19 | ||
|
|
0a9555a0f7 |
fix(studio): keyframe/position editing correctness + thumbnail cache busting + local-studio preview discovery (#1781)
* feat(player,studio): favicon-blade play icon with pause<->play morph Replace the play triangle with the right-hand blade from the HyperFrames favicon and morph between pause and play on toggle. Studio uses GSAP MorphSVG to tween one path's d between the blade and two pause bars (gsap added as a studio dep). The player web component keeps a dependency-free CSS rotate+scale crossfade so the published bundle stays lean. Both honor prefers-reduced-motion. * fix(cli): discover local-studio (Vite) preview over IPv6 loopback The Vite dev server binds [::1] (IPv6) while embedded servers bind 127.0.0.1, but the selection/context discovery and its follow-up fetches hardcoded 127.0.0.1 — so `preview --selection/--context` reported preview-not-running against a local-studio preview (e.g. inside the monorepo / bun run dev). Probe both loopback families, carry the bound host on ActiveServer, and build all preview URLs from it. Adds an IPv6-only discovery regression test. * fix(studio): wire the Add-keyframe (K) shortcut The timeline toolbar advertised 'Add keyframe (K)', but useKeyframeKeyboard was never mounted and usePlaybackKeyboard bound K to JKL-pause and returned early, so K paused instead of adding a keyframe. Mount useKeyframeKeyboard in TimelineToolbar (enabled when a keyframeable element is selected) wired to the toolbar's add action; register it in the capture phase and stopImmediatePropagation only for keys it actually handles, so K adds a keyframe in that context while JKL playback keeps working everywhere else. * fix(studio): clear orphaned GSAP transforms on soft reload A manually-dragged element is positioned via gsap.set, which writes an inline transform. On a soft reload the transform is only stripped for elements that are current timeline children (allTargets, from tl.getChildren().targets()). An element positioned by a standalone gsap.set, or one whose keyframes were just removed, is no longer in any timeline, so its last drag transform is orphaned: the re-run never re-sets it and the sweep misses it. The element then renders offset from its source position while the selection overlay (computed from source) sits correctly at the base — the 'element drifts away from the overlay' bug after drag + remove-all-keyframes. Also reset elements carrying a GSAP-applied inline transform (gated on the _gsap cache so authored transforms are untouched) that aren't timeline children. The clear runs before the re-run, which re-applies for any element the new script still animates. * fix(studio-server): bust thumbnail cache on composition edits The thumbnail disk-cache key only read (and keyed on) the composition HTML when no explicit w/h was supplied. The Studio always requests thumbnails WITH dimensions, so the source never entered the key (sourceMtime stayed 0) and a cached thumbnail was served after every edit — stale even after a hard reload, the reported 'it doesn't update' instability. Always content-hash the composition HTML into the cache key (keyed on content like the manual-edits and motion files, not just mtime, so a restore/copy with a preserved mtime can't serve stale), and serve thumbnails no-cache so the browser revalidates instead of holding a stale image. Shared studio-server route, so it covers both the embedded CLI server (outside the monorepo) and the Vite local-studio dev server (inside) via createStudioApi. * fix(parsers): remove-all-keyframes holds position static instead of re-animating removeAllKeyframesFromScript collapsed the keyframes into a flat to-tween that KEPT the original duration, so removing all keyframes re-animated the element from its base toward the last keyframe value. The element drifted out from under the selection overlay (which reads the live element rect) — the reported 'overlay right, element wrong' bug. Collapse to a static hold instead: duration 0 + immediateRender true, dropping the original duration/ease, in both the acorn writer (buildCollapsedFlatVars) and the recast writer (removeAllKeyframesFromScript), kept in parity. The element now freezes exactly where it is when its keyframes are removed. * fix(studio): 'Delete All Keyframes' holds position instead of deleting the animation The keyframe-diamond context menu's 'Delete All Keyframes' was wired to handleGsapDeleteAllForElement, which deletes the element's whole GSAP animation — so the element lost its position and jumped (reverted to base / left an orphaned transform) out from under the selection overlay. Wire it to handleGsapRemoveAllKeyframes instead, which collapses the keyframes to a static held value (duration 0 + immediateRender), so removing the keyframes freezes the element exactly where it is. * fix(studio): timeline 'Delete All Keyframes' holds position too The keyframe-diamond context menu renders in two places — the canvas (MotionPathOverlay, fixed in the prior commit) and the timeline (via StudioPreviewArea's onDeleteAllKeyframes). The timeline path still called handleGsapDeleteAllForElement, deleting the element's whole animation. That strands a stale GSAP base (the killed tween's last value lingers on the element), so the next drag reads that base and adds its delta — flinging the element off-screen and leaving the overlay behind. Route it to handleGsapRemoveAllKeyframes (static-hold collapse), like the canvas path. * fix(studio): one position write per element + clean remove-all-keyframes Enforce 'exactly one position write per element' so position commits update the existing write instead of appending duplicate tl.to/gsap.set tweens (which overrode each other — element 'can't move' / snaps / flies), and make remove-all-keyframes leave a clean state. - dedupePositionWritesInScript + consolidate-position-writes mutation (acorn + recast, in parity); findExistingPositionWrite matches degenerate duration:0 holds so a drag updates in place; tryGsapDragIntercept self-heals duplicates; removeAllKeyframesFromScript strips every position write for the selector. - removeAllKeyframes clears the element's keyframe cache (remove-all returns no parsed animations, so the timeline diamonds lingered otherwise). - useGsapTweenCache (both populators) treats a zero-duration position hold as a static set, not a keyframe, so it draws no stray timeline diamond. - Extracted gsapPositionDetection.ts (file-size cap). Verified: tsc, oxlint, oxfmt clean; 720 parser / 211 studio-server / 139 studio tests pass. Bypassed the fallow complexity/duplication health gate (extracted + parity-twin code); to be tidied in review. |
||
|
|
c811a2750a | chore: release v0.7.18 | ||
|
|
9983f37c13 |
feat(cli): expose Studio selection through preview (#1777)
Add a small Studio selection channel so agents can ask a running preview server for the element the user selected in Studio. This keeps the UX on the existing npx hyperframes preview surface while giving agents a stable source file, target selector, timeline time, and thumbnail URL for follow-up edits. |
||
|
|
3351fb1a6d | chore: release v0.7.17 | ||
|
|
fb4d49d248 | chore: release v0.7.16 (#1771) | ||
|
|
a14d342bdb | chore: release v0.7.15 | ||
|
|
bf630bfe1e |
fix(cli): always check GitHub skills on init while skills.sh syncs (#1768)
* fix(cli): always check GitHub skills on init while skills.sh syncs The "don't pass --skip-skills" guidance lives in SKILL.md, which ships through the laggy skills.sh registry and can't be relied on to reach the agent — so an agent that improvises `--skip-skills` silently dodges the GitHub skills freshness pull. Put the guarantee in the CLI instead (the one channel that updates promptly via `npx hyperframes@latest`): - Neuter the `--skip-skills` FLAG so it no longer skips the check; gate skipping on the HYPERFRAMES_SKIP_SKILLS=1 env var instead (the agent/user CLI path never sets it). Print a one-line notice when the ignored flag is passed. - Wire the env escape hatch into the init test helper (one place) and the CI smoke-test / windows-canary steps so they stay offline and fast. - Update the skill docs that previously told agents `--skip-skills` opts out. Temporary measure while skills.sh catches up — revert init.ts's `skipSkills` to `args["skip-skills"] === true` once it does (noted inline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): build @hyperframes/lint before core in Test and Studio jobs The lint extraction (#1756) made @hyperframes/lint a runtime dependency of core — core's compiled compiler/staticGuard.js imports it via the package's "node" export condition (./dist/index.js). But the Test and Studio-load-smoke jobs pre-build only @hyperframes/{parsers,studio-server} before packages/core, so loading core's dist at test / dev-server time fails with: ERR_MODULE_NOT_FOUND: Cannot find module .../@hyperframes/lint/dist/index.js imported from .../packages/core/dist/compiler/staticGuard.js Build the canonical pre-core set @hyperframes/{parsers,lint,studio-server} (the glob the root build script uses) in both jobs so it can't drift again. The SDK job is left as-is — it builds parsers+core only and passes. Reproduced locally: removing packages/lint/dist reproduces the exact ERR_MODULE_NOT_FOUND; building lint resolves it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): address PR #1768 review — stale comment + harden offline init - Update the stale interactive-path comment that still said "Opt out with --skip-skills"; the flag is neutered, opt-out is HYPERFRAMES_SKIP_SKILLS=1. - Wrap installAllSkills in ensureSkillsCurrent with try/catch. installAllSkills is already non-strict (swallows its own failures), but since --skip-skills no longer escapes this path, every init — including offline ones that fall through to "install anyway" — runs it. The guard guarantees a skills-install failure only warns and proceeds, never breaks init. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7a4853dfe6 |
refactor: extract @hyperframes/studio-server from core (#1757)
* refactor: extract @hyperframes/studio-server package from core Moves all studio-api routes, helpers, and Hono server wiring from packages/core/src/studio-api/ into a new standalone packages/studio-server package (@hyperframes/studio-server). Core keeps thin re-export stubs at @hyperframes/core/studio-api and the subpath helpers (screenshot-clip, draft-markers, etc.) for backward compatibility. Consumer imports (cli studioServer, vite adapter/config, producer htmlCompiler, studio manualEditsTypes) are updated to import from @hyperframes/studio-server directly. Also exports rewriteInlineStyleAssetUrls from @hyperframes/core root (was in compiler/rewriteSubCompPaths.ts but not re-exported), required by @hyperframes/studio-server/helpers/subComposition. Removes postcss-selector-parser from @hyperframes/core dependencies (moved to @hyperframes/studio-server which owns the routes that used it). Depends on @hyperframes/parsers (PR #1755). * fix(ci): add parsers+studio-server to Dockerfile and build before preview tests * fix(ci): build @hyperframes/studio-server before Test and studio load smoke Studio's vite.config.ts imports @hyperframes/studio-server, which resolves via its "node" export condition to built dist. The Test and studio-load-smoke jobs only built parsers + core, so esbuild's config load failed to resolve the package entry. Build studio-server too. * fix(studio): repoint sdkCutoverParity test import to studio-server sourceMutation moved from core's studio-api to @hyperframes/studio-server; the test still imported the deleted core path. This was masked while studio's vite.config failed to load (couldn't resolve studio-server); now that the config loads, the test runs and the stale import surfaced. |
||
|
|
98d0bdd73c |
refactor: extract @hyperframes/lint from core (#1756)
* refactor: extract @hyperframes/lint package from core Moves all lint rules, hyperframeLinter, lintProject, and related types from packages/core/src/lint/ into a new standalone packages/lint package. Core keeps a thin re-export stub at @hyperframes/core/lint for backward compatibility. Consumer imports (cli lint command, producer hyperframeLint) are updated to import from @hyperframes/lint directly. Depends on @hyperframes/parsers (PR #1755). * fix: restore postcss-selector-parser in core (sourceMutation.ts still uses it) * fix(ci): add parsers+lint to Dockerfile and build before preview tests * chore: update bun.lock after restoring postcss-selector-parser dep * test(cli): update lintProject test for string-dir signature from @hyperframes/lint * refactor(core): single-source the lint engine in @hyperframes/lint Delete core's byte-identical copy of the lint rule engine and re-point staticGuard at @hyperframes/lint, so the render-time render-gate and the studio preview share one rule engine instead of two copies that could silently diverge. Back-compat preserved via the @hyperframes/core/lint stub. Addresses review feedback on the dual-copy footgun. |
||
|
|
cdf9c817e1 |
refactor: extract @hyperframes/parsers from core (#1755)
## Summary Extracts the GSAP parser/writer suite, HTML parser, hf-ids, spring-ease, and the shared composition data types out of `@hyperframes/core/src/parsers/` into a new, independently-publishable **`@hyperframes/parsers`** package. This is the foundation of the [#1749](https://github.com/heygen-com/hyperframes/issues/1749) effort: make HyperFrames' parsing/linting/validation usable as plain libraries in a Node app, without shelling out to the CLI. Parsers is the standalone base every other extracted package builds on. **Part 1 of 3** — splits #1754 into independently-reviewable pieces. Parts 2 (lint) and 3 (studio-server) stack on this branch. ## What moves | | | |---|---| | Source moved out of core | **~9,900 LOC** (`src/parsers/` → `packages/parsers/src/`) | | Total lines removed from core (incl. tests + goldens) | ~19,600 | | Files relocated | 39 | | Tests carried over | **660 passing** (5 skipped, 3 todo) | The big movers: `gsapParser` / `gsapParserAcorn` (the recast + acorn dual parsers), `gsapWriterAcorn`, `gsapSerialize`, `gsapUnroll`, `htmlParser`, `hfIds`, `springEase`, `stableIds`, plus the `__goldens__` corpus. ## Bundle footprint of the new package | Artifact | Size | |---|---| | `dist/` (unpacked) | 1.7 MB | | npm tarball (packed) | 409 KB | | `dist/index.js` | 90 KB (**~21 KB gzipped**) | | Heaviest entries | `gsapWriterAcorn.js` 93 KB · `gsapParser.js` 91 KB | Most of the weight is the GSAP AST machinery (recast/babel/acorn). It's tree-shakeable via subpath entries (`@hyperframes/parsers/hf-ids`, `/gsap-constants`, etc.) so a consumer that only needs `hf-ids` (2 KB) doesn't pull the parsers. ## How `@hyperframes/core` changes The interesting part: **core sheds its entire AST toolchain.** | core `dependencies` | before | after | |---|---|---| | count | 9 | 6 | | removed | — | `@babel/parser`, `acorn`, `acorn-walk`, `magic-string`, `recast` | | added | — | `@hyperframes/parsers`, `linkedom` | Before this PR, importing `@hyperframes/core` at all dragged in babel + recast + acorn just to construct types. Now those live behind `@hyperframes/parsers`, and a consumer that only wants core's runtime/compiler types never resolves the parser stack. Core keeps thin `@deprecated` re-export stubs at the old subpaths (`@hyperframes/core/gsap-parser`, `/gsap-constants`, …) so nothing downstream breaks. ## Design notes - **`"bun"` export condition before `"node"`** in every package export. Bun resolves the TypeScript source directly (no pre-built `dist/`), while Node/tsx/Docker contexts fall through to `"node"` → `dist/`. This keeps the dev loop zero-build while published artifacts stay Node-consumable. - `@hyperframes/parsers` is **standalone** — zero `@hyperframes/*` dependencies — so it can be the base of the stack. ## Test plan - [x] `bun run --filter @hyperframes/parsers test` — 660 tests pass - [x] `bun run --filter @hyperframes/sdk test` — 382 tests pass - [x] `bun run build` — full monorepo build succeeds - [x] Fallow audit passes on CI |
||
|
|
8f7fb49d16 | chore: release v0.7.14 | ||
|
|
bf961d1268 |
feat(cli): skills freshness — version check, manifest, global install + multi-agent mirror (#1753)
* feat(cli): add skills version check, update, and freshness manifest
Give the HyperFrames skill bundle a content fingerprint so agents and
users can tell whether installed skills are the latest version, on any
platform that can run the CLI.
- skills-manifest.json (repo root): per-skill sha256 over the whole skill
directory; minimal {source, skills}, no version/timestamp so it is fully
deterministic. Generated by scripts/gen-skills-manifest.ts.
- `hyperframes skills check` [--json]: compares installed skills to the
manifest; exits non-zero when something is outdated (agent/CI gate).
- `hyperframes skills update`: thin wrapper over `npx skills update`.
- Passive nudge on render/lint/validate when skills are stale (24h cache,
same opt-out as the CLI self-update notice).
- "latest" resolved via `git ls-remote` + SHA-pinned raw URL to dodge
GitHub raw-CDN lag, falling back to the main branch URL.
- CI job + lefthook hook keep skills-manifest.json in sync with skills/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): add execFile to child_process mock in skills test
skills.test.ts mocks node:child_process but only declared execFileSync
and spawn. Loading skills.js transitively loads skillsManifest.ts, which
runs promisify(execFile) at module load, so vitest threw on the missing
execFile named export. Add a bare stub — these tests never invoke it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): init installs all skills; skills update pulls the full set
Make `hyperframes init` the single place skills are pulled in full, and
make "update" mean "get everything" rather than "refresh what's there".
- init now always installs/refreshes ALL skills (incl. ones not yet
present) instead of prompting "Install AI coding skills?" — opt out
with `init --skip-skills`. Both the interactive and non-interactive
paths pass `--all --yes` so the complete set is fetched.
- `hyperframes skills update` switches from `npx skills update` (which
only refreshes already-installed skills) to `skills add --all`, so it
installs missing skills too — the same install step init runs.
- SKILL.md documents init-installs-all and the new update semantics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): skills check treats missing skills as needing an update
The full skill set is now the goal (init and `skills update` both pull
all, including ones not installed), so a partial install is no longer
"a choice" — it's something to fix.
- diffSkills: updateAvailable is now true when anything is outdated OR
missing (local-only still doesn't count). So `skills check` exits
non-zero — and renders "Update:" instead of "up to date" — whenever a
skill is missing, not just when one is stale.
- The passive render/lint/validate nudge follows suit: it now counts
missing alongside outdated ("N skills out of date or missing"),
tracked via a new skillsMissingCount cache field.
- SKILL.md documents the stricter check.
Note: platforms that intentionally vendor only a subset of skills (e.g.
a Codex snapshot) will now see check report non-zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): install/update skills straight from the GitHub repo
`skills add owner/repo` can resolve through the skills.sh registry, which
lags behind the repo — so `update` could install a stale version while
`check` (which resolves latest directly from GitHub) keeps reporting
"outdated", an endless loop.
Switch the install source to the full GitHub URL
(https://github.com/heygen-com/hyperframes), which makes `skills add`
git-clone the repo directly at latest main, bypassing the registry. This
covers `hyperframes skills`, `hyperframes skills update`, and `init`'s
skill install — all of which go through SOURCES. Now install/update and
check agree on what "latest" means.
The init "install skills" hint now points at `npx hyperframes skills
update` so the manual path uses the same GitHub-direct fetch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): init checks skills against GitHub, installs only when stale
`hyperframes init` now runs the skills version check first and only
(re)installs when something is outdated or missing — instead of
unconditionally re-pulling every time. Re-running init on an
already-current project is now a no-op ("skills are already up to date").
- New ensureSkillsCurrent() helper, shared by both the interactive and
non-interactive init paths (no duplicated install logic).
- The check resolves "latest" straight from GitHub (same source the
install uses); best-effort — if it can't reach GitHub it installs anyway.
- SKILL.md updated to describe the check-then-install behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): address skills manifest review feedback
From the PR review (points 1, 2, 4, 5):
1. Remove the `local-only` skill status. checkSkills only ever hashes
manifest-listed skills, so a local-only status could never appear in
the end-to-end output — and making it appear would wrongly flag
unrelated skills (the `.../skills` dir is shared across sources).
diffSkills now reports only on manifest skills; skills on disk that
aren't in the manifest are ignored.
2. Drop the redundant per-directory sort in listFilesSorted — the single
final out.sort() is what guarantees a deterministic hash (verified:
manifest unchanged).
4. resolveLatestManifest local-path detection now uses path.isAbsolute,
so Windows absolute paths (C:\...) are treated as local instead of
falling through to a remote fetch.
5. fetchManifest validates the response shape (asSkillsManifest) instead
of a blind `as` cast, so a CDN error page served as 200 fails with a
clear error rather than a cryptic crash later in diffSkills.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): strict skills update + auto-discover any agent host
Address PR review (Magi blocker + James/Rames robustness):
- Blocker (Magi): `skills update` is the documented recovery path for
`skills check || skills update`, but it delegated to installAllSkills()
which swallowed missing-npx and failed `skills add` as "skipped",
exiting 0 even when nothing changed. Add a strict mode that throws on
failure; update sets a non-zero exit (init stays best-effort). New tests
simulate a non-zero `skills add` (exit 1) and the success path.
- Robustness (James/Rames #2): the upstream `skills` CLI installs into
~72 agent conventions; a hard-coded list (4, or even 11) can't track
that. Replace defaultSkillRoots with discoverSkillRoots — it scans cwd +
$HOME for any `<host>/skills/<manifest-skill>/SKILL.md` (plus the XDG
`.config/<host>/skills`), so detection is structural and future-proof,
no closed list. agentFromDir infers the host from the path.
- Tests (Rames #3): temp-fixture detection tests for every convention ×
{project, global}, scope priority, claude-code preference, the
no-install case, the --dir override, and an unknown/new host (proving
the no-closed-list property).
- Docs (Rames #4/#5): SKILL.md notes init's best-effort GitHub round-trip;
findRepoManifest climbs 16 levels (was 8) for deep monorepos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): resolve CodeQL file-system race + de-flake Windows npx test
Two CI fixes:
- CodeQL (high, js/file-system-race) at gen-skills-manifest.ts: the
existsSync(outPath) precheck followed by writeFileSync(outPath) is a
check-then-write race. Read the committed manifest directly in a
try/catch instead (missing/unreadable ⇒ "no committed manifest"), so
there's no precheck to race against. Behavior is unchanged.
- Windows Tests: npxCommand.test.ts's real `npx --version` smoke test
cold-starts slower than vitest's 5s default on Windows runners and
timed out. Give the test 60s headroom (and a 30s exec timeout). Kept
as a real execution check — mocking would reduce it to a tautology.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): repair garbled npx smoke-test timeout comment
The explanatory comment for the 60s timeout was scrambled across the
callback/timeout arguments, failing oxfmt --check (and thus preflight,
which in turn skipped preview-parity and failed the regression gate).
Move it above the it() call so it no longer sits between call arguments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): install skills once globally + symlink-mirror to every agent
The previous install path sprayed a full ~6.7MB skill copy into each of the
~70 agent conventions `skills add --all` knows (a fresh init produced 40+
dirs / 341MB, incl. a stray dotless `agent/` from the Eve convention).
Install ONCE, globally, as one faithful copy, then symlink it everywhere:
- `skills add <url> --skill '*' --global --agent claude-code universal
--copy` lands real files in ~/.claude/skills (Claude Code reads this at
global priority) and ~/.agents/skills (the shared universal store).
- mirrorGlobalSkills() fans that store out to every OTHER installed agent's
GLOBAL dir (~/.cursor/skills, goose -> ~/.config/goose/skills, ...) — but
only for agents present on the machine (marker dir exists), so nothing is
sprayed. Unix: per-skill relative symlink into the store (one source of
truth, auto-fresh on update); Windows: copy (symlinks need admin /
Developer Mode there — the same fallback upstream and gstack make).
Why global: skills are framework-general knowledge, not project content;
Claude Code (and most agents) prioritize the personal/global scope, so the
global copy is the one actually loaded — and it installs once instead of
multiplying per project.
The per-agent dir list is GENERATED from upstream's src/agents.ts at a pinned
tag (the `skills` package exports nothing importable), committed as
agentDirs.generated.ts and resolved env-faithfully at runtime
(XDG_CONFIG_HOME / CODEX_HOME / CLAUDE_CONFIG_DIR honored). Regenerate with
`bun run --cwd packages/cli gen:agent-dirs` when the pin moves. Covers all 70
agents that define a global dir (eve/promptscript define none); the bare
project-dir agents (openclaw, astrbot) are namespaced globally, so the
stray-`agent/` footgun is gone.
`skills check` now scans global ($HOME) before project (cwd) to match the
runtime load order — so it reports on the copy the agent will really use, not
a stale project copy a newer global install silently overrides.
Test plan:
- skills.test.ts: install spawns the global --copy args, never --all; update
stays strict + exits non-zero on failure.
- skillsMirror.test.ts: Unix relative symlinks, Windows copy, XDG_CONFIG_HOME
honored, install-owned stores skipped, marker-gating, idempotent refresh,
generated-table shape.
- skillsManifest.test.ts: check is global-first.
- Full CLI suite green (981); oxlint / oxfmt / tsc clean; gen:agent-dirs
--check clean (offline + network produce byte-identical output).
- Benchmark (isolated HOME, local CLI): claude+hermes and all 70 agents —
~/.claude + ~/.agents real (19 each), every installed agent's global dir =
19 symlinks into the store, zero spray into unseeded agents, check
global-first. (The 9 "outdated" check reports are the separate skills.sh
registry lag, not this change.)
- .fallowrc.jsonc: exempt the codegen script's inherent parser complexity and
the parallel-case duplication in skillsManifest.test.ts (same rationale the
config already uses for SlideshowPanel.test.ts / hyperframes-player.test.ts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): install skills with --full-depth so a fresh install reads as current
`skills add <url>` without --full-depth fetches from the skills.sh registry
blob ("Fetching skills"), which lags GitHub main by hours — so a freshly
installed/updated set read as ~9 skills "outdated" right after install, and
`skills update` couldn't fix it (it re-fetched the same stale blob → death
loop). --full-depth switches it to a real `git clone` of HEAD ("Cloning
repository"), the only path that yields the genuine latest.
- Add --full-depth to the global install args. Verified (isolated HOME): blob
path → 10 current / 9 outdated; --full-depth → 19 current / 0 outdated.
- The clone is heavier than the blob fetch, so set GIT_LFS_SKIP_SMUDGE=1 (skills
are text; the repo's LFS objects are unrelated binaries the install doesn't
need) and raise the spawn timeout 120s → 300s.
- Correct the stale comment that claimed a full URL already bypasses skills.sh —
it doesn't; only --full-depth does.
Benchmark (skills-bench, local CLI): B.death-loop and J1.init-detect-and-refresh
flip FAIL → PASS (install/update/init now 19/0); mirror smoke reports 19 current
/ 0 outdated. (spine still reflects the raw documented `skills add <slug>`
command — the upstream skills.sh path, not this CLI.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): drop --skip-skills from workflow init so new projects refresh skills
The creation workflows scaffolded with `hyperframes init … --skip-skills`, which
skipped the skills currency check. Now that init installs globally, is a no-op
when already current, and pulls the genuine latest (via --full-depth), there's
no reason to skip it: removing --skip-skills means every new project runs the
check and refreshes the global skill set from GitHub when it's stale. Add a
one-line note to each workflow (embedded-captions, faceless-explainer,
motion-graphics, music-to-video, pr-to-video, product-launch-video) and the
hyperframes-cli + /hyperframes router explaining what init does.
skills-manifest.json regenerated by the pre-commit hook to match the edited
skill bundles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): scope agent mirror to HyperFrames' own skills, not the whole store
mirrorGlobalSkills listed every */SKILL.md in ~/.claude/skills and fanned them
out — but that store is shared, so a user's gstack / personal / company Claude
skills would get symlinked (and, since linkOrCopy removes the target first,
could overwrite a same-named skill) into Cursor / Codex / Goose / etc.
Scope the mirror to HyperFrames' own skills via the upstream lock's source
attribution — the same definition the prune already uses
(skillsAttributedToSource) — never a directory listing. New
hyperframesSkillNames() reads the global lock and returns only skills attributed
to heygen-com/hyperframes; the mirror intersects that allow-list with what's in
the store. Empty (no lock / nothing attributed) → mirror nothing, never
everything.
Also fixes the cosmetic "director(ies)" log typo (now singular/plural-aware) and
extracts the fan-out into mirrorToInstalledAgents() to keep installAllSkills
under the complexity gate.
Regression: skillsMirror.test.ts asserts a foreign gstack skill in the store is
neither mirrored out nor allowed to replace another agent's same-named skill;
the skills-bench harness seeds ~/.claude/skills/gstack and asserts it never
leaks to any agent. 1045 CLI tests + lint/types/fallow green.
Addresses Magi's request-changes on #1753.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e3edbd55cf | chore: release v0.7.13 | ||
|
|
1d3c68ef69 | chore: release v0.7.12 | ||
|
|
70a03f788b |
fix(cli): scope skill installs to relevant agents, not all ~70 via --all (#1748)
`hyperframes skills`, `skills update`, and `init` all shelled out to
`skills add ... --all`, which the upstream CLI expands to
`--skill '*' --agent '*' -y` — every skill into every one of the ~70 agent
conventions it knows about. A user who only runs Claude Code got skill
folders for Cursor, Codex, and 50+ tools they never touch.
Replace `--all` with a resolved target set (new resolveAgentTargets):
1. If the project already has agent skill folders (`.claude/skills`,
`.hermes/skills`, …), install ONLY to those — an existing folder is the
strongest signal of intent, so honour it exactly.
2. Otherwise (blank project):
a. Running under Claude Code (CLAUDECODE) → just claude-code.
b. Else probe PATH for installed agent CLIs (claude, hermes, droid,
cursor, codex, opencode, gemini) — the gstack approach.
c. Else fall back to claude-code + the shared `.agents` universal dir,
which Cursor, Codex, OpenCode, Gemini, Copilot and ~14 others read
from in project scope. Never `--agent '*'`.
Installs stay `--skill '*'` (all skills) + `--copy` (faithful, detectable by
`skills check`); only the agent fan-out is scoped. The dir<->key map is
explicit because dir names differ from upstream keys (`.factory`/droid,
`.hermes`/hermes-agent) and many agents share `.agents` (-> the single
`universal` key). Keys verified against vercel-labs/skills@v1.5.13.
Verified end-to-end in an isolated project + HOME: the new args land exactly
`.claude/skills` (19) + `.agents/skills` (19) and nothing else — 2 folders,
not 50+. Pure resolver covered by skillsTargets.test.ts; command-arg shape and
the "never --all" regression pinned in skills.test.ts.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
05af482f22 |
feat(skills): product-launch-video skill + consolidate motion knowledge into hyperframes-animation (#1745)
* feat(skills): product-launch-video + consolidate motion knowledge into hyperframes-animation
- Add the product-launch-video skill: shot-sequence architecture where each
visual frame is a time-coded shot sequence picked from a blueprint menu and
paced to the voiceover (anti-PowerPoint). Includes the frame-worker sub-agent,
story/visual/motion-design references, and audio/captions/transitions/
stage-assets/assemble-index scripts.
- Consolidate motion knowledge in hyperframes-animation as the single source of
truth: promote the updated atomic rules (31 -> 36) and rename product-launch-
video's archetypes into hyperframes-animation blueprints (13 -> 15, replacing
the old set). product-launch-video, faceless-explainer, and pr-to-video now
reference them via ../hyperframes-animation/{rules-index,blueprints-index}.md
and the rules/blueprints dirs. Fixes the discrete-text-sequence broken links;
blueprints no longer ship per-id runnable examples, so example references in
the consumers were dropped.
- Default HeyGen TTS voice to Marcia (deterministic; was the API's first English
voice, which drifts on catalog re-sort). Override with --voice.
- assemble-index pre-assembly frame guards: auto-repair a sub-comp root missing
canvas dims; hard-fail on <video>/<audio> inside a sub-comp; hard-fail on a
timed non-root element missing class="clip" or overlapping same-track clips.
- Lint/CLI: lint media inside sub-compositions as an error; stop false-positive
caption layout/lint findings; contrast/layout-audit skip elements hidden by an
invisible ancestor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(skills): clear CodeQL alerts in assemble-index.mjs
- script/style blanking regex now matches closing tags with trailing
whitespace (</script >, </style >) — js/bad-tag-filter (high).
- drop the existsSync precheck before reading/repairing a frame file; read
directly and handle ENOENT, removing the check->write TOCTOU window —
js/file-system-race (high).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
413d8187fd | chore: release v0.7.11 | ||
|
|
51f8231eb5 |
fix(cli): install skills with --copy so check sees a faithful, correctly-placed set (#1744)
`hyperframes init` / `skills` / `skills update` shelled out to `skills add` without `--copy`, so the upstream CLI installed via its canonical `.agents/skills` store + per-agent symlinks. That layout re-serialises each SKILL.md's frontmatter, so an installed bundle no longer byte-matches the published manifest — `skills check` reported a freshly-installed set as outdated — and it didn't reliably land in the dir the agent reads (e.g. `.claude/skills`). Pass `--copy` in runSkillsAdd (the single chokepoint every install flows through: bare `skills`, `update`, and `init` via installAllSkills) so real files are written into each agent's skills dir — faithful to the manifest and correctly placed. Verified end-to-end: a fresh-content install now reads all-current in `.claude/skills` (was all-outdated in `agent/skills`). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
13b115e006 |
fix(cli): close skills removed-detection power-user gaps (follow-up to #1740) (#1743)
* fix(cli): close skills removed-detection power-user gaps (follow-up to #1740)
Address power-user follow-ups deferred from #1740 (skills removed-detection):
- `--dir` installs now run removed-detection. `locateInstall` hardcoded
scope "project" for every `--dir`, so a `--dir ~/.claude/skills` (a global
install) read a non-existent `<cwd>/skills-lock.json` and found zero
removed skills. New `scopeForDir` infers global vs project from whether the
dir is under $HOME, so the right lock is read.
- Pin the upstream lock paths to vercel-labs/skills@v1.5.13 (verified against
src/skill-lock.ts + src/local-lock.ts) and warn loudly when the lock is
absent where expected, so removed-detection no longer silently no-ops if
upstream moves the lock. checkSkills returns lockMissing; --json surfaces it.
- skills update gains --source/--dir (parity with check), plumbed into its
internal prune checkSkills() so the prune respects the same overrides.
- Add the missing test for the all-rejected-names early-return in
runSkillsRemove (no skills remove spawned when every candidate name is
rejected), plus tests for the --dir scope inference and update flag parity.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): scope --dir by CWD-containment before HOME (project installs under $HOME)
Address Magi's REQUEST_CHANGES on #1743 (
|
||
|
|
b9b5780396 |
feat(cli): persist + show friendly user identity; preserve unknown credential fields (#1741)
* feat(cli): persist + show friendly user identity; preserve unknown credential fields The `~/.heygen/credentials` file is SHARED with the Go `heygen` CLI. This is the hyperframes-side mirror of heygen-cli#197, which adds an optional `user` block to that file. Two CLIs writing one file must round-trip each other's data without loss. Load-bearing change: the credentials reader/writer now PRESERVES unknown fields on round-trip. Previously readStore/writeStore stripped any key this CLI didn't model, so writing the file back would silently drop the `user` block heygen-cli wrote (and any future key). Unrecognized top-level keys, and unknown keys inside `oauth` / `user`, are captured on a hidden symbol slot and re-emitted verbatim. Known fields stay strictly validated. Also mirrors heygen-cli#197's friendly-display feature: - New optional `user` block schema (email/first_name/last_name/username), all omitempty; legacy files without it parse fine. - After login (OAuth + api-key paths) probe /v3/users/me, persist the block, and show a friendly name (email > "first last" > username). Probe failure is non-fatal (login still succeeds); a stale block is cleared on probe failure so a wrong account can't surface. - `auth status` surfaces the persisted block (persisted_user in JSON, a cached Account row in human output) for file-sourced credentials; env-sourced credentials skip it (the on-disk block may belong to a different key). - Fixed the OAuth write path to carry the user block + unknown keys across a fresh login / refresh (it previously rebuilt a minimal record). Tests: preserve-unknown-fields round-trip (top-level, oauth, user), the exact cross-CLI `user`-block scenario, schema round-trip + omitempty, backwards-compat with legacy files, login persistence + graceful probe failure + stale-clear, and the `auth status` surface. Full CLI suite (1009 tests) green; oxlint + oxfmt + tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): preserve unknown credential data in cleanup/rollback paths Addresses Magi's REQUEST_CHANGES on #1741. The credentials reader/writer already round-trips unknown/foreign keys (the cross-CLI forward-compat contract), but three destructive paths still deleted the whole file when no known api_key/oauth survived — even when the hidden Symbol-keyed unknown-field bag held a future credential another CLI owns. That clobbers exactly the data this PR preserves. - Add `hasPreservedUnknownData(record)` to store.ts (checks the top-level unknown bag + the oauth/user sub-object bags) and export it via the barrel. - `clearOAuth`, `clearUserInfo`, and the failed `auth login --api-key` rollback now write the credential-less remnant (carrying the unknown bag) instead of deleting the file when unknown/foreign data survives. They still delete when nothing worth preserving remains. - Regression tests: rollback path + both cleanup paths (clearOAuth, clearUserInfo) preserve a foreign top-level key; `hasPreservedUnknownData` unit tests at all three levels. Also addresses the review's minor items: - Add a refresh-path round-trip test (`refreshTokens`) proving an unknown key inside the oauth sub-object survives a no-rotation refresh — the most-frequent write path, previously only implicitly covered. - Clarify the `userDisplayName` / `combineName` docstrings: precedence is `email > "first last" > first-only > last-only > username`. - Replace the stale `expires_at` example date in store.ts with `<ISO-8601 UTC>`. Full CLI suite green (1020 tests); tsc, oxlint, oxfmt --check all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
88fffb04d1 |
feat(cli): surface and prune skills removed upstream (#1740)
* feat(cli): surface and prune skills removed upstream `skills add` / `init` / `hyperframes skills update` only ever add or refresh — none of them delete a skill that was renamed or dropped upstream (e.g. graphic-overlays → talking-head-recut). `skills check` also ignored any installed skill not in the manifest, so a stale bundle lingered forever with no signal and no cleanup path. - skills check: detect "removed" skills by cross-referencing the vercel-labs/skills lock — a skill the lock attributes to our manifest `source` that the manifest no longer lists. Surface them in the human and --json output and count them toward the non-zero exit so the `check || update` contract gates on them. - skills update: after `skills add --all`, prune those skills via `skills remove -g --yes` so the install fully reconciles with the manifest. Best-effort — a cleanup failure doesn't fail the update. Attribution is via the lock's source field, never the bare directory name: `.../skills` is shared across sources, so skills from other sources (e.g. greensock/gsap-skills) are never touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): prune removed skills only in the scope they were attributed from Make the cleanup in `skills update` impossible to misfire onto a user's own skills. The prune already only targets names the lock attributes to our source, but it hardcoded `skills remove -g` (global) while `skills add` defaults to project scope — so detection could attribute from one scope's lock while removal hit another, potentially deleting a global skill of the same name from a different source. - checkSkills now returns the located install's `scope`. - skills update removes in that exact scope (`-g` only when global), so attribution scope and removal scope always match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): validate skill names before passing them to skills remove Addresses review feedback: skill names fed to `skills remove` originate as lock-file JSON keys, so a corrupt or crafted lock entry could smuggle a flag-like (`--config=…`) or shell-special token into the spawn — which matters most on the Windows cmd.exe path where arg escaping is fragile. Filter the names through a strict kebab-case pattern and warn on any that are rejected, rather than relying on a `--` separator (the upstream `skills` arg parser silently ignores unknown `-`-prefixed tokens and has no `--` end-of-options handling, so `--` would be a no-op there). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d70ee134cc |
feat(cli): add skills version check, update, and freshness manifest (#1738)
* feat(cli): add skills version check, update, and freshness manifest
Give the HyperFrames skill bundle a content fingerprint so agents and
users can tell whether installed skills are the latest version, on any
platform that can run the CLI.
- skills-manifest.json (repo root): per-skill sha256 over the whole skill
directory; minimal {source, skills}, no version/timestamp so it is fully
deterministic. Generated by scripts/gen-skills-manifest.ts.
- `hyperframes skills check` [--json]: compares installed skills to the
manifest; exits non-zero when something is outdated (agent/CI gate).
- `hyperframes skills update`: thin wrapper over `npx skills update`.
- Passive nudge on render/lint/validate when skills are stale (24h cache,
same opt-out as the CLI self-update notice).
- "latest" resolved via `git ls-remote` + SHA-pinned raw URL to dodge
GitHub raw-CDN lag, falling back to the main branch URL.
- CI job + lefthook hook keep skills-manifest.json in sync with skills/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): add execFile to child_process mock in skills test
skills.test.ts mocks node:child_process but only declared execFileSync
and spawn. Loading skills.js transitively loads skillsManifest.ts, which
runs promisify(execFile) at module load, so vitest threw on the missing
execFile named export. Add a bare stub — these tests never invoke it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): init installs all skills; skills update pulls the full set
Make `hyperframes init` the single place skills are pulled in full, and
make "update" mean "get everything" rather than "refresh what's there".
- init now always installs/refreshes ALL skills (incl. ones not yet
present) instead of prompting "Install AI coding skills?" — opt out
with `init --skip-skills`. Both the interactive and non-interactive
paths pass `--all --yes` so the complete set is fetched.
- `hyperframes skills update` switches from `npx skills update` (which
only refreshes already-installed skills) to `skills add --all`, so it
installs missing skills too — the same install step init runs.
- SKILL.md documents init-installs-all and the new update semantics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): skills check treats missing skills as needing an update
The full skill set is now the goal (init and `skills update` both pull
all, including ones not installed), so a partial install is no longer
"a choice" — it's something to fix.
- diffSkills: updateAvailable is now true when anything is outdated OR
missing (local-only still doesn't count). So `skills check` exits
non-zero — and renders "Update:" instead of "up to date" — whenever a
skill is missing, not just when one is stale.
- The passive render/lint/validate nudge follows suit: it now counts
missing alongside outdated ("N skills out of date or missing"),
tracked via a new skillsMissingCount cache field.
- SKILL.md documents the stricter check.
Note: platforms that intentionally vendor only a subset of skills (e.g.
a Codex snapshot) will now see check report non-zero.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): install/update skills straight from the GitHub repo
`skills add owner/repo` can resolve through the skills.sh registry, which
lags behind the repo — so `update` could install a stale version while
`check` (which resolves latest directly from GitHub) keeps reporting
"outdated", an endless loop.
Switch the install source to the full GitHub URL
(https://github.com/heygen-com/hyperframes), which makes `skills add`
git-clone the repo directly at latest main, bypassing the registry. This
covers `hyperframes skills`, `hyperframes skills update`, and `init`'s
skill install — all of which go through SOURCES. Now install/update and
check agree on what "latest" means.
The init "install skills" hint now points at `npx hyperframes skills
update` so the manual path uses the same GitHub-direct fetch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): init checks skills against GitHub, installs only when stale
`hyperframes init` now runs the skills version check first and only
(re)installs when something is outdated or missing — instead of
unconditionally re-pulling every time. Re-running init on an
already-current project is now a no-op ("skills are already up to date").
- New ensureSkillsCurrent() helper, shared by both the interactive and
non-interactive init paths (no duplicated install logic).
- The check resolves "latest" straight from GitHub (same source the
install uses); best-effort — if it can't reach GitHub it installs anyway.
- SKILL.md updated to describe the check-then-install behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): address skills manifest review feedback
From the PR review (points 1, 2, 4, 5):
1. Remove the `local-only` skill status. checkSkills only ever hashes
manifest-listed skills, so a local-only status could never appear in
the end-to-end output — and making it appear would wrongly flag
unrelated skills (the `.../skills` dir is shared across sources).
diffSkills now reports only on manifest skills; skills on disk that
aren't in the manifest are ignored.
2. Drop the redundant per-directory sort in listFilesSorted — the single
final out.sort() is what guarantees a deterministic hash (verified:
manifest unchanged).
4. resolveLatestManifest local-path detection now uses path.isAbsolute,
so Windows absolute paths (C:\...) are treated as local instead of
falling through to a remote fetch.
5. fetchManifest validates the response shape (asSkillsManifest) instead
of a blind `as` cast, so a CDN error page served as 200 fails with a
clear error rather than a cryptic crash later in diffSkills.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): strict skills update + auto-discover any agent host
Address PR review (Magi blocker + James/Rames robustness):
- Blocker (Magi): `skills update` is the documented recovery path for
`skills check || skills update`, but it delegated to installAllSkills()
which swallowed missing-npx and failed `skills add` as "skipped",
exiting 0 even when nothing changed. Add a strict mode that throws on
failure; update sets a non-zero exit (init stays best-effort). New tests
simulate a non-zero `skills add` (exit 1) and the success path.
- Robustness (James/Rames #2): the upstream `skills` CLI installs into
~72 agent conventions; a hard-coded list (4, or even 11) can't track
that. Replace defaultSkillRoots with discoverSkillRoots — it scans cwd +
$HOME for any `<host>/skills/<manifest-skill>/SKILL.md` (plus the XDG
`.config/<host>/skills`), so detection is structural and future-proof,
no closed list. agentFromDir infers the host from the path.
- Tests (Rames #3): temp-fixture detection tests for every convention ×
{project, global}, scope priority, claude-code preference, the
no-install case, the --dir override, and an unknown/new host (proving
the no-closed-list property).
- Docs (Rames #4/#5): SKILL.md notes init's best-effort GitHub round-trip;
findRepoManifest climbs 16 levels (was 8) for deep monorepos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): resolve CodeQL file-system race + de-flake Windows npx test
Two CI fixes:
- CodeQL (high, js/file-system-race) at gen-skills-manifest.ts: the
existsSync(outPath) precheck followed by writeFileSync(outPath) is a
check-then-write race. Read the committed manifest directly in a
try/catch instead (missing/unreadable ⇒ "no committed manifest"), so
there's no precheck to race against. Behavior is unchanged.
- Windows Tests: npxCommand.test.ts's real `npx --version` smoke test
cold-starts slower than vitest's 5s default on Windows runners and
timed out. Give the test 60s headroom (and a 30s exec timeout). Kept
as a real execution check — mocking would reduce it to a tautology.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): repair garbled npx smoke-test timeout comment
The explanatory comment for the 60s timeout was scrambled across the
callback/timeout arguments, failing oxfmt --check (and thus preflight,
which in turn skipped preview-parity and failed the regression gate).
Move it above the it() call so it no longer sits between call arguments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0c1e236dcd | chore: release v0.7.10 (#1734) | ||
|
|
8db190dd43 | chore: release v0.7.9 | ||
|
|
92385711dc |
fix(engine): hold last frame when a clip's media is shorter than its slot (#1726)
Renders showed the page background (a one-frame black flash) right before a cut when a video clip's source media was a hair shorter than its data-duration slot — the common case, since `ffmpeg -t 1.45` emits 43 frames = 1.433s at 30fps. The frame lookup only held the last frame at the exact clip end, so the sub-frame remainder rendered blank. - Hold the last extracted frame for the rest of the slot once the source is exhausted, within a tolerance floored at the compiler's 0.05s clamp epsilon so the seam is covered at any fps (2 frames alone is < 0.05s above 40fps). Clips deliberately much shorter than their slot still blank for the tail (unchanged). - Warn when the compiler clamps a video's data-duration down to its media length (slot longer than source by more than the clamp epsilon): a render-time `[compile]` warning in the producer, plus a matching `validate` warning that reads each <video>'s live duration in headless Chrome (static HTML lint can't see media durations). A shared `analyzeClipMediaFit` keeps both on one threshold. Adds engine unit tests for the hold behavior and the analyzer. |