Skip to main content

pptx-generator

"Compile Markdown into a valid Microsoft PowerPoint (.pptx) file using pure JavaScript. Pipeline: Markdown → AST → Slide IR → Layout IR → OOXML → ZIP. Images embedded from local files."

Quellinformationen

Repository
guyoung/boxagnts
Letzte Quellaktivität
24. Juli 2026 um 13:19
Erkannte Sprache von SKILL.md
Englisch
Sterne
11
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
pptx-generator
description
Compile Markdown into a valid Microsoft PowerPoint (.pptx) file using pure JavaScript. Pipeline: Markdown → AST → Slide IR → Layout IR → OOXML → ZIP. Images embedded from local files.
when_to_use
Use when generating PowerPoint (.pptx) files from Markdown input.
tools
jsexec, read, write, bash
args
[{"name":"output_path","description":"Absolute file path for the generated .pptx file","required":true},{"name":"markdown","description":"Markdown text input. Supports headings, lists, code, tables, blockquotes, inline formatting, links, images (local paths), and --- slide breaks","required":true},{"name":"show_slide_numbers","description":"When true, renders N / M page numbers at bottom-right","required":false}]
> **CODE SKILL — VERBATIM REQUIRED** > This skill targets a formal specification (OOXML). > Adaptation is prohibited per C2-A Rule 3. ## CRITICAL: Execution Constraints The code below is the **SOLE IMPLEMENTATION** — it MUST NOT be replaced, simplified, or reimplemented. **Prohibited deviations:** - Do NOT write your own `crc32()`, `ZipBuilder`, `parseMarkdownAST()`, `buildSlideIR()`, `layoutSlide()`, `renderSlideXML()` - Do NOT skip any OOXML file from the 13+ core file baseline - Do NOT use compression (method 8 / DEFLATE) — STORE mode only (method 0) - Do NOT skip `scanAndAssignLinks()` — hyperlinks MUST be registered as relationships - Do NOT use remote URLs for images — images MUST be local file paths ## Correct Invocation Pattern 1. Copy the **complete script** from `## Runnable Entry Point` below 2. Replace ONLY `{{markdown}}`, `{{output_path}}`, `{{show_slide_numbers}}` with actual values 3. Execute as a single `jsexec` call with **NO modifications** ## Argument Details | Arg | Type | Required | Default | Description | |-----|------|----------|---------|-------------| | `markdown` | `string` | yes | — | Markdown input for slide generation | | `output_path` | `string` | yes | — | Output .pptx file path | | `show_slide_numbers` | `boolean` | no | `false` | Show N / M page numbers | ## Runnable Entry Point (COMPLETE IIFE, DO NOT MODIFY) ```js // ===== Runnable Entry Point (IIFE) - DO NOT MODIFY ===== (async function() { const fs = require("fs"); const pathModule = require("path"); // -- XML Escape -- function esc(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); } // -- ID Generators -- let shapeId = 1; let relId = 2; // reserve rId1 for slideLayout in slideRels let imageIndex = 1; function nextShapeId() { return shapeId++; } function nextRelId() { return 'rId' + relId++; } function nextImageName(ext = 'png') { return `image${imageIndex++}.${ext}`; } // -- CRC32 (ZIP REQUIRED) -- const CRCT = new Uint32Array(256); for (let i = 0; i < 256; i++) { let c = i; for (let j = 0; j < 8; j++) { c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); } CRCT[i] = c; } function crc32(buf) { let c = 0xFFFFFFFF; for (let i = 0; i < buf.length; i++) { c = CRCT[(c ^ buf[i]) & 0xFF] ^ (c >>> 8); } return (c ^ 0xFFFFFFFF) >>> 0; } // -- ZIP Builder (STORE mode -- no compression, delayed-concat optimization) -- class ZipBuilder { constructor() { this.localChunks = []; this.centralChunks = []; this.offset = 0; this.count = 0; } add(path, content) { const buf = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content; const crc = crc32(buf); const nameBuf = Buffer.from(path, 'utf-8'); const localHeader = Buffer.alloc(30); localHeader.writeUInt32LE(0x04034b50, 0); localHeader.writeUInt16LE(20, 4); localHeader.writeUInt16LE(0, 6); localHeader.writeUInt16LE(0, 8); localHeader.writeUInt16LE(0, 10); localHeader.writeUInt16LE(0, 12); localHeader.writeUInt32LE(crc, 14); localHeader.writeUInt32LE(buf.length, 18); localHeader.writeUInt32LE(buf.length, 22); localHeader.writeUInt16LE(nameBuf.length, 26); localHeader.writeUInt16LE(0, 28); this.localChunks.push(localHeader, nameBuf, buf); const cd = Buffer.alloc(46); cd.writeUInt32LE(0x02014b50, 0); cd.writeUInt16LE(20, 4); cd.writeUInt16LE(20, 6); cd.writeUInt16LE(0, 8); cd.writeUInt16LE(0, 10); cd.writeUInt16LE(0, 12); cd.writeUInt16LE(0, 14); cd.writeUInt32LE(crc, 16); cd.writeUInt32LE(buf.length, 20); cd.writeUInt32LE(buf.length, 24); cd.writeUInt16LE(nameBuf.length, 28); cd.writeUInt16LE(0, 30); cd.writeUInt16LE(0, 32); cd.writeUInt16LE(0, 34); cd.writeUInt16LE(0, 36); cd.writeUInt32LE(0, 38); cd.writeUInt32LE(this.offset, 42); this.centralChunks.push(cd, nameBuf); this.offset += localHeader.length + nameBuf.length + buf.length; this.count++; } build() { const localBlock = Buffer.concat(this.localChunks); const centralBlock = Buffer.concat(this.centralChunks); const cdSize = centralBlock.length; const cdOffset = this.offset; const n = this.count; const eocd = Buffer.alloc(22); eocd.writeUInt32LE(0x06054b50, 0); eocd.writeUInt16LE(0, 4); eocd.writeUInt16LE(0, 6); eocd.writeUInt16LE(n, 8); eocd.writeUInt16LE(n, 10); eocd.writeUInt32LE(cdSize, 12); eocd.writeUInt32LE(cdOffset, 16); eocd.writeUInt16LE(0, 20); return Buffer.concat([localBlock, centralBlock, eocd]); } } // -- Inline Formatting Parser (Recursive Tokenizer) -- // Handles nested/overlapping formats: **bold `code`**, **[link](url)**, etc. // Priorities: `code` (highest, no interior formatting) > **bold** > *italic* > [link](url) > plain text function parseInlineRuns(raw) { const runs = []; let i = 0; while (i < raw.length) { // Backtick code span — no interior formatting if (raw[i] === '`') { const end = raw.indexOf('`', i + 1); if (end !== -1) { runs.push({ text: raw.slice(i + 1, end), mono: true }); i = end + 1; continue; } } // Bold — recursive interior parsing if (raw[i] === '*' && raw[i + 1] === '*') { let end = raw.indexOf('**', i + 2); // If no closing **, try matching at line end (treat as plain text) if (end === -1) { runs.push({ text: raw.slice(i, i + 2) }); i += 2; continue; } const inner = raw.slice(i + 2, end); const innerRuns = parseInlineRuns(inner); for (const r of innerRuns) { r.bold = true; runs.push(r); } i = end + 2; continue; } // Italic (single *, not **) if (raw[i] === '*' && raw[i + 1] !== '*') { const end = raw.indexOf('*', i + 1); if (end !== -1) { const inner = raw.slice(i + 1, end); const innerRuns = parseInlineRuns(inner); for (const r of innerRuns) { r.italic = true; runs.push(r); } i = end + 1; continue; } } // Link [text](url) — recursive interior parsing for link text if (raw[i] === '[') { const closeBracket = raw.indexOf('](', i + 1); if (closeBracket !== -1) { const closeParen = raw.indexOf(')', closeBracket + 2); if (closeParen !== -1) { const linkText = raw.slice(i + 1, closeBracket); const linkUrl = raw.slice(closeBracket + 2, closeParen); const innerRuns = parseInlineRuns(linkText); for (const r of innerRuns) { r.link = linkUrl; runs.push(r); } i = closeParen + 1; continue; } } } // Plain text — consume until next special character let end = i + 1; while (end < raw.length && '`*['.indexOf(raw[end]) === -1) { end++; } runs.push({ text: raw.slice(i, end) }); i = end; } if (runs.length === 0) { runs.push({ text: raw }); } return runs; } // -- Markdown to AST Parser -- function parseMarkdownAST(md) { const lines = md.split('\n'); const doc = { blocks: [] }; let i = 0; while (i < lines.length) { let line = lines[i].trim(); if (line === '---') { doc.blocks.push({ type: 'slide_break' }); i++; continue; } if (line.startsWith('#')) { const level = line.match(/^#+/)[0].length; doc.blocks.push({ type: 'heading', level, text: line.replace(/^#+\s*/, '') }); i++; continue; } if (line.startsWith('```')) { const lang = line.replace('```', '').trim(); i++; const code = []; while (i < lines.length && !lines[i].startsWith('```')) { code.push(lines[i]); i++; } doc.blocks.push({ type: 'code', lang, code: code.join('\n') }); i++; continue; } if (line.startsWith('- ')) { const items = []; while (i < lines.length && lines[i].startsWith('- ')) { items.push(lines[i].slice(2)); i++; } doc.blocks.push({ type: 'list', items }); continue; } if (/^\d+\.\s/.test(line)) { const items = []; while (i < lines.length && /^\d+\.\s/.test(lines[i])) { items.push(lines[i].replace(/^\d+\.\s+/, '')); i++; } doc.blocks.push({ type: 'ordered_list', items }); continue; } if (line.includes('|') && lines[i + 1]?.includes('|')) { const rows = []; while (i < lines.length && lines[i].includes('|')) { const cells = lines[i].split('|').map(c => c.trim()).filter(Boolean); // Separator row check: each cell matches :?---+:? and we only have the header row so far const isSeparator = cells.length > 0 && cells.every(c => /^:?-+:?$/.test(c)) && rows.length === 1; if (!isSeparator) { rows.push(cells); } i++; } doc.blocks.push({ type: 'table', rows }); continue; } if (line.startsWith('> ')) { const items = []; while (i < lines.length && lines[i].startsWith('> ')) { items.push(lines[i].slice(2)); i++; } doc.blocks.push({ type: 'blockquote', items }); continue; }
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen