agentleFS
reasonix-aireasonix-ai/skills/pptx-deck/SKILL.md

pptx-deck

Skill1 starsChanged 29 days agoMITView on GitHub

Plan and build a PowerPoint (.pptx) deck from the user's material - one claim per slide, plain wording, a real layout per slide, then generate the file with the bundled script. Use when asked for slides, a presentation or a .pptx.

  • Installs packages
  • In this repositoryOne other file it gives its agents.

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 8 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Ask only what you cannot infer, in one message: who will see it, what they should do or decide afterwards, how long it runs, and whether a …

    line 14
  2. Distinctive:

    Write the outline as one full sentence per slide stating that slide's point (the headline), not a topic label.

    line 22
  3. Very specific:

    Write a JSON file with the slides (format below).

    line 39
  4. Distinctive:

    Only read the files the user pointed to and write the spec and the output next to them.

    line 86
  5. Distinctive:

    Do not invent figures, quotes or sources.

    line 88
  6. Distinctive:

    No colon-subtitles, no question headlines unless the slide answers it at once.

    line 99
  7. Distinctive:

    No emoji as bullets or decoration.

    line 109
  8. Distinctive:

    Use the same words for the same thing across slides.

    line 110
  9. Distinctive:

    Never stretch an image. Say what a chart shows in its headline, not in a caption.

    line 122

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

pptx-deck

Turn the user's material into a deck that looks made by a person who knows the subject, not by a template. Work in this order and do not skip the first two steps; a deck built straight from a blank spec is where the generic look comes from.

1. Pin down the deck

Ask only what you cannot infer, in one message: who will see it, what they should do or decide afterwards, how long it runs, and whether a template or brand colour is given. If the material is a file, read it first. Write the answer to "what should the audience do after the last slide" as one sentence; every slide must serve it.

2. Outline as claims

Write the outline as one full sentence per slide stating that slide's point (the headline), not a topic label. Cut every slide whose sentence does not support the goal. Typical length: one slide per minute of speaking, plus a title and a closing slide. Show the outline and wait for a go-ahead or edits before building.

3. Choose a layout per slide

Pick from the layouts the script supports and vary them: title, section, points (headline plus up to 5 short bullets), two-column, image (one picture, optional caption), table, quote, numbers (up to 4 big figures with labels). Never use points for more than two slides in a row. Follow the Style rules section below for wording and visual rules; read it before writing any slide text.

4. Write the spec

Write a JSON file with the slides (format below). Put the longer explanation in notes, not on the slide. Use only images the user supplied or that you made locally; never fetch images or fonts from the network.

{
  "title": "Deck title",
  "theme": {"font": "Calibri", "accent": "1F4E79", "ink": "1A1A1A", "paper": "FFFFFF"},
  "slides": [
    {"layout": "title", "title": "...", "subtitle": "..."},
    {"layout": "points", "title": "A full-sentence claim", "bullets": ["...", "..."], "notes": "..."},
    {"layout": "numbers", "title": "...", "items": [{"value": "42%", "label": "..."}]},
    {"layout": "two-column", "title": "...", "left": {"heading": "...", "bullets": []}, "right": {"heading": "...", "bullets": []}},
    {"layout": "image", "title": "...", "image": "chart.png", "caption": "..."},
    {"layout": "table", "title": "...", "header": ["..."], "rows": [["..."]]},
    {"layout": "quote", "quote": "...", "by": "..."},
    {"layout": "section", "title": "..."}
  ]
}

5. Build and check

Save the script from the Script section below as build_deck.py in the user's working folder (read it back once so you know what it does). It needs the python-pptx package. If python3 -c "import pptx" fails, tell the user the one command to install it (python3 -m pip install python-pptx, preferably in a virtual environment) and let them run it; do not install packages yourself.

python3 build_deck.py spec.json out.pptx

The script prints a warning for each slide that breaks a size rule (too many bullets, long bullets, long headline, missing image). Fix the warnings by cutting words, not by shrinking text. If LibreOffice is installed (soffice --headless --convert-to pdf out.pptx), convert and look at the pages; otherwise say the deck was not visually checked.

6. Hand over

Give the path, the slide count, any warning that remains and why it stays, and what you could not check. Offer to adjust the outline rather than the visuals first.

Rules

  • Only read the files the user pointed to and write the spec and the output next to them; run nothing else from those files.
  • Do not invent figures, quotes or sources. A number on a slide comes from the user's material or is marked as an assumption in the notes.
  • The script below is plain text; show it to the user if they ask what it does.

Style rules

Wording and visual rules for the deck. Apply them to every slide.

Headlines

  • A full sentence that states the point ("Support tickets fell 31% after the new routing"), at most 12 words. Not a label ("Results", "Overview").
  • No colon-subtitles, no question headlines unless the slide answers it at once.

Body text

  • At most 5 bullets, each at most 14 words, each a complete thought. If a slide needs more, it is two slides or its detail belongs in the notes.
  • Plain verbs and concrete nouns. Name the actual thing, number or person.
  • Cut these habits: "leverage", "robust", "seamless", "cutting-edge", "game-changing", "unlock", "empower", "in today's fast-paced world", "it is important to note", three-item lists made only for rhythm, a closing line that restates the slide, and a bold lead-in on every bullet.
  • No emoji as bullets or decoration. No rhetorical "not just X, but Y" frames.
  • Use the same words for the same thing across slides; do not rotate synonyms.
  • Numbers carry their unit and period ("12.4 s median, March"). Round to what the audience can use.

Visuals

  • One idea, one main visual per slide. Prefer a real chart or the user's own image over decoration. Leave empty space; do not fill it.
  • One accent colour plus ink on paper. Contrast must stay readable on a projector: body text at least 20 pt, headlines at least 28 pt.
  • Align to one grid; the same margins on every slide. Do not centre long text.
  • A table has at most 6 rows and 5 columns on a slide; show the rest in the notes or an appendix.
  • Never stretch an image. Say what a chart shows in its headline, not in a caption.

Structure

  • Open with the point of the whole deck, not an agenda. Skip the agenda slide unless the audience needs to navigate a long deck.
  • Close with the decision or action asked of the audience, with owner and date if known.
  • Speaker notes hold the explanation, sources and caveats, in the speaker's own words, first person, as it would be said aloud.

Script

Save as build_deck.py. It reads the spec and the local images the spec names, writes one .pptx, and uses no network.

#!/usr/bin/env python3
"""Build a .pptx from a JSON deck spec. Reads the spec and any local images it
names, writes one file, uses no network. Needs: python3 -m pip install python-pptx

usage: build_deck.py spec.json out.pptx
"""
import json
import os
import sys

try:
    from pptx import Presentation
    from pptx.dml.color import RGBColor
    from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
    from pptx.util import Emu, Inches, Pt
except ImportError:
    sys.exit("python-pptx is not installed: python3 -m pip install python-pptx")

W, H = Inches(13.333), Inches(7.5)
MARGIN = Inches(0.8)
MAX_BULLETS, MAX_WORDS, MAX_HEADLINE = 5, 14, 12
warnings = []


def rgb(hex_):
    return RGBColor.from_string(hex_.lstrip("#").upper())


def words(text):
    return len(str(text).split())


