Slides
fcakyon/claude-codex-settings
Create and edit presentation slide decks (.pptx) with PptxGenJS, bundled layout helpers, and render/validation utilities.
PowerPoint decks built with pptxgenjs and native charts, existing decks edited in place with python-pptx, then rendered to images and audited for overflow, overlap, bounds and typography before…
$ npx skills add ginlix-ai/LangAlpha --skill pptx -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install ginlix-ai/LangAlpha pptx --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/ginlix-ai/LangAlpha.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/langalpha_deliverables/skills/pptx .claude/skills/pptx && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "pptx" agent skill from https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptx into .claude/skills/pptx/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pptx", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptxType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add ginlix-ai/LangAlpha --skill pptx -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install ginlix-ai/LangAlpha pptx --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ginlix-ai/LangAlpha.git skills-src && mkdir -p .agents/skills && cp -r skills-src/plugins/langalpha_deliverables/skills/pptx .agents/skills/pptx && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "pptx" agent skill from https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptx into .agents/skills/pptx/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pptx", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add ginlix-ai/LangAlpha --skill pptx -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install ginlix-ai/LangAlpha pptx --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ginlix-ai/LangAlpha.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/plugins/langalpha_deliverables/skills/pptx .cursor/skills/pptx && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "pptx" agent skill from https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptx into .cursor/skills/pptx/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pptx", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/ginlix-ai/LangAlpha.git --path plugins/langalpha_deliverables/skills/pptx--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add ginlix-ai/LangAlpha --skill pptx -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install ginlix-ai/LangAlpha pptx --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ginlix-ai/LangAlpha.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/plugins/langalpha_deliverables/skills/pptx .gemini/skills/pptx && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "pptx" agent skill from https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptx into .gemini/skills/pptx/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pptx", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install ginlix-ai/LangAlpha pptxInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add ginlix-ai/LangAlpha --skill pptx -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/ginlix-ai/LangAlpha.git skills-src && mkdir -p .github/skills && cp -r skills-src/plugins/langalpha_deliverables/skills/pptx .github/skills/pptx && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "pptx" agent skill from https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptx into .github/skills/pptx/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pptx", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add ginlix-ai/LangAlpha --skill pptx -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install ginlix-ai/LangAlpha pptx --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/ginlix-ai/LangAlpha.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/plugins/langalpha_deliverables/skills/pptx .opencode/skills/pptx && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "pptx" agent skill from https://github.com/ginlix-ai/LangAlpha/tree/main/plugins/langalpha_deliverables/skills/pptx into .opencode/skills/pptx/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pptx", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
pptxPowerPoint decks built with pptxgenjs and native charts, existing decks edited in place with python-pptx, then rendered to images and audited for overflow, overlap, bounds and typography before…
PPTX is an agent skill from ginlix-ai/LangAlpha. PowerPoint decks built with pptxgenjs and native charts, existing decks edited in place with python-pptx, then rendered to images and audited for overflow, overlap, bounds and typography before delivery
Its SKILL.md is about 7.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 4 other files, including scripts (for example `scripts/check.py`, `scripts/extract.py` and `scripts/render.py`).
It sits in Documents & Office, covering PowerPoint presentations. It works with Microsoft PowerPoint and python-pptx. The repository describes itself as: Claude Code for Financial Market. The licence is Apache-2.0.
6 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit e05bd91. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 3 files in scripts/ (Python), which the agent can run.
Shell commands in SKILL.md call:
pythonnpmpython3markitdownsofficeFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npm, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
PPTX loads about 7.3k tokens when it runs. Until then it costs about 52 tokens; SKILL.md has 3,864 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from ginlix-ai/LangAlpha at commit e05bd91, republished under its Apache-2.0 licence (© ginlix-ai). 3,864 words, ~7,284 tokens.
.claude/skills/pptx/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.Build or edit a PowerPoint deck and write it into the task directory (e.g. acme_q4/acme_q4_review.pptx). Someone will stand in front of it, or forward it to a colleague who will. That is what the format is for: a deck is a sequence of single claims, each sized to be read from the back of a room. A slide that needs a paragraph is not a slide, it is a page, and it belongs in a report.
This is the right output when the deck itself is the deliverable: an investment committee pack, a diligence readout, a board update, an earnings walkthrough. It is the wrong output for anything the reader will study alone at their own pace (that is html-report) and for anything they will change numbers in (that is xlsx).
User preferences override these defaults. A house template, a brand palette, a required title slide, a fixed section order: those outrank every rule here. If the user hands you a deck to work in, work in it and match what is already there. The rules below are for when nothing has been specified.
| Want | Use |
|---|---|
| Something to present from, or hand to someone who will present it and edit slide by slide | pptx (this skill) |
| A document to read alone, print, or export to PDF | html-report |
| A model they will change inputs in | xlsx |
| One chart or table inside the conversation | inline-widget |
| A live app with a backend | interactive-dashboard |
## Slide N headings each carrying one declarative sentence: the claim the slide makes, not its topic. "Revenue grew 21 percent on flat headcount" is a slide. "Revenue" is a filing cabinet. If you cannot write the sentence, you do not have the slide yet.<task>/build_<name>.js, and run it with NODE_PATH=$(npm root -g) node <task>/build_<name>.js. Never assemble a deck through ad-hoc calls. The script is the source of truth; when the user asks for a change, edit the script and rerun.python .agents/skills/pptx/scripts/check.py <task>/<name>.pptx --strict reads what the file contains, including the position a placeholder inherits from its layout, and reports shapes off the slide, boxes covering each other, overflowing text, fonts and placeholders. It compares the slide's own shapes, so branding that lives on the layout or the master is something only the render in step 4 will show you covered. Fix every fail; for every warn, either fix it or write the one line in the delivery that says why it stands.python .agents/skills/pptx/scripts/render.py <task>/<name>.pptx --montage, then view montage.png. This is not optional. Clipped text, a legend over a bar, a title that jumps, one slide twice as dense as its neighbours: all of it is visible here and nowhere else.check.py passes and the render looks right..pptx and the .js beside it, and say in the reply what each slide claims.Geometry. 16:9 (LAYOUT_WIDE, 13.333 x 7.5 in) unless the user's screen is not. The 0.6 in margin is the left and right content margin, so content spans 12.13 in; vertically each band sits at the y below, from the title at 0.45 to the footer band ending at 7.25. One geometry for every content slide:
| Band | y | Height |
|---|---|---|
| Title | 0.45 | 0.60 |
| Kicker (units, period, scope) | 1.05 | 0.35 |
| Rule | 1.42 | 0.02 |
| Content | 1.75 | up to 4.85 |
| Footer and page number | 6.95 | 0.30 |
Write that as one function in the build script and call it for every slide. A title that moves by a tenth of an inch between slides reads as a flicker when the deck is clicked through, and check.py reports it.
One idea per slide. The title states the finding. The body supports it. If the body supports two findings, that is two slides. Three bullets is a good slide, six is a document, and nine is a filing.
Type. Two families at most, and one is usually enough. Sizes:
| Role | Size | Weight |
|---|---|---|
| Cover title | 40pt | bold |
| Slide title | 28pt | bold |
| Card or section heading | 18pt | bold |
| Body and bullets | 16 to 18pt | regular |
| Table text | 14pt | header bold |
| Chart axis and data labels | 12pt | regular |
| Footer, source line, footnote | 10pt | regular, secondary colour |
Nothing below 10pt, ever, and nothing below 14pt in a table. A number small enough to need leaning in is a number nobody checks.
Budgets check.py holds you to. Its overflow estimate assumes an average glyph 0.50 em wide (0.54 bold) and 1.2 line spacing, so a box fits about width_in * 72 / (pt * 0.5) characters per line: a 28pt bold title across the 12.13 in content width is one line up to about 56 characters, and the 0.60 in title band holds exactly one. Write the finding in under 56 characters or drop to 24pt. The checker takes the largest text in the top 30 percent of the slide (2.25 in) as the title when there is no title placeholder, which is every pptxgenjs deck, so a 40pt number in that band is read as a wandering title: big numbers sit below 2.25 in. Page numbers are a 10pt addText box in the footer band, never slide.slideNumber, which ignores fontSize and renders at 18pt in a box too short for it.
Fonts. Arial, Calibri, Cambria, Times New Roman and Courier New each have a metric-compatible clone installed here (Liberation Sans, Carlito, Caladea, Liberation Serif, Liberation Mono), so the render you inspect breaks its lines exactly where PowerPoint will. Georgia, Verdana, Tahoma, Trebuchet MS and anything else do not: they render substituted, so the layout you checked is not the layout the reader gets. Set fontFace explicitly on every text element; a run with no font takes whatever the theme hands it.
Colour. One accent, used for one meaning. Everything else is ink, grey, and paper.
| Role | Hex |
|---|---|
| Slide ground | FFFFFF |
| Title and body ink | 1A1A1A |
| Secondary text, labels, footers | 5A5A5A |
| Rules, borders, gridlines | D8D5D0 |
| Panel and card fill | F4F2EE |
| Accent, and chart series 1 | 1F4E79 |
| Chart series 2, 3 | 4E86B8, 8FB4D4 |
| Positive, gain | 1A7F4F |
| Negative, loss | B42318 |
Green and red mean gain and loss and nothing else. Never encode a category in a colour that already means a direction.
Speaker notes when the user will present, or when they asked. One or two sentences per slide saying what to say, not a transcript of the slide. check.py --require-notes enforces the presence, not the quality.
pptxgenjs 4.0.1 is installed globally, so the script needs NODE_PATH=$(npm root -g).
const PptxGenJS = require("pptxgenjs");
const INK = "1A1A1A", MUTED = "5A5A5A", RULE = "D8D5D0", ACCENT = "1F4E79";
const FONT = "Arial", M = 0.6, W = 13.333, CONTENT_W = W - 2 * M;
const pptx = new PptxGenJS();
pptx.layout = "LAYOUT_WIDE";
// One geometry for every content slide, so the title never moves.
function contentSlide(title, kicker) {
const slide = pptx.addSlide();
slide.background = { color: "FFFFFF" };
slide.addText(title, { x: M, y: 0.45, w: CONTENT_W, h: 0.6,
fontFace: FONT, fontSize: 28, bold: true, color: INK, valign: "middle" });
slide.addText(kicker, { x: M, y: 1.05, w: CONTENT_W, h: 0.35,
fontFace: FONT, fontSize: 14, color: MUTED, valign: "top" });
slide.addShape("rect", { x: M, y: 1.42, w: CONTENT_W, h: 0.02, fill: { color: RULE } });
return slide;
}
const s = contentSlide("Revenue grew 21 percent on flat headcount", "Three findings drive the FY2026 view");
s.addText(
[
{ text: "Q4 revenue of $1,240mm beat consensus by 3.4 percent.", options: { bullet: true } },
{ text: "Gross margin held at 64.2 percent on a heavier hardware mix.", options: { bullet: true } },
{ text: "Net retention of 118 percent carries half the FY2026 plan.", options: { bullet: true } },
],
{ x: M, y: 1.9, w: CONTENT_W, h: 2.6, fontFace: FONT, fontSize: 18, color: INK,
lineSpacingMultiple: 1.4, valign: "top" }
);
s.addNotes("Do not read the bullets out; the room has already read them. Lead with the margin point.");
pptx.writeFile({ fileName: "acme_q4/acme_q4_review.pptx" })
.then(() => console.log("written")); // the check runs on the file: see step 3Geometry is checked on the written file by check.py, which reads the real slide XML rather than the build script's intent. A shape crossing a slide edge, or two text boxes covering each other by more than 0.1 in in both directions, is a fail. Fix every one in the script and rebuild.
addChart writes a real chart part that the reader can click into, retheme, and read numbers off. A picture of a chart is a dead end for them and blurs at any zoom.
slide.addChart(
pptx.ChartType.bar,
[{ name: "Revenue ($mm)", labels: ["Q1", "Q2", "Q3", "Q4"], values: [980, 1035, 1128, 1240] }],
{ x: 0.6, y: 1.75, w: 7.4, h: 4.6,
barDir: "col", chartColors: [ACCENT], showLegend: false, showValue: true,
dataLabelFontFace: FONT, dataLabelFontSize: 12, dataLabelColor: INK,
catAxisLabelFontFace: FONT, catAxisLabelFontSize: 12, catAxisLabelColor: MUTED,
valAxisLabelFontFace: FONT, valAxisLabelFontSize: 12, valAxisLabelColor: MUTED,
valAxisMaxVal: 1400, valGridLine: { color: RULE, style: "solid", size: 1 },
catGridLine: { style: "none" }, valAxisLineShow: false, catAxisLineShow: false }
);Series data is [{ name, labels, values }], one object per series, labels identical across series.
One series: pass one colour. chartColors with several entries colours each bar separately, which looks like four categories where there is one.
Units go in the kicker or the axis title, not on every label. Turn the legend off when there is one series.
pptx.ChartType.bar with barDir: "col" is vertical, "bar" is horizontal. line, pie, doughnut, area, scatter are the rest of the useful set.
A combo chart (bars for one series, a line for another) is an array of { type, data, options } in place of the single type, with the shared geometry and axis options in the second argument:
slide.addChart(
[{ type: pptx.ChartType.bar, data: [{ name: "Revenue ($B)", labels, values: revenue }], options: { chartColors: [ACCENT], barDir: "col" } },
{ type: pptx.ChartType.line, data: [{ name: "FCF ($B)", labels, values: fcf }], options: { chartColors: [SERIES2], lineSize: 3, lineDataSymbol: "circle" } }],
{ x: 0.6, y: 1.75, w: 7.3, h: 4.7, showLegend: true, legendPos: "b", legendFontSize: 12,
catAxisLabelFontSize: 12, valAxisLabelFontSize: 12, valGridLine: { color: RULE, style: "solid", size: 1 } }
);Reach for an image only when the chart type genuinely does not exist here (a waterfall, a slope chart, a small-multiple grid). Then say in the reply that it is an image.
Put the takeaway beside the chart in words. A chart with no sentence next to it makes the room do the work.
const head = { fill: { color: ACCENT }, color: "FFFFFF", bold: true };
slide.addTable(
[[{ text: "Segment", options: head }, { text: "Q4 revenue", options: head }],
["Platform", "742"],
[{ text: "Total", options: { bold: true } }, { text: "1,240", options: { bold: true } }]],
{ x: M, y: 1.9, w: CONTENT_W, colW: [4.6, 7.53], fontFace: FONT, fontSize: 14,
color: INK, rowH: 0.42, valign: "middle", border: { type: "solid", pt: 1, color: RULE } }
);A table is as tall as its rows: rowH times the row count, whatever h says, and that sum is what the geometry checks measure. Keep y plus the sum inside the content band.
Six rows and four columns is a slide. Twelve rows is a handout, so cut it to the rows that carry the argument and put the full table in an appendix slide or an xlsx. Right-align numbers, put units in the header, and state totals rather than implying them.
Never rebuild a human's deck to change three words. Their master, layouts, theme, and every slide you are not touching are work you would be throwing away, and rebuilding loses the parts nobody documented. Open it with python-pptx and write into it.
from pptx import Presentation
from pptx.util import Inches, Pt
prs = Presentation("acme_q4/client_deck.pptx")
def set_text(shape, new_text):
"""Replace a shape's words and keep its formatting.
The first run carries the font, size and colour, so writing into it and
dropping the rest preserves the look without having to know what it is.
`shape.text = "..."` throws all of that away.
"""
frame = shape.text_frame
paragraph = frame.paragraphs[0]
if not paragraph.runs:
paragraph.add_run()
paragraph.runs[0].text = new_text
for run in paragraph.runs[1:]:
run._r.getparent().remove(run._r)
for extra in frame.paragraphs[1:]:
extra._p.getparent().remove(extra._p)
slide = prs.slides[3]
set_text(slide.shapes.title, "Segment detail, restated")
# A new slide comes from a layout the deck already has, so it inherits the design.
layout = next(l for l in prs.slide_layouts if l.name == "Title and Content")
new = prs.slides.add_slide(layout)
new.shapes.title.text = "FY2026 bridge"
new.placeholders[1].text_frame.text = "Volume carries 14 of the 21 points."
prs.save("acme_q4/client_deck_v2.pptx")Read the deck first: python .agents/skills/pptx/scripts/extract.py deck.pptx gives every slide's text, notes, tables, charts, and the shape names and coordinates you will write into. markitdown deck.pptx is faster for prose alone but drops every position, so it answers "what does this say" and never "which box do I write into". A legacy .ppt opens with python -c "import anydoc,sys; print(anydoc.to_markdown(sys.argv[1]))" old.ppt for the same read-only view (never pass ocr="hosted", which uploads the file to an external service); to edit one, convert it with soffice --headless --convert-to pptx old.ppt and start from the .pptx.
What a python-pptx save preserved when it was measured, on one six-slide deck with a chart, a table and notes: the theme part came back byte for byte, the slide parts that were not edited came back with the same elements, attributes and order (differing only in the whitespace between tags), and the native chart kept its series values. Two parts were rewritten and both are harmless: [Content_Types].xml is rebuilt from the parts present, and a relationship target is normalised from an absolute to a relative path. That is a measurement of one deck, not a guarantee for yours, so prove it on your own edit:
python3 -c "
import zipfile
a, b = zipfile.ZipFile('before.pptx'), zipfile.ZipFile('after.pptx')
print([n for n in sorted(set(a.namelist()) & set(b.namelist()))
if not n.endswith('/') and a.read(n) != b.read(n)])"Then render both with render.py and compare the slides you did not touch. That comparison is mandatory, not a formality: a part that survives the byte diff can still come out different once a renderer lays it out. Rules that follow:
extract.py and reuse them. Do not restyle a deck the user did not ask you to restyle.shape.left gives the inherited value; writing to it pins the shape and ends the inheritance, so leave it alone unless moving it is the task.font.size of None means inherited, not 18pt. Do not "fix" it by setting a size.sldId from prs.slides._sldIdLst when you must.chart.replace_data(CategoryChartData(...)) refreshes the numbers and keeps the formatting. Deleting and re-adding the chart loses it.check.py and render.py on the edited file. An edit is a deck change like any other.All three live under .agents/skills/pptx/scripts/, take a .pptx path (or --help), and print JSON to stdout. They read the saved file, so they judge a deck the same way whoever built it: yours, or the one the user sent you.
render.py <deck.pptx> [--out DIR] [--dpi N] [--montage] [--cols N] [--keep-pdf]: LibreOffice to PDF, pdftoppm to PNG, one image per slide, plus a numbered contact sheet with --montage. A flag outside those five is an error, so a misspelt --montage stops the run instead of rendering the deck without the contact sheet and reporting success; --dpi and --cols take whole numbers, 1 to 600 and 1 to 12.
{"status": "success", "out": "...", "dpi": 110, "pages_rendered": 6, "slides_in_deck": 6,
"pages": ["...slide-1.png"], "montage": "...montage.png"}status is success or error (no PDF, no images, no contact sheet), and only error exits non-zero; an error carries previous_render_kept, since the whole set is drawn beside DIR and swapped in only once every part of it exists, the contact sheet included. A warning field appears when the page count does not match the slide count, and slide_of_page says which deck slide each image shows: hidden slides, which LibreOffice leaves out of the PDF, are listed in hidden_slides and the images and montage tiles are then numbered by deck slide rather than by page, while anything else it dropped or split leaves slide_of_page null, the mapping being unknown. Look at the montage. A script cannot tell you the deck is ugly.
check.py <deck.pptx> [--strict] [--require-notes] [--slide N]: the audit. --slide counts from 1 and errors when the deck has no such slide, rather than passing an audit of nothing. A flag outside those three is an error too, so a misspelt --strict stops the run instead of dropping the gate and exiting 0.
{"status": "fail", "stats": {"slides": 6, "chart_parts": 1, "images": 0, "tables": 1,
"pictures": 0, "notes_slides": 6, "aspect": 1.778, "fonts_used": ["Arial"],
"fonts_theme": {"major": "Calibri Light", "minor": "Calibri"}},
"findings": [{"check": "bounds", "level": "fail", "count": 2,
"message": "shape crosses a slide edge; part of it will never be seen",
"examples": ["slide 2 (Shape 1: right 15.00 > 13.33)"]}]}status is pass or fail. fail-level findings block delivery, warn is a judgement call, info is context. Only --strict makes a failing deck exit non-zero, so put --strict in the loop and let it stop you. fonts_theme is the first master's faces, and a deck carrying more than one master adds fonts_theme_by_master, since each slide resolves its fonts against the theme of its own master.
| Check | Level | What it means |
|---|---|---|
package, package_slides, package_media | fail / warn | corrupt zip, slide parts that do not match the slide list, media nothing references |
bounds | fail | a shape crosses a slide edge; a table is measured by the sum of its row heights |
overlap, overlap_shapes | fail / warn | two shapes cover 15 percent of the smaller one, at least 0.1 in in both directions; containment is exempt only when the outer shape holds no text and the slide paints it first (a card, a full-slide background), and is reported when it holds text |
occluded | fail | a filled shape fully covers text that sits earlier in the shape tree, so the render shows the fill and none of the words; a shape with no fill of its own is exempt, so is one whose fill the author turned down to 100% transparency, and a group is judged by its members, since they are what paint |
text_overflow, text_tight | fail / warn | estimated line count needs more height than the box has |
placeholder | fail | Click to add, Lorem, TODO or [INSERT still in the deck |
font_size, font_size_table | fail | body under 10pt, table text under 14pt; a run that states no size of its own is judged on the size it inherits, resolved through its paragraph, its shape's list style, its layout placeholder, its master placeholder and the master's text styles; a run in a body PowerPoint autofitted is then judged at the size it draws, the stated or inherited size times the fontScale recorded on a:normAutofit, which the finding names as 32pt scaled to 20pt by autofit |
fonts, fonts_metric | fail / warn | more than two families; a family with no metric-compatible clone |
title_drift | warn | a title away from the position the rest of the deck uses; the title is the placeholder or, without one, the largest text in the top 30 percent of the slide; slide 1 is exempt as the cover, whether or not it carries a title of its own |
aspect | warn | not 16:9 |
notes | fail | with --require-notes, a content slide with none |
charts_as_pictures | warn | a large bitmap, or one named like a chart, where a native chart belongs |
font_size_inherited, geometry_unknown, hidden_shapes | info | a run whose size nothing in that chain states and a shape with no position of its own, so the rule could not be applied; and the shapes the file marks hidden, named once and left out of every pass since PowerPoint draws none of them, a hidden group taking its members with it |
Overflow is an estimate from font size, box width, character count and the line breaks inside a paragraph, not a measurement: it is deliberately loose and it reports lines, so read a finding as "go look at that slide in the render". An autofitted body is estimated at the size it draws, with its leading tightened by the lnSpcReduction recorded alongside the fontScale, since PowerPoint shrinks that text rather than letting it spill. Geometry checks reach inside a group, measuring each member where the group's own scale puts it and giving it the group's place in the paint order, and they measure a rotated shape by the box its spun corners cover rather than the rectangle stored in the file, a rotated group included: its angle spins its members about the group's centre, so a member is measured in the group's unrotated space, turned about that centre, and swept through the angles it and its groups add up to, a group inside a group included. Text inside a group is still read for the content checks and measured for overflow in the box the group's scale gives it rather than the box stored on the child, since resizing a group rescales that box and leaves the point size alone, so an 18pt run inside a group at half scale still renders at 18pt.
extract.py <deck.pptx> [--slide N] [--text-only]: the deck as data, for reading before editing. Per slide: title and title_from (placeholder, or largest_in_top_band when the deck has no title placeholders, which is every pptxgenjs deck; that is the rule check.py uses too, and topmost_text is the last resort when nothing sits in the top band), layout, text (including table rows), notes, a shapes inventory with names, kinds, inches and font sizes (a table reports height as the extent the slide shows, with frame_height and rows_height beside it so you can see the two disagree), plus tables (cells) and charts (type, categories, series values; an XY or bubble series reports x_values, y_values and, for bubbles, bubble_sizes in place of values). A chart's cached points are optional in the format, and one that carries only its workbook references reports those instead, as values_ref on the series and categories_ref on the chart, rather than an empty list that reads as a chart with no numbers. A plot type python-pptx does not model, a 3-D pie or a stock chart, is named from the element in the part and carries series_unread: one such chart used to raise through the run and cost the inventory of every other slide in the deck. Both reach inside groups, in document order: a grouped chart is not in the slide's own shape list, so its numbers would otherwise reach neither the report nor check-deck. A member's left, top, width and height are slide inches like every other shape's, not the coordinate space its group stores them in, so they line up with the slide and with check.py's findings; moving or resizing a group rewrites the group alone and leaves the stored numbers on every child untouched. Rotation is reported as the angle and never folded into the frame: a rotated shape or group lists its unrotated box, the numbers an edit assigns back, and check.py is where the turned box is measured. A shape the file marks hidden is left out of text, tables and charts, the way check.py leaves it out of every pass, so the report reads as the slide shows: a stale figure parked behind the live one is not a second claim about it and a hidden Source: line cites nothing. The shapes inventory still lists it, carrying "hidden": true, a hidden group taking its members with it, since an edit may be what unhides it. --slide counts from 1 and errors when the deck has no such slide, rather than returning an empty slides. A flag outside those two is an error too, so a misspelt --slide stops the run instead of returning the whole deck as though it had never been typed.
pptxgenjs
x: "50%") resolve against the slide at render time, so a mix of inches and percentages in one layout is a layout you cannot reason about. Pick inches.autoPage is off by default, so a table taller than the slide runs off the bottom silently. check.py takes a table's bounds from the sum of its row heights, which is what PowerPoint draws rather than the frame height stored in the file, and report it out of bounds. Cut the table, or set autoPage: true and accept that pptxgenjs picks the break points and the extra slides skip your title helper.fit: "shrink" or autoFit. pptxgenjs writes <a:normAutofit/> with no fontScale, so LibreOffice shrinks the text to fit and PowerPoint does not: the render passes and the reader sees the overflow. Size the box to the text instead.valign: "top" on every content box.[{ text, options }]. breakLine: true ends a paragraph, bullet: true makes a bullet. A plain string is a single run and takes the box's options.{ bullet: true }, never a literal • character, which cannot be restyled and breaks indentation.sizing: { type: "contain" | "cover" | "crop", w, h } preserves the aspect ratio. Setting w and h alone stretches the picture.RRGGBB with no #. "#1F4E79" is silently wrong.writeFile returns a promise. Do the work that follows inside .then() or the process can exit before the file lands.LAYOUT_WIDE is 13.333 x 7.5 in, LAYOUT_16x9 is 10 x 5.625 in. Same ratio, different coordinate system. Choose once, at the top.python-pptx
shape.text = "..." and cell.text = "..." destroy every run's formatting. Use the set_text helper above.shape.left and friends are EMUs. Inches(1.5) to write, shape.left.inches to read.cell.text_frame.paragraphs[0].runs[0], same rules as any other text frame.CategoryChartData; the pptxgenjs data shape is not the same.check.py --strict passes.stats.chart_parts is not zero when the deck has charts), one colour per series, units stated once.Click to add, no TODO, no Lorem, no source line missing from a slide that quotes a number.© ginlix-ai, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 3 other files (scripts) in plugins/langalpha_deliverables/skills/pptx of ginlix-ai/LangAlpha.
Open the folder on GitHubat commit e05bd91
PPTX next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| PPTX this skillginlix-ai/LangAlpha | 1.8k | — | ~7.3k | Automated safety check: Pass | Apache-2.0 | |
| Slidesfcakyon/claude-codex-settings | 1.2k | 1 repos | ~1.1k | Automated safety check: Pass | MIT | |
| Thesis Defense PPTX Builderzouchenzhen/thesis-defense-pptx-skill | 266 | — | ~2.4k | Automated safety check: Pass | Apache-2.0 | |
| Powerpoint SlidesNoi1r/powerpoint-skill | 124 | — | ~12k | Automated safety check: Notes | MIT | |
| PPTX HTML Fidelity Auditsanqiufong/slides-from-anything | 132 | 1 repos | ~3.5k | Automated safety check: Pass | Apache-2.0 | |
| PPTX Chart Forensicsaiden0z/pptx-renderer | 127 | — | ~970 | Automated safety check: Pass | Apache-2.0 |
fcakyon/claude-codex-settings
Create and edit presentation slide decks (.pptx) with PptxGenJS, bundled layout helpers, and render/validation utilities.
zouchenzhen/thesis-defense-pptx-skill
Builds an editable thesis defense PowerPoint from a thesis PDF or LaTeX project while preserving a supplied university or lab template, then runs a visual quality check.
Noi1r/powerpoint-skill
Create visually rich PowerPoint (.pptx) presentations from academic papers, research notes, or any content the user wants in slide format.
sanqiufong/slides-from-anything
Audit a python-pptx export against its source HTML deck, identify layout/content drift (footer overflow, cropped content, missing italic/em, lost styling, off-rhythm spacing), and re-export with…
aiden0z/pptx-renderer
A skill your agent uses when diagnosing or fixing PPTX renderer chart mismatches, chart oracle failures, ECharts option drift, OOXML chart XML parsing gaps, axis or legend discrepancies, plotArea…
aiden0z/pptx-renderer
A skill your agent uses when running, refreshing, comparing, or summarizing PPTX renderer oracle evaluations, including shape, SmartArt, chart, table, connector, fillstroke, python-pptx, cases…
ginlix-ai/LangAlpha
Quality-checks an investment deck in .pptx form before it goes out: number consistency, chart and narrative alignment, source coverage, language and a circulation verdict.
ginlix-ai/LangAlpha
Produces a first-time equity research initiation report in five tasks: company research, financial model, valuation, charts and a DOCX report.
ginlix-ai/LangAlpha
Builds or repairs an integrated income statement, balance sheet and cash flow model in Excel with live formulas, supporting schedules, scenarios and a Checks sheet.
ginlix-ai/LangAlpha
Audits an existing Excel financial model without editing it, checking structure, formulas, integrity identities and source tie-out, and ends in a prioritized issue log.
ginlix-ai/LangAlpha
Builds a live Excel DCF valuation workbook with free cash flow projections, WACC, terminal value, three scenarios, sensitivity grids and a reverse DCF.
ginlix-ai/LangAlpha
Builds Word files with python-docx, edits existing ones in place with tracked changes and comments, then renders and validates the result.
Works with
Categories
PowerPoint decks built with pptxgenjs and native charts, existing decks edited in place with python-pptx, then rendered to images and audited for overflow, overlap, bounds and typography before…. PPTX is an agent skill from ginlix-ai/LangAlpha.
PPTX fits situations like: tasks that involve PowerPoint presentations.
Run `npx skills add ginlix-ai/LangAlpha --skill pptx -a claude-code`. Or copy the skill folder (plugins/langalpha_deliverables/skills/pptx in ginlix-ai/LangAlpha) into .claude/skills/pptx in your project. Claude Code loads it when a task matches its description.
Run `npx skills add ginlix-ai/LangAlpha --skill pptx -a codex`. Or copy the skill folder (plugins/langalpha_deliverables/skills/pptx in ginlix-ai/LangAlpha) into .agents/skills/pptx in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add ginlix-ai/LangAlpha --skill pptx -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/pptx, .gemini/skills/pptx, .github/skills/pptx and .opencode/skills/pptx in your project.
Going by SKILL.md and its folder, PPTX needs Python for the scripts in its folder and the command-line tools its instructions call (python, npm, python3, markitdown and soffice). Our summary lists: Python 3.
SKILL.md contains no URLs. Its commands use npm, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
PPTX is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.3k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with PPTX: Slides (fcakyon/claude-codex-settings, 1.2k stars), Thesis Defense PPTX Builder (zouchenzhen/thesis-defense-pptx-skill, 266 stars), Powerpoint Slides (Noi1r/powerpoint-skill, 124 stars) and PPTX HTML Fidelity Audit (sanqiufong/slides-from-anything, 132 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
ginlix-ai (a GitHub organization) maintains it in ginlix-ai/LangAlpha, which has 1,811 GitHub stars. The repository holds 37 skills in this directory. The repository was last updated on October 9, 2026.
Source: ginlix-ai/LangAlpha on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.