CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

Five-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, and produce the deck illustrations + thumbnail visual layer. Includes a 102-entry Presentation Patterns taxonomy (91 observable, 11 unobservable go-live items) for scoring, brainstorming, and go-live preparation.

93

1.43x
Quality

94%

Does it follow best practices?

Impact

93%

1.43x

Average score across 21 eval scenarios

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

generate-talk-timings.pyskills/presentation-creator/scripts/

#!/usr/bin/env python3
"""Generate timemytalk.app timing files from outline.yaml.

Walks `chapters[]` from the validated Outline and emits one line per
chapter with cumulative MM:SS start timestamps. Adds an optional Q&A
chapter and a final FINISH marker.

Usage:
    python3 generate-talk-timings.py <outline.yaml>
    python3 generate-talk-timings.py <outline.yaml> --qa 5
    python3 generate-talk-timings.py <outline.yaml> --output timings.txt
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
    sys.path.insert(0, str(_HERE))

import yaml  # noqa: E402
from pydantic import ValidationError  # noqa: E402

import outline_schema as _os  # noqa: E402


def format_seconds(total_seconds: int) -> str:
    minutes = total_seconds // 60
    secs = total_seconds % 60
    return f"{minutes}:{secs:02d}"


def generate_timings(
    chapters: list[_os.Chapter],
    qa_minutes: int = 0,
) -> list[str]:
    """Emit cumulative MM:SS timing lines for each chapter + Q&A + FINISH."""
    lines: list[str] = []
    cumulative = 0
    for c in chapters:
        lines.append(f"{format_seconds(cumulative)} {c.title}")
        # Round to the nearest second — `int()` truncates and accumulates
        # drift on fractional-minute chapters (1.333 min → 79.98s, not 79s).
        cumulative += round(c.target_min * 60)
    if qa_minutes > 0:
        lines.append(f"{format_seconds(cumulative)} Q&A")
        cumulative += qa_minutes * 60
    lines.append(f"{format_seconds(cumulative)} FINISH")
    return lines


def main():
    parser = argparse.ArgumentParser(
        description="Generate timemytalk.app timing file from outline.yaml.",
        epilog="Produces MM:SS chapter lines for the timemytalk.app timer.",
    )
    parser.add_argument("outline", help="Path to outline.yaml")
    parser.add_argument(
        "--qa", type=int, default=0,
        help="Q&A duration in minutes to add before FINISH",
    )
    parser.add_argument(
        "--output", "-o",
        help="Output path (default: stdout)",
    )
    args = parser.parse_args()

    try:
        outline = _os.load_outline(args.outline)
    except (OSError, yaml.YAMLError, ValidationError) as exc:
        print(f"failed to load {args.outline}: {exc}", file=sys.stderr)
        sys.exit(1)

    lines = generate_timings(outline.chapters, qa_minutes=args.qa)
    output = "\n".join(lines) + "\n"

    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(output)
        print(f"Timings written to {args.output}", file=sys.stderr)
    else:
        sys.stdout.write(output)


if __name__ == "__main__":
    main()

skills

presentation-creator

SKILL.md

README.md

tile.json