Generated banner for PPTX Reference Deck Analysis
Generated logo for PPTX Reference Deck Analysis

PPTX Reference Deck Analysis

Seth HobsonCommunityCreated by kimtthDistributed by Seth Hobson

  • Skill
  • Research and analysis
  • Documents and content
  • MIT
  • Low risk
  • Has scripts

From the author

Use when analyzing a reference PPTX for read-only structure, theme, typography, layout rhythm, diagnostics, derived template catalogs, or safe OOXML package inspection.
— the description by kimtth

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a secure, read-only framework for inspecting PowerPoint (.pptx) files to extract design evidence and structural metadata. It is designed to help users understand the layout, typography, theme, and OOXML package integrity of a reference deck, facilitating the creation of new presentations based on existing design patterns without directly copying or mutating the source files.

What it does

  • Extracts slide counts, text summaries, and shape counts from PPTX files.
  • Analyzes design elements including theme colors, fonts, and layout rhythm.
  • Performs OOXML package diagnostics to identify malformed XML or broken relationships.
  • Generates a derived template catalog from source deck masters and layouts.
  • Provides tools for raw package inspection, including relationship mapping and media identification.
  • Maintains a strict read-only policy to ensure source deck integrity.

When to use it

  • When you need to reverse-engineer the design system of an existing presentation.
  • When auditing a deck for structural errors or broken internal relationships.
  • When you need to extract specific layout or theme data to inform a new presentation project.
  • When performing security-conscious inspection of untrusted OOXML packages.

Advantages

  • Ensures source files remain unmodified and safe from accidental changes.
  • Provides granular visibility into the underlying OOXML structure.
  • Includes built-in safety measures against malicious XML content.
  • Facilitates consistent design by extracting reusable layout and theme tokens.

Usage examples

“Analyze the structure and theme of corporate-deck.pptx to create a style guide.”
The agent will output a report detailing the color scheme, font usage, and layout patterns found in the deck, providing a basis for a new template.
“Check the integrity of legacy-presentation.pptx for broken links or malformed XML.”
The agent will run a validation script and return a manifest highlighting any orphaned parts or content-type gaps within the package.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/wshobson/pptx-reference-deck-analysis/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip pptx-reference-deck-analysis.zip -d .agents/skills

Documentation by kimtth

PPTX Reference Deck Analysis

Inspect a reference deck as design evidence. This skill never copies, clones, or mutates a source deck.

Contract

Implement required extraction on demand with a small task-local python-pptx script. Capture only the information required for the new deck:

  • compact prompt context: slide count and size, text summaries, shape counts, styles, brand signals, template use, and layout rhythm;

  • full extraction: summary, slides, and read-only layout_tree evidence;

  • folder diagnostics: one result per deck plus a manifest;

  • style-master analysis: colors, fonts, size distribution, master/layout use, and flow patterns;

  • derived template catalog: zero-based source indices, layout roles, usable regions, placeholders, visual structures, and constraints.

Rules

  • Keep the source deck read-only and independently author every target-slide coordinate.

  • Use the bundled OOXML utilities only for raw themes, relationships, notes, comments, animations, media, masters, or layouts that high-level extraction cannot expose.

  • Record inspected parts and parsing exceptions in the analysis manifest.

  • Do not use extracted content, fonts, images, or proprietary assets in a generated deck without explicit permission and license evidence.

OOXML package inspection

Install defusedxml from requirements.txt before using the bundled utilities.

  1. Run scripts/inspect.py <deck.pptx> for a compact JSON report of slide order, text, theme tokens, relationships, notes, comments, animations, and media.

  2. Run scripts/validate_package.py <deck.pptx> --output <report.json> for malformed XML, broken internal relationships, content-type gaps, duplicate layout links, and orphaned parts.

  3. Run scripts/unpack.py <deck.pptx> <output-dir> only when raw-package evidence is necessary.

  4. Resolve relationship targets relative to the .rels owner; never infer slide order from filenames.

Safety
  • Never modify a supplied deck or blindly copy package parts into a new deck.

  • Run the scripts from a trusted workspace; they reject path traversal, symlinks, oversized members, and archive bombs.

  • Parse untrusted XML with defusedxml; do not enable entity expansion, DTD loading, or network access.

  • Treat theme colors as tokens unless fully resolved against the color scheme.

See references/reference-deck-analysis.md for output shapes, references/reference-deck-analysis-patterns.md for documentation-only patterns, and references/ooxml-parsing.md for package part maps.

Files

scripts/inspect.py

#!/usr/bin/env python3
"""Read a PPTX OOXML package into a compact, JSON-safe inspection report."""
from __future__ import annotations
import json
import posixpath
import stat
import sys
import zipfile
from collections import Counter
from pathlib import Path, PurePosixPath
from defusedxml import ElementTree as ET

P = "{http://schemas.openxmlformats.org/presentationml/2006/main}"
A = "{http://schemas.openxmlformats.org/drawingml/2006/main}"
R = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}"
PR = "{http://schemas.openxmlformats.org/package/2006/relationships}"
MAX_MEMBERS = 5_000
MAX_MEMBER_SIZE = 100 * 1024 * 1024
MAX_TOTAL_SIZE = 512 * 1024 * 1024
MAX_COMPRESSION_RATIO = 1_000


def _write_stdout(value: str) -> None:
    """Write UTF-8 JSON without depending on the console code page."""
    sys.stdout.buffer.write(value.encode("utf-8"))


