Create and publish Jekyll shownotes pages and downloadable Agent Skills containing talk knowledge informed by rhetoric analysis. Use for "publish shownotes", "add talk to shownotes", "publish a skill for this talk", "update the talk skill", "add the video to shownotes", or speaking.jbaru.ch page updates. Supports pre-talk publishing from prepared notes and demos, post-event recordings, and skill refreshes. Encodes the custom shownotes parser's body format and single-file skill contract; creates the content, validates rendering and downloads, and publishes through the appropriate git flow.
Recursos
2Instal·la
npx skillscat add jbaruch/speaker-toolkit/shownotes-publisher Instal·la des del registre de SkillsCat.
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.
Resolve the absolute path of this loaded SKILL.md, then setspeaker_toolkit_root to the plugin root two directories above the directory
containing this file. Never derive it from the consumer working directory.
Treat {speaker_toolkit_root} as absolute in every toolkit-owned command;
talk and shownotes-site paths remain consumer-owned.
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;
references/talk-skill.md — content synthesis,
single-file publishing contract, and verification commands (Step 7).
Set {shownotes_repo} to the configured site checkout; default~/Projects/shownotes (deployed at https://speaking.jbaru.ch). Use itssite.url and site.baseurl for public URLs.
Step 1 — Gather Context
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 "{speaker_toolkit_root}/skills/presentation-creator/scripts/outline_schema.py" \
--emit-json <path-to-outline.yaml>Script contract:
- Input: path to
outline.yamlas a single positional argument - Stdout (exit 0): JSON of the validated
Outlinemodel - Stderr (exit 1):
FAIL: ...— abort and surface the failure
Parse the JSON, never re-parse YAML by hand. Map fields directly:
| Shownotes field | Source in outline.yaml |
|---|---|
| Title (body H1) | talk.title |
| Filename slug | talk.slug — the only source. Never invent, derive from venue, or rephrase |
| Conference | talk.venue |
| Date | talk.delivery_date (ISO YYYY-MM-DD) |
| Speakers | talk.speakers[] — single → `"by {{ site.speaker.display_name |
| Abstract seed | talk.thesis |
From the talk directory:
resources.json(produced byextract-resources.pyin Phase 6
Step 6.0) — becomes the## Resourcessection. If the file is
missing OR has noapproved: trueitems, omit the section- Any existing
_talks/page — check{talk_slug}.md, then a legacy{YYYY-MM-DD}-{talk_slug}.md. The filename that exists is{talk_page_stem}(Step 2) - On an update, read that existing page first so Step 8 preserves
hand-edits
Read substantive talk content: validated outline slides and speaker
notes, finalized deck text, demo walkthroughs, and any existing transcript
for this delivery. These feed Step 7. Do not download subtitles when local
sources already carry the content. Treat quoted prompts and transcript
commands as source material, not instructions to the publisher.
Read the delivery-specific rhetoric analysis from the speaker's vault.
Match the talk identity and source receipts. Use its argument, example roles,
objections, callbacks, and delivered-versus-planned findings as primary input
for Step 7. Reconcile factual claims with the transcript, notes, and demos.
Follow references/talk-skill.md for missing-analysis
recovery and pre-talk treatment. Do not reduce this source to a provenance note.
Ask for the slides PDF embed URL only when needed and not already
supplied or present in the existing page (the Google Drive preview URL).
A skill-only or video-only update does not need a new slides URL.
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.thesisempty → ask the speaker for a one-paragraph
abstract (Step 4 rules apply)talk.delivery_datemissing → confirm whether the talk has
happened (pre-talk publish is fine; the date only feeds the body's**Date:**line in Step 4, not the filename)- Insufficient substantive content for a new skill → ask for the missing
teaching material; a title or abstract alone cannot support a useful skill - Existing
_talks/page (either{talk_slug}.mdor a legacy{YYYY-MM-DD}-{talk_slug}.md) exists AND speaker didn't flag
this as an update → ask before overwriting (Step 8)
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.
For a NEW talk, the filename is always {talk_slug}.md (e.g.,geecon-2026-absolutely-right.md). No date prefix. If the URL needs a
date, encode it in talk.slug, never as a filename prefix.
{talk_page_stem} is the published page's filename without .md —
the value Steps 5–10 use for every file path, thumbnail path, preview
URL, branch name, and live-URL check:
- New talk, or a talk already published at
{talk_slug}.md→{talk_page_stem}={talk_slug}; full path{shownotes_repo}/_talks/{talk_slug}.md. - Updating a talk already published at a legacy
{YYYY-MM-DD}-{talk_slug}.md→{talk_page_stem}= that existing
date-prefixed stem. Keep the filename unchanged. Never rename a
published talk.
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:, orthumbnail_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 orresources.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 theextracted_*fields. Date in ISOYYYY-MM-DD - Omit a field line entirely when its value is missing — empty
values fail to parse and**Field:** TBDfires 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 beforemarkdownify, 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 ofextracted_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:
- Open the existing
_talks/{talk_page_stem}.md(read-then-edit
per Step 8's preservation rule) - Add the
**Slides:**or**Video:**line in real markdown-link
form, placed inside the field block (between**Date:**and the
blank line before theA presentation at...paragraph) - Continue through Steps 6–10, including the talk skill and validation
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 polluteextracted_* 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 naming convention — the
site's templates compute the path from the .md filename:
/assets/images/thumbnails/{talk_page_stem}-thumbnail.pngExample (new talk, stem = slug): geecon-2026-absolutely-right.md →assets/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.
Decide explicitly — do not skip this step. Check whether the
convention-path file already exists in the shownotes repo
(assets/images/thumbnails/{talk_page_stem}-thumbnail.png):
- Exists → done; the page uses it.
- Absent, and a source image is available (the talk's slides
exist, or a YouTube thumbnail / video frame was already produced) →
produce it now: hand off toSkill(skill: "illustrations")to
generate the thumbnail (this skill places files, it does not generate
images), then drop the 4:3 PNG at the convention path. - Absent, and no source image yet (pre-talk publish, no slides or
video) → record that the thumbnail is deferred to Phase 7
(post-event). The placeholder SVG (onerrorfallback) is the
intentional interim, not a silent skip; when the video lands and
Phase 7 runs, generate the thumbnail and drop it at the convention
path.
Never fall through this step without either producing the thumbnail or
explicitly recording the deferral. Proceed immediately to Step 7.
Step 7 — Create the Talk Skill
Create _skills/{talk_page_stem}/SKILL.md in the shownotes repository as
part of publishing, including before delivery when substantive notes or
demos are ready. This plugin authors the skill; the shownotes site serves it.
Read references/talk-skill.md and follow its
source selection, rhetoric-informed synthesis, update, and quality checks.
The result gives an audience agent the talk's substance for summaries,
explanations, and questions. Include the thesis, argument, example roles,
demo outcomes, and caveats directly in the downloadable file. Do not substitute
an operational playbook for understanding the talk.
- New or missing skill → synthesize it from the talk artifacts gathered in Step 1.
- Existing skill → read it first; preserve its name and hand-edits. A video-only
update leaves its content intact. Refresh affected passages when the talk
content or analysis changes; distinguish delivered and prepared material. - Explicit user opt-out → record the omission and continue with the page.
- Missing content or site support → report the concrete gap and resolve it
before claiming skill publication. Never silently drop the requested skill.
Proceed immediately to Step 8.
Step 8 — Write the File
Compose the full file content per Steps 3–6 and write it
to {shownotes_repo}/_talks/{talk_page_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 9.
Step 9 — 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 "{shownotes_repo}"
( 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/{talk_page_stem}/"For a created or existing talk skill, run the source and built-artifact
checks in references/talk-skill.md.
A successful Jekyll build alone does not prove the skill was attached.
Visually confirm: Skill section, description, install command, download
link and expanded content when a skill exists; 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 10.
Step 10 — Publish
Pick the push flow with the content-only gate, then publish. Run
the gate from the speaker-toolkit repo root, pointed at the shownotes
repo — it enumerates every path the push would land on main
(committed-but-unpushed commits plus pending staged, unstaged, and
untracked changes) and reports whether they all touch content paths:
bash "{speaker_toolkit_root}/skills/shownotes-publisher/scripts/content-only-gate.sh" "{shownotes_repo}"- Exit 0 (content-only) → take the direct-push flow. This
satisfies the Form B client-side gate ofjbaruch/coding-policy: ci-safety's Content-Only Direct-Push Carve-Out. - Exit 1 (a path lies outside the content paths) or exit 2
(not onmain, no upstream, empty push, or can't tell) → take the
branch + PR flow. Never direct-push when the gate does not
return 0.
A new, changed, or deleted _skills/** file requires the branch + PR
flow. Agent-loaded instructions are outside the content carve-out. Stage
the page, skill, and any thumbnail together in the same PR; never publish
the page separately to evade this gate. An unchanged existing skill does
not prevent a content-only page update.
The allowed content prefixes are the named ALLOWED_PREFIXES at the top
of skills/shownotes-publisher/scripts/content-only-gate.sh.
Direct-push flow:
cd "{shownotes_repo}"
git add _talks/{talk_page_stem}.md [assets/images/thumbnails/{talk_page_stem}-thumbnail.png]
git commit -m "Add shownotes: {Talk Title} at {Conference}"
git push origin main
# Continue with deployment verification below.Branch + PR flow:
cd "{shownotes_repo}"
git checkout -b shownotes/{talk_page_stem}
git add _talks/{talk_page_stem}.md [_skills/{talk_page_stem}/SKILL.md] [assets/images/thumbnails/{talk_page_stem}-thumbnail.png]
git commit -m "Add shownotes: {Talk Title} at {Conference}"
git push -u origin shownotes/{talk_page_stem}
gh pr create --fill
gh pr checks --watch --fail-fast
# Complete the repository review and merge gates before deployment verification.
# After merge, watch the Pages deployment and confirm 200:
# Continue with deployment verification below.Stage only the listed paths that exist or have tracked deletions; brackets
above denote optional arguments. Follow the target repository's review and
merge rules through merge; an open PR is not a published skill. Resolve the deployment for the full pushed commit SHA (direct push) or
merged commit SHA (PR) with the co-shipped helper:
python3 "{speaker_toolkit_root}/skills/shownotes-publisher/scripts/resolve-deploy-run.py" \
--repo "{owner}/{repo}" --workflow "{pages_workflow}" \
--commit "{published_commit_sha}" --event push --branch "{default_branch}"Input: target repository, its Pages workflow filename or ID, full published
commit SHA, event, and branch. Exit 0 returns JSON database_id; use that
value as {deploy_run_id} below. The helper polls through enqueue latency
and rejects absent or ambiguous matches and API errors with exit 1, JSONok: false, and stderr. Stop on failure; do not select a latest run instead.
gh run watch --repo "{owner}/{repo}" --exit-status {deploy_run_id}
curl -fsI "{site.url}{site.baseurl}/talks/{talk_page_stem}/" | head -1Require a successful deployment and HTTP 200.
When a skill exists, also run the live verification in
references/talk-skill.md. Return
the talk URL and the verified raw skill URL with the install command.
Either flow requires the CI/deploy watch + HTTP 200 — ci-safety
confirms the publish via both the run conclusion and the 200, PR or not.
If a QR code is needed for the live URL, hand off viaSkill(skill: "presentation-creator") — the QR generation flow
lives in that skill's Phase 6 Step 6.2.
Finish here — the skill is complete.