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:
- Structure the content as JSON
- Call the
gen_pptx.pyscript - Return the path to the generated file
Generate a PDF Report
“Create a PDF report summarizing today’s system logs”
OpenClaw will:
- Gather the relevant information
- Format it as Markdown
- 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
- Use templates: Create base templates for common document types
- Include branding: Add logos and consistent colors
- Structure data first: Always organize content before generating
- 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.