Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"./skills/hyperframes-creative",
"./skills/hyperframes-keyframes",
"./skills/hyperframes-registry",
"./skills/hyperframes-studio",
"./skills/media-use"
]
},
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This repo ships AI agent skills via [vercel-labs/skills](https://github.com/verc

```bash
npx hyperframes skills update # default: installs/refreshes the core set — workflows install on demand
npx hyperframes skills # all 20 published skills at once
npx hyperframes skills # all 21 published skills at once
npx skills add heygen-com/hyperframes # interactive picker (terminal only; repo-internal skills are excluded by default)
```

Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ Open-source video rendering framework: write HTML, render video.

## Skills

This repo ships 20 AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. **Default to the core set**: the `/hyperframes` router installs each creation workflow on demand; install all 20 only when the user explicitly asks for the full set.
This repo ships 21 AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. **Default to the core set**: the `/hyperframes` router installs each creation workflow on demand; install all 21 only when the user explicitly asks for the full set.

```bash
npx hyperframes skills update # default: installs/refreshes the core set — workflows install on demand
npx hyperframes skills # all 20 published skills at once — only on explicit request
npx hyperframes skills # all 21 published skills at once — only on explicit request
npx skills add heygen-com/hyperframes # interactive picker (terminal only; --all also pulls the 6 repo-internal skills under .claude/skills)
npx skills add heygen-com/hyperframes --skill <name> # just one (bare name, no leading slash)
```
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Install the HyperFrames skills, then describe the video you want:
npx skills add heygen-com/hyperframes
```

> The picker opens with nothing pre-selected — the **Core Skills** group is all you need: the `/hyperframes` router installs each creation workflow on demand. Agents and non-interactive runs should use `npx hyperframes skills update` instead — it installs exactly the core set, whereas `skills add --all` installs all 20 published skills. The six repo-internal skills under `.claude/skills` / `.agents/skills` are excluded by default. For the full published set use `npx hyperframes skills`.
> The picker opens with nothing pre-selected — the **Core Skills** group is all you need: the `/hyperframes` router installs each creation workflow on demand. Agents and non-interactive runs should use `npx hyperframes skills update` instead — it installs exactly the core set, whereas `skills add --all` installs all 21 published skills. The six repo-internal skills under `.claude/skills` / `.agents/skills` are excluded by default. For the full published set use `npx hyperframes skills`.
>
> `skills add` resolves the skills.sh registry blob, which can lag `main` by hours. `npx hyperframes skills update` installs from the current `main`, so reach for it when you need the newest copy of a skill.

Expand All @@ -53,9 +53,9 @@ The skills teach agents the HyperFrames production loop: plan the video, write v

## Skills

HyperFrames ships 20 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below.
HyperFrames ships 21 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below.

Default to the **core set** — the router installs each creation workflow on demand. `npx hyperframes skills update` installs exactly that from anywhere; the interactive picker (`npx skills add heygen-com/hyperframes`) lists it as the "Core Skills" group, nothing pre-selected. The picker is interactive-only — a non-interactive or agent run without `--skill` installs all 20. Use `npx skills add heygen-com/hyperframes --all` to install all 20 deliberately (skips the picker), or `npx skills add heygen-com/hyperframes --skill <name>` for just one (bare name, no leading `/`).
Default to the **core set** — the router installs each creation workflow on demand. `npx hyperframes skills update` installs exactly that from anywhere; the interactive picker (`npx skills add heygen-com/hyperframes`) lists it as the "Core Skills" group, nothing pre-selected. The picker is interactive-only — a non-interactive or agent run without `--skill` installs all 21. Use `npx skills add heygen-com/hyperframes --all` to install all 20 deliberately (skips the picker), or `npx skills add heygen-com/hyperframes --skill <name>` for just one (bare name, no leading `/`).

Installs stay lean after that: `npx hyperframes init` keeps the **core set** fresh (the router, the `hyperframes-*` domain skills, and `media-use` — plus whatever is already installed; `/figma` stays on demand) and never expands a partial install; the creation workflows install **on demand** — the router runs `npx hyperframes skills update <workflow>` before entering one. Nothing re-pulls the full set behind your back.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"player:perf": "bun run --filter @hyperframes/player perf",
"format:check": "oxfmt --check .",
"knip": "knip",
"test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-large-files.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts && vitest run scripts/catalog/",
"test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-large-files.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs scripts/creator-editing-recipes.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts && vitest run scripts/catalog/",
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
"test:skills": "node --test 'skills/**/*.test.mjs'",
"generate:previews": "tsx scripts/generate-template-previews.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/docs/data-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Core attributes for controlling element timing and behavior.
## Media

- `data-media-start="2"` — Media playback offset / trim point (seconds)
- `data-volume="0.8"` — Audio/video gain. `1` is 0 dB, `0` is silence, and values above `1` boost up to `3.98` (+12 dB)
- `data-volume="0.8"` — Audio/video gain. `1` is 0 dB, `0` is silence, and values above `1` boost up to `3.98` (+12 dB). For fades and ducking use the `data-automation` volume lane (`hyperframes-core` skill, `creator-editing-recipes.md`)
- `data-has-audio="true"` — Indicates video has an audio track

## Composition
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/utils/skillsManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const FALLBACK_CORE_SKILLS: readonly string[] = [
"hyperframes-creative",
"hyperframes-keyframes",
"hyperframes-registry",
"hyperframes-studio",
"media-use",
];

Expand Down
2 changes: 1 addition & 1 deletion packages/studio/src/components/editor/domEditing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ describe("patch builders and prompt builder", () => {
expect(prompt).toContain("Computed styles (browser-resolved):");
expect(prompt).toContain("Target HTML:");
expect(prompt).toContain("Guardrails:");
expect(prompt).toContain("Do not modify other elements' data-* attributes or positioning.");
expect(prompt).toContain("Do not modify other elements' data-* attributes or positioning");
});

it("uses an absolute source path in copied agent prompts when provided", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ describe("buildElementAgentPrompt", () => {
"Text: Hello world",
"",
"Guardrails:",
"- Make a targeted change to this element only.",
"- Preserve the rest of the composition and its timing.",
"- Do not modify other elements' data-* attributes or positioning.",
"- Make a targeted change to this element only, unless the request is a timeline edit.",
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
"- For timeline edits (trim, split, speed, volume, copy, swap), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
].join("\n"),
);
Expand Down Expand Up @@ -160,9 +161,10 @@ describe("buildPickerAgentPrompt", () => {
// Both end on the identical guardrails block, byte for byte.
const guardrails = [
"Guardrails:",
"- Make a targeted change to this element only.",
"- Preserve the rest of the composition and its timing.",
"- Do not modify other elements' data-* attributes or positioning.",
"- Make a targeted change to this element only, unless the request is a timeline edit.",
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
"- For timeline edits (trim, split, speed, volume, copy, swap), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
].join("\n");
expect(domPrompt.endsWith(guardrails)).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ export interface AgentPromptElementInfo {

const GUARDRAIL_LINES = [
"Guardrails:",
"- Make a targeted change to this element only.",
"- Preserve the rest of the composition and its timing.",
"- Do not modify other elements' data-* attributes or positioning.",
"- Make a targeted change to this element only, unless the request is a timeline edit.",
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
"- For timeline edits (trim, split, speed, volume, copy, swap), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
];

Expand Down
75 changes: 75 additions & 0 deletions scripts/creator-editing-recipes.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { lintHyperframeHtml } from "../packages/lint/src/index.ts";

const OWNER = "skills/hyperframes-core/references/creator-editing-recipes.md";
const STUDIO_SKILL = "skills/hyperframes-studio/SKILL.md";
const VOLUME_TWEEN = /\.(?:to|from|fromTo|set)\(\s*["'`]#[\w-]+["'`]\s*,\s*\{[^}]*\bvolume\s*:/;
const TWEEN_SCANNED = [
"skills/hyperframes-core/references/variables-and-media.md",
"skills/hyperframes-core/references/data-attributes.md",
"skills/hyperframes-animation/adapters/gsap.md",
"skills/hyperframes-audio/SKILL.md",
"skills/hyperframes-audio/references/attributes.md",
"skills/music-to-video/references/montage.md",
"skills/media-use/references/operations.md",
"packages/cli/src/docs/data-attributes.md",
];