def _workspace_path(value: str) -> Path:
    root = Path.cwd().resolve()
    path = Path(value).expanduser().resolve()
    if not path.is_relative_to(root):
        raise ValueError(f"Path escapes the current workspace: {value}")
    return path

def _validate_archive(archive: zipfile.ZipFile) -> None:
    members = archive.infolist()
    if len(members) > MAX_MEMBERS:
        raise ValueError("Archive contains too many entries")
    total = 0
    for member in members:
        if stat.S_ISLNK(member.external_attr >> 16):
            raise ValueError(f"Archive contains a symlink: {member.filename}")
        if member.file_size > MAX_MEMBER_SIZE:
            raise ValueError(f"Archive entry is too large: {member.filename}")
        total += member.file_size
        if total > MAX_TOTAL_SIZE:
            raise ValueError("Archive uncompressed size is too large")
        if member.compress_size and member.file_size / member.compress_size > MAX_COMPRESSION_RATIO:
            raise ValueError(f"Suspicious compression ratio: {member.filename}")

def _source_part(rels_name: str) -> str | None:
    path = PurePosixPath(rels_name)
    if str(path) == "_rels/.rels":
        return ""
    if path.parent.name != "_rels" or not path.name.endswith(".rels"):
        return None
    return str(path.parent.parent / path.name.removesuffix(".rels"))


def _part_target(rels_name: str, target: str) -> str | None:
    source = _source_part(rels_name)
    if source is None:
        return None
    if target.startswith("/"):
        resolved = posixpath.normpath(target.lstrip("/"))
    else:
        resolved = posixpath.normpath(posixpath.join(posixpath.dirname(source), target))
    return resolved if resolved not in {"", ".", ".."} and not resolved.startswith("../") else None

def _xml(archive: zipfile.ZipFile, name: str):
    return ET.fromstring(archive.read(name))

def _relationships(archive: zipfile.ZipFile, name: str) -> dict[str, dict[str, str]]:
    if name not in archive.namelist():
        return {}
    relationships = {}
    for item in _xml(archive, name).findall(f"{PR}Relationship"):
        mode = item.get("TargetMode", "Internal")
        target = item.get("Target", "")
        relationships[item.get("Id", "")] = {
            "type": item.get("Type", "").rsplit("/", 1)[-1],
            "target": target if mode == "External" else _part_target(name, target),
            "mode": mode,
        }
    return relationships

def inspect(path: str) -> dict:
    with zipfile.ZipFile(path) as archive:
        _validate_archive(archive)
        names = set(archive.namelist())
        presentation_rels = _relationships(archive, "ppt/_rels/presentation.xml.rels")
        presentation = _xml(archive, "ppt/presentation.xml")
        theme_name = "ppt/theme/theme1.xml"
        theme = _xml(archive, theme_name) if theme_name in names else None
        colors = [] if theme is None else [{"name": item.tag.removeprefix(A), "value": next((child.get("val") for child in item), None)} for item in theme.findall(f".//{A}clrScheme/*")]
        fonts = {} if theme is None else {key: node.get("typeface") for key, node in (("major_latin", theme.find(f".//{A}majorFont/{A}latin")), ("minor_latin", theme.find(f".//{A}minorFont/{A}latin"))) if node is not None}
        slides = []
        for index, item in enumerate(presentation.findall(f".//{P}sldId"), start=1):
            rel = presentation_rels.get(item.get(f"{R}id", ""), {})
            slide_name = rel.get("target", "")
            if slide_name not in names:
                slides.append({"index": index, "part": slide_name, "error": "missing slide part"})
                continue
            slide = _xml(archive, slide_name)
            rels_name = f"{PurePosixPath(slide_name).parent}/_rels/{PurePosixPath(slide_name).name}.rels"
            rels = _relationships(archive, rels_name)
            text = "".join(node.text or "" for node in slide.findall(f".//{A}t"))
            shape_counts = Counter(node.tag.removeprefix(P) for node in slide.findall(f".//{P}spTree/*"))
            slides.append({"index": index, "part": slide_name, "slide_id": item.get("id"), "hidden": item.get("show") == "0", "text": text, "shape_counts": dict(shape_counts), "relationships": list(rels.values()), "has_transition": slide.find(f"{P}transition") is not None, "has_timing": slide.find(f"{P}timing") is not None})
        return {"deck": path, "slide_count": len(slides), "theme": {"colors": colors, "fonts": fonts}, "slides": slides, "media": sorted(name for name in names if name.startswith("ppt/media/")), "notes_parts": sorted(name for name in names if name.startswith("ppt/notesSlides/notesSlide")), "comment_parts": sorted(name for name in names if "/comments" in name.lower())}

def main(argv: list[str] | None = None) -> None:
    argv = sys.argv[1:] if argv is None else argv
    if len(argv) != 1:
        raise SystemExit("Usage: python inspect.py <deck.pptx>")
    path = _workspace_path(argv[0])
    if not path.is_file():
        raise SystemExit(f"Input package does not exist: {path}")
    _write_stdout(json.dumps(inspect(str(path)), ensure_ascii=False, indent=2) + "\n")

if __name__ == "__main__":
    main()

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
kimtth
Publisher
Seth Hobson (Community)
Distributed by
Seth Hobson
Source
wshobson/agents · plugins/pptx-deck-creation/skills/pptx-reference-deck-analysis · commit 4bde316
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by kimtth or Seth Hobson.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download pptx-reference-deck-analysis.zip