Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1c90360
feat(cli): add website capture with AI-powered DESIGN.md generation
ukimsanov Apr 7, 2026
2b6ed8f
feat(cli): add gemini 3.1 pro, playwright screenshots, replica refine…
ukimsanov Apr 8, 2026
dd98642
docs: update session handoff with slack research findings
ukimsanov Apr 8, 2026
5ac5ca9
refactor(cli): simplify capture pipeline, remove replica generator
ukimsanov Apr 9, 2026
5701f6a
feat(capture): add Lottie detection and WebGL shader extraction
ukimsanov Apr 9, 2026
a67753e
refactor(capture): clean pipeline + shader-first creative workflow
ukimsanov Apr 10, 2026
4ecd8fc
docs: add skill architecture redesign spec
ukimsanov Apr 14, 2026
96b605a
docs: add implementation plan for skill architecture redesign
ukimsanov Apr 14, 2026
3b07dc2
refactor(capture): remove AI auto-generation and SDK dependencies
ukimsanov Apr 14, 2026
b6c8ec0
fix(capture): convert extracted colors to HEX format
ukimsanov Apr 14, 2026
98967be
refactor(capture): remove AI key path, add asset descriptions generator
ukimsanov Apr 14, 2026
4c0e131
refactor(capture): update agent prompt, remove hasDesignMd, add asset…
ukimsanov Apr 14, 2026
2abeafc
feat(capture): pre-wire shader transitions in index.html scaffold
ukimsanov Apr 14, 2026
26f47b5
chore: remove duplicate visual-styles.md (canonical is in hyperframes/)
ukimsanov Apr 14, 2026
5ad7eae
refactor(skill): rewrite website-to-hyperframes as phase-based orches…
ukimsanov Apr 14, 2026
b74bcaa
feat(skill): add Phase 1 understand reference
ukimsanov Apr 14, 2026
78589d4
feat(skill): add Phase 2 design reference with full DESIGN.md schema
ukimsanov Apr 14, 2026
67c96d8
feat(skill): add Phase 3 creative direction reference
ukimsanov Apr 14, 2026
56b0b5d
feat(skill): add Phase 4 build reference with inline shader example
ukimsanov Apr 14, 2026
179f1e3
feat(skill): upgrade Visual Identity Gate to produce full DESIGN.md
ukimsanov Apr 14, 2026
0795530
docs: update CLAUDE.md skill references for phase-based workflow
ukimsanov Apr 14, 2026
2d96b32
fix: address code review findings
ukimsanov Apr 14, 2026
b61fcce
fix(capture): regex double-escape + simplify scaffold + fix asset des…
ukimsanov Apr 14, 2026
26faa83
fix: code review — 16 bugs, 7-step skill rewrite, cleanup
ukimsanov Apr 15, 2026
a11661a
chore: remove dev artifacts, research docs, wrong lockfiles
ukimsanov Apr 15, 2026
ea6ac1b
fix(CLAUDE.md): align with main — slim format, add website-to-hyperfr…
ukimsanov Apr 15, 2026
4e5d8d1
fix(cli): add capture command to help groups
ukimsanov Apr 15, 2026
fff79b7
style: format skill reference files (oxfmt)
ukimsanov Apr 15, 2026
6335934
fix: regenerate bun.lock after rebase
ukimsanov Apr 15, 2026
6266708
fix: address PR review comments + improve capture quality
ukimsanov Apr 16, 2026
0f3c88d
fix: address PR review comments + improve capture quality
ukimsanov Apr 16, 2026
3b2e1cb
refactor(capture): split index.ts (1175 to 566 lines) into modules
ukimsanov Apr 16, 2026
be0f194
chore(capture): remove --split flow (splitter, verify, cssPurger, pur…
ukimsanov Apr 16, 2026
48ec6a0
fix(security): add ssrf protection, lottie injection fix, oom guard
ukimsanov Apr 16, 2026
44b9cbd
fix(capture): security fixes, timeout, sub-agent dispatch instructions
ukimsanov Apr 16, 2026
8af3814
fix(capture): catalog before DOM mutation, networkidle2, faster Gemini
ukimsanov Apr 16, 2026
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
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# No environment variables required for basic usage.
# Run `pnpm dev` to start the studio, `npx hyperframes render` to render video.
# Run `bun run dev` to start the studio, `npx hyperframes render` to render video.

# Optional integrations:
# ANTHROPIC_API_KEY= # For AI-assisted composition via MCP
# GEMINI_API_KEY= # AI image captioning during website capture (~$0.001/image, https://aistudio.google.com/apikey)
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ packages/producer/src/services/fontData.generated.ts

# Test artifacts
my-video/
examples/
packages/studio/data/
.desloppify/
.worktrees/

# Playwright MCP browser cache
.playwright-mcp/

# Installed skills (user-specific)
.agents/
.claude/skills/
skills-lock.json

# Skills from other PRs (not managed here)
skills/hyperframes-animation-map/
skills/hyperframes-contrast/

# Capture outputs
captures/
# Legacy test captures at repo root (use captures/ instead)
*-capture/
*-demo/
*-ad/
*-tour/
*-brand/
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ When adding a new CLI command:

## Skills

Composition authoring (not repo development) is guided by skills installed via `npx skills add heygen-com/hyperframes`. See `skills/` for source. Invoke `/hyperframes`, `/hyperframes-cli`, or `/gsap` when authoring compositions.
Composition authoring (not repo development) is guided by skills installed via `npx skills add heygen-com/hyperframes`. See `skills/` for source. Invoke `/hyperframes`, `/hyperframes-cli`, or `/gsap` when authoring compositions. When a user provides a website URL and wants a video, invoke `/website-to-hyperframes` — it runs the full 7-step capture-to-video pipeline.
84 changes: 78 additions & 6 deletions bun.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,27 @@
"mime-types": "^3.0.2",
"open": "^10.0.0",
"postcss": "^8.5.8",
"prettier": "^3.8.1",
"puppeteer-core": "^24.39.1"
},
"devDependencies": {
"@clack/prompts": "^1.1.0",
"@hono/node-server": "^1.0.0",
"@hyperframes/core": "workspace:*",
"@hyperframes/engine": "workspace:*",
"@hyperframes/producer": "workspace:*",
"@types/adm-zip": "^0.5.7",
"@types/mime-types": "^3.0.1",
"@types/node": "^22.0.0",
"adm-zip": "^0.5.16",
"hono": "^4.0.0",
"linkedom": "^0.18.12",
"mime-types": "^3.0.2",
"picocolors": "^1.1.1",
"tsup": "^8.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0",
"vitest": "^3.2.4"
},
"optionalDependencies": {
"@google/genai": "^1.50.0"
},
"engines": {
"node": ">=22"
}
Expand Down
159 changes: 159 additions & 0 deletions packages/cli/src/capture/agentPromptGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* Generate CLAUDE.md (and .cursorrules) for captured website projects.
*
* This file generates a DATA INVENTORY that tells the AI agent what files
* exist and what they contain. The actual workflow lives in the
* /website-to-hyperframes skill — this file points agents there.
*/

import { writeFileSync } from "node:fs";
import { join } from "node:path";
import type { DesignTokens } from "./types.js";
import type { AnimationCatalog } from "./animationCataloger.js";
import type { CatalogedAsset } from "./assetCataloger.js";

export function generateAgentPrompt(
outputDir: string,
url: string,
tokens: DesignTokens,
animations: AnimationCatalog | undefined,
hasScreenshot: boolean,
hasLottie?: boolean,
hasShaders?: boolean,
catalogedAssets?: CatalogedAsset[],
): void {
const prompt = buildPrompt(
url,
tokens,
animations,
hasScreenshot,
hasLottie,
hasShaders,
catalogedAssets,
);
writeFileSync(join(outputDir, "CLAUDE.md"), prompt, "utf-8");
writeFileSync(join(outputDir, ".cursorrules"), prompt, "utf-8");
}

function buildPrompt(
url: string,
tokens: DesignTokens,
animations: AnimationCatalog | undefined,
hasScreenshot: boolean,
hasLottie?: boolean,
hasShaders?: boolean,
catalogedAssets?: CatalogedAsset[],
): string {
const hostname = new URL(url).hostname.replace(/^www\./, "");
const title = tokens.title || hostname;
const cues = detectImplementationCues(tokens, animations);

const colorSummary = tokens.colors.slice(0, 10).join(", ");
const fontSummary = tokens.fonts.join(", ") || "none detected";
const sectionCount = tokens.sections?.length ?? 0;
const headingCount = tokens.headings?.length ?? 0;
const ctaCount = tokens.ctas?.length ?? 0;

const videoUrls = catalogedAssets
? catalogedAssets
.filter((a) => a.type === "Video" && a.url.startsWith("http"))
.map((a) => a.url)
.filter((u, i, arr) => arr.indexOf(u) === i) // deduplicate
: [];

return `# ${title} — Captured Website

Source: ${url}

## How to Create a Video

Invoke the \`/website-to-hyperframes\` skill. It walks you through the full workflow: read data → create DESIGN.md → plan video → build compositions → lint/validate/preview.

If you don't have the skill installed, run: \`npx skills add heygen-com/hyperframes\`

## What's in This Capture

| File | Contents |
|------|----------|
${hasScreenshot ? "| `screenshots/scroll-*.png` | Viewport screenshots covering the full page (1920x1080 each, 30% overlap). **View scroll-000.png FIRST** (hero section), then scan through the rest to understand the full page. |" : ""}
| \`extracted/tokens.json\` | Design tokens: ${tokens.colors.length} colors, ${tokens.fonts.length} fonts, ${headingCount} headings, ${ctaCount} CTAs, ${sectionCount} sections |
| \`extracted/visible-text.txt\` | All visible text content in DOM order — use exact strings, never paraphrase |
| \`extracted/assets-catalog.json\` | Every asset URL (images, fonts, videos, icons) with HTML context |
| \`extracted/animations.json\` | Animation catalog: ${animations?.summary?.webAnimations ?? 0} web animations, ${animations?.summary?.scrollTargets ?? 0} scroll triggers, ${animations?.summary?.canvases ?? 0} canvases |
| \`assets/svgs/\` | Extracted inline SVGs (logos, icons, illustrations) |
| \`assets/\` | Downloaded images and font files — **Read every image file to see what it contains** |
${hasLottie ? "| `extracted/lottie-manifest.json` | Lottie animations found on this site — read this to see what animations are available (name, dimensions, duration). Embed via `lottie.loadAnimation({ path: 'assets/lottie/animation-0.json' })`. Do NOT read the raw JSON files — they are machine data. |" : ""}
${videoUrls.length > 0 ? "| `extracted/video-manifest.json` | Video manifest: every `<video>` element with its URL, surrounding heading/caption context, and a preview screenshot. **Read this + view each preview image** to understand what each video shows before using it. |" : ""}
${hasShaders ? "| `extracted/shaders.json` | Captured WebGL shader source code (GLSL vertex + fragment shaders) |" : ""}
| \`extracted/asset-descriptions.md\` | One-line description of every downloaded asset — read this first |

> **DESIGN.md does not exist yet.** It will be created when you run the \`/website-to-hyperframes\` workflow. Do not write compositions without it.

## Brand Summary

- **Colors**: ${colorSummary || "see tokens.json"}
- **Fonts**: ${fontSummary}
- **Sections**: ${sectionCount} page sections detected
- **Headings**: ${headingCount} headings extracted
- **CTAs**: ${ctaCount} calls-to-action found
${
cues.length > 0
? `
## Source Patterns Detected

${cues.map((c) => `- ${c}`).join("\n")}
`
: ""
}
## Example Prompts

Try asking:

- "Make me a 15-second social ad from this capture"
- "Create a 30-second product tour video"
- "Turn this into a vertical Instagram reel"
- "Build a feature announcement video highlighting the top 3 features"
`;
}

function detectImplementationCues(
tokens: DesignTokens,
animations: AnimationCatalog | undefined,
): string[] {
const cues: string[] = [];

if (Object.keys(tokens.cssVariables).length > 10) {
cues.push(
"CSS custom properties used extensively — preserve design tokens for colors, spacing, and typography.",
);
}

if (tokens.fonts.length > 0) {
cues.push(
`Typography: ${tokens.fonts.join(", ")}. Match these exact font families and weights.`,
);
}

if (animations?.summary) {
if (animations.summary.scrollTargets > 20) {
cues.push(`${animations.summary.scrollTargets} scroll-triggered animations detected.`);
}
if (animations.summary.webAnimations > 5) {
cues.push(`${animations.summary.webAnimations} active Web Animations detected.`);
}
if (animations.summary.canvases > 0) {
cues.push(`${animations.summary.canvases} Canvas/WebGL elements detected.`);
}
}

const hasMarquee = animations?.cssDeclarations?.some(
(d) =>
d.animation?.name?.toLowerCase().includes("marquee") ||
d.animation?.name?.toLowerCase().includes("scroll"),
);
if (hasMarquee) {
cues.push("Marquee/ticker animation present — preserve continuous scrolling behavior.");
}

return cues;
}
Loading
Loading