Welcome to Day 7 of our OpenClaw series! Today we’re turning our AI assistant into an office automation powerhouse. By the end of this tutorial, your Raspberry Pi will be generating professional PowerPoint presentations and PDF documents on demand.

Why Document Automation?

Imagine telling your AI: “Create a presentation about Q4 sales results” or “Generate a PDF report from this data” — and having it just… happen. That’s what we’re building today.

Common use cases:

  • Weekly reports generated automatically
  • Meeting summaries turned into shareable PDFs
  • Data visualizations packaged into presentations
  • Documentation created from code or notes

Prerequisites

Before we start, make sure you have:

  • OpenClaw running on your Raspberry Pi (from Day 1)
  • Python 3.9+ installed
  • Basic familiarity with the command line

Installing the Required Tools

1. Python Libraries for Document Generation

# PowerPoint generation
pip install python-pptx

# PDF generation
pip install reportlab
pip install weasyprint  # For HTML-to-PDF conversion

# Optional: Markdown to PDF
pip install markdown
pip install pdfkit

2. System Dependencies (for WeasyPrint)

sudo apt-get update
sudo apt-get install -y \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libgdk-pixbuf2.0-0 \
    libffi-dev \
    shared-mime-info

3. wkhtmltopdf (for pdfkit)

sudo apt-get install -y wkhtmltopdf

Creating a PowerPoint Generation Skill

Let’s create a skill that generates PowerPoint presentations. Create a new directory:

mkdir -p ~/.openclaw/workspace/skills/pptx-generator
cd ~/.openclaw/workspace/skills/pptx-generator

The Skill Definition (SKILL.md)

# PowerPoint Generator Skill

Generate professional PowerPoint presentations from structured content.

## Usage

Call the script with a JSON structure containing slides:

\`\`\`bash
python3 gen_pptx.py '<json_content>' output.pptx
\`\`\`

## JSON Format

\`\`\`json
{
  "title": "Presentation Title",
  "author": "Your Name",
  "slides": [
    {
      "type": "title",
      "title": "Main Title",
      "subtitle": "Subtitle"
    },
    {
      "type": "content",
      "title": "Slide Title",
      "bullets": ["Point 1", "Point 2", "Point 3"]
    },
    {
      "type": "two_column",
      "title": "Comparison",
      "left": ["Left 1", "Left 2"],
      "right": ["Right 1", "Right 2"]
    }
  ]
}
\`\`\`

The Generator Script (gen_pptx.py)

#!/usr/bin/env python3
"""
PowerPoint Generator for OpenClaw
Generates .pptx files from JSON content
"""

import json
import sys
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RgbColor
from pptx.enum.text import PP_ALIGN

def create_presentation(content: dict, output_path: str):
    """Create a PowerPoint presentation from structured content."""
    prs = Presentation()
    prs.slide_width = Inches(13.333)
    prs.slide_height = Inches(7.5)
    
    for slide_data in content.get('slides', []):
        slide_type = slide_data.get('type', 'content')
        
        if slide_type == 'title':
            add_title_slide(prs, slide_data)
        elif slide_type == 'content':
            add_content_slide(prs, slide_data)
        elif slide_type == 'two_column':
            add_two_column_slide(prs, slide_data)
        elif slide_type == 'image':
            add_image_slide(prs, slide_data)
    
    prs.save(output_path)
    print(f"Presentation saved to {output_path}")

def add_title_slide(prs, data):
    """Add a title slide."""
    layout = prs.slide_layouts[6]  # Blank
    slide = prs.slides.add_slide(layout)
    
    # Title
    title_box = slide.shapes.add_textbox(
        Inches(0.5), Inches(2.5), Inches(12.333), Inches(1.5)
    )
    title_frame = title_box.text_frame
    title_para = title_frame.paragraphs[0]
    title_para.text = data.get('title', 'Untitled')
    title_para.font.size = Pt(44)
    title_para.font.bold = True
    title_para.alignment = PP_ALIGN.CENTER
    
    # Subtitle
    if 'subtitle' in data:
        sub_box = slide.shapes.add_textbox(
            Inches(0.5), Inches(4.2), Inches(12.333), Inches(1)
        )
        sub_frame = sub_box.text_frame
        sub_para = sub_frame.paragraphs[0]
        sub_para.text = data['subtitle']
        sub_para.font.size = Pt(24)
        sub_para.alignment = PP_ALIGN.CENTER

def add_content_slide(prs, data):
    """Add a content slide with bullets."""
    layout = prs.slide_layouts[6]  # Blank
    slide = prs.slides.add_slide(layout)
    
    # Title
    title_box = slide.shapes.add_textbox(
        Inches(0.5), Inches(0.5), Inches(12.333), Inches(1)
    )
    title_frame = title_box.text_frame
    title_para = title_frame.paragraphs[0]
    title_para.text = data.get('title', '')
    title_para.font.size = Pt(32)
    title_para.font.bold = True
    
    # Bullets
    content_box = slide.shapes.add_textbox(
        Inches(0.75), Inches(1.75), Inches(11.833), Inches(5)
    )
    tf = content_box.text_frame
    tf.word_wrap = True
    
    for i, bullet in enumerate(data.get('bullets', [])):
        if i == 0:
            p = tf.paragraphs[0]
        else:
            p = tf.add_paragraph()
        p.text = f"• {bullet}"
        p.font.size = Pt(20)
        p.space_after = Pt(12)

def add_two_column_slide(prs, data):
    """Add a two-column comparison slide."""
    layout = prs.slide_layouts[6]
    slide = prs.slides.add_slide(layout)
    
    # Title
    title_box = slide.shapes.add_textbox(
        Inches(0.5), Inches(0.5), Inches(12.333), Inches(1)
    )
    title_frame = title_box.text_frame
    title_para = title_frame.paragraphs[0]
    title_para.text = data.get('title', '')
    title_para.font.size = Pt(32)
    title_para.font.bold = True
    
    # Left column
    left_box = slide.shapes.add_textbox(
        Inches(0.75), Inches(1.75), Inches(5.5), Inches(5)
    )
    left_tf = left_box.text_frame
    for i, item in enumerate(data.get('left', [])):
        p = left_tf.paragraphs[0] if i == 0 else left_tf.add_paragraph()
        p.text = f"• {item}"
        p.font.size = Pt(18)
    
    # Right column
    right_box = slide.shapes.add_textbox(
        Inches(7), Inches(1.75), Inches(5.5), Inches(5)
    )
    right_tf = right_box.text_frame
    for i, item in enumerate(data.get('right', [])):
        p = right_tf.paragraphs[0] if i == 0 else right_tf.add_paragraph()
        p.text = f"• {item}"
        p.font.size = Pt(18)

def add_image_slide(prs, data):
    """Add a slide with an image."""
    layout = prs.slide_layouts[6]
    slide = prs.slides.add_slide(layout)
    
    # Title
    if 'title' in data:
        title_box = slide.shapes.add_textbox(
            Inches(0.5), Inches(0.5), Inches(12.333), Inches(1)
        )
        title_para = title_box.text_frame.paragraphs[0]
        title_para.text = data['title']
        title_para.font.size = Pt(32)
        title_para.font.bold = True
    
    # Image
    if 'image_path' in data:
        slide.shapes.add_picture(
            data['image_path'],
            Inches(1), Inches(1.75),
            width=Inches(11.333)
        )

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: python gen_pptx.py '<json_content>' output.pptx")
        sys.exit(1)
    
    content = json.loads(sys.argv[1])
    output_path = sys.argv[2]
    create_presentation(content, output_path)

Creating a PDF Generation Skill

Now let’s add PDF generation capabilities:

mkdir -p ~/.openclaw/workspace/skills/pdf-generator

PDF Generator Script (gen_pdf.py)

#!/usr/bin/env python3
"""
PDF Generator for OpenClaw
Generates PDF files from Markdown or HTML content
"""

import sys
import markdown
from weasyprint import HTML, CSS
from pathlib import Path

DEFAULT_CSS = """
@page {
    size: A4;
    margin: 2cm;
}

body {
    font-family: 'Helvetica Neue', Arial, sans-serif;
    font-size: 11pt;
    line-height: 1.6;
    color: #333;
}

h1 {
    color: #2c3e50;
    border-bottom: 2px solid #3498db;
    padding-bottom: 10px;
}

h2 {
    color: #34495e;
    margin-top: 24px;
}

h3 {
    color: #7f8c8d;
}

code {
    background: #f4f4f4;
    padding: 2px 6px;
    border-radius: 3px;
    font-family: 'Fira Code', monospace;
}

pre {
    background: #2d2d2d;
    color: #f8f8f2;
    padding: 16px;
    border-radius: 8px;
    overflow-x: auto;
}

pre code {
    background: none;
    padding: 0;
}

blockquote {
    border-left: 4px solid #3498db;
    margin-left: 0;
    padding-left: 16px;
    color: #666;
}

table {
    border-collapse: collapse;
    width: 100%;
    margin: 16px 0;
}

th, td {
    border: 1px solid #ddd;
    padding: 12px;
    text-align: left;
}

th {
    background: #3498db;
    color: white;
}

tr:nth-child(even) {
    background: #f9f9f9;
}
"""

def markdown_to_pdf(md_content: str, output_path: str, css: str = None):
    """Convert Markdown content to PDF."""
    # Convert Markdown to HTML
    html_content = markdown.markdown(
        md_content,
        extensions=['tables', 'fenced_code', 'codehilite', 'toc']
    )
    
    # Wrap in full HTML document
    full_html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        {html_content}
    </body>
    </html>
    """
    
    # Generate PDF
    css_to_use = css or DEFAULT_CSS
    HTML(string=full_html).write_pdf(
        output_path,
        stylesheets=[CSS(string=css_to_use)]
    )
    print(f"PDF saved to {output_path}")

def html_to_pdf(html_path: str, output_path: str):
    """Convert HTML file to PDF."""
    HTML(filename=html_path).write_pdf(output_path)
    print(f"PDF saved to {output_path}")

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: python gen_pdf.py <input.md|input.html> output.pdf")
        sys.exit(1)
    
    input_path = sys.argv[1]
    output_path = sys.argv[2]
    
    if input_path.endswith('.md'):
        with open(input_path, 'r') as f:
            md_content = f.read()
        markdown_to_pdf(md_content, output_path)
    elif input_path.endswith('.html'):
        html_to_pdf(input_path, output_path)
    else:
        # Treat as raw markdown content
        markdown_to_pdf(input_path, output_path)

Using the Skills with OpenClaw

Now that we have our generators, let’s use them! Here are some example prompts:

Generate a Presentation

“Create a PowerPoint presentation about the benefits of AI automation with 5 slides”

OpenClaw will:

  1. Structure the content as JSON
  2. Call the gen_pptx.py script
  3. Return the path to the generated file

Generate a PDF Report

“Create a PDF report summarizing today’s system logs”

OpenClaw will:

  1. Gather the relevant information
  2. Format it as Markdown
  3. Convert to PDF using gen_pdf.py

Practical Example: Weekly Report Automation

Let’s create a cron job that generates a weekly PDF report:

openclaw cron add \
  --name "weekly-report" \
  --schedule "0 9 * * 1" \
  --message "Generate a weekly summary report as PDF. Include: system uptime, tasks completed, any issues encountered. Save to ~/reports/week-$(date +%V).pdf"

Tips for Better Documents

  1. Use templates: Create base templates for common document types
  2. Include branding: Add logos and consistent colors
  3. Structure data first: Always organize content before generating
  4. Validate output: Check generated files for formatting issues

What’s Next?

Tomorrow in Day 8, we’ll integrate OpenClaw with Home Assistant to control your smart home! Your AI will be able to turn on lights, check sensors, and automate your entire house.

Resources


This is part of our 10-day series on building a personal AI assistant with OpenClaw on Raspberry Pi. Follow along at 67ailab.com.