const read = (path) => readFile(new URL(`../${path}`, import.meta.url), "utf8");

const htmlBlocks = (md) => [...md.matchAll(/```html\n([\s\S]*?)```/g)].map((m) => m[1]);

const wrap = (fragment) =>
fragment.includes("<html")
? fragment
: `<html><body><div id="root" data-composition-id="main" data-start="0" data-duration="120" data-width="1920" data-height="1080">${fragment}</div><script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script><script>window.__timelines = window.__timelines || {};</script></body></html>`;

test("every html example in the owner doc lints with no errors and no volume double-automation", async () => {
const blocks = htmlBlocks(await read(OWNER));
assert.ok(blocks.length >= 10, "expected the owner doc to keep its worked examples");
for (const [i, block] of blocks.entries()) {
const { findings } = await lintHyperframeHtml(wrap(block), { filePath: "index.html" });
const bad = findings.filter(
(f) => f.severity === "error" || f.code.startsWith("audio_volume_"),
);
assert.deepEqual(
bad.map((f) => `${f.code}: ${f.message}`),
[],
`example ${i + 1} in ${OWNER}`,
);
}
});

test("no other doc teaches a timeline tween as the way to fade volume", async () => {
for (const path of TWEEN_SCANNED) {
const lines = (await read(path)).split("\n");
const hit = lines.findIndex((l) => VOLUME_TWEEN.test(l));
assert.equal(hit, -1, `${path}:${hit + 1} teaches a volume tween; point at ${OWNER}`);
}
});

