Skip to main content

compono

Agent-oriented, code-based PPTX generation. Describe a deck as typed primitives — an LLM agent never writes raw coordinates or touches OOXML.

compono lets an LLM agent (or a human) describe a slide deck as data — headers, bullet text, stats, tables, charts, images, process sequences, shapes — and get back a real, editable .pptx file. The agent never writes raw x/y/w/h coordinates: a constraint-based layout resolver computes every position from a small set of typed primitives.

Every rendered element is a genuine, editable native shape (p:sp, p:pic, p:graphicFrame) — never a flattened image or embedded video. Open the result in PowerPoint and drag a box around; it's a real object, not a picture of one.

This file is both the human-facing README and the in-context reference an agent uses to call compono correctly — see skills/compono/SKILL.md for the packaged version of the same content.

See it in action

compono isn't scoped to one deck genre — the same primitives compose into client proposals, conference talks, research talks, college presentations, or a lighter explainer. Every image below is rendered directly from the matching examples/*.json spec (.pptx → PNG via LibreOffice, see scripts/render_example_screenshots.py) — nothing here is a mockup:

Client proposal Conference talk
KPI grid Planner/Executor architecture
Research talk Fun explainer
Loss curves Roast levels

shape + connector compose into real diagrams, not just colored boxes — a layered system architecture, built entirely from examples/architecture_diagram.json:

Layered architecture: client → gateway → services → queue → database

See examples/ for the full specs (client_proposal.json, conference_talk.json, research_talk.json, college_presentation.json, fun_explainer.json, architecture_diagram.json, and full_catalog.json).

Install

pip install compono
# or
uv add compono

For local development, see CONTRIBUTING.md.

Quickstart

from compono import render_deck

spec = {
    "slides": [
        {
            "header": {"title": "Q3 Results", "subtitle": "Engineering team"},
            "body": [
                {
                    "primitive": "text",
                    "mode": "bullets",
                    "content": [
                        "Shipped the new layout resolver",
                        "Cut render time by 40%",
                        "Zero overflow bugs in production",
                    ],
                    "emphasis_indices": [1],
                }
            ],
        }
    ]
}

report = render_deck(spec, "deck.pptx")
print(report.pptx_path, report.warnings)

Or from the command line:

compono validate spec.json
compono render spec.json -o deck.pptx

