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 102-entry Presentation Patterns taxonomy (91 observable, 11 unobservable go-live items) for scoring, brainstorming, and go-live preparation.

89

1.30x
Quality

94%

Does it follow best practices?

Impact

89%

1.30x

Average score across 25 eval scenarios

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

SKILL.mdskills/shownotes-publisher/

name:
shownotes-publisher
description:
Publish a talk page to the Jekyll-based shownotes site (e.g., speaking.jbaru.ch). Composes a markdown file in the site's `_talks/` collection so the custom Jekyll parser plugin extracts the right fields, the talk.html layout renders correctly, and the "Video Coming Soon" badge fires when the recording isn't ready yet. Use when the user says "publish shownotes", "create shownotes page", "add talk to shownotes", "shownotes for [some talk]", "shownotes site", "speaking.jbaru.ch", or asks to update a talk page (e.g., "add the video to the shownotes", "the video is out", "update shownotes with the recording"). Also trigger for first-time publishing before a talk is delivered, when only the slides URL exists. The Jekyll site at `~/Projects/shownotes` uses a custom markdown parser (`_plugins/markdown_parser.rb`) that imposes specific format rules — this skill encodes those rules so the agent doesn't author content that silently fails to render.
user-invocable:
Yes

Shownotes Publisher

Process steps in order. Do not skip ahead. This skill writes a markdown file into the Jekyll shownotes site's _talks/ collection where a custom parser plugin (_plugins/markdown_parser.rb in the target site) extracts structured fields by pattern matching on the body. The format is strict — small mistakes silently flatten content or break conditional rendering.

Reference files in this skill: references/parser-contract.md — extraction grammar per extracted_* field; references/template-conditionals.md — how talk.html renders each field; references/common-mistakes.md — 13 failure modes with the right way.