test("the Studio skill holds conventions only and points at the owner doc for edits", async () => {
const skill = await read(STUDIO_SKILL);
assert.match(skill, /creator-editing-recipes\.md/);
assert.equal(htmlBlocks(skill).length, 0, "a recipe restated in the Studio skill will drift");
});

test("the Remotion translation docs do not claim volume ramps are unsupported", async () => {
for (const path of [
"skills/remotion-to-hyperframes/references/limitations.md",
"skills/remotion-to-hyperframes/references/media.md",
]) {
assert.doesNotMatch(await read(path), /static `data-volume` only/, path);
}
});

test("the Studio skill's safe boxes equal the preview's", async () => {
const src = await read("packages/studio/src/utils/previewSafeMargins.ts");
const action = src.match(/ACTION_SAFE_PERCENT = (\d+)/)?.[1];
const title = src.match(/TITLE_SAFE_PERCENT = (\d+)/)?.[1];
const skill = await read(STUDIO_SKILL);
assert.match(skill, new RegExp(`Action-safe\\s*\\|\\s*${action}%`));
assert.match(skill, new RegExp(`Title-safe\\s*\\|\\s*${title}%`));
});
18 changes: 11 additions & 7 deletions skills-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"source": "heygen-com/hyperframes",
"skills": {
"embedded-captions": {
"hash": "4023b0b051ba1e75",
"hash": "f1b298aa899d86df",
"files": 142
},
"faceless-explainer": {
Expand All @@ -18,11 +18,11 @@
"files": 4
},
"hyperframes": {
"hash": "d0a114a8f894758e",
"hash": "ffacb7af2a96d788",
"files": 26
},
"hyperframes-animation": {
"hash": "4225f299e6ff9a0e",
"hash": "492f6f39edf2a8e5",
"files": 121
},
"hyperframes-audio": {
Expand All @@ -34,7 +34,7 @@
"files": 11
},
"hyperframes-core": {
"hash": "3e29cc2454953b05",
"hash": "054fe5577b064927",
"files": 11
},
"hyperframes-creative": {
Expand All @@ -49,16 +49,20 @@
"hash": "51e6dba95a8dfb45",
"files": 12
},
"hyperframes-studio": {
"hash": "b063eaa9eb1e4b9b",
"files": 1
},
"media-use": {
"hash": "4531919cdbda573e",
"hash": "8f21655cd07a62ea",
"files": 158
},
"motion-graphics": {
"hash": "32641ae2b94c4a8f",
"files": 23
},
"music-to-video": {
"hash": "7fad41aa135aea23",
"hash": "e69f4646ef354be9",
"files": 169
},
"pr-to-video": {
Expand All @@ -70,7 +74,7 @@
"files": 30
},
"remotion-to-hyperframes": {
"hash": "65222f2d894e8776",
"hash": "c8eb6f48889f5c06",
"files": 77
},
"slideshow": {
Expand Down
5 changes: 2 additions & 3 deletions skills/embedded-captions/references/aesthetic-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,8 @@ Chunk at natural pauses ≥ 250ms. A caption spanning a breath-break feels wrong

### 17. Safe zones per platform, always

- 9:16 TikTok/IG/Shorts: caption zone `y ∈ [12%, 78%]` (bottom 22% is UI)
- 16:9 broadcast: title-safe = center 80%
- TV export: 5% margin on all sides
- Use the safe boxes in `/hyperframes-studio` (Safe zones) for wide and vertical
framings; that skill owns the values.

Bake into the layout solver. Never eyeball.

Expand Down
2 changes: 1 addition & 1 deletion skills/embedded-captions/references/rail.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ with only the climax(es) promoted to embed. Rail is not a fallback — it's the

- **Lower third, horizontally centered.** Landscape (16:9): baseline ~80–120px above the
bottom edge. Portrait (9:16): lower-middle, ~ 600–700px from the bottom (clear of platform UI).
- **Title-safe margins:** keep text within ~90% width / inside any letterbox-pillarbox bars
- **Title-safe margins:** keep text within the title-safe box (80% of the frame, `/hyperframes-studio` Safe zones) / inside any letterbox-pillarbox bars
(see the letterbox probe). Never flush to the frame edge.
- **One caption group on screen at a time.** No accumulation, no cascade — that's embed-track behaviour.
- It rides above the subject; the matte does **not** occlude it (rail = in front).
Expand Down
Loading
Loading