class Deck:
    def __init__(self, theme):
        self.prs = Presentation()
        self.prs.slide_width, self.prs.slide_height = W, H
        self.font = theme.get("font", "Calibri")
        self.accent = rgb(theme.get("accent", "1F4E79"))
        self.ink = rgb(theme.get("ink", "1A1A1A"))
        self.paper = rgb(theme.get("paper", "FFFFFF"))
        self.muted = rgb(theme.get("muted", "666666"))

    def slide(self, number):
        s = self.prs.slides.add_slide(self.prs.slide_layouts[6])
        bg = s.background.fill
        bg.solid()
        bg.fore_color.rgb = self.paper
        self.current = number
        return s

    def text(self, slide, x, y, w, h, value, size, bold=False, color=None, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP):
        box = slide.shapes.add_textbox(x, y, w, h)
        frame = box.text_frame
        frame.word_wrap = True
        frame.vertical_anchor = anchor
        frame.margin_left = frame.margin_right = 0
        lines = value if isinstance(value, list) else [value]
        for i, line in enumerate(lines):
            p = frame.paragraphs[0] if i == 0 else frame.add_paragraph()
            p.alignment = align
            p.space_after = Pt(size * 0.5)
            run = p.add_run()
            run.text = str(line)
            run.font.size = Pt(size)
            run.font.bold = bold
            run.font.name = self.font
            run.font.color.rgb = color or self.ink
        return box

    def headline(self, slide, value):
        if words(value) > MAX_HEADLINE:
            warnings.append(f"slide {self.current}: headline has {words(value)} words (max {MAX_HEADLINE})")
        self.text(slide, MARGIN, Inches(0.6), W - 2 * MARGIN, Inches(1.3), value, 30, bold=True, anchor=MSO_ANCHOR.TOP)
        bar = slide.shapes.add_shape(1, MARGIN, Inches(1.95), Inches(0.9), Emu(38100))
        bar.fill.solid()
        bar.fill.fore_color.rgb = self.accent
        bar.line.fill.background()

    def bullets(self, slide, x, y, w, items, size=22):
        if len(items) > MAX_BULLETS:
            warnings.append(f"slide {self.current}: {len(items)} bullets (max {MAX_BULLETS})")
        for item in items:
            if words(item) > MAX_WORDS:
                warnings.append(f"slide {self.current}: a bullet has {words(item)} words (max {MAX_WORDS}): {str(item)[:40]}")
        self.text(slide, x, y, w, Inches(4.6), [f"•  {i}" for i in items], size)

    def notes(self, slide, value):
        if value:
            slide.notes_slide.notes_text_frame.text = str(value)


def layout_title(d, s, spec):
    d.text(s, MARGIN, Inches(2.4), W - 2 * MARGIN, Inches(1.6), spec.get("title", ""), 44, bold=True, anchor=MSO_ANCHOR.BOTTOM)
    if spec.get("subtitle"):
        d.text(s, MARGIN, Inches(4.2), W - 2 * MARGIN, Inches(1.0), spec["subtitle"], 22, color=d.muted)


def layout_section(d, s, spec):
    d.text(s, MARGIN, Inches(3.0), W - 2 * MARGIN, Inches(1.5), spec.get("title", ""), 38, bold=True, color=d.accent, anchor=MSO_ANCHOR.MIDDLE)


def layout_points(d, s, spec):
    d.headline(s, spec.get("title", ""))
    d.bullets(s, MARGIN, Inches(2.4), W - 2 * MARGIN, spec.get("bullets", []))


def layout_two(d, s, spec):
    d.headline(s, spec.get("title", ""))
    half = (W - 2 * MARGIN - Inches(0.6)) / 2
    for i, side in enumerate(("left", "right")):
        col = spec.get(side, {})
        x = MARGIN + i * (half + Inches(0.6))
        if col.get("heading"):
            d.text(s, x, Inches(2.4), half, Inches(0.6), col["heading"], 24, bold=True, color=d.accent)
        d.bullets(s, x, Inches(3.1), half, col.get("bullets", []), size=20)


def layout_image(d, s, spec):
    d.headline(s, spec.get("title", ""))
    path = spec.get("image", "")
    if not path or not os.path.isfile(path):
        warnings.append(f"slide {d.current}: image not found: {path!r}")
        return
    box_w, box_h = W - 2 * MARGIN, Inches(4.2)
    pic = s.shapes.add_picture(path, MARGIN, Inches(2.3))
    ratio = min(box_w / pic.width, box_h / pic.height)
    pic.width, pic.height = int(pic.width * ratio), int(pic.height * ratio)
    pic.left = int((W - pic.width) / 2)
    if spec.get("caption"):
        d.text(s, MARGIN, Inches(6.65), W - 2 * MARGIN, Inches(0.5), spec["caption"], 14, color=d.muted, align=PP_ALIGN.CENTER)


def layout_table(d, s, spec):
    d.headline(s, spec.get("title", ""))
    header, rows = spec.get("header", []), spec.get("rows", [])
    if len(rows) > 6 or len(header) > 5:
        warnings.append(f"slide {d.current}: table is {len(rows)} rows x {len(header)} columns (max 6 x 5)")
    if not header:
        return
    shape = s.shapes.add_table(len(rows) + 1, len(header), MARGIN, Inches(2.4), W - 2 * MARGIN, Inches(0.5) * (len(rows) + 1))
    for c, label in enumerate(header):
        cell = shape.table.cell(0, c)
        cell.text = str(label)
        cell.fill.solid()
        cell.fill.fore_color.rgb = d.accent
        cell.text_frame.paragraphs[0].runs[0].font.color.rgb = d.paper
        cell.text_frame.paragraphs[0].runs[0].font.bold = True
    for r, row in enumerate(rows, start=1):
        for c, value in enumerate(row[: len(header)]):
            shape.table.cell(r, c).text = str(value)
    for row in shape.table.rows:
        for cell in row.cells:
            for p in cell.text_frame.paragraphs:
                for run in p.runs:
                    run.font.size = Pt(18)
                    run.font.name = d.font


def layout_quote(d, s, spec):
    d.text(s, Inches(1.4), Inches(2.0), W - Inches(2.8), Inches(3.0), "“" + spec.get("quote", "") + "”", 32, anchor=MSO_ANCHOR.MIDDLE)
    if spec.get("by"):
        d.text(s, Inches(1.4), Inches(5.2), W - Inches(2.8), Inches(0.6), "— " + spec["by"], 20, color=d.muted)


def layout_numbers(d, s, spec):
    d.headline(s, spec.get("title", ""))
    items = spec.get("items", [])[:4]
    if len(spec.get("items", [])) > 4:
        warnings.append(f"slide {d.current}: more than 4 figures; showing 4")
    if not items:
        return
    gap = Inches(0.5)
    each = (W - 2 * MARGIN - gap * (len(items) - 1)) / len(items)
    for i, item in enumerate(items):
        x = MARGIN + i * (each + gap)
        d.text(s, x, Inches(2.8), each, Inches(1.4), item.get("value", ""), 54, bold=True, color=d.accent)
        d.text(s, x, Inches(4.3), each, Inches(1.4), item.get("label", ""), 20, color=d.muted)


LAYOUTS = {"title": layout_title, "section": layout_section, "points": layout_points, "two-column": layout_two,
           "image": layout_image, "table": layout_table, "quote": layout_quote, "numbers": layout_numbers}


def main():
    if len(sys.argv) != 3:
        sys.exit(__doc__)
    spec_path, out_path = sys.argv[1], sys.argv[2]
    with open(spec_path, encoding="utf-8") as f:
        spec = json.load(f)
    base = os.path.dirname(os.path.abspath(spec_path))
    deck = Deck(spec.get("theme", {}))
    for n, item in enumerate(spec.get("slides", []), start=1):
        kind = item.get("layout", "points")
        if kind not in LAYOUTS:
            warnings.append(f"slide {n}: unknown layout {kind!r}, used points")
            kind = "points"
        if item.get("image") and not os.path.isabs(item["image"]):
            item["image"] = os.path.join(base, item["image"])
        slide = deck.slide(n)
        LAYOUTS[kind](deck, slide, item)
        deck.notes(slide, item.get("notes"))
    deck.prs.save(out_path)
    for line in warnings:
        print("warning:", line)
    print(f"wrote {out_path}: {len(deck.prs.slides)} slides, {len(warnings)} warning(s)")


if __name__ == "__main__":
    main()

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.