Default target: ~/Projects/shownotes (deployed at https://speaking.jbaru.ch).

Step 1 — Gather Context Automatically; Ask Only for the Slides URL

Everything the shownotes page needs is already in the talk's artifacts by the time this skill runs. Do not ask the user to re-supply anything that is derivable. Read automatically:

From outline.yaml — the talk's presentation spec. Load via the schema script's JSON emit mode:

python3 skills/presentation-creator/scripts/outline_schema.py \
    --emit-json <path-to-outline.yaml>

Script contract:

  • Input: path to outline.yaml as a single positional argument
  • Stdout (exit 0): JSON of the validated Outline model
  • Stderr (exit 1): FAIL: ... — abort and surface the failure

Parse the JSON, never re-parse YAML by hand. Map fields directly:

Shownotes fieldSource in outline.yaml
Title (body H1)talk.title
Filename slugtalk.slug — the only source. Never invent, derive from venue, or rephrase
Conferencetalk.venue
Datetalk.delivery_date (ISO YYYY-MM-DD)
Speakerstalk.speakers[] — single → `"by {{ site.speaker.display_name
Abstract seedtalk.thesis

From the talk directory:

  • resources.json (produced by extract-resources.py in Phase 6 Step 6.0) — becomes the ## Resources section. If the file is missing OR has no approved: true items, omit the section
  • Any existing _talks/{filename_stem}.md — if this is an update (not a first publish), read it first so Step 7 preserves hand-edits. {filename_stem} is defined in Step 2

Ask the user EXACTLY one question — the slides PDF embed URL (Google Drive https://drive.google.com/file/d/.../preview form). That's the only value not in any file — the speaker just uploaded the deck. Don't ask about Video; URLs arrive post-recording, and omitting the line is what fires the "Video Coming Soon" badge (Step 5). If the user volunteers a video URL in the same turn, capture it.

Ask follow-ups ONLY on ambiguity:

  • outline.yaml absent or invalid → STOP, invoke Skill(skill: "presentation-creator") to repair, then resume
  • talk.thesis empty → ask the speaker for a one-paragraph abstract (Step 4 rules apply)
  • talk.delivery_date missing → confirm whether the talk has happened (pre-talk publish is fine; Step 2 picks the filename convention by the field's presence)
  • Existing _talks/{filename_stem}.md exists AND speaker didn't flag this as an update → ask before overwriting (Step 7)

Proceed immediately to Step 2.

Step 2 — Compose Filename

The talk slug {talk_slug} is outline.yaml::talk.slug — the spec is the only source. Already kebab-case validated by outline_schema.py. Never invent it, never derive it from the venue or title, never rephrase.

Filename convention in _talks/, picked by rule:

  • If talk.delivery_date is set → {YYYY-MM-DD}-{talk_slug}.md (e.g., 2026-05-07-devoxx-uk-2026-300-tokens.md)
  • If talk.delivery_date is not set (pre-talk publish) → {talk_slug}.md (e.g., geecon-2026-absolutely-right.md)

Don't ask the user which convention to use. The presence of delivery_date is the signal.

The chosen filename's stem (everything before .md) is the {filename_stem} used by Steps 6, 8, and 9 for the thumbnail path and the live URL. Examples:

talk_slugdelivery_datefilenamefilename_stem
devoxx-uk-2026-300-tokens2026-05-072026-05-07-devoxx-uk-2026-300-tokens.md2026-05-07-devoxx-uk-2026-300-tokens
geecon-2026-absolutely-right(absent)geecon-2026-absolutely-right.mdgeecon-2026-absolutely-right

If an existing file at the conventional path was created in the OTHER convention (e.g., the file is {talk_slug}.md but talk.delivery_date is now set), keep the existing filename unchanged. Renaming a published talk breaks the live URL and any QR codes printed against it.

Full path: ~/Projects/shownotes/_talks/{filename_stem}.md.

Proceed immediately to Step 3.

Step 3 — Compose Frontmatter

Minimum (and maximum) frontmatter:

---
layout: talk
---

The Jekyll plugin auto-inserts this if absent; write it explicitly for portability. Do NOT add title:, video:, slides:, conference:, date:, description:, abstract:, or thumbnail_url: — every one of these is either extracted from the body, ignored by the templates, or duplicates content (see references/common-mistakes.md entries 1c and 5 for the failure modes).

Proceed immediately to Step 4.

Step 4 — Compose the Body

Body structure is a strict sequence the parser expects. Every value in {braces} below is derived from outline.yaml or resources.json per Step 1 — not asked from the user (except the slides URL, which is the one question from Step 1).

# {talk.title}

**Conference:** {talk.venue}
**Date:** {talk.delivery_date}
**Slides:** [View Slides]({slides_url_from_step_1})
**Video:** [View Video]({video_url})       # ← omit this line entirely if no video yet

A presentation at {talk.venue} in
                    {Month YYYY} in
                    {City, Country} by
                    {{ site.speaker.display_name | default: site.speaker.name }}

## Abstract

{talk.thesis, lightly adapted into one flowing paragraph. No
sub-headings, no lists, no code blocks. See
[references/parser-contract.md](references/parser-contract.md) for
the exact capture-and-flatten mechanic.}

## Resources

- [{title}]({url})            # one bullet per approved resources.json entry
- [{title}]({url})

### Optional Sub-Sections

Sub-sections under Resources DO render correctly — Resources
captures everything after `## Resources` until end-of-file.

Authoring rules (full grammar in references/parser-contract.md):

  • Field-block lines: **FieldName:** value, one per line. URLs wrapped as [text](url) — bare URLs don't populate the extracted_* fields. Date in ISO YYYY-MM-DD
  • Omit a field line entirely when its value is missing — empty values fail to parse and **Field:** TBD fires the wrong state (covered in Step 5)
  • Presentation-context paragraph starts with A presentation at (the parser anchor) and runs to the next ## heading
  • Abstract is exactly one paragraph — the parser joins all lines with spaces and collapses whitespace before markdownify, so any sub-headings / lists / code blocks inside flatten to literal text
  • Resources is passed verbatim to markdownify — sub-headings, lists, and formatting all render

Proceed immediately to Step 5.

Step 5 — "Coming Soon" Patterns: Omit, Don't Placeholder

talk.html's conditional rendering keys off truthiness of extracted_slides and extracted_video. Any non-empty value flips the badge to "Available" and the embed include tries to load the value as a URL — so placeholder strings always produce the wrong badge plus a broken iframe. The only correct way to express "not yet available" is to omit the field line entirely.

Common placeholder shapes and what they break are enumerated in references/common-mistakes.md entries 1 (Video) and 1b (Slides). The fix in every case: omit the line; add it when the real URL lands.

Updating a published file when the URL lands:

  1. Open the existing _talks/{filename_stem}.md (read-then-edit per Step 7's preservation rule)
  2. Add the **Slides:** or **Video:** line in real markdown-link form, placed inside the field block (between **Date:** and the blank line before the A presentation at... paragraph)
  3. Commit + publish via Step 9's flow

The badge and embed automatically fill in on the next build.

Never commit <!-- TODO --> HTML comments — inline comments after a link line are captured by the parser's value group and pollute extracted_* values. Tracking for incomplete work belongs in a PR description or an issue, not the committed file.

Proceed immediately to Step 6.

Step 6 — Thumbnail

Thumbnails are resolved by filename-stem naming convention — the site's templates compute the path from the .md filename:

/assets/images/thumbnails/{filename_stem}-thumbnail.png

({filename_stem} as defined in Step 2.) Examples:

Talk fileExpected thumbnail file
2026-05-07-devoxx-uk-2026-300-tokens.mdassets/images/thumbnails/2026-05-07-devoxx-uk-2026-300-tokens-thumbnail.png
geecon-2026-absolutely-right.mdassets/images/thumbnails/geecon-2026-absolutely-right-thumbnail.png

Drop the thumbnail file (4:3 PNG; the site resizes to 400×300) at that path. Do NOT set thumbnail_url: in frontmatter — it's checked only for truthiness as a featured-talks signal and never read as a URL; see references/common-mistakes.md entry 1c and references/template-conditionals.md for the three template locations that hard-code the slug-derived path. Missing thumbnails fall back via onerror to the placeholder SVG — page renders fine without one.

If a thumbnail needs to be produced, hand off to Skill(skill: "illustrations") — this skill places files, doesn't generate images.

Proceed immediately to Step 7.

Step 7 — Write the File

Compose the full file content per Steps 3 + 4 + 5 + 6 and write it to ~/Projects/shownotes/_talks/{filename_stem}.md.

If a file at that path already exists, this is an UPDATE — typically the video-add case from Step 5, or a resource refresh. In the update case:

  • Read the existing file first
  • Modify only the lines that need to change (e.g., add the **Video:** line, append a new resource)
  • Preserve every other line — comments, source URLs, unrelated resources, any speaker-added prose

NEVER overwrite an existing file with a fresh composition unless the user explicitly requests a full rewrite — speakers often hand-edit shownotes post-publish (typo fixes, resource additions) and a re-author wipes those edits.

Proceed immediately to Step 8.

Step 8 — Validate Locally

Before pushing, validate the file parses cleanly. The subshell + pipefail is required — without it, tail's successful exit masks a failing jekyll build:

cd ~/Projects/shownotes
( set -o pipefail && bundle exec jekyll build 2>&1 | tail -20 ) \
  || { echo "Build failed — fix per Step 4 and re-run"; exit 1; }

The || { ...; exit 1; } guard makes the build a hard gate: a non-zero exit aborts before any of the visual-check commands run. Only after the build is green, open the rendered page locally:

bundle exec jekyll serve --port 4000 2>&1 &
open "http://localhost:4000/talks/{filename_stem}/"

Visually confirm: title, conference + date + correct video badge (Available vs Coming Soon), slides embed, single-paragraph abstract, resources list. If a field doesn't render, the parser didn't match — re-check Step 4 rules.

Proceed immediately to Step 9.

Step 9 — Publish

Default flow — branch + PR. Required under jbaruch/coding-policy: ci-safety unless the target repo has the Content-Only Direct-Push Carve-Out wired (authority-of-record rule

  • server-side path enforcement on _talks/** / assets/images/thumbnails/** — see that rule for full preconditions).
cd ~/Projects/shownotes
git checkout -b shownotes/{filename_stem}
git add _talks/{filename_stem}.md [assets/images/thumbnails/{filename_stem}-thumbnail.png]
git commit -m "Add shownotes: {Talk Title} at {Conference}"
git push -u origin shownotes/{filename_stem}
gh pr create --fill
gh pr checks --watch --fail-fast
# After merge, watch the Pages deployment and confirm 200:
gh run watch --exit-status $(gh run list --workflow=pages-build-deployment --branch=main --limit=1 --json databaseId --jq '.[0].databaseId')
curl -fsI "{site.url}/talks/{filename_stem}/" | head -1   # expect: HTTP/2 200

Direct-push (carve-out wired AND changes touch only carve-out paths):

cd ~/Projects/shownotes
git add _talks/{filename_stem}.md [assets/images/thumbnails/{filename_stem}-thumbnail.png]
git commit -m "Add shownotes: {Talk Title} at {Conference}"
git push
gh run watch --exit-status $(gh run list --workflow=pages-build-deployment --branch=main --limit=1 --json databaseId --jq '.[0].databaseId')
curl -fsI "{site.url}/talks/{filename_stem}/" | head -1   # expect: HTTP/2 200

The carve-out bypasses the PR cycle but NOT the CI/deploy watch — ci-safety requires both the run conclusion and the 200 to confirm the publish landed. If the carve-out status is unknown, default to the branch + PR flow.

If a QR code is needed for the live URL, hand off via Skill(skill: "presentation-creator") — the QR generation flow lives in that skill's Phase 6 Step 6.2.

Finish here — the skill is complete.

skills

README.md

tile.json