Core concepts

  • Two required verbs, one optional third. render_deck(spec, output_path) and validate(spec) are the core loop — validate is cheap, no pptx write, millisecond-scale, so an agent can iterate on a spec before paying render cost. review(spec) is a separate, never-blocking third verb for design-quality suggestions (contrast, whitespace, image fit) — pair it with the other two, it doesn't replace either.
  • A spec is plain data. Either a raw dict/JSON (what an agent's tool-calling naturally produces) or the typed builder classes (Deck, Header, Text, ...) — both serialize to the identical shape. There's no divergence between the two paths.
  • You never write coordinates. Every primitive claims space in a slide; the resolver (a CSS-flexbox-style directional box model) computes real EMU positions. grid is the one primitive that does true 2D row/column math.
  • Errors are fixes, not diagnoses. Every validation/render failure is {slide, primitive, field, error, detail, fix} — see Error shape below.
  • render_deck returns a report, not just a file{pptx_path, manifest, warnings, actual_layout} — so an agent can reason about what happened without reopening the file.

API reference

from compono import (
    render_deck, validate, review, reference,
    Deck, Slide, Header, Text, Image, Stat, Grid, Table, Sequence, Chart, Shape,
    DeckValidationError,
)
Symbol Signature Notes
render_deck render_deck(spec, output_path, *, template=None) -> RenderReport Validates, resolves layout, writes a real .pptx. Raises DeckValidationError on any error — nothing is written on failure.
validate validate(spec, *, template=None) -> ValidationReport Schema + layout + text-overflow checks. No file I/O. Never raises — check .valid/.errors.
review review(spec, *, template=None) -> ReviewReport Design-quality suggestions (contrast, whitespace, image fit, font-size proximity to overflow). Never blocking — no valid/invalid, only .suggestions (possibly empty) and .warnings. Complements validate, doesn't replace it.
reference reference() -> str The full agent-facing reference doc (this file's content), packaged inside compono itself — for an agent with only shell/code-exec access, no MCP connection or Claude Code skill loaded. Also compono reference on the CLI.
DeckValidationError exc.errors -> list[dict] The one exception type. Carries the structured error list below.

A Deck is {template?: str, slides: [Slide, ...]}. A Slide is {header?: Header, body: [primitive, ...], notes?: str}. body (and grid.items) accept any primitive, keyed by its "primitive" field.

Error shape

{
  "slide": 3,
  "primitive": "grid.items[1]",
  "field": "content",
  "error": "overflow",
  "detail": "Text is ~14pt too tall for the box at font size 18pt (6 lines).",
  "fix": "Shorten the text, reduce bullet/line count, or split into two slides."
}

The feedback loop in practice

This is the actual point of validate() being cheap and separate from render_deck() — an agent runs it first, gets back something it can act on, and only pays render cost once the spec is clean. No new API for this, just the two verbs above used the way they're meant to be:

1st pass — a real spec, sharing a slide with two stats, packs in three long bullets:

spec = {
    "slides": [{
        "header": {"title": "Q3 Roadmap"},
        "body": [
            {"primitive": "text", "mode": "bullets", "content": [
                "Ship onboarding redesign across web, iOS, and Android, "
                "with full localization support for every launch market",
                "Migrate billing to the new usage-based pricing engine, "
                "including proration, credits, and dunning retries",
                "Roll out SSO and SCIM provisioning for enterprise "
                "customers across every supported identity provider",
            ]},
            {"primitive": "stat", "value": "42%", "label": "YoY revenue growth"},
            {"primitive": "stat", "value": "99.97%", "label": "platform uptime"},
        ],
    }]
}
validate(spec).errors
[{
  "slide": 0, "primitive": "body[0]", "field": "content", "error": "overflow",
  "detail": "Text is ~22pt too tall for the box at font size 18pt (6 lines).",
  "fix": "Shorten the text, reduce bullet/line count, or split into two slides."
}]

2nd pass — the agent applies the fix verbatim (shortens the bullets), nothing else about the spec changes:

spec["slides"][0]["body"][0]["content"] = [
    "Ship onboarding redesign across web, iOS, and Android",
    "Migrate billing to usage-based pricing",
    "Roll out SSO and SCIM for enterprise customers",
]
validate(spec).valid  # True
render_deck(spec, "q3-roadmap.pptx")  # now succeeds

render_deck would have raised DeckValidationError on the 1st-pass spec instead of writing a broken file — the loop above is what an agent actually runs, not a hypothetical.

Design review (review())

validate() answers "will this render without breaking." review() answers "does this look good" — a separate, never-blocking verb: no .valid, just .suggestions (possibly empty) and .warnings. Pair the two — review() assumes a structurally valid deck.

spec = {
    "slides": [{
        "header": {"title": "Architecture"},
        "body": [{
            "primitive": "shape", "kind": "rounded_rect", "fill": "#111827",
            "text": {"content": "Gateway", "color": "#1F2937"},
        }],
    }]
}
review(spec).suggestions
[{
  "slide": 0, "primitive": "body[0]", "field": "text.color", "category": "contrast",
  "detail": "text.color '#1F2937' against fill '#111827' has a contrast ratio of ~1.2:1 (WCAG AA wants 4.5:1).",
  "fix": "Pick a lighter/darker text.color for more contrast against fill, or use a lighter/darker fill."
}]

Four categories today:

Category Checks Requires
contrast WCAG-style ratio between shape.text.color and shape.fill Both set explicitly — never guesses a color that wasn't given.
whitespace A body of exactly one primitive left alone in a tall box Nothing — but never fires on a header-only slide (no body at all). A title/closing slide being sparse is the deliberate pattern that fix shipped in 0.1.1; there's nothing to be "too empty" relative to.
image_fit A real image (not a placeholder) whose aspect ratio diverges a lot from its box, under fit="cover" (crops) or fit="contain" (large empty bars) A real src, not a placeholder — nothing to measure otherwise.
font_size Text using most of its box's height without (yet) overflowing A font (same fallback as overflow validation) — skipped, not faked, otherwise.

Primitive catalog

Every primitive accepts an optional id (needed if another primitive references it, e.g. a connector) and an optional notes (speaker notes).

Primitive Key fields Purpose
header title, subtitle?, eyebrow?, align Slide title region.
text mode (paragraph/bullets), content, columns?, emphasis_indices? Prose or bullet list.
image src?, placeholder, caption?, fit (cover/contain) A real picture, or a first-class placeholder — see below.
stat value, label, trend? A headline number with a label.
grid items, columns, direction, align, justify The one primitive with true 2D layout. Items can be any primitive, including nested grids.
table headers, rows, emphasis_row?, emphasis_col? Renders as a real OOXML table (p:graphicFrame), not an image.
sequence steps ({label, description?}), orientation A row/column of connected step boxes — process/timeline diagrams.
chart chart_type (bar/line/pie), categories, series A real, editable native chart with live data — not a picture of a chart.
shape kind (rect/rounded_rect/oval/line/arrow/connector), fill, fill_style (solid default, or gradient), border, connects?, text? (content, align, valign, autofit, color?) Freeform shape, optionally with text inside, or a connector between two other primitives by id. Set text.color explicitly against a dark fillreview()'s contrast check can only evaluate it when both are given.

Every schema field's description is written as an instruction (e.g. "Keep under ~60 characters — longer titles will be shrunk by the resolver"), not a bare type label — call Header.model_json_schema() (or any primitive class) to get the full JSON Schema with these descriptions inline.

Image placeholders

Set "placeholder": true (with an optional caption) instead of src when you don't have a real image yet. It renders as an intentional design element — dashed border, centered caption — and render_deck's RenderReport.manifest gets one entry per placeholder: {slide, primitive, rect: {x, y, w, h}, caption}. A later pass (image search/generation/human upload) can fill each reserved rect directly from the manifest EMU rect — no re-layout needed, and the deck-building agent itself never needs image-generation capability.

Worked examples

1. Title slide

{
  "slides": [
    { "header": { "title": "2026 Roadmap", "subtitle": "Platform team", "eyebrow": "Q1 Kickoff" } }
  ]
}

2. Two-column comparison with a connector

{
  "slides": [{
    "header": { "title": "Before vs. After" },
    "body": [
      {
        "primitive": "grid",
        "columns": 2,
        "items": [
          { "id": "before", "primitive": "shape", "kind": "rounded_rect", "fill": "#EF4444",
            "text": { "content": "Manual layout" } },
          { "id": "after", "primitive": "shape", "kind": "rounded_rect", "fill": "#10B981",
            "text": { "content": "Resolver-computed layout" } }
        ]
      },
      { "primitive": "shape", "kind": "connector", "connects": { "from_id": "before", "to_id": "after" } }
    ]
  }]
}

3. Stat + table + chart dashboard

{
  "slides": [{
    "header": { "title": "Q3 Metrics" },
    "body": [
      { "primitive": "stat", "value": "42%", "label": "YoY growth", "trend": "+12% vs Q2" },
      { "primitive": "table", "headers": ["Quarter", "Revenue"], "rows": [["Q1", "10"], ["Q2", "14"]] },
      { "primitive": "chart", "chart_type": "bar", "categories": ["Q1", "Q2"],
        "series": [{ "name": "Revenue", "values": [10, 14] }] }
    ]
  }]
}

4. Process sequence

{
  "slides": [{
    "header": { "title": "Our Process" },
    "body": [{
      "primitive": "sequence",
      "orientation": "horizontal",
      "steps": [
        { "label": "Discover", "description": "Understand the problem" },
        { "label": "Design", "description": "Sketch options" },
        { "label": "Ship", "description": "Release to users" }
      ]
    }]
  }]
}

See examples/full_catalog.json for a complete, runnable spec (also used as a test fixture).

Fonts and templates

A deck's typeface comes from its template, not a per-primitive field — Deck.template (default "default") names a config file under src/compono/templates/. Two ship today:

Template font_family
default Calibri
modern Georgia
{ "template": "modern", "slides": [ ... ] }

or via the CLI: compono render spec.json --template modern -o deck.pptx (a CLI/kwarg template always overrides the spec's own template field). An unknown name is a structured unknown_template error (validate/render alike), not a crash — the fix lists what's available.

If a user asks the agent for a font that isn't default or modern: there is no schema field to smuggle an arbitrary typeface through a single render call — that's deliberate (see Core concepts); fonts live in a reviewed template file, not agent-request data. So:

  • A coding agent with write access to this repo (e.g. Claude Code working on compono itself) can add a new src/compono/templates/<name>.yaml — copy default.yaml's page/margin values, set the requested font_family — then use {"template": "<name>"} going forward. This is a one-time, reviewed, host-side change, the same as adding modern.yaml was.
  • An agent that only has render_deck/validate as tools (e.g. over MCP, no filesystem access to compono's own package) cannot invent a template on the fly. It should tell the user the requested font isn't available, list the templates that are, and either fall back to one of them or ask a human to add the template file.

Overflow checking (validate's layout errors, and the "shrink text on overflow" behavior it protects against) reads real glyph advance widths via fonttools — no rendering required. As of this release, no font is bundled into the package yet (src/compono/fonts/ is a placeholder); validation falls back to a system font if one is found (e.g. arial.ttf on Windows), and is skipped — not faked — with a warning if none is available. This is independent of font_family above — overflow metrics don't yet reflect the template's chosen typeface (known limitation, see CHANGELOG). A bundled, OFL-licensed safe-font list is planned before the first tagged release; this section will list it once shipped.

CLI

compono validate spec.json
compono review spec.json
compono render spec.json --template modern -o deck.pptx
compono reference

Mirrors validate/review/render_deck exactly — useful for agent frameworks that can only shell out rather than import Python. reference prints the same content as this README to stdout, for any agent with shell access but no MCP connection or Claude Code skill loaded (a bare pip install compono gets neither of those automatically — see When compono has no context below).

MCP server

compono-mcp exposes validate/review/render_deck as MCP tools, for any MCP-compatible client — not just Claude Code.

pip install compono-mcp
# or
uv add compono-mcp

Add to your MCP client config (Claude Desktop / Claude Code style):

{ "mcpServers": { "compono": { "command": "compono-mcp" } } }

Exposes validate_deck/review_deck/render_deck_tool tools (identical spec shape to the Python API) and a compono://reference resource carrying the full agent-facing reference doc, for clients without Claude Code's skill system.

When compono has no context

Two channels put this reference in front of an agent automatically: Claude Code with this skill installed, and an MCP client that fetches the compono://reference resource above. A bare pip install compono and "write me code using this" to a generic LLM gets neither — that LLM has no built-in knowledge of compono's primitives or conventions.

Two ways it can still self-serve, depending on what access it has:

  • Code-exec access: the schema is deliberately self-documenting — every field's description is agent-facing prose, not a bare type label (see Primitive catalog). Deck.model_json_schema() or help(compono.Header) gets real guidance without needing this file at all.
  • Shell access, no code-exec: compono reference (or python -c "import compono; print(compono.reference())") prints this exact content — the same reason it's packaged inside compono itself rather than only living in SKILL.md/compono-mcp.

An LLM with neither (pure text generation, no tools) has nothing beyond whatever it can recall from training data, or the PyPI/GitHub README page if it happens to search for it.

Claude Code plugin

This repo is also a Claude Code plugin marketplace, bundling the skills/compono/SKILL.md reference doc as an installable skill:

/plugin marketplace add Shaik-Hamzah123/compono
/plugin install compono

Contributing

See CONTRIBUTING.md for dev setup, branching, and code style. If you're using Claude Code, .claude/README.md describes the build-workflow skill, review subagent, and commit/format hooks set up for this repo.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

compono-0.1.4.tar.gz (32.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

compono-0.1.4-py3-none-any.whl (40.3 kB view details)

Uploaded Python 3

File details

Details for the file compono-0.1.4.tar.gz.

File metadata

  • Download URL: compono-0.1.4.tar.gz
  • Upload date:
  • Size: 32.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for compono-0.1.4.tar.gz
Algorithm Hash digest
SHA256 4340a115da8eb96dc00a4056e7f3770ed9e21f0b456e522fc664bba6ed4a3994
MD5 02ca84de956a9aeb7ea924e77606a783
BLAKE2b-256 c9598c6ed84139ba2ae335e5a6244ec1ef955bbb592c37b078f81c1fe983aaad

See more details on using hashes here.

File details

Details for the file compono-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: compono-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 40.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for compono-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 70e605d8cf8db1396211c48c34ce228d942460ea7dc39b38757d665cfab11a25
MD5 ccbf00c80c82baf5680249c388bb79fa
BLAKE2b-256 8ff9412af4a1bfd9b3fd129f81d9ca9e6e5cf98728d8e4355afeeb1f68fb9573

See more details on using hashes here.

Release history Release notifications | RSS feed

0.3.0

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

This release

0.1.4 This release

2 files

0.1.3

2 files

0.1.2

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page