feat: extract presentation generator into executable code with builtin tool

Move the presentation implementation out of the skill string into real
TypeScript files (types.ts, presentation-generator.tsx) and add a
generatePresentation builtin tool so the agent calls it directly instead
of writing code. Rewrite the skill to guidance-only with content limits,
preference gathering, and JSON examples for each slide type.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Arjun 2026-01-30 13:30:06 +05:30
parent a3e681a7c4
commit e6c6571b07
9 changed files with 1392 additions and 1 deletions

View file

@ -17,6 +17,8 @@
"@google-cloud/local-auth": "^3.0.1",
"@modelcontextprotocol/sdk": "^1.25.1",
"@openrouter/ai-sdk-provider": "^1.2.6",
"@react-pdf/renderer": "^4.3.2",
"@types/react": "^19.2.7",
"@x/shared": "workspace:*",
"ai": "^5.0.102",
"awilix": "^12.0.5",
@ -27,6 +29,7 @@
"node-html-markdown": "^2.0.0",
"ollama-ai-provider-v2": "^1.5.4",
"openid-client": "^6.8.1",
"react": "^19.2.3",
"yaml": "^2.8.2",
"zod": "^4.2.1"
},

View file

@ -26,6 +26,8 @@ Rowboat is an agentic assistant for everyday work - emails, meetings, projects,
**Meeting Prep:** When users ask you to prepare for a meeting, prep for a call, or brief them on attendees, load the \`meeting-prep\` skill first. It provides structured guidance for gathering context about attendees from the knowledge base and creating useful meeting briefs.
**Create Presentations:** When users ask you to create a presentation, slide deck, pitch deck, or PDF slides, load the \`create-presentations\` skill first. It provides structured guidance for generating PDF presentations using context from the knowledge base.
**Document Collaboration:** When users ask you to work on a document, collaborate on writing, create a new document, edit/refine existing notes, or say things like "let's work on [X]", "help me write [X]", "create a doc for [X]", or "let's draft [X]", you MUST load the \`doc-collab\` skill first. This is required for any document creation or editing task. The skill provides structured guidance for creating, editing, and refining documents in the knowledge base.
## Memory That Compounds

View file

@ -0,0 +1,446 @@
import React from 'react';
import {
Document,
Page,
Text,
View,
Image,
StyleSheet,
renderToFile,
} from '@react-pdf/renderer';
import type { Slide, Theme, PresentationData, TitleSlide, ContentSlide, SectionSlide, StatsSlide, TwoColumnSlide, QuoteSlide, ImageSlide, TeamSlide, CTASlide } from './types.js';
const defaultTheme: Theme = {
primaryColor: '#6366f1',
secondaryColor: '#8b5cf6',
accentColor: '#f59e0b',
textColor: '#1f2937',
textLight: '#6b7280',
background: '#ffffff',
backgroundAlt: '#f9fafb',
fontFamily: 'Helvetica',
};
const SLIDE_WIDTH = 1280;
const SLIDE_HEIGHT = 720;
const createStyles = (theme: Theme) =>
StyleSheet.create({
slide: {
width: SLIDE_WIDTH,
height: SLIDE_HEIGHT,
padding: 60,
backgroundColor: theme.background,
position: 'relative',
},
slideAlt: {
backgroundColor: theme.backgroundAlt,
},
slideGradient: {
backgroundColor: theme.primaryColor,
},
pageNumber: {
position: 'absolute',
bottom: 30,
right: 40,
fontSize: 14,
color: theme.textLight,
},
slideTitle: {
fontSize: 42,
fontWeight: 'bold',
color: theme.textColor,
marginBottom: 30,
},
slideBody: {
fontSize: 24,
color: theme.textColor,
lineHeight: 1.6,
},
titleSlide: {
justifyContent: 'center' as const,
alignItems: 'center' as const,
},
mainTitle: {
fontSize: 64,
fontWeight: 'bold',
color: '#ffffff',
textAlign: 'center' as const,
marginBottom: 20,
},
mainSubtitle: {
fontSize: 28,
color: 'rgba(255, 255, 255, 0.9)',
textAlign: 'center' as const,
marginBottom: 30,
},
presenter: {
fontSize: 20,
color: 'rgba(255, 255, 255, 0.8)',
textAlign: 'center' as const,
},
titleDecoration: {
position: 'absolute' as const,
bottom: 0,
left: 0,
right: 0,
height: 8,
backgroundColor: theme.accentColor,
},
sectionNumber: {
fontSize: 80,
fontWeight: 'bold',
color: theme.primaryColor,
opacity: 0.2,
marginBottom: -20,
},
sectionTitle: {
fontSize: 56,
fontWeight: 'bold',
color: theme.textColor,
},
sectionSubtitle: {
fontSize: 24,
color: theme.textLight,
marginTop: 15,
},
contentList: {
marginTop: 10,
},
listItem: {
flexDirection: 'row' as const,
marginBottom: 16,
alignItems: 'flex-start' as const,
},
listBullet: {
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: theme.primaryColor,
marginRight: 20,
marginTop: 8,
},
listText: {
flex: 1,
fontSize: 24,
color: theme.textColor,
lineHeight: 1.5,
},
columnsContainer: {
flexDirection: 'row' as const,
flex: 1,
gap: 60,
},
column: {
flex: 1,
},
columnTitle: {
fontSize: 24,
fontWeight: 'bold',
color: theme.primaryColor,
marginBottom: 15,
},
statsGrid: {
flexDirection: 'row' as const,
justifyContent: 'space-around' as const,
alignItems: 'center' as const,
flex: 1,
},
statItem: {
alignItems: 'center' as const,
padding: 30,
},
statValue: {
fontSize: 72,
fontWeight: 'bold',
color: theme.primaryColor,
marginBottom: 10,
},
statLabel: {
fontSize: 20,
color: theme.textLight,
textTransform: 'uppercase' as const,
letterSpacing: 1,
},
statsNote: {
textAlign: 'center' as const,
fontSize: 18,
color: theme.textLight,
marginTop: 20,
},
quoteSlide: {
justifyContent: 'center' as const,
alignItems: 'center' as const,
},
quoteText: {
fontSize: 36,
fontStyle: 'italic',
color: theme.textColor,
textAlign: 'center' as const,
maxWidth: 900,
lineHeight: 1.5,
},
quoteAttribution: {
fontSize: 20,
color: theme.textLight,
marginTop: 30,
textAlign: 'center' as const,
},
imageContainer: {
flex: 1,
justifyContent: 'center' as const,
alignItems: 'center' as const,
marginVertical: 20,
},
slideImage: {
maxWidth: '100%',
maxHeight: 450,
objectFit: 'contain' as const,
},
imageCaption: {
textAlign: 'center' as const,
fontSize: 18,
color: theme.textLight,
},
teamGrid: {
flexDirection: 'row' as const,
justifyContent: 'center' as const,
gap: 50,
flex: 1,
alignItems: 'center' as const,
},
teamMember: {
alignItems: 'center' as const,
maxWidth: 200,
},
memberPhotoPlaceholder: {
width: 120,
height: 120,
borderRadius: 60,
backgroundColor: theme.primaryColor,
marginBottom: 15,
},
memberPhoto: {
width: 120,
height: 120,
borderRadius: 60,
marginBottom: 15,
},
memberName: {
fontSize: 20,
fontWeight: 'bold',
color: theme.textColor,
textAlign: 'center' as const,
},
memberRole: {
fontSize: 16,
color: theme.primaryColor,
marginTop: 5,
textAlign: 'center' as const,
},
memberBio: {
fontSize: 14,
color: theme.textLight,
marginTop: 10,
textAlign: 'center' as const,
lineHeight: 1.4,
},
ctaSlide: {
justifyContent: 'center' as const,
alignItems: 'center' as const,
},
ctaTitle: {
fontSize: 56,
fontWeight: 'bold',
color: '#ffffff',
textAlign: 'center' as const,
marginBottom: 20,
},
ctaSubtitle: {
fontSize: 24,
color: 'rgba(255, 255, 255, 0.9)',
textAlign: 'center' as const,
marginBottom: 40,
},
ctaContact: {
fontSize: 20,
color: 'rgba(255, 255, 255, 0.8)',
textAlign: 'center' as const,
},
});
type Styles = ReturnType<typeof createStyles>;
const TitleSlideComponent: React.FC<{ slide: TitleSlide; styles: Styles }> = ({ slide, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={[styles.slide, styles.slideGradient, styles.titleSlide]}>
<Text style={styles.mainTitle}>{slide.title}</Text>
{slide.subtitle && <Text style={styles.mainSubtitle}>{slide.subtitle}</Text>}
{slide.presenter && <Text style={styles.presenter}>{slide.presenter}</Text>}
<View style={styles.titleDecoration} />
</Page>
);
const SectionSlideComponent: React.FC<{ slide: SectionSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={[styles.slide, styles.slideAlt]}>
<View style={{ flex: 1, justifyContent: 'center' }}>
<Text style={styles.sectionNumber}>{String(pageNum).padStart(2, '0')}</Text>
<Text style={styles.sectionTitle}>{slide.title}</Text>
{slide.subtitle && <Text style={styles.sectionSubtitle}>{slide.subtitle}</Text>}
</View>
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const ContentSlideComponent: React.FC<{ slide: ContentSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={styles.slide}>
<Text style={styles.slideTitle}>{slide.title}</Text>
{slide.content && <Text style={styles.slideBody}>{slide.content}</Text>}
{slide.items && (
<View style={styles.contentList}>
{slide.items.map((item, i) => (
<View key={i} style={styles.listItem}>
<View style={styles.listBullet} />
<Text style={styles.listText}>{item}</Text>
</View>
))}
</View>
)}
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const TwoColumnSlideComponent: React.FC<{ slide: TwoColumnSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={styles.slide}>
<Text style={styles.slideTitle}>{slide.title}</Text>
<View style={styles.columnsContainer}>
{slide.columns.map((col, i) => (
<View key={i} style={styles.column}>
{col.title && <Text style={styles.columnTitle}>{col.title}</Text>}
{col.content && <Text style={styles.slideBody}>{col.content}</Text>}
{col.items && (
<View style={styles.contentList}>
{col.items.map((item, j) => (
<View key={j} style={styles.listItem}>
<View style={styles.listBullet} />
<Text style={styles.listText}>{item}</Text>
</View>
))}
</View>
)}
</View>
))}
</View>
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const StatsSlideComponent: React.FC<{ slide: StatsSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={styles.slide}>
<Text style={styles.slideTitle}>{slide.title}</Text>
<View style={styles.statsGrid}>
{slide.stats.map((stat, i) => (
<View key={i} style={styles.statItem}>
<Text style={styles.statValue}>{stat.value}</Text>
<Text style={styles.statLabel}>{stat.label}</Text>
</View>
))}
</View>
{slide.note && <Text style={styles.statsNote}>{slide.note}</Text>}
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const QuoteSlideComponent: React.FC<{ slide: QuoteSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={[styles.slide, styles.slideAlt, styles.quoteSlide]}>
<Text style={styles.quoteText}>"{slide.quote}"</Text>
{slide.attribution && <Text style={styles.quoteAttribution}> {slide.attribution}</Text>}
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const ImageSlideComponent: React.FC<{ slide: ImageSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={styles.slide}>
<Text style={styles.slideTitle}>{slide.title}</Text>
<View style={styles.imageContainer}>
<Image src={slide.imagePath} style={styles.slideImage} />
</View>
{slide.caption && <Text style={styles.imageCaption}>{slide.caption}</Text>}
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const TeamSlideComponent: React.FC<{ slide: TeamSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={styles.slide}>
<Text style={styles.slideTitle}>{slide.title}</Text>
<View style={styles.teamGrid}>
{slide.members.map((member, i) => (
<View key={i} style={styles.teamMember}>
{member.photoPath ? (
<Image src={member.photoPath} style={styles.memberPhoto} />
) : (
<View style={styles.memberPhotoPlaceholder} />
)}
<Text style={styles.memberName}>{member.name}</Text>
<Text style={styles.memberRole}>{member.role}</Text>
{member.bio && <Text style={styles.memberBio}>{member.bio}</Text>}
</View>
))}
</View>
<Text style={styles.pageNumber}>{pageNum}</Text>
</Page>
);
const CTASlideComponent: React.FC<{ slide: CTASlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => (
<Page size={[SLIDE_WIDTH, SLIDE_HEIGHT]} style={[styles.slide, styles.slideGradient, styles.ctaSlide]}>
<Text style={styles.ctaTitle}>{slide.title}</Text>
{slide.subtitle && <Text style={styles.ctaSubtitle}>{slide.subtitle}</Text>}
{slide.contact && <Text style={styles.ctaContact}>{slide.contact}</Text>}
<Text style={[styles.pageNumber, { color: 'rgba(255,255,255,0.6)' }]}>{pageNum}</Text>
</Page>
);
const renderSlide = (
slide: Slide,
index: number,
styles: Styles
): React.ReactElement => {
const pageNum = index + 1;
switch (slide.type) {
case 'title':
return <TitleSlideComponent key={index} slide={slide} styles={styles} />;
case 'section':
return <SectionSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'content':
return <ContentSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'two-column':
return <TwoColumnSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'stats':
return <StatsSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'quote':
return <QuoteSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'image':
return <ImageSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'team':
return <TeamSlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
case 'cta':
return <CTASlideComponent key={index} slide={slide} pageNum={pageNum} styles={styles} />;
default:
return <ContentSlideComponent key={index} slide={slide as ContentSlide} pageNum={pageNum} styles={styles} />;
}
};
const Presentation: React.FC<PresentationData> = ({ slides, theme }) => {
const mergedTheme = { ...defaultTheme, ...theme };
const styles = createStyles(mergedTheme);
return <Document>{slides.map((slide, i) => renderSlide(slide, i, styles))}</Document>;
};
export async function generatePresentation(
data: PresentationData,
outputPath: string
): Promise<string> {
await renderToFile(<Presentation {...data} />, outputPath);
return outputPath;
}

View file

@ -0,0 +1,367 @@
export const skill = String.raw`
# PDF Presentation Generator Skill
## Overview
This skill enables Rowboat to create stunning PDF presentations from natural language requests. Use the built-in **generatePresentation** tool to render slides to PDF.
## When to Use This Skill
Activate this skill when the user requests:
- Creating presentations, slide decks, or pitch decks
- Making PDF slides for meetings, talks, or pitches
- Generating visual summaries or reports in presentation format
- Keywords: "presentation", "slides", "deck", "pitch deck", "slide deck", "PDF presentation"
## Knowledge Sources
Before creating any presentation, gather context from the user's knowledge base:
~~~
~/.rowboat/knowledge/
company/
about.md # Company description, mission, vision
team.md # Founder bios, team members
metrics.md # KPIs, growth numbers, financials
product.md # Product description, features, roadmap
branding.md # Colors, fonts, logo paths, style guide
fundraising/
previous-rounds.md # Past funding history
investors.md # Current investors, target investors
use-of-funds.md # How funds will be allocated
projections.md # Financial projections
market/
problem.md # Problem statement
solution.md # How product solves it
competitors.md # Competitive landscape
tam-sam-som.md # Market size analysis
traction.md # Customer testimonials, case studies
assets/
logo.png # Company logo
product-screenshots/
team-photos/
~~~
**Important:** Always check for and read relevant files from ~/.rowboat/knowledge/ before generating content. If files don't exist, ask the user for the information and offer to save it for future use.
## Workflow
### Step 1: Understand the Request & Gather Preferences
Before doing anything else, ask the user about their preferences:
1. **Content density**: Should the slides be text-heavy with detailed explanations, or minimal with just key points and big numbers?
2. **Color / theme**: Do they have brand colors or a color preference? (e.g., "use our brand blue #2563eb" or "dark theme" or "keep it default")
3. **Presentation type**: pitch deck, product demo, team intro, investor update, etc.
4. **Audience**: investors, customers, internal team, conference
5. **Tone**: formal, casual, technical, inspirational
6. **Length**: number of slides (default: 10-12 for pitch decks)
Ask these as a concise set of questions in a single message. Use any answers the user already provided in their initial request and only ask about what's missing.
### Step 2: Gather Knowledge
~~~bash
# Check what knowledge exists
ls -la ~/.rowboat/knowledge/ 2>/dev/null || echo "No knowledge directory found"
# Read relevant files based on presentation type
# For a pitch deck, prioritize:
cat ~/.rowboat/knowledge/company/about.md 2>/dev/null
cat ~/.rowboat/knowledge/market/problem.md 2>/dev/null
cat ~/.rowboat/knowledge/company/metrics.md 2>/dev/null
cat ~/.rowboat/knowledge/company/branding.md 2>/dev/null
~~~
### Step 3: Present the Outline for Approval
Before generating slides, present a structured outline to the user:
~~~
## Proposed Presentation Outline
**Title:** [Presentation Title]
**Slides:** [N] slides
**Estimated read time:** [X] minutes
### Flow:
1. **Title Slide**
- Company name, tagline, presenter name
2. **Problem**
- [One sentence summary of the problem]
3. **Solution**
- [One sentence summary of your solution]
...
---
Does this look good? I can adjust the outline, then I'll go ahead and generate the PDF for you.
- Add/remove slides
- Reorder sections
- Adjust emphasis on any area
~~~
After the user approves (or after incorporating their feedback), immediately ask: **"I'll generate the PDF now — where should I save it?"** If the user has already indicated a path or preference, skip asking and generate directly.
**IMPORTANT:** Always generate the PDF. Never suggest the user copy content into Keynote, Google Slides, or any other tool. The whole point of this skill is to produce a finished PDF.
### Step 4: Generate the Presentation
Once approved, call the **generatePresentation** tool with the slides JSON and output path. Apply the user's theme/color preferences from Step 1.
## Slide Types Reference
| Type | Description | Required Fields |
|------|-------------|-----------------|
| title | Opening slide with gradient background | title |
| section | Section divider with large number | title |
| content | Standard content slide | title, content or items |
| two-column | Two column layout | title, columns (array of 2) |
| stats | Big numbers display | title, stats (array of {value, label}) |
| quote | Testimonial/quote | quote |
| image | Image with caption | title, imagePath |
| team | Team member grid | title, members (array) |
| cta | Call to action / closing | title |
## Slide Type Details
### title
~~~json
{
"type": "title",
"title": "Company Name",
"subtitle": "Tagline or description",
"presenter": "Name • Context • Date"
}
~~~
### content
~~~json
{
"type": "content",
"title": "Slide Title",
"content": "Optional paragraph text",
"items": ["Bullet point 1", "Bullet point 2", "Bullet point 3"]
}
~~~
### section
~~~json
{
"type": "section",
"title": "Section Title",
"subtitle": "Optional subtitle"
}
~~~
### stats
~~~json
{
"type": "stats",
"title": "Key Metrics",
"stats": [
{ "value": "$5M", "label": "Revenue" },
{ "value": "150%", "label": "YoY Growth" },
{ "value": "10K+", "label": "Users" }
],
"note": "Optional footnote"
}
~~~
### two-column
~~~json
{
"type": "two-column",
"title": "Comparison",
"columns": [
{
"title": "Column A",
"content": "Optional text",
"items": ["Item 1", "Item 2"]
},
{
"title": "Column B",
"content": "Optional text",
"items": ["Item 1", "Item 2"]
}
]
}
~~~
### quote
~~~json
{
"type": "quote",
"quote": "The quote text goes here.",
"attribution": "Person Name, Title"
}
~~~
### image
~~~json
{
"type": "image",
"title": "Product Screenshot",
"imagePath": "/absolute/path/to/image.png",
"caption": "Optional caption"
}
~~~
### team
~~~json
{
"type": "team",
"title": "Our Team",
"members": [
{
"name": "Jane Doe",
"role": "CEO",
"bio": "Optional short bio",
"photoPath": "/absolute/path/to/photo.png"
}
]
}
~~~
### cta
~~~json
{
"type": "cta",
"title": "Let's Build Together",
"subtitle": "email@company.com",
"contact": "website.com • github.com/org"
}
~~~
## Theme Customization
Pass an optional theme object to customize colors:
~~~json
{
"primaryColor": "#2563eb",
"secondaryColor": "#7c3aed",
"accentColor": "#f59e0b",
"textColor": "#1f2937",
"textLight": "#6b7280",
"background": "#ffffff",
"backgroundAlt": "#f9fafb",
"fontFamily": "Helvetica"
}
~~~
All theme fields are optional defaults are used for any omitted fields.
## Example: Calling generatePresentation
~~~json
{
"slides": [
{
"type": "title",
"title": "Acme Corp",
"subtitle": "Revolutionizing Widget Manufacturing",
"presenter": "Jane Doe • Series A • 2025"
},
{
"type": "content",
"title": "The Problem",
"items": [
"Widget production is slow and expensive",
"Legacy systems can't keep up with demand",
"Quality control remains manual"
]
},
{
"type": "stats",
"title": "Traction",
"stats": [
{ "value": "500+", "label": "Customers" },
{ "value": "$2M", "label": "ARR" },
{ "value": "3x", "label": "YoY Growth" }
]
},
{
"type": "cta",
"title": "Let's Talk",
"subtitle": "jane@acme.com",
"contact": "acme.com"
}
],
"theme": {
"primaryColor": "#2563eb"
},
"outputPath": "/Users/user/Desktop/acme_pitch.pdf"
}
~~~
## Pitch Deck Templates
### Series A Pitch Deck (12 slides)
Standard flow for investor presentations:
1. **Title** (type: title) - Company name, tagline, presenter
2. **Problem** (type: content) - What pain point you solve
3. **Solution** (type: content) - Your product/service
4. **Product** (type: image) - Demo/screenshots
5. **Market** (type: stats) - TAM/SAM/SOM
6. **Business Model** (type: content) - How you make money
7. **Traction** (type: stats) - Metrics and growth
8. **Competition** (type: two-column) - Your differentiation
9. **Team** (type: team) - Key team members
10. **Financials** (type: content or stats) - Projections
11. **The Ask** (type: content) - Funding amount and use
12. **Contact** (type: cta) - CTA with contact info
### Product Demo Deck (8 slides)
1. **Title** - Product name and tagline
2. **Problem** - User pain points
3. **Solution** - High-level approach
4. **Features** - Key capabilities (two-column)
5. **Demo** - Screenshots (image)
6. **Pricing** - Plans and pricing
7. **Testimonials** - Customer quotes (quote)
8. **Get Started** - CTA
## Content Limits Per Slide (IMPORTANT)
Each slide is a fixed 1280x720 page. Content that exceeds the available space will be clipped. Follow these limits strictly:
| Slide Type | Max Items / Content |
|------------|-------------------|
| content | 5 bullet points max (keep each bullet to 1 line, ~80 chars). If using paragraph text instead, max ~4 lines. |
| two-column | 4 bullet points per column max. Keep bullets short (~60 chars). |
| stats | 3-4 stats max. Keep labels short (1-2 words). |
| team | 4 members max per slide. Split into multiple team slides if needed. |
| quote | Keep quotes under ~200 characters. |
| image | Caption should be 1 line. |
**If the user's content needs more space**, split it across multiple slides of the same type rather than cramming it into one. For example, if there are 8 bullet points, use two content slides (4 each) with titles like "Key Benefits (1/2)" and "Key Benefits (2/2)".
## Best Practices
1. **Keep slides simple** - One idea per slide
2. **Use stats slides for numbers** - Big, bold metrics
3. **Limit bullet points** - 3-5 max per slide, keep them short
4. **Use two-column for comparisons** - Us vs. them, before/after
5. **End with clear CTA** - What do you want them to do?
6. **Gather knowledge first** - Check ~/.rowboat/knowledge/ before generating
7. **Use absolute paths** for images (PNG, JPG supported)
8. **Never overflow** - If content doesn't fit, split across multiple slides
## Output
The generatePresentation tool produces:
- **PDF file** at the specified outputPath
- **16:9 aspect ratio** (1280x720px per slide)
- **Print-ready** quality
- **Embedded fonts** for portability
`;
export default skill;

View file

@ -0,0 +1,100 @@
export interface SlideBase {
type: string;
title?: string;
subtitle?: string;
content?: string;
}
export interface TitleSlide extends SlideBase {
type: 'title';
title: string;
subtitle?: string;
presenter?: string;
}
export interface ContentSlide extends SlideBase {
type: 'content';
title: string;
content?: string;
items?: string[];
}
export interface SectionSlide extends SlideBase {
type: 'section';
title: string;
subtitle?: string;
}
export interface StatsSlide extends SlideBase {
type: 'stats';
title: string;
stats: Array<{ value: string; label: string }>;
note?: string;
}
export interface TwoColumnSlide extends SlideBase {
type: 'two-column';
title: string;
columns: [
{ title?: string; content?: string; items?: string[] },
{ title?: string; content?: string; items?: string[] }
];
}
export interface QuoteSlide extends SlideBase {
type: 'quote';
quote: string;
attribution?: string;
}
export interface ImageSlide extends SlideBase {
type: 'image';
title: string;
imagePath: string;
caption?: string;
}
export interface TeamSlide extends SlideBase {
type: 'team';
title: string;
members: Array<{
name: string;
role: string;
bio?: string;
photoPath?: string;
}>;
}
export interface CTASlide extends SlideBase {
type: 'cta';
title: string;
subtitle?: string;
contact?: string;
}
export type Slide =
| TitleSlide
| ContentSlide
| SectionSlide
| StatsSlide
| TwoColumnSlide
| QuoteSlide
| ImageSlide
| TeamSlide
| CTASlide;
export interface Theme {
primaryColor: string;
secondaryColor: string;
accentColor: string;
textColor: string;
textLight: string;
background: string;
backgroundAlt: string;
fontFamily: string;
}
export interface PresentationData {
slides: Slide[];
theme?: Partial<Theme>;
}

View file

@ -8,6 +8,7 @@ import mcpIntegrationSkill from "./mcp-integration/skill.js";
import meetingPrepSkill from "./meeting-prep/skill.js";
import organizeFilesSkill from "./organize-files/skill.js";
import workflowAuthoringSkill from "./workflow-authoring/skill.js";
import createPresentationsSkill from "./create-presentations/skill.js";
import workflowRunOpsSkill from "./workflow-run-ops/skill.js";
const CURRENT_FILE = fileURLToPath(import.meta.url);
@ -29,6 +30,13 @@ type ResolvedSkill = {
};
const definitions: SkillDefinition[] = [
{
id: "create-presentations",
title: "Create Presentations",
folder: "create-presentations",
summary: "Create PDF presentations and slide decks from natural language requests using knowledge base context.",
content: createPresentationsSkill,
},
{
id: "doc-collab",
title: "Document Collaboration",

View file

@ -12,6 +12,7 @@ import * as workspace from "../../workspace/workspace.js";
import { IAgentsRepo } from "../../agents/repo.js";
import { WorkDir } from "../../config/config.js";
import type { ToolContext } from "./exec-tool.js";
import { generatePresentation } from "../assistant/skills/create-presentations/presentation-generator.js";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const BuiltinToolsSchema = z.record(z.string(), z.object({
@ -606,6 +607,71 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
},
},
generatePresentation: {
description: 'Generate a PDF presentation from slide data. Creates a 16:9 PDF with styled slides.',
inputSchema: z.object({
slides: z.array(z.object({
type: z.enum(['title', 'content', 'section', 'stats', 'two-column', 'quote', 'image', 'team', 'cta']),
title: z.string().optional(),
subtitle: z.string().optional(),
content: z.string().optional(),
presenter: z.string().optional(),
items: z.array(z.string()).optional(),
stats: z.array(z.object({ value: z.string(), label: z.string() })).optional(),
note: z.string().optional(),
columns: z.array(z.object({
title: z.string().optional(),
content: z.string().optional(),
items: z.array(z.string()).optional(),
})).optional(),
quote: z.string().optional(),
attribution: z.string().optional(),
imagePath: z.string().optional(),
caption: z.string().optional(),
members: z.array(z.object({
name: z.string(),
role: z.string(),
bio: z.string().optional(),
photoPath: z.string().optional(),
})).optional(),
contact: z.string().optional(),
})).describe('Array of slide objects'),
theme: z.object({
primaryColor: z.string().optional(),
secondaryColor: z.string().optional(),
accentColor: z.string().optional(),
textColor: z.string().optional(),
textLight: z.string().optional(),
background: z.string().optional(),
backgroundAlt: z.string().optional(),
fontFamily: z.string().optional(),
}).optional().describe('Optional theme customization'),
outputPath: z.string().describe('Absolute path for the output PDF file'),
}),
execute: async ({ slides, theme, outputPath }: {
slides: Array<Record<string, unknown>>;
theme?: Record<string, string>;
outputPath: string;
}) => {
try {
const result = await generatePresentation(
{ slides: slides as never, theme },
outputPath,
);
return {
success: true,
outputPath: result,
slideCount: slides.length,
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
},
},
executeCommand: {
description: 'Execute a shell command and return the output. Use this to run bash/shell commands.',
inputSchema: z.object({

View file

@ -4,7 +4,8 @@
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"types": ["node"]
"types": ["node"],
"jsx": "react-jsx"
},
"include": [
"src"

398
apps/x/pnpm-lock.yaml generated
View file

@ -299,6 +299,12 @@ importers:
'@openrouter/ai-sdk-provider':
specifier: ^1.2.6
version: 1.5.4(ai@5.0.117(zod@4.2.1))(zod@4.2.1)
'@react-pdf/renderer':
specifier: ^4.3.2
version: 4.3.2(react@19.2.3)
'@types/react':
specifier: ^19.2.7
version: 19.2.7
'@x/shared':
specifier: workspace:*
version: link:../shared
@ -329,6 +335,9 @@ importers:
openid-client:
specifier: ^6.8.1
version: 6.8.1
react:
specifier: ^19.2.3
version: 19.2.3
yaml:
specifier: ^2.8.2
version: 2.8.2
@ -631,6 +640,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.28.6':
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@ -1947,6 +1960,49 @@ packages:
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
'@react-pdf/fns@3.1.2':
resolution: {integrity: sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==}
'@react-pdf/font@4.0.4':
resolution: {integrity: sha512-8YtgGtL511txIEc9AjiilpZ7yjid8uCd8OGUl6jaL3LIHnrToUupSN4IzsMQpVTCMYiDLFnDNQzpZsOYtRS/Pg==}
'@react-pdf/image@3.0.4':
resolution: {integrity: sha512-z0ogVQE0bKqgXQ5smgzIU857rLV7bMgVdrYsu3UfXDDLSzI7QPvzf6MFTFllX6Dx2rcsF13E01dqKPtJEM799g==}
'@react-pdf/layout@4.4.2':
resolution: {integrity: sha512-gNu2oh8MiGR+NJZYTJ4c4q0nWCESBI6rKFiodVhE7OeVAjtzZzd6l65wsN7HXdWJqOZD3ttD97iE+tf5SOd/Yg==}
'@react-pdf/pdfkit@4.1.0':
resolution: {integrity: sha512-Wm/IOAv0h/U5Ra94c/PltFJGcpTUd/fwVMVeFD6X9tTTPCttIwg0teRG1Lqq617J8K4W7jpL/B0HTH0mjp3QpQ==}
'@react-pdf/png-js@3.0.0':
resolution: {integrity: sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==}
'@react-pdf/primitives@4.1.1':
resolution: {integrity: sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==}
'@react-pdf/reconciler@2.0.0':
resolution: {integrity: sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@react-pdf/render@4.3.2':
resolution: {integrity: sha512-el5KYM1sH/PKcO4tRCIm8/AIEmhtraaONbwCrBhFdehoGv6JtgnXiMxHGAvZbI5kEg051GbyP+XIU6f6YbOu6Q==}
'@react-pdf/renderer@4.3.2':
resolution: {integrity: sha512-EhPkj35gO9rXIyyx29W3j3axemvVY5RigMmlK4/6Ku0pXB8z9PEE/sz4ZBOShu2uot6V4xiCR3aG+t9IjJJlBQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@react-pdf/stylesheet@6.1.2':
resolution: {integrity: sha512-E3ftGRYUQGKiN3JOgtGsLDo0hGekA6dmkmi/MYACytmPTKxQRBSO3126MebmCq+t1rgU9uRlREIEawJ+8nzSbw==}
'@react-pdf/textkit@6.1.0':
resolution: {integrity: sha512-sFlzDC9CDFrJsnL3B/+NHrk9+Advqk7iJZIStiYQDdskbow8GF/AGYrpIk+vWSnh35YxaGbHkqXq53XOxnyrjQ==}
'@react-pdf/types@2.9.2':
resolution: {integrity: sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==}
'@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
@ -2307,6 +2363,9 @@ packages:
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@swc/helpers@0.5.18':
resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}
'@szmarczak/http-timer@4.0.6':
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
engines: {node: '>=10'}
@ -2931,6 +2990,9 @@ packages:
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
abs-svg-path@0.1.1:
resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@ -3071,6 +3133,10 @@ packages:
base32-encode@1.2.0:
resolution: {integrity: sha512-cHFU8XeRyx0GgmoWi5qHMCVRiqU6J3MHWxVgun7jggCBUpVzm1Ir7M9dYr2whjSNc3tFeXfQ/oZjQu/4u55h9A==}
base64-js@0.0.8:
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
engines: {node: '>= 0.4'}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@ -3078,6 +3144,9 @@ packages:
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
hasBin: true
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
bignumber.js@9.3.1:
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
@ -3114,6 +3183,12 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
brotli@1.3.3:
resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
browserify-zlib@0.2.0:
resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==}
browserslist@4.28.1:
resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@ -3251,6 +3326,10 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
clone@2.1.2:
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
engines: {node: '>=0.8'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@ -3271,6 +3350,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@ -3369,6 +3451,9 @@ packages:
resolution: {integrity: sha512-n63i0lZ0rvQ6FXiGQ+/JFCKAUyPFhLQYJIqKaa+tSJtfKeULF/IDNDAbdnSIxgS4NTuw2b0+lj8LzfITuq+ZxQ==}
engines: {node: '>=12.10'}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
css-select@5.2.2:
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
@ -3612,6 +3697,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
dfa@1.2.0:
resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
dir-compare@4.2.0:
resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==}
@ -3674,6 +3762,9 @@ packages:
engines: {node: '>= 12.20.55'}
hasBin: true
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -3975,6 +4066,9 @@ packages:
debug:
optional: true
fontkit@2.0.4:
resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@ -4298,6 +4392,12 @@ packages:
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
hsl-to-hex@1.0.0:
resolution: {integrity: sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==}
hsl-to-rgb-for-reals@1.1.1:
resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==}
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
@ -4330,6 +4430,9 @@ packages:
humanize-ms@1.2.1:
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
hyphen@1.14.1:
resolution: {integrity: sha512-kvL8xYl5QMTh+LwohVN72ciOxC0OEV79IPdJSTwEXok9y9QHebXGdFgrED4sWfiax/ODx++CAMk3hMy4XPJPOw==}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@ -4419,6 +4522,9 @@ packages:
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-arrayish@0.3.4:
resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
@ -4506,6 +4612,9 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jay-peg@1.1.1:
resolution: {integrity: sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==}
jest-worker@27.5.1:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
@ -4679,6 +4788,9 @@ packages:
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
engines: {node: '>= 12.0.0'}
linebreak@1.1.0:
resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
@ -4735,6 +4847,10 @@ packages:
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
@ -4855,6 +4971,9 @@ packages:
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
media-engine@1.0.3:
resolution: {integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==}
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
@ -5200,6 +5319,9 @@ packages:
normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
normalize-svg-path@1.1.0:
resolution: {integrity: sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==}
normalize-url@6.1.0:
resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
engines: {node: '>=10'}
@ -5317,6 +5439,12 @@ packages:
package-manager-detector@1.6.0:
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -5335,6 +5463,9 @@ packages:
resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==}
engines: {node: '>=0.10.0'}
parse-svg-path@0.1.2:
resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==}
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
@ -5428,6 +5559,9 @@ packages:
points-on-path@0.2.1:
resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@ -5472,6 +5606,9 @@ packages:
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
engines: {node: '>=10'}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
@ -5565,6 +5702,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
queue@6.0.2:
resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
quick-lru@5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
@ -5588,6 +5728,9 @@ packages:
peerDependencies:
react: ^19.2.3
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-refresh@0.18.0:
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'}
@ -5745,6 +5888,9 @@ packages:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
restructure@3.0.2:
resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==}
retry@0.12.0:
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
engines: {node: '>= 4'}
@ -5807,6 +5953,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
scheduler@0.25.0-rc-603e6108-20241029:
resolution: {integrity: sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==}
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@ -5897,6 +6046,9 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
simple-swizzle@0.2.4:
resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==}
slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
@ -6034,6 +6186,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
svg-arc-to-cubic-bezier@3.2.0:
resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==}
tailwind-merge@3.4.0:
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
@ -6077,6 +6232,9 @@ packages:
tiny-each-async@2.0.3:
resolution: {integrity: sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==}
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
tinyexec@1.0.2:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'}
@ -6201,6 +6359,12 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
unicode-properties@1.4.1:
resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
unicode-trie@2.0.0:
resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@ -6325,6 +6489,10 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite-compatible-readable-stream@3.6.1:
resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==}
engines: {node: '>= 6'}
vite@7.3.0:
resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -6508,6 +6676,9 @@ packages:
resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
engines: {node: '>=18'}
yoga-layout@3.2.1:
resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==}
zod-to-json-schema@3.25.1:
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
peerDependencies:
@ -7159,6 +7330,8 @@ snapshots:
'@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
'@babel/runtime@7.28.6': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@ -8648,6 +8821,107 @@ snapshots:
'@radix-ui/rect@1.1.1': {}
'@react-pdf/fns@3.1.2': {}
'@react-pdf/font@4.0.4':
dependencies:
'@react-pdf/pdfkit': 4.1.0
'@react-pdf/types': 2.9.2
fontkit: 2.0.4
is-url: 1.2.4
'@react-pdf/image@3.0.4':
dependencies:
'@react-pdf/png-js': 3.0.0
jay-peg: 1.1.1
'@react-pdf/layout@4.4.2':
dependencies:
'@react-pdf/fns': 3.1.2
'@react-pdf/image': 3.0.4
'@react-pdf/primitives': 4.1.1
'@react-pdf/stylesheet': 6.1.2
'@react-pdf/textkit': 6.1.0
'@react-pdf/types': 2.9.2
emoji-regex-xs: 1.0.0
queue: 6.0.2
yoga-layout: 3.2.1
'@react-pdf/pdfkit@4.1.0':
dependencies:
'@babel/runtime': 7.28.6
'@react-pdf/png-js': 3.0.0
browserify-zlib: 0.2.0
crypto-js: 4.2.0
fontkit: 2.0.4
jay-peg: 1.1.1
linebreak: 1.1.0
vite-compatible-readable-stream: 3.6.1
'@react-pdf/png-js@3.0.0':
dependencies:
browserify-zlib: 0.2.0
'@react-pdf/primitives@4.1.1': {}
'@react-pdf/reconciler@2.0.0(react@19.2.3)':
dependencies:
object-assign: 4.1.1
react: 19.2.3
scheduler: 0.25.0-rc-603e6108-20241029
'@react-pdf/render@4.3.2':
dependencies:
'@babel/runtime': 7.28.6
'@react-pdf/fns': 3.1.2
'@react-pdf/primitives': 4.1.1
'@react-pdf/textkit': 6.1.0
'@react-pdf/types': 2.9.2
abs-svg-path: 0.1.1
color-string: 1.9.1
normalize-svg-path: 1.1.0
parse-svg-path: 0.1.2
svg-arc-to-cubic-bezier: 3.2.0
'@react-pdf/renderer@4.3.2(react@19.2.3)':
dependencies:
'@babel/runtime': 7.28.6
'@react-pdf/fns': 3.1.2
'@react-pdf/font': 4.0.4
'@react-pdf/layout': 4.4.2
'@react-pdf/pdfkit': 4.1.0
'@react-pdf/primitives': 4.1.1
'@react-pdf/reconciler': 2.0.0(react@19.2.3)
'@react-pdf/render': 4.3.2
'@react-pdf/types': 2.9.2
events: 3.3.0
object-assign: 4.1.1
prop-types: 15.8.1
queue: 6.0.2
react: 19.2.3
'@react-pdf/stylesheet@6.1.2':
dependencies:
'@react-pdf/fns': 3.1.2
'@react-pdf/types': 2.9.2
color-string: 1.9.1
hsl-to-hex: 1.0.0
media-engine: 1.0.3
postcss-value-parser: 4.2.0
'@react-pdf/textkit@6.1.0':
dependencies:
'@react-pdf/fns': 3.1.2
bidi-js: 1.0.3
hyphen: 1.14.1
unicode-properties: 1.4.1
'@react-pdf/types@2.9.2':
dependencies:
'@react-pdf/font': 4.0.4
'@react-pdf/primitives': 4.1.1
'@react-pdf/stylesheet': 6.1.2
'@remirror/core-constants@3.0.0': {}
'@rolldown/pluginutils@1.0.0-beta.53': {}
@ -9093,6 +9367,10 @@ snapshots:
'@standard-schema/spec@1.1.0': {}
'@swc/helpers@0.5.18':
dependencies:
tslib: 2.8.1
'@szmarczak/http-timer@4.0.6':
dependencies:
defer-to-connect: 2.0.1
@ -9827,6 +10105,8 @@ snapshots:
abbrev@1.1.1: {}
abs-svg-path@0.1.1: {}
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
@ -9966,10 +10246,16 @@ snapshots:
to-data-view: 1.1.0
optional: true
base64-js@0.0.8: {}
base64-js@1.5.1: {}
baseline-browser-mapping@2.9.11: {}
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
bignumber.js@9.3.1: {}
bl@4.1.0:
@ -10019,6 +10305,14 @@ snapshots:
dependencies:
fill-range: 7.1.1
brotli@1.3.3:
dependencies:
base64-js: 1.5.1
browserify-zlib@0.2.0:
dependencies:
pako: 1.0.11
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.9.11
@ -10180,6 +10474,8 @@ snapshots:
clone@1.0.4: {}
clone@2.1.2: {}
clsx@2.1.1: {}
cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
@ -10203,6 +10499,11 @@ snapshots:
color-name@1.1.4: {}
color-string@1.9.1:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.4
colorette@2.0.20: {}
combined-stream@1.0.8:
@ -10283,6 +10584,8 @@ snapshots:
cross-zip@4.0.1: {}
crypto-js@4.2.0: {}
css-select@5.2.2:
dependencies:
boolbase: 1.0.0
@ -10542,6 +10845,8 @@ snapshots:
dependencies:
dequal: 2.0.3
dfa@1.2.0: {}
dir-compare@4.2.0:
dependencies:
minimatch: 3.1.2
@ -10655,6 +10960,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
emoji-regex-xs@1.0.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@ -11046,6 +11353,18 @@ snapshots:
follow-redirects@1.15.11: {}
fontkit@2.0.4:
dependencies:
'@swc/helpers': 0.5.18
brotli: 1.3.3
clone: 2.1.2
dfa: 1.2.0
fast-deep-equal: 3.1.3
restructure: 3.0.2
tiny-inflate: 1.0.3
unicode-properties: 1.4.1
unicode-trie: 2.0.0
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@ -11535,6 +11854,12 @@ snapshots:
hosted-git-info@2.8.9: {}
hsl-to-hex@1.0.0:
dependencies:
hsl-to-rgb-for-reals: 1.1.1
hsl-to-rgb-for-reals@1.1.1: {}
html-url-attributes@3.0.1: {}
html-void-elements@3.0.0: {}
@ -11580,6 +11905,8 @@ snapshots:
dependencies:
ms: 2.1.3
hyphen@1.14.1: {}
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@ -11645,6 +11972,8 @@ snapshots:
is-arrayish@0.2.1: {}
is-arrayish@0.3.4: {}
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
@ -11712,6 +12041,10 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jay-peg@1.1.1:
dependencies:
restructure: 3.0.2
jest-worker@27.5.1:
dependencies:
'@types/node': 25.0.3
@ -11865,6 +12198,11 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.2
lightningcss-win32-x64-msvc: 1.30.2
linebreak@1.1.0:
dependencies:
base64-js: 0.0.8
unicode-trie: 2.0.0
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
@ -11925,6 +12263,10 @@ snapshots:
longest-streak@3.1.0: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
lower-case@2.0.2:
dependencies:
tslib: 2.8.1
@ -12173,6 +12515,8 @@ snapshots:
mdurl@2.0.0: {}
media-engine@1.0.3: {}
media-typer@1.1.0: {}
mem@4.3.0:
@ -12628,6 +12972,10 @@ snapshots:
semver: 5.7.2
validate-npm-package-license: 3.0.4
normalize-svg-path@1.1.0:
dependencies:
svg-arc-to-cubic-bezier: 3.2.0
normalize-url@6.1.0: {}
npm-run-path@2.0.2:
@ -12742,6 +13090,10 @@ snapshots:
package-manager-detector@1.6.0: {}
pako@0.2.9: {}
pako@1.0.11: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@ -12769,6 +13121,8 @@ snapshots:
dependencies:
error-ex: 1.3.4
parse-svg-path@0.1.2: {}
parse5@7.3.0:
dependencies:
entities: 6.0.1
@ -12845,6 +13199,8 @@ snapshots:
path-data-parser: 0.1.0
points-on-curve: 0.2.0
postcss-value-parser@4.2.0: {}
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@ -12888,6 +13244,12 @@ snapshots:
err-code: 2.0.3
retry: 0.12.0
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
property-information@7.1.0: {}
prosemirror-changeset@2.3.1:
@ -13032,6 +13394,10 @@ snapshots:
queue-microtask@1.2.3: {}
queue@6.0.2:
dependencies:
inherits: 2.0.4
quick-lru@5.1.1: {}
random-path@0.1.2:
@ -13058,6 +13424,8 @@ snapshots:
react: 19.2.3
scheduler: 0.27.0
react-is@16.13.1: {}
react-refresh@0.18.0: {}
react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.3):
@ -13248,6 +13616,8 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
restructure@3.0.2: {}
retry@0.12.0: {}
reusify@1.1.0: {}
@ -13340,6 +13710,8 @@ snapshots:
safer-buffer@2.1.2: {}
scheduler@0.25.0-rc-603e6108-20241029: {}
scheduler@0.27.0: {}
schema-utils@4.3.3:
@ -13453,6 +13825,10 @@ snapshots:
signal-exit@4.1.0: {}
simple-swizzle@0.2.4:
dependencies:
is-arrayish: 0.3.4
slice-ansi@5.0.0:
dependencies:
ansi-styles: 6.2.3
@ -13618,6 +13994,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
svg-arc-to-cubic-bezier@3.2.0: {}
tailwind-merge@3.4.0: {}
tailwindcss@4.1.18: {}
@ -13660,6 +14038,8 @@ snapshots:
tiny-each-async@2.0.3:
optional: true
tiny-inflate@1.0.3: {}
tinyexec@1.0.2: {}
tinyglobby@0.2.15:
@ -13770,6 +14150,16 @@ snapshots:
undici-types@7.16.0: {}
unicode-properties@1.4.1:
dependencies:
base64-js: 1.5.1
unicode-trie: 2.0.0
unicode-trie@2.0.0:
dependencies:
pako: 0.2.9
tiny-inflate: 1.0.3
unified@11.0.5:
dependencies:
'@types/unist': 3.0.3
@ -13903,6 +14293,12 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite-compatible-readable-stream@3.6.1:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2):
dependencies:
esbuild: 0.27.2
@ -14082,6 +14478,8 @@ snapshots:
yoctocolors-cjs@2.1.3: {}
yoga-layout@3.2.1: {}
zod-to-json-schema@3.25.1(zod@4.2.1):
dependencies:
zod: 4.2.1