diff --git a/apps/x/packages/core/src/application/assistant/instructions.ts b/apps/x/packages/core/src/application/assistant/instructions.ts index d70ef66a..3785c493 100644 --- a/apps/x/packages/core/src/application/assistant/instructions.ts +++ b/apps/x/packages/core/src/application/assistant/instructions.ts @@ -150,15 +150,9 @@ When a user asks for ANY task that might require external capabilities (web sear - NEVER ask what OS the user is on - they are on macOS. - Load the \`organize-files\` skill for guidance on file organization tasks. -**Command Approval:** -- Approved shell commands are listed in \`~/.rowboat/config/security.json\`. Read this file to see what commands are allowed. -- Only use commands from the approved list. Commands not in the list will be blocked. -- If you cannot accomplish a task with the approved commands, tell the user which command you need and ask them to add it to \`security.json\`. -- Always confirm with the user before executing commands that modify files outside \`~/.rowboat/\` (e.g., "I'll move 12 screenshots to ~/Desktop/Screenshots. Proceed?"). - ## Builtin Tools vs Shell Commands -**IMPORTANT**: Rowboat provides builtin tools that are internal and do NOT require security allowlist entries: +**IMPORTANT**: Rowboat provides builtin tools that are internal and do NOT require any user approval: - \`workspace-readFile\`, \`workspace-writeFile\`, \`workspace-edit\`, \`workspace-remove\` - File operations - \`workspace-readdir\`, \`workspace-exists\`, \`workspace-stat\`, \`workspace-glob\`, \`workspace-grep\` - Directory exploration and file search - \`workspace-mkdir\`, \`workspace-rename\`, \`workspace-copy\` - File/directory management @@ -166,13 +160,20 @@ When a user asks for ANY task that might require external capabilities (web sear - \`addMcpServer\`, \`listMcpServers\`, \`listMcpTools\`, \`executeMcpTool\` - MCP server management and execution - \`loadSkill\` - Skill loading -These tools work directly and are NOT filtered by \`.rowboat/config/security.json\`. +**Prefer these tools whenever possible** — they work instantly with zero friction. For file operations inside \`~/.rowboat/\`, always use these instead of \`executeCommand\`. + +**Shell commands via \`executeCommand\`:** +- You can run ANY shell command via \`executeCommand\`. Some commands are pre-approved in \`~/.rowboat/config/security.json\` and run immediately. +- Commands not on the pre-approved list will trigger a one-time approval prompt for the user — this is fine and expected, just a minor friction. Do NOT let this stop you from running commands you need. +- **Never say "I can't run this command"** or ask the user to run something manually. Just call \`executeCommand\` and let the approval flow handle it. +- When calling \`executeCommand\`, do NOT provide the \`cwd\` parameter unless absolutely necessary. The default working directory is already set to the workspace root. +- Always confirm with the user before executing commands that modify files outside \`~/.rowboat/\` (e.g., "I'll move 12 screenshots to ~/Desktop/Screenshots. Proceed?"). **CRITICAL: MCP Server Configuration** - ALWAYS use the \`addMcpServer\` builtin tool to add or update MCP servers—it validates the configuration before saving - NEVER manually edit \`config/mcp.json\` using \`workspace-writeFile\` for MCP servers - Invalid MCP configs will prevent the agent from starting with validation errors -**Only \`executeCommand\` (shell/bash commands) is filtered** by the security allowlist. If you need to delete a file, use the \`workspace-remove\` builtin tool, not \`executeCommand\` with \`rm\`. If you need to create a file, use \`workspace-writeFile\`, not \`executeCommand\` with \`touch\` or \`echo >\`. +**Only \`executeCommand\` (shell/bash commands) goes through the approval flow.** If you need to delete a file, use the \`workspace-remove\` builtin tool, not \`executeCommand\` with \`rm\`. If you need to create a file, use \`workspace-writeFile\`, not \`executeCommand\` with \`touch\` or \`echo >\`. -The security allowlist in \`security.json\` only applies to shell commands executed via \`executeCommand\`, not to Rowboat's internal builtin tools.`; +Rowboat's internal builtin tools never require approval — only shell commands via \`executeCommand\` do.`; diff --git a/apps/x/packages/core/src/application/assistant/skills/create-presentations/presentation-generator.tsx b/apps/x/packages/core/src/application/assistant/skills/create-presentations/presentation-generator.tsx deleted file mode 100644 index ffeef70e..00000000 --- a/apps/x/packages/core/src/application/assistant/skills/create-presentations/presentation-generator.tsx +++ /dev/null @@ -1,446 +0,0 @@ -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; - -const TitleSlideComponent: React.FC<{ slide: TitleSlide; styles: Styles }> = ({ slide, styles }) => ( - - {slide.title} - {slide.subtitle && {slide.subtitle}} - {slide.presenter && {slide.presenter}} - - -); - -const SectionSlideComponent: React.FC<{ slide: SectionSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - - {String(pageNum).padStart(2, '0')} - {slide.title} - {slide.subtitle && {slide.subtitle}} - - {pageNum} - -); - -const ContentSlideComponent: React.FC<{ slide: ContentSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - {slide.title} - {slide.content && {slide.content}} - {slide.items && ( - - {slide.items.map((item, i) => ( - - - {item} - - ))} - - )} - {pageNum} - -); - -const TwoColumnSlideComponent: React.FC<{ slide: TwoColumnSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - {slide.title} - - {slide.columns.map((col, i) => ( - - {col.title && {col.title}} - {col.content && {col.content}} - {col.items && ( - - {col.items.map((item, j) => ( - - - {item} - - ))} - - )} - - ))} - - {pageNum} - -); - -const StatsSlideComponent: React.FC<{ slide: StatsSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - {slide.title} - - {slide.stats.map((stat, i) => ( - - {stat.value} - {stat.label} - - ))} - - {slide.note && {slide.note}} - {pageNum} - -); - -const QuoteSlideComponent: React.FC<{ slide: QuoteSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - "{slide.quote}" - {slide.attribution && — {slide.attribution}} - {pageNum} - -); - -const ImageSlideComponent: React.FC<{ slide: ImageSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - {slide.title} - - - - {slide.caption && {slide.caption}} - {pageNum} - -); - -const TeamSlideComponent: React.FC<{ slide: TeamSlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - {slide.title} - - {slide.members.map((member, i) => ( - - {member.photoPath ? ( - - ) : ( - - )} - {member.name} - {member.role} - {member.bio && {member.bio}} - - ))} - - {pageNum} - -); - -const CTASlideComponent: React.FC<{ slide: CTASlide; pageNum: number; styles: Styles }> = ({ slide, pageNum, styles }) => ( - - {slide.title} - {slide.subtitle && {slide.subtitle}} - {slide.contact && {slide.contact}} - {pageNum} - -); - -const renderSlide = ( - slide: Slide, - index: number, - styles: Styles -): React.ReactElement => { - const pageNum = index + 1; - - switch (slide.type) { - case 'title': - return ; - case 'section': - return ; - case 'content': - return ; - case 'two-column': - return ; - case 'stats': - return ; - case 'quote': - return ; - case 'image': - return ; - case 'team': - return ; - case 'cta': - return ; - default: - return ; - } -}; - -const Presentation: React.FC = ({ slides, theme }) => { - const mergedTheme = { ...defaultTheme, ...theme }; - const styles = createStyles(mergedTheme); - - return {slides.map((slide, i) => renderSlide(slide, i, styles))}; -}; - -export async function generatePresentation( - data: PresentationData, - outputPath: string -): Promise { - await renderToFile(, outputPath); - return outputPath; -} diff --git a/apps/x/packages/core/src/application/assistant/skills/create-presentations/skill.ts b/apps/x/packages/core/src/application/assistant/skills/create-presentations/skill.ts index efb57478..594c0f7b 100644 --- a/apps/x/packages/core/src/application/assistant/skills/create-presentations/skill.ts +++ b/apps/x/packages/core/src/application/assistant/skills/create-presentations/skill.ts @@ -1,216 +1,83 @@ export const skill = String.raw` # PDF Presentation Generator Skill -## Overview +## When to Use -This skill enables Rowboat to create visually compelling PDF presentations from natural language requests. You have full freedom to write and execute your own code to generate presentations — install any npm packages you need, generate charts, use custom layouts, and make the output look polished and professional. - -A minimal reference implementation using @react-pdf/renderer exists in the codebase at: -- **Types:** src/application/assistant/skills/create-presentations/types.ts -- **Generator:** src/application/assistant/skills/create-presentations/presentation-generator.tsx - -**This code is just a starting point.** It shows one basic approach to PDF generation. You are NOT limited to it. Feel free to: -- Write your own code from scratch -- Use different libraries (e.g., pdfkit, puppeteer with HTML/CSS, jsPDF, or anything else) -- Install any npm packages you need via executeCommand -- Generate charts and visualizations (e.g., chartjs-node-canvas, d3-node, vega-lite, mermaid) -- Render charts as PNG images and embed them in slides -- Create custom layouts, gradients, decorative elements — whatever makes the presentation look great - -## 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. +Activate when the user wants to create presentations, slide decks, or pitch decks. ## Workflow -### Step 1: Understand the Request & Gather Preferences +1. Check ~/.rowboat/knowledge/ for relevant context about the company, product, team, etc. +2. Ensure Playwright is installed: 'npm install playwright && npx playwright install chromium' +3. Create an HTML file (e.g., /tmp/presentation.html) with slides (1280x720px each) +4. Create a Node.js script to convert HTML to PDF: -Before doing anything else, ask the user about their preferences: +~~~javascript +// save as /tmp/convert.js +const { chromium } = require('playwright'); +const path = require('path'); -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", "dark theme", "warm tones", "professional and clean") -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 +(async () => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto('file:///tmp/presentation.html', { waitUntil: 'networkidle' }); + await page.pdf({ + path: path.join(process.env.HOME, 'Desktop', 'presentation.pdf'), + width: '1280px', + height: '720px', + printBackground: true, + }); + await browser.close(); + console.log('Done: ~/Desktop/presentation.pdf'); +})(); ~~~ -### Step 3: Present the Outline for Approval +5. Run it: 'node /tmp/convert.js' +6. Tell the user: "Your presentation is ready at ~/Desktop/presentation.pdf" -Before generating slides, present a structured outline to the user: +Do NOT show HTML code to the user. Do NOT explain how to export. Just create the PDF and deliver it. -~~~ -## Proposed Presentation Outline +## PDF Export Rules -**Title:** [Presentation Title] -**Slides:** [N] slides -**Style:** [Color scheme / theme description] +**These rules prevent rendering issues in PDF. Violating them causes overlapping rectangles and broken layouts.** -### Flow: +1. **No layered elements** - Never create separate elements for backgrounds or shadows. Style content elements directly. +2. **No box-shadow** - Use borders instead: \`border: 1px solid #e5e7eb\` +3. **Bullets via CSS only** - Use \`li::before\` pseudo-elements, not separate DOM elements +4. **Content must fit** - Slides are 1280x720px with 60px padding. Safe area is 1160x600px. Use \`overflow: hidden\`. -1. **Title Slide** - - Company name, tagline, presenter name +## Required CSS -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 +~~~css +@page { size: 1280px 720px; margin: 0; } +html { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } +.slide { + width: 1280px; + height: 720px; + padding: 60px; + overflow: hidden; + page-break-after: always; + page-break-inside: avoid; +} +.slide:last-child { page-break-after: auto; } ~~~ -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. +## Playwright Export -**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. +~~~typescript +import { chromium } from 'playwright'; -### Step 4: Generate the Presentation - -Write code to generate the presentation. You have complete freedom here: - -1. **Install any packages you need** via executeCommand (e.g., npm install @react-pdf/renderer chartjs-node-canvas) -2. **Write a script** that generates the PDF — you can use the reference code as inspiration or write something entirely different -3. **Generate charts** for any data that would benefit from visualization (revenue growth, market size, traction metrics, competitive positioning, etc.) — use chartjs-node-canvas, d3, vega, or any charting library -4. **Execute the script** to produce the final PDF - -## Visual Quality Guidelines - -**Do NOT produce plain, boring slides.** Make them look professional and visually engaging: - -- **Use color intentionally** — gradient backgrounds on title/CTA slides, accent colors for bullets and highlights, colored stat numbers -- **Apply the user's brand colors** throughout — not just on the title slide, but as accents, backgrounds, and highlights across all slides -- **Charts and visualizations** — whenever there are numbers (revenue, growth, market size, user counts), generate a chart instead of just listing numbers. Bar charts, line charts, pie charts, and simple diagrams make slides far more impactful -- **Visual hierarchy** — large bold headings, generous whitespace, clear separation between sections -- **Consistent theming** — every slide should feel like part of the same deck, with consistent colors, fonts, and spacing -- **Decorative elements** — subtle accent bars, colored bullets, gradient sections, and background tints add polish - -## Slide Types (Reference) - -These are common slide patterns. You can implement these or create your own: - -| Type | Description | When to Use | -|------|-------------|-------------| -| Title | Bold opening with gradient/colored background | First slide | -| Section | Section divider between topics | Between major sections | -| Content | Text with bullet points | Explaining concepts, lists | -| Two-column | Side-by-side comparison | Us vs. them, before/after | -| Stats | Big bold numbers | Key metrics, traction, market size | -| Chart | Data visualization | Revenue growth, market breakdown, trends | -| Quote | Testimonial or notable quote | Customer feedback, press quotes | -| Image | Full or partial image with caption | Product screenshots, team photos | -| Team | Grid of team member cards | Team introduction | -| CTA | Call to action / closing | Final slide | - -## Content Limits Per Slide - -Each slide is a fixed page. Content that exceeds the available space will overflow. Follow these limits: - -| Slide Type | Max Items / Content | -|------------|-------------------| -| Content | 5 bullet points max (~80 chars each). Paragraph text: max ~4 lines. | -| Two-column | 4 bullet points per column max (~60 chars each). | -| Stats | 3-4 stats max. Keep labels short. | -| Team | 4 members max per slide. Split into multiple slides if needed. | -| Quote | Keep quotes under ~200 characters. | - -**If the user's content needs more space**, split it across multiple slides rather than cramming it into one. - -## Pitch Deck Templates - -### Series A Pitch Deck (12 slides) - -1. **Title** - Company name, tagline, presenter -2. **Problem** - What pain point you solve -3. **Solution** - Your product/service -4. **Product** - Demo/screenshots -5. **Market** - TAM/SAM/SOM (use a chart!) -6. **Business Model** - How you make money -7. **Traction** - Metrics and growth (use charts!) -8. **Competition** - Positioning (two-column or matrix chart) -9. **Team** - Key team members -10. **Financials** - Projections (use a chart!) -11. **The Ask** - Funding amount and use (pie chart for allocation) -12. **Contact** - 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 -6. **Pricing** - Plans and pricing -7. **Testimonials** - Customer quotes -8. **Get Started** - CTA - -## Best Practices - -1. **Keep slides simple** - One idea per slide -2. **Use charts for numbers** - Never just list numbers when a chart would be more impactful -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 -9. **Make it visually rich** - Colors, charts, gradients — not just text on white backgrounds +const browser = await chromium.launch(); +const page = await browser.newPage(); +await page.goto('file://' + htmlPath, { waitUntil: 'networkidle' }); +await page.pdf({ + path: '~/Desktop/presentation.pdf', + width: '1280px', + height: '720px', + printBackground: true, +}); +await browser.close(); +~~~ `; -export default skill; +export default skill; \ No newline at end of file diff --git a/apps/x/packages/core/src/application/assistant/skills/create-presentations/types.ts b/apps/x/packages/core/src/application/assistant/skills/create-presentations/types.ts deleted file mode 100644 index 888eb2e7..00000000 --- a/apps/x/packages/core/src/application/assistant/skills/create-presentations/types.ts +++ /dev/null @@ -1,100 +0,0 @@ -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; -} diff --git a/apps/x/packages/core/src/application/lib/builtin-tools.ts b/apps/x/packages/core/src/application/lib/builtin-tools.ts index 0d3756f8..49cb7d46 100644 --- a/apps/x/packages/core/src/application/lib/builtin-tools.ts +++ b/apps/x/packages/core/src/application/lib/builtin-tools.ts @@ -12,7 +12,6 @@ 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({ @@ -607,76 +606,11 @@ export const BuiltinTools: z.infer = { }, }, - 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>; - theme?: Record; - 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({ command: z.string().describe('The shell command to execute (e.g., "ls -la", "cat file.txt")'), - cwd: z.string().optional().describe('Working directory to execute the command in (defaults to workspace root)'), + cwd: z.string().optional().describe('Working directory to execute the command in (defaults to workspace root). You do not need to set this unless absolutely necessary.'), }), execute: async ({ command, cwd }: { command: string, cwd?: string }, ctx?: ToolContext) => { try {