From 1c90360066dceb68b8d91ef81d07411926c4a608 Mon Sep 17 00:00:00 2001 From: ularkim Date: Tue, 7 Apr 2026 17:22:23 -0400 Subject: [PATCH 01/36] feat(cli): add website capture with AI-powered DESIGN.md generation Adds `hyperframes capture ` command that extracts a complete design system from any website, producing AI-agent-ready output: - Full-page screenshot (lazy-load aware, nav at top) - AI-generated DESIGN.md via Claude API (colors, typography, elevation, components, do's/don'ts) with programmatic asset catalog (136+ assets with HTML context annotations like img[src], css url(), link[rel=preload]) - CSS-purged compositions (87% size reduction via PurgeCSS) - HTML-prettified compositions (one-tag-per-line for AI readability) - CLAUDE.md + .cursorrules auto-generated for AI agent instructions - Asset deduplication (srcset variants) and tracking pixel filtering --- .../research/aura-build-and-design-systems.md | 252 ++ docs/research/aura-prompt-comparison.md | 127 + package.json | 3 + packages/cli/package.json | 5 +- packages/cli/pnpm-lock.yaml | 1244 +++++++++ .../cli/src/capture/_test_notion_compare.ts | 147 + .../cli/src/capture/_test_notion_links.ts | 28 + .../cli/src/capture/agentPromptGenerator.ts | 182 ++ .../cli/src/capture/animationCataloger.ts | 232 ++ packages/cli/src/capture/assetCataloger.ts | 241 ++ packages/cli/src/capture/assetDownloader.ts | 151 ++ packages/cli/src/capture/briefGenerator.ts | 394 +++ packages/cli/src/capture/cssPurger.ts | 125 + packages/cli/src/capture/designMdGenerator.ts | 393 +++ packages/cli/src/capture/htmlExtractor.ts | 267 ++ packages/cli/src/capture/index.ts | 500 ++++ packages/cli/src/capture/screenshotCapture.ts | 154 ++ .../src/capture/splitter/compositionGen.ts | 150 + packages/cli/src/capture/splitter/index.ts | 61 + .../cli/src/capture/splitter/sectionParser.ts | 272 ++ packages/cli/src/capture/tokenExtractor.ts | 136 + packages/cli/src/capture/types.ts | 163 ++ packages/cli/src/capture/verify/index.ts | 308 +++ packages/cli/src/cli.ts | 1 + packages/cli/src/commands/capture.ts | 179 ++ pnpm-lock.yaml | 2406 +++++++++++++++++ skills/website-to-hyperframes/SKILL.md | 154 ++ .../references/animation-recreation.md | 145 + .../references/section-refinement.md | 100 + .../references/tts-integration.md | 228 ++ .../references/video-recipes.md | 415 +++ 31 files changed, 9162 insertions(+), 1 deletion(-) create mode 100644 docs/research/aura-build-and-design-systems.md create mode 100644 docs/research/aura-prompt-comparison.md create mode 100644 packages/cli/pnpm-lock.yaml create mode 100644 packages/cli/src/capture/_test_notion_compare.ts create mode 100644 packages/cli/src/capture/_test_notion_links.ts create mode 100644 packages/cli/src/capture/agentPromptGenerator.ts create mode 100644 packages/cli/src/capture/animationCataloger.ts create mode 100644 packages/cli/src/capture/assetCataloger.ts create mode 100644 packages/cli/src/capture/assetDownloader.ts create mode 100644 packages/cli/src/capture/briefGenerator.ts create mode 100644 packages/cli/src/capture/cssPurger.ts create mode 100644 packages/cli/src/capture/designMdGenerator.ts create mode 100644 packages/cli/src/capture/htmlExtractor.ts create mode 100644 packages/cli/src/capture/index.ts create mode 100644 packages/cli/src/capture/screenshotCapture.ts create mode 100644 packages/cli/src/capture/splitter/compositionGen.ts create mode 100644 packages/cli/src/capture/splitter/index.ts create mode 100644 packages/cli/src/capture/splitter/sectionParser.ts create mode 100644 packages/cli/src/capture/tokenExtractor.ts create mode 100644 packages/cli/src/capture/types.ts create mode 100644 packages/cli/src/capture/verify/index.ts create mode 100644 packages/cli/src/commands/capture.ts create mode 100644 pnpm-lock.yaml create mode 100644 skills/website-to-hyperframes/SKILL.md create mode 100644 skills/website-to-hyperframes/references/animation-recreation.md create mode 100644 skills/website-to-hyperframes/references/section-refinement.md create mode 100644 skills/website-to-hyperframes/references/tts-integration.md create mode 100644 skills/website-to-hyperframes/references/video-recipes.md diff --git a/docs/research/aura-build-and-design-systems.md b/docs/research/aura-build-and-design-systems.md new file mode 100644 index 00000000..2c57b6d1 --- /dev/null +++ b/docs/research/aura-build-and-design-systems.md @@ -0,0 +1,252 @@ +# Research: Aura.build, DESIGN.md Systems, and HyperFrames Integration + +> Compiled 2026-04-07. Source: 19 Aura.build pages, web-shader-extractor repo, HyperFrames codebase analysis. + +--- + +## 1. Aura.build — What It Is and How It Works + +**Category**: AI website/landing page builder (closed-source SaaS) +**Founder**: Meng To (Design+Code creator) +**Users**: 65,000+ | **Trustpilot**: 4.0/5 +**Output**: HTML + Tailwind CSS + vanilla JS, Figma files, React components +**Models**: Gemini 3.1 Pro (best for animations), Claude 4 (precise instructions), GPT-5.1 (structured UI) + +### Core Philosophy +"A prompt alone is not enough. You also need examples, templates, and assets." — Meng To + +### The Prompt Builder (Flagship Feature) +Visual no-code prompt construction with 6 categories: +1. **Layout Types** — Hero, Features, Onboarding, Docs, Pricing, Dashboard, etc. +2. **Layout Patterns** — Card, List, Bento, Table, Sidebar, Carousel, etc. +3. **Framing** — Full Screen, Card, Browser, Mac App, Clay Web, iPhone, iPad +4. **Visual Styles** — Flat, Outline, Minimalist, Glass, iOS, Material +5. **Typography** — Family (Sans/Serif/Mono/Condensed/Handwritten), heading/body fonts, sizes, weights, spacing +6. **Animation** — Type (Fade/Slide/Scale/Rotate/Blur/3D/Pulse/Bounce/Morph/Skew/Color/Clip), Scene (all at once/sequence/word-by-word/letter-by-letter), Duration, Timing (Linear/Ease/Spring/Bounce), Iterations, Direction + +Plus: Theme (Light/Dark), Accent Color (17 options), Background Color (17 options), Border Color, Shadow (None through XXL + Beautiful/Bevel/3D/Inner) + +**Generated prompts** include pre-filled suggestions like: +- "Create a landing page for {your-app}, a {app-type} app that {feature} in the style of @hero" +- "Animate fade in, slide in, blur in, element by element" +- "Create 'Masked Staggered Word Reveal' animation on scroll using GSAP ScrollTrigger" +- "Add a subtle flashlight effect on hover/mouse position to background and border of cards" + +### @ Context System +The `@` symbol feeds up to 100,000 characters (~2,000 lines of code) as context. Users can reference templates, components, code snippets, and previous iterations. This is what makes AI output consistent — "constrained AI produces more consistent output than unconstrained AI." + +### Image-to-HTML +Upload any screenshot/mockup/wireframe → AI analyzes structure → generates responsive HTML + Tailwind → edit and refine → export. Works with Midjourney outputs, Figma mockups, and arbitrary screenshots. + +### Key Insight: Screenshot + DESIGN.md = Best Results +When Aura has both a full-page screenshot AND design system data, output quality is dramatically better than either alone. The user demonstrated this with Notion — the version with screenshot was "SO FAST, and so exactly damn right." + +### Aura Skills System +Hosts AI Agent Skills directory: Skill Creator, Web Interface Guidelines, GSAP Web Animation Skill, Anime.js v4 Skill. Follows SKILL.md format compatible with Claude Code, Cursor, Gemini CLI. + +### Pricing +| Plan | Cost | Prompts/Month | +|------|------|---------------| +| Free | $0 | 10 | +| Pro | $20/mo | 120 | +| Max | $40/mo | 240 | +| Ultra | $100/mo | 560 | + +--- + +## 2. DESIGN.md — The Format + +### Origin +Popularized by Google Stitch. VoltAgent/awesome-design-md curates 58+ brand examples (Apple, Stripe, Figma, Linear, Notion, SpaceX). + +### Standard Structure (9 sections) +1. **Overview** — Visual identity, tone, design philosophy +2. **Colors** — Semantic roles (surface, text, accent), hex values with usage context +3. **Typography** — Font families, hierarchy tables, weight/size/tracking rules +4. **Elevation & Depth** — Shadow systems, glassmorphism, border patterns +5. **Components** — Buttons, cards, navigation, inputs across states +6. **Layout** — Grid, spacing scale, whitespace philosophy +7. **Do's and Don'ts** — Design guardrails and anti-patterns +8. **Assets** — Images, fonts, icons with URLs and descriptions +9. **Responsive Behavior** — Breakpoints, touch targets, collapse strategies + +### Why It Works +"Markdown is the format LLMs read best." The DESIGN.md acts as constraints, not suggestions. AI treats design rules as hard requirements, producing consistent output across multiple generations. + +### Examples from User's Session +- **Notion DESIGN.md** (16.4 KB) — "Digital Paper" aesthetic, NotionInter/LyonText/iAWriterMonoS fonts, border-reliance elevation, bento card system, logo marquee, interactive calculator component +- **Soulscape DESIGN.md** (7.7 KB) — "Void" #020204 background, Cormorant Garamond serif + Geist Mono, glassmorphism, cinematic accordion, grain overlays, HUD explorer navigation + +--- + +## 3. Web Shader Extractor (lixiaolin94/skills) + +**Repository**: github.com/lixiaolin94/skills (MIT, 161 stars) +**What it does**: 7-phase pipeline that extracts WebGL/Canvas/Shader effects from websites, deobfuscates minified JS, and ports to standalone native JavaScript projects. + +### Phases +0. Auto-installs Node.js + Playwright + Chromium +1. Parallel source fetching (rendered DOM, canvas metadata, network requests, screenshots) +2. Tech stack identification (Three.js, Babylon.js, PixiJS, Raw WebGL, 2D Canvas) +3. Configuration extraction (REST APIs, Nuxt payloads, `__NEXT_DATA__`, bundle defaults) +4. Shader code extraction from 1MB+ minified bundles +5. Porting to standalone project (raw WebGL2 or original framework via CDN) +6. Simplification proposal +7. Optional extraction report + +### HyperFrames Integration Potential +- **Standalone JS output (zero deps)** embeds directly into HTML compositions +- Could add WebGL detection to `hyperframes capture` +- Extracted shaders become reusable backgrounds/transitions +- Challenge: adapting `requestAnimationFrame` loops to HyperFrames' deterministic frame adapter system + +--- + +## 4. HyperFrames Current State — What We Have + +### visual-style.md (minimal, auto-generated during capture) +```yaml +--- +name: "Page Title" +source_url: "URL" +colors: + - hex: "#..." + role: "color-0" +typography: + fonts: ["Font1"] +mood: + keywords: ["extracted", "website-captured"] +--- +``` + +### house-style.md (rich, in skills directory) +- 9 palette categories (Bold/Energetic, Warm/Editorial, Dark/Premium, Clean/Corporate, etc.) +- Easing vocabulary (power1-4, back.out, elastic.out, circ.out, expo.out) +- Timing rules (0.3-0.6s for most moves, exits 2x faster) +- Entrance patterns (position, scale, rotation, clip-path, blur, 3D transforms) +- 11 "anti-defaults" (no Inter/Roboto, no mid-gray, no blue accent, no uniform spacing) +- Typography rules (weight contrast, tracking, case) +- Scene pacing (Build 0-30%, Breathe 30-70%, Resolve 70-100%) + +### data-in-motion.md +- Visual continuity rules for sequential stats +- "Numbers need visual weight" (pair every metric with visual element) +- Avoid web patterns (no pie charts, no multi-axis, no 6-panel dashboards) + +### patterns.md +- Picture-in-Picture, Title Card, Slide Show templates with code + +### Token Extraction (tokenExtractor.ts) +- Colors (12), fonts, headings (h1-h4 with size/weight/color), CTAs, sections (type/heading/bg), CSS variables, logos, images + +### Animation Cataloging (animationCataloger.ts) +- Web Animations API keyframes, CSS transition/animation declarations, IntersectionObserver scroll targets, CDP animation events, Canvas elements + +### Gap: What's Missing +1. No color role inference (just raw hex values) +2. No typography hierarchy table (just font family names) +3. No component documentation (nav, cards, buttons not described) +4. No motion language (animations cataloged but not summarized in human terms) +5. No do's/don'ts (house-style has them but they're not in capture output) +6. No full-page screenshot (only per-section viewport shots) +7. No prompt templates (brief suggests one generic prompt) +8. No HyperFrames-specific recommendations (transitions, composition templates) +9. No shader/WebGL detection + +--- + +## 5. Mapping Aura Concepts to HyperFrames + +| Aura Concept | HyperFrames Equivalent | Status | +|---|---|---| +| DESIGN.md | visual-style.md | Exists but minimal — needs enrichment | +| Prompt Builder UI | CLI + prompt-catalog.md | Doesn't exist — create prompt catalog | +| @ Context (100K chars) | Purged compositions (32-276KB, readable) | Built today! CSS purge + prettify | +| Template library | Captured section compositions | Built today! Website capture pipeline | +| Screenshot input | Full-page screenshot | Missing — add to capture | +| Image-to-HTML | website-to-hyperframes capture | Exists but different goal (video, not web) | +| Animation snippets | house-style.md + GSAP skills | Rich but not in capture output | +| Multi-model support | Claude Code (any model) | Inherited from agent | +| Code Mode editing | Edit purged compositions | Built today! AI-readable files | +| Component remix | Remix demo (scenes 1-4) | Demonstrated today | + +--- + +## 6. Future Directions + +### Chrome Extension +Aura has a web app. HyperFrames could have a Chrome extension that: +- One-click captures the current page +- Generates DESIGN.md + full-page screenshot +- Opens HyperFrames Studio with captured components +- Similar to Aura's "Adapt from HTML" but for video + +### Visual Prompt Builder (Web UI) +A Prompt Builder for video compositions could live in HyperFrames Studio: +- Pick scene types (Hero, Features, Stats, Testimonial, CTA) +- Select transitions between scenes +- Choose animation style (Energetic, Calm, Corporate, Cinematic) +- Set duration and pacing +- Generate a composition from the selections + +### Shader Integration +Combine web-shader-extractor with capture: +- Detect WebGL canvases during capture +- Extract shader code into standalone files +- Embed as animated backgrounds in compositions +- Challenge: deterministic rendering of shader time uniforms + +### DESIGN.md Ecosystem Play +"Take your DESIGN.md and now all your videos are on-brand with HyperFrames": +- Accept any DESIGN.md (from Aura, Google Stitch, or hand-written) +- Extend with HyperFrames-specific sections (transitions, GSAP presets, composition templates) +- Use as context for AI-generated compositions +- Portable: same file works in Aura (web), Cursor (code), HyperFrames (video) + +--- + +## 7. Aura Prompt Builder — Complete Category Reference + +### Layout Types (Web) +Hero, Features, Onboarding, Docs, Updates, Portfolio, Pricing, Gallery, Dashboard, Login, Email, Testimonials, Payment, Footer, FAQ, Explore, Settings, About, Blog, Video, Landing Page + +### Layout Types (Mobile) +Hero, Features, Onboarding, Dashboard, Login, Settings, Profile, Gallery, Explore, FAQ + +### Layout Configuration +Card, List, 2-2 Square, Table, Sidebar Left, Sidebar Right, 1-1 Split, 1-1 Vertical, 1/3 2/3 Bento, 2/3 1/3 Bento, 1x4 Bento, Feature Bento, Featured Right, Featured Top, 1/4 2/4 1/4 Bento, 2/4 1/4 1/4 Bento, 2-1 Split, 1-2 Split, 1-1-1 Equal, Header Focus, 3-3 Grid, Carousel, Modal, Alert + +### Framing +Full Screen, Card, Browser, Mac App, Clay Web (desktop) +iPhone, Android, iPad, Clay Mobile (mobile) + +### Visual Styles +Flat, Outline, Minimalist, Glass, iOS, Material + +### Animation Types +Fade, Slide, Scale, Rotate, Blur, 3D, Pulse, Shake, Bounce, Morph, Skew, Color, Hue, Perspective, Clip + +### Animation Scene +All at once, Sequence, Word by word, Letter by letter + +### Animation Timing +Linear, Ease, Ease In, Ease Out, Ease In Out, Spring, Bounce + +### Shadow Options +None, Small, Medium, Large, Extra Large, XXL, Beautiful sm/md/lg, Light Blue sm/md/lg, Bevel, 3D, Inner Shadow + +### Typeface Families +Sans, Serif, Monospace, Condensed, Expanded, Rounded, Handwritten + +### Ready-Made Prompts (partial list) +- "Create a landing page for {your-app}, a {app-type} app that {feature} in the style of @hero" +- "Hero section @hero. Add sections @feature, @testimonial, @pricing and add @footer" +- "Use @border-gradient for buttons and cards" +- "Add a beam animation to the vertical lines" +- "Add noodles that connect elements and beam animation" +- "Add a subtle flashlight effect on hover/mouse position" +- "Create 'Masked Staggered Word Reveal' animation on scroll using GSAP ScrollTrigger" +- "Add parallax scrolling to the background" +- "Apply alpha masking using mask-image: linear-gradient" +- "Animate fade in, slide in, blur in, element by element" +- "Animate when in view observed, fade in, slide in, blur in, element by element" diff --git a/docs/research/aura-prompt-comparison.md b/docs/research/aura-prompt-comparison.md new file mode 100644 index 00000000..e082e40f --- /dev/null +++ b/docs/research/aura-prompt-comparison.md @@ -0,0 +1,127 @@ +# Aura Prompt Comparison: With Screenshot vs Without + +## Soulscape (NO screenshot — timed out) + +``` +Recreate the imported webpage as faithfully as possible using the captured +page structure from the imported site as the structural reference and the +attached DESIGN.md as the design-system and asset reference. + +Screenshot capture timed out during import, so no screenshot reference is +attached. Use the captured page structure from the imported site to preserve +exact structure, section order, and supporting source details. Use the +DESIGN.md to preserve exact design-system choices and asset references. + +Rebuild all major sections supported by the source, preserving the original +page flow instead of collapsing it into a smaller subset. Match the +composition, section order, spacing, imagery placement, and responsive +structure as closely as practical from the imported source. + +Detect and preserve any motion patterns evidenced by the captured import +structure, including scroll reveals, marquee effects, hover states, masked +text reveals, parallax, or ambient background movement. + +Avoid long inline SVG markup unless there is no practical alternative. Let +the captured page structure from the imported site drive supporting structure +and let the DESIGN.md drive design-system and asset decisions instead of +adding extra interpretation. + +Rebuild it as clean, maintainable HTML, CSS, and JavaScript instead of +converting it into React. + +Attached Files: DESIGN.md (7.7 KB) + +This import is in EXACTLY mode. Screenshot capture timed out, so treat the +captured page structure from the imported site as the required structural +reference and the attached DESIGN.md as the design-system and asset +reference. Match the original texts, names, numbers, and brand references +unless something is clearly broken or inaccessible. + +Preserve motion cues from the captured import structure when present, use +the attached DESIGN.md as the supporting design-system and asset reference, +and do not replace the imported design with Aura defaults or a new house +style. + +Detected source implementation cues that must be preserved: +- Preserve the source CSS custom properties and theme tokens +- Typography is explicitly defined in the source. Match the original font + families, weights, and headline/body hierarchy +- Preserve the detected runtime motion stack (GSAP, ScrollTrigger) and + reproduce comparable scroll reveals, text animations, hover states +- Marquee-style motion is present in the source. Keep that behavior +- Three.js evidence is present in the imported source. Preserve the 3D + scene or the closest faithful equivalent +``` + +## Notion (WITH screenshot) + +``` +Recreate the attached webpage EXACTLY like the screenshot as an HTML + +JavaScript implementation using Tailwind CSS. Treat the screenshot as the +primary visual reference. Use the captured page structure from the imported +site as the structural reference and the attached DESIGN.md as a secondary +typography and asset inventory reference. + +Use DESIGN.md only to preserve font families, font weights, typographic +tone, and asset references. If DESIGN.md conflicts with the screenshot on +colors, surfaces, layout, or composition, follow the screenshot. + +Avoid long inline SVG markup unless there is no practical alternative. Let +the screenshot drive styling decisions instead of adding extra interpretation. + +Attached Files: DESIGN.md (16.4 KB) + +This import is in EXACTLY mode. Treat the screenshot as the primary visual +reference, the captured page structure from the imported site as the +structural source of truth, and the attached DESIGN.md as a secondary +typography and asset inventory reference. + +If the screenshot and captured structure conflict, follow the screenshot +for layout, surfaces, typography, and motion. Use DESIGN.md only to +preserve font families, font weights, typographic tone, and asset +references. If DESIGN.md conflicts with the screenshot on colors, surfaces, +layout, or composition, follow the screenshot. + +Match the original texts, names, numbers, and brand references unless +something is clearly broken or inaccessible. + +Preserve motion cues from the screenshot when present, use the captured +import structure as supporting structure, use the attached DESIGN.md as a +secondary typography and asset inventory reference. + +Do not replace the imported design with Aura defaults or a new house style. + +Detected source implementation cues that must be preserved: +- Preserve the source CSS custom properties and theme tokens +- Typography is explicitly defined in the source. Match the original font + families, weights, and headline/body hierarchy +- Marquee-style motion is present in the source. Keep that behavior +``` + +## Key Differences + +| Aspect | NO screenshot | WITH screenshot | +|--------|--------------|-----------------| +| **Primary reference** | "captured page structure" (HTML) | "screenshot" (image) | +| **DESIGN.md role** | "design-system and asset reference" (PRIMARY) | "secondary typography and asset inventory reference" (SECONDARY) | +| **Conflict resolution** | DESIGN.md drives design decisions | "If DESIGN.md conflicts with the screenshot, follow the screenshot" | +| **Output format** | "clean, maintainable HTML, CSS, and JavaScript" | "HTML + JavaScript implementation using Tailwind CSS" | +| **Motion detection** | Lists specific: GSAP, ScrollTrigger, Three.js | Only: marquee, CSS custom properties | +| **Structure emphasis** | "Rebuild all major sections...preserving the original page flow" | Less emphasis on structure completeness | + +## What's SAME in Both + +1. "EXACTLY mode" — match original texts, names, numbers, brand references +2. "Do not replace the imported design with Aura defaults or a new house style" +3. "Avoid long inline SVG markup" +4. "Detected source implementation cues" section (dynamic, varies per site) +5. "Preserve motion cues" +6. DESIGN.md always attached +7. "Captured page structure" always referenced + +## What's Dynamic (Varies Per Site) + +The "Detected source implementation cues" section changes based on what Aura found in the HTML: +- Soulscape: GSAP, ScrollTrigger, Three.js, marquee (JS-heavy site) +- Notion: CSS custom properties, marquee only (CSS-heavy site) +- This section is programmatically generated based on source code analysis diff --git a/package.json b/package.json index 21c9beab..efe87adf 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,9 @@ "generate:previews": "tsx scripts/generate-template-previews.ts", "prepare": "test -d .git && lefthook install || true" }, + "dependencies": { + "purgecss": "^8.0.0" + }, "devDependencies": { "@commitlint/cli": "^20.5.0", "@commitlint/config-conventional": "^20.5.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index d9cd6429..620ee150 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -25,6 +25,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@anthropic-ai/sdk": "^0.82.0", "@hono/node-server": "^1.8.0", "@puppeteer/browsers": "^2.13.0", "adm-zip": "^0.5.16", @@ -36,7 +37,9 @@ "mime-types": "^3.0.2", "open": "^10.0.0", "postcss": "^8.5.8", - "puppeteer-core": "^24.39.1" + "prettier": "^3.8.1", + "puppeteer-core": "^24.39.1", + "purgecss": "^8.0.0" }, "devDependencies": { "@clack/prompts": "^1.1.0", diff --git a/packages/cli/pnpm-lock.yaml b/packages/cli/pnpm-lock.yaml new file mode 100644 index 00000000..24aa1397 --- /dev/null +++ b/packages/cli/pnpm-lock.yaml @@ -0,0 +1,1244 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@anthropic-ai/sdk': + specifier: ^0.82.0 + version: 0.82.0 + compare-versions: + specifier: ^6.1.1 + version: 6.1.1 + giget: + specifier: ^3.2.0 + version: 3.2.0 + postcss: + specifier: ^8.5.8 + version: 8.5.8 + prettier: + specifier: ^3.8.1 + version: 3.8.1 + purgecss: + specifier: ^8.0.0 + version: 8.0.0 + devDependencies: + '@hyperframes/core': + specifier: workspace:* + version: link:../core + '@hyperframes/engine': + specifier: workspace:* + version: link:../engine + '@hyperframes/producer': + specifier: workspace:* + version: link:../producer + vitest: + specifier: ^3.2.4 + version: 3.2.4 + +packages: + + '@anthropic-ai/sdk@0.82.0': + resolution: {integrity: sha512-xdHTjL1GlUlDugHq/I47qdOKp/ROPvuHl7ROJCgUQigbvPu7asf9KcAcU1EqdrP2LuVhEKaTs7Z+ShpZDRzHdQ==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + giget@3.2.0: + resolution: {integrity: sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + purgecss@8.0.0: + resolution: {integrity: sha512-QFJyps9y5oHeXnNA3Ql1EaAqWBivNwQn19Pw1lt9RxfB+4e+bIyqCyuombk79D6Fxe+lPXggVfI1WtRGEBwgbQ==} + hasBin: true + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@anthropic-ai/sdk@0.82.0': + dependencies: + json-schema-to-ts: 3.1.1 + + '@babel/runtime@7.29.2': {} + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.3.1)': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1 + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + cac@6.7.14: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + commander@12.1.0: {} + + compare-versions@6.1.1: {} + + cssesc@3.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + giget@3.2.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + js-tokens@9.0.1: {} + + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.2 + ts-algebra: 2.0.0 + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.1: {} + + purgecss@8.0.0: + dependencies: + commander: 12.1.0 + fast-glob: 3.3.3 + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + + queue-microtask@1.2.3: {} + + reusify@1.1.0: {} + + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-algebra@2.0.0: {} + + util-deprecate@1.0.2: {} + + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - yaml + + vite@7.3.1: + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.1 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.4: + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.1) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.1 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/packages/cli/src/capture/_test_notion_compare.ts b/packages/cli/src/capture/_test_notion_compare.ts new file mode 100644 index 00000000..c75f064d --- /dev/null +++ b/packages/cli/src/capture/_test_notion_compare.ts @@ -0,0 +1,147 @@ +/** + * Compare real Notion page vs our capture — find what's missing. + * Run: npx tsx packages/cli/src/capture/_test_notion_compare.ts + */ + +import puppeteer from "puppeteer-core"; +import { readFileSync } from "node:fs"; + +async function main() { + const { ensureBrowser } = await import("../browser/manager.js"); + const browser = await ensureBrowser(); + const b = await puppeteer.launch({ + headless: true, + executablePath: browser.executablePath, + args: ["--no-sandbox"], + }); + const page = await b.newPage(); + await page.setViewport({ width: 1920, height: 1080 }); + + console.log("Loading notion.com..."); + await page.goto("https://www.notion.com", { + waitUntil: "networkidle0", + timeout: 30000, + }); + await new Promise((r) => setTimeout(r, 5000)); + + // Scroll through to trigger lazy loading + await page.evaluate(`(async () => { + var h = document.body.scrollHeight; + for (var y = 0; y < h + 1000; y += 500) { + window.scrollTo(0, y); + await new Promise(function(r) { setTimeout(r, 300); }); + } + window.scrollTo(0, 0); + await new Promise(function(r) { setTimeout(r, 1000); }); + })()`); + + // Get the REAL page structure + const realPage = await page.evaluate(`(() => { + var sections = []; + + // Find all major sections + document.querySelectorAll("section, main > div, [class*='section']").forEach(function(el) { + var rect = el.getBoundingClientRect(); + if (rect.height < 100) return; + var h = el.querySelector("h1, h2, h3"); + var heading = h ? h.textContent.trim().slice(0, 60) : ""; + if (!heading) return; + + // Count images in this section + var imgs = el.querySelectorAll("img"); + var visibleImgs = Array.from(imgs).filter(function(img) { + return img.offsetWidth > 0 && img.naturalWidth > 0; + }); + var brokenImgs = Array.from(imgs).filter(function(img) { + return img.offsetWidth > 0 && img.naturalWidth === 0; + }); + + // Count SVGs + var svgs = el.querySelectorAll("svg"); + + // Count videos + var videos = el.querySelectorAll("video"); + + // Count iframes + var iframes = el.querySelectorAll("iframe"); + + // Check for background images + var bgImgs = 0; + el.querySelectorAll("*").forEach(function(child) { + var bg = getComputedStyle(child).backgroundImage; + if (bg && bg !== "none" && !bg.includes("gradient")) bgImgs++; + }); + + sections.push({ + heading: heading, + y: Math.round(rect.top + window.scrollY), + height: Math.round(rect.height), + totalImgs: imgs.length, + visibleImgs: visibleImgs.length, + brokenImgs: brokenImgs.length, + svgCount: svgs.length, + videoCount: videos.length, + iframeCount: iframes.length, + bgImgCount: bgImgs, + // Sample image srcs + imgSrcs: Array.from(visibleImgs).slice(0, 3).map(function(img) { + return { src: img.src.slice(0, 80), w: img.naturalWidth, h: img.naturalHeight }; + }), + }); + }); + + // Deduplicate by heading + var seen = {}; + sections = sections.filter(function(s) { + if (seen[s.heading]) return false; + seen[s.heading] = true; + return true; + }); + + return { + totalSections: sections.length, + pageHeight: document.body.scrollHeight, + totalImages: document.querySelectorAll("img").length, + totalSvgs: document.querySelectorAll("svg").length, + totalVideos: document.querySelectorAll("video").length, + sections: sections, + }; + })()`); + + console.log("\n=== REAL NOTION PAGE ==="); + console.log(`Page height: ${realPage.pageHeight}px`); + console.log(`Total images: ${realPage.totalImages}`); + console.log(`Total SVGs: ${realPage.totalSvgs}`); + console.log(`Total videos: ${realPage.totalVideos}`); + console.log(`\nSections (${realPage.totalSections}):`); + + for (const s of realPage.sections) { + console.log(`\n "${s.heading}"`); + console.log(` y=${s.y} h=${s.height}`); + console.log(` imgs: ${s.visibleImgs} visible, ${s.brokenImgs} broken, ${s.totalImgs} total`); + console.log( + ` svgs: ${s.svgCount}, videos: ${s.videoCount}, iframes: ${s.iframeCount}, bgImgs: ${s.bgImgCount}`, + ); + if (s.imgSrcs.length > 0) { + for (const img of s.imgSrcs) { + console.log(` → ${img.src} (${img.w}x${img.h})`); + } + } + } + + // Now compare with our capture + console.log("\n\n=== OUR CAPTURE ==="); + try { + const tokens = JSON.parse(readFileSync("/tmp/notion-capture5/extracted/tokens.json", "utf-8")); + console.log(`Captured sections: ${tokens.sections.length}`); + for (const s of tokens.sections) { + console.log(` ${s.type}: "${s.heading}" y=${s.y}`); + } + } catch { + console.log("Could not read capture data"); + } + + await b.close(); +} + +main().catch(console.error); diff --git a/packages/cli/src/capture/_test_notion_links.ts b/packages/cli/src/capture/_test_notion_links.ts new file mode 100644 index 00000000..c31c1910 --- /dev/null +++ b/packages/cli/src/capture/_test_notion_links.ts @@ -0,0 +1,28 @@ +import puppeteer from "puppeteer-core"; +async function main() { + const { ensureBrowser } = await import("../browser/manager.js"); + const browser = await ensureBrowser(); + const b = await puppeteer.launch({ + headless: true, + executablePath: browser.executablePath, + args: ["--no-sandbox"], + }); + const page = await b.newPage(); + await page.setViewport({ width: 1920, height: 1080 }); + await page.goto("https://notion.so", { waitUntil: "networkidle0", timeout: 30000 }); + await new Promise((r) => setTimeout(r, 5000)); + + const info = await page.evaluate(`(() => { + var headLinks = Array.from(document.head.querySelectorAll('link[rel="stylesheet"][href]')); + var allLinks = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')); + return { + headLinkCount: headLinks.length, + headLinkHrefs: headLinks.map(function(l) { return l.href.slice(0, 80); }), + allLinkCount: allLinks.length, + }; + })()`); + + console.log(JSON.stringify(info, null, 2)); + await b.close(); +} +main().catch(console.error); diff --git a/packages/cli/src/capture/agentPromptGenerator.ts b/packages/cli/src/capture/agentPromptGenerator.ts new file mode 100644 index 00000000..c4e136f2 --- /dev/null +++ b/packages/cli/src/capture/agentPromptGenerator.ts @@ -0,0 +1,182 @@ +/** + * Generate CLAUDE.md (and .cursorrules) for captured website projects. + * + * This is the equivalent of Aura.build's auto-generated prompt that tells + * the AI agent how to use the DESIGN.md and screenshot to create output. + * + * The file is placed at the project root so AI agents read it automatically: + * - Claude Code reads CLAUDE.md + * - Cursor reads .cursorrules + * - Other agents typically read README.md or project-root markdown + */ + +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { DesignTokens } from "./types.js"; +import type { AnimationCatalog } from "./animationCataloger.js"; + +export function generateAgentPrompt( + outputDir: string, + url: string, + tokens: DesignTokens, + animations: AnimationCatalog | undefined, + hasScreenshot: boolean, + hasDesignMd: boolean, +): void { + const prompt = buildPrompt(url, tokens, animations, hasScreenshot, hasDesignMd); + + // Write CLAUDE.md (Claude Code reads this automatically) + writeFileSync(join(outputDir, "CLAUDE.md"), prompt, "utf-8"); + + // Write .cursorrules (Cursor reads this automatically) + writeFileSync(join(outputDir, ".cursorrules"), prompt, "utf-8"); +} + +function buildPrompt( + url: string, + tokens: DesignTokens, + animations: AnimationCatalog | undefined, + hasScreenshot: boolean, + hasDesignMd: boolean, +): string { + const hostname = new URL(url).hostname.replace(/^www\./, ""); + + // Detect implementation cues from the source + const cues = detectImplementationCues(tokens, animations); + + const _screenshotRef = hasScreenshot ? "screenshots/full-page.png" : null; + + // Build the main instruction block + let md = `# ${tokens.title || hostname} — Captured Website + +This project was captured from [${url}](${url}) using \`hyperframes capture\`. + +## What's in this project + +${hasDesignMd ? `- **DESIGN.md** — Complete design system (colors, typography, elevation, components, do's/don'ts, and every asset URL with its HTML context). **READ THIS FIRST.**` : ""} +${hasScreenshot ? `- **screenshots/full-page.png** — Full-page screenshot of the live website. **READ THIS IMAGE** as your primary visual reference.` : ""} +- **compositions/** — Extracted page sections as editable HyperFrames HTML compositions (CSS-purged, prettified, AI-readable). +- **assets/** — Downloaded fonts, logos, images, and favicon. +- **extracted/** — Raw HTML, CSS, and animation data from the source page. + +## How to use this capture + +`; + + if (hasScreenshot && hasDesignMd) { + // BOTH screenshot and DESIGN.md available (best case — like Aura's Notion capture) + md += `Treat the **full-page screenshot** as the primary visual reference. Use the **DESIGN.md** as the design-system and asset inventory reference. Use the **compositions/** as ready-made building blocks that can be used as-is or remixed. + +If DESIGN.md conflicts with the screenshot on colors, surfaces, layout, or composition, **follow the screenshot**. Use DESIGN.md to preserve exact font families, font weights, typographic tone, asset URLs, and design tokens. + +`; + } else if (hasDesignMd) { + // Only DESIGN.md (screenshot failed — like Aura's Soulscape capture) + md += `Screenshot capture was not available. Use the **DESIGN.md** as both the design-system reference and the structural guide. Use the **compositions/** as the primary structural reference for the page layout and content. + +Use the DESIGN.md to preserve exact design-system choices, asset references, colors, typography, and component patterns. Do not invent new design patterns — follow what's documented. + +`; + } else { + md += `Use the **compositions/** as the primary reference for design and structure. Reference **assets/** for fonts, logos, and images. + +`; + } + + // Common instructions + md += `Match the original texts, names, numbers, and brand references from the source site. Do not replace the captured design with generic defaults or a different house style. + +`; + + // Detected implementation cues + if (cues.length > 0) { + md += `## Detected source implementation cues + +These patterns were detected in the source and should be preserved: + +`; + for (const cue of cues) { + md += `- ${cue}\n`; + } + md += "\n"; + } + + // HyperFrames-specific instructions + md += `## Creating video compositions + +This capture is designed for use with **HyperFrames** — an HTML-to-video framework. When creating video compositions: + +1. Use \`/hyperframes-compose\` skill before writing any composition HTML +2. Reference the DESIGN.md for exact colors, fonts, and component styling +3. Use compositions from \`compositions/\` as background footage or extract individual components to remix +4. Reference assets by their URLs from the DESIGN.md Assets section +5. After creating or editing compositions, run \`npx hyperframes lint\` and \`npx hyperframes validate\` + +### Quick start prompts + +**15-second social ad:** +> Create a 15s social ad video using this captured website's design system. Use the DESIGN.md for brand colors, typography, and component styling. Create 4 scenes: Hook (4s), Features (5s), Social Proof (3s), CTA (3s). + +**30-second product tour:** +> Create a 30s product tour video. Walk through the main sections of the captured website, highlighting key features. Use the exact screenshots and component patterns from the capture. + +**Component remix video:** +> Extract individual UI components from the captured compositions and remix them into a new narrative. Change the text, rearrange the layout, combine elements from different sections. +`; + + return md; +} + +function detectImplementationCues( + tokens: DesignTokens, + animations: AnimationCatalog | undefined, +): string[] { + const cues: string[] = []; + + // CSS custom properties + if (Object.keys(tokens.cssVariables).length > 10) { + cues.push( + "The source uses CSS custom properties extensively. Preserve these design tokens for backgrounds, text, buttons, and spacing instead of using hardcoded values.", + ); + } + + // Typography + if (tokens.fonts.length > 0) { + cues.push( + `Typography is explicitly defined in the source (${tokens.fonts.join(", ")}). Match the original font families, weights, and headline/body hierarchy instead of defaulting to a system stack.`, + ); + } + + // Animations + if (animations?.summary) { + if (animations.summary.scrollTargets > 20) { + cues.push( + `Scroll-triggered animations are present in the source (${animations.summary.scrollTargets} IntersectionObserver targets). Preserve scroll reveal behavior.`, + ); + } + if (animations.summary.webAnimations > 5) { + cues.push( + `Web Animations API is used extensively (${animations.summary.webAnimations} active animations). Preserve entrance and interaction animations.`, + ); + } + if (animations.summary.canvases > 0) { + cues.push( + `Canvas/WebGL elements detected (${animations.summary.canvases}). The source may use shader effects or 3D rendering.`, + ); + } + } + + // Marquee detection (from CSS or animation data) + const hasMarquee = animations?.cssDeclarations?.some( + (d) => + d.animation?.name?.toLowerCase().includes("marquee") || + d.animation?.name?.toLowerCase().includes("scroll"), + ); + if (hasMarquee) { + cues.push( + "Marquee-style motion is present in the source. Preserve continuous horizontal scrolling behavior instead of replacing with a static section.", + ); + } + + return cues; +} diff --git a/packages/cli/src/capture/animationCataloger.ts b/packages/cli/src/capture/animationCataloger.ts new file mode 100644 index 00000000..acb46212 --- /dev/null +++ b/packages/cli/src/capture/animationCataloger.ts @@ -0,0 +1,232 @@ +/** + * Catalog all animations on a rendered page. + * + * Captures: + * 1. Web Animations API — active animations with full keyframes + timing + * 2. CSS animation/transition declarations via getComputedStyle + * 3. IntersectionObserver targets (scroll-triggered elements) + * 4. CDP Animation domain events + * + * The catalog is saved as animations.json and gives Claude Code + * everything needed to recreate animations in GSAP. + * + * NOTE: Must be used on a page with ALL scripts running (not stripped). + * Call setupAnimationCapture() BEFORE page.goto() for IO patching. + * Call collectAnimationCatalog() AFTER page has loaded and settled. + */ + +import type { Page, CDPSession } from "puppeteer-core"; + +export interface AnimationCatalog { + /** Active animations via document.getAnimations() — includes keyframes */ + webAnimations: WebAnimationEntry[]; + /** Elements with CSS animation/transition properties declared */ + cssDeclarations: CssAnimationEntry[]; + /** Elements being watched by IntersectionObserver (scroll triggers) */ + scrollTargets: ScrollTarget[]; + /** CDP Animation domain events captured during page lifecycle */ + cdpAnimations: CdpAnimationEntry[]; + /** Total counts summary */ + summary: { + webAnimations: number; + cssDeclarations: number; + scrollTargets: number; + cdpAnimations: number; + canvases: number; + }; +} + +export interface WebAnimationEntry { + type: string; + playState: string; + animationName?: string; + targetSelector?: string; + targetRect?: { x: number; y: number; width: number; height: number }; + keyframes?: Array>; + timing?: { + duration: number; + delay: number; + iterations: number; + easing: string; + direction: string; + }; +} + +export interface CssAnimationEntry { + selector: string; + animation?: { name: string; duration: string; easing: string }; + transition?: { property: string; duration: string }; +} + +export interface ScrollTarget { + selector: string; + rect: { top: number; height: number; width: number }; +} + +export interface CdpAnimationEntry { + id: string; + name: string; + type: string; + duration?: number; + delay?: number; +} + +/** + * Set up animation capture hooks BEFORE navigating to the page. + * This patches IntersectionObserver to track scroll-triggered elements. + */ +export async function setupAnimationCapture(page: Page): Promise { + await page.evaluateOnNewDocument(` + window.__hf_io_targets = []; + var OrigIO = window.IntersectionObserver; + window.IntersectionObserver = function(callback, options) { + var observer = new OrigIO(callback, options); + var origObserve = observer.observe.bind(observer); + observer.observe = function(target) { + var sel = target.id ? '#' + target.id : target.tagName.toLowerCase(); + if (target.className && typeof target.className === 'string') { + var cls = Array.from(target.classList).slice(0, 2).join('.'); + if (cls) sel += '.' + cls; + } + try { + var rect = target.getBoundingClientRect(); + window.__hf_io_targets.push({ + selector: sel, + rect: { top: Math.round(rect.top + window.scrollY), height: Math.round(rect.height), width: Math.round(rect.width) } + }); + } catch(e) {} + return origObserve(target); + }; + return observer; + }; + window.IntersectionObserver.prototype = OrigIO.prototype; + `); +} + +/** + * Start CDP Animation domain listener. + * Returns the CDPSession and a reference to the captured array. + */ +export async function startCdpAnimationCapture( + page: Page, +): Promise<{ cdp: CDPSession; animations: CdpAnimationEntry[] }> { + const cdp = await page.createCDPSession(); + await cdp.send("Animation.enable"); + const animations: CdpAnimationEntry[] = []; + + cdp.on("Animation.animationStarted", (event: any) => { + animations.push({ + id: event.animation.id, + name: event.animation.name || "", + type: event.animation.type, + duration: event.animation.source?.duration, + delay: event.animation.source?.delay, + }); + }); + + return { cdp, animations }; +} + +/** + * Collect the full animation catalog after page has loaded and settled. + * Should be called after scrolling through the page to trigger all animations. + */ +export async function collectAnimationCatalog( + page: Page, + cdpAnimations: CdpAnimationEntry[], + cdp: CDPSession, +): Promise { + // Scroll through page to trigger scroll-based animations + await page.evaluate(`(async () => { + var height = document.body.scrollHeight; + for (var y = 0; y < height; y += window.innerHeight * 0.5) { + window.scrollTo(0, y); + await new Promise(function(r) { setTimeout(r, 400); }); + } + window.scrollTo(0, 0); + await new Promise(function(r) { setTimeout(r, 1000); }); + })()`); + + // Collect from Web Animations API + computed styles + IO targets + const result = (await page.evaluate(`(() => { + var webAnimations = []; + var cssDeclarations = []; + + // 1. Web Animations API + try { + var anims = document.getAnimations(); + webAnimations = anims.map(function(anim) { + var r = { type: anim.constructor.name, playState: anim.playState, animationName: anim.animationName || null }; + var effect = anim.effect; + if (effect && effect.target) { + var t = effect.target; + r.targetSelector = t.id ? '#' + t.id : t.tagName.toLowerCase(); + if (t.className && typeof t.className === 'string') { + var cls = Array.from(t.classList).slice(0, 3).join('.'); + if (cls) r.targetSelector += '.' + cls; + } + try { r.targetRect = t.getBoundingClientRect().toJSON(); } catch(e) {} + } + if (effect && typeof effect.getKeyframes === 'function') { + try { r.keyframes = effect.getKeyframes(); } catch(e) {} + } + if (effect && typeof effect.getComputedTiming === 'function') { + try { + var timing = effect.getComputedTiming(); + r.timing = { duration: timing.duration, delay: timing.delay, iterations: timing.iterations, easing: timing.easing, direction: timing.direction }; + } catch(e) {} + } + return r; + }); + } catch(e) {} + + // 2. CSS animation/transition scan + var allEls = document.querySelectorAll('*'); + for (var i = 0; i < allEls.length && i < 5000; i++) { + var el = allEls[i]; + try { + var cs = getComputedStyle(el); + var hasAnim = cs.animationName && cs.animationName !== 'none'; + var hasTrans = cs.transitionProperty && cs.transitionProperty !== 'all' && cs.transitionProperty !== 'none' && cs.transitionDuration !== '0s'; + if (hasAnim || hasTrans) { + var sel = el.id ? '#' + el.id : el.tagName.toLowerCase(); + if (el.className && typeof el.className === 'string') { + var cls = Array.from(el.classList).slice(0, 2).join('.'); + if (cls) sel += '.' + cls; + } + var entry = { selector: sel }; + if (hasAnim) entry.animation = { name: cs.animationName, duration: cs.animationDuration, easing: cs.animationTimingFunction }; + if (hasTrans) entry.transition = { property: cs.transitionProperty, duration: cs.transitionDuration }; + cssDeclarations.push(entry); + } + } catch(e) {} + } + + // 3. IO targets (collected by monkey-patch) + var scrollTargets = (window.__hf_io_targets || []).map(function(t) { + return { selector: t.selector, rect: t.rect }; + }); + + // 4. Canvas summary + var canvasCount = document.querySelectorAll('canvas').length; + + return { webAnimations: webAnimations, cssDeclarations: cssDeclarations, scrollTargets: scrollTargets, canvasCount: canvasCount }; + })()`)) as any; + + // Stop CDP listener + await cdp.send("Animation.disable"); + + return { + webAnimations: result.webAnimations, + cssDeclarations: result.cssDeclarations, + scrollTargets: result.scrollTargets, + cdpAnimations, + summary: { + webAnimations: result.webAnimations.length, + cssDeclarations: result.cssDeclarations.length, + scrollTargets: result.scrollTargets.length, + cdpAnimations: cdpAnimations.length, + canvases: result.canvasCount, + }, + }; +} diff --git a/packages/cli/src/capture/assetCataloger.ts b/packages/cli/src/capture/assetCataloger.ts new file mode 100644 index 00000000..451f7791 --- /dev/null +++ b/packages/cli/src/capture/assetCataloger.ts @@ -0,0 +1,241 @@ +/** + * Comprehensive asset cataloger. + * + * Scans rendered HTML and CSS for every referenced asset (images, videos, + * fonts, icons, stylesheets, backgrounds) and records the HTML context + * where each was found (e.g., img[src], css url(), link[rel=preload]). + * + * This is the programmatic Part 1 of DESIGN.md generation — deterministic + * extraction, no AI involved. + */ + +import type { Page } from "puppeteer-core"; + +export interface CatalogedAsset { + url: string; + type: "Image" | "Video" | "Font" | "Icon" | "Background" | "Other"; + contexts: string[]; + notes?: string; +} + +/** + * Extract all referenced assets from the rendered page with their HTML contexts. + */ +export async function catalogAssets(page: Page): Promise { + const assets = await page.evaluate(`(() => { + var assetMap = {}; + + function add(url, type, context, notes) { + if (!url || url === '' || url.startsWith('data:') || url.startsWith('blob:') || url === 'about:blank') return; + // Normalize URL + try { url = new URL(url, document.baseURI).href; } catch(e) { return; } + // Skip tiny inline data URIs but keep base64 SVGs + if (url.length > 50000) return; + // Filter tracking pixels and analytics + var lurl = url.toLowerCase(); + if (lurl.indexOf('analytics.') > -1 || lurl.indexOf('adsct') > -1 || lurl.indexOf('pixel.') > -1 || lurl.indexOf('tracking.') > -1 || lurl.indexOf('pdscrb.') > -1 || lurl.indexOf('doubleclick') > -1 || lurl.indexOf('googlesyndication') > -1 || lurl.indexOf('facebook.com/tr') > -1 || lurl.indexOf('bat.bing') > -1 || lurl.indexOf('clarity.ms') > -1) return; + if (lurl.indexOf('bci=') > -1 && lurl.indexOf('twpid=') > -1) return; + if (lurl.indexOf('cachebust=') > -1 || lurl.indexOf('event_id=') > -1) return; + // Filter CSS fragment references to SVG filter IDs (not real downloadable assets) + // e.g., "page.css#hph-illustration-filter", "#linkedin-clip-1" + if (url.indexOf('.css#') > -1) return; + // Filter same-page fragment references like "https://site.com/#clip-1" + try { var parsed = new URL(url); if (parsed.hash && parsed.pathname.length <= 1) return; } catch(e2) {} + + if (!assetMap[url]) { + assetMap[url] = { url: url, type: type, contexts: [], notes: null }; + } + var entry = assetMap[url]; + if (entry.contexts.indexOf(context) === -1) { + entry.contexts.push(context); + } + if (notes && !entry.notes) { + entry.notes = notes; + } + } + + // ── Images: and ── + document.querySelectorAll('img[src]').forEach(function(img) { + var notes = img.alt || img.getAttribute('aria-label') || null; + add(img.src, 'Image', 'img[src]', notes); + if (img.srcset) { + img.srcset.split(',').forEach(function(entry) { + var u = entry.trim().split(/\\s+/)[0]; + if (u) add(u, 'Image', 'img[srcset]', notes); + }); + } + }); + + // ── Picture sources: ── + document.querySelectorAll('source[srcset]').forEach(function(src) { + src.srcset.split(',').forEach(function(entry) { + var u = entry.trim().split(/\\s+/)[0]; + if (u) add(u, 'Image', 'source[srcset]', null); + }); + }); + + // ── Videos: