CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

Six-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, and publish talk pages to a Jekyll shownotes site. Includes a 111-entry Presentation Patterns taxonomy (81 observable: 62 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

notes-to-packed.pyskills/presentation-creator/scripts/

#!/usr/bin/env python3
"""Pack a 0-based speaker-notes JSON map into the SetSpeakerNotes wire format.

Input JSON: `{"<0-based slide #>": "notes text", ...}` — the historical
inject-speaker-notes.py format. Output: a UTF-8 file of records
`<1-based #>US<text>` joined by RS, where US=Chr(31), RS=Chr(30) (non-printing
control chars that don't occur in prose notes). AppleScript reads the file as
UTF-8 and passes it to the SetSpeakerNotes VBA macro, which splits it.

Slide numbers are converted 0-based -> 1-based (python-pptx uses 0-based
indices; PowerPoint VBA uses 1-based). Empty notes are dropped.

Usage:
    notes-to-packed.py <notes.json> <out.packed>
"""

import json
import sys
from pathlib import Path

RS = "\x1e"  # record separator
US = "\x1f"  # unit separator


def pack_notes(notes_map: object) -> str:
    """Return RS-joined `<1-based #>US<text>` records, sorted by slide number.

    0-based keys become 1-based; empty notes are dropped. Raises ValueError with
    an actionable message on malformed input (non-object map, non-integer key,
    non-string text, or text containing the RS/US control chars).
    """
    if not isinstance(notes_map, dict):
        raise ValueError(
            'notes JSON must be an object {"<0-based slide #>": "text", ...}'
        )

    def slide_num(key: object) -> int:
        # JSON object keys are strings; an int key can only come from a manifest
        # built in-process. Anything else takes the same rejection path it
        # always did, now without asking int() to consume an arbitrary object.
        if isinstance(key, (str, int)):
            try:
                return int(key)
            except ValueError:
                pass
        raise ValueError(f"notes key {key!r} is not an integer")

    records = []
    for key in sorted(notes_map, key=slide_num):
        num = slide_num(key)
        if num < 0:
            raise ValueError(
                f"notes key {key!r} is negative; slide indices are 0-based (>= 0)"
            )
        text = notes_map[key]
        if text is None or text == "":
            continue
        if not isinstance(text, str):
            raise ValueError(
                f"notes for slide {key} must be a string, got {type(text).__name__}"
            )
        if RS in text or US in text:
            raise ValueError(
                f"notes for slide {key} contain a reserved control char (RS/US)"
            )
        records.append(f"{num + 1}{US}{text}")
    return RS.join(records)


def main() -> None:
    if len(sys.argv) != 3:
        print("usage: notes-to-packed.py <notes.json> <out.packed>", file=sys.stderr)
        sys.exit(2)
    try:
        notes_map = json.loads(Path(sys.argv[1]).read_text())
        packed = pack_notes(notes_map)
    except (OSError, ValueError, json.JSONDecodeError) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        sys.exit(1)
    out_path = sys.argv[2]
    Path(out_path).write_text(packed, encoding="utf-8")
    count = 0 if packed == "" else packed.count(RS) + 1
    print(json.dumps({"records": count, "output": out_path}))


if __name__ == "__main__":
    main()

skills

presentation-creator

SKILL.md

README.md

tile.json