Eleven skills for conference speakers covering rhetoric analysis, talk authoring, deck building and export, CFP submissions, publishing, illustrations, shownotes, and screencast production, informed by a 113-entry Presentation Patterns taxonomy (83 observable: 64 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns).
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
First-time setup for the PowerPoint-native deck-editing tooling
(RunDeckOps.bas + run-deck-ops.sh; see rules/deck-editing-rules.md).
macOS + Microsoft PowerPoint only.
Steps 1–4 are manual GUI actions only the user can perform; the agent presents each and the user acts, then the agent proceeds. The Step 5 smoke test verifies the whole setup end-to-end before any real edit — so the agent does not pause to confirm each manual step, it confirms once via the smoke test. Steps 1–4 run once per machine. Step 6 is the recurring per-build flow — read it before every real deck build.
Every path below is absolute. {speaker_toolkit_root} is resolved in
SKILL.md; {vault_root} is config.vault_root from the tracking database.
Expand both before showing a command or a path to the user — they cannot resolve
a placeholder, and Step 3 in particular hands them a path to type.
Never assume this is a first run, and never assume it isn't:
"{python_path}" "{speaker_toolkit_root}/skills/presentation-creator/scripts/deckops-doctor.py" \
--vault-root "{vault_root}"It touches no deck, no template, no macro container, and never launches
PowerPoint. It does restore missing drivers into the plugin's own scripts
directory — the same install-restore the .sh wrappers do, and what keeps a
fresh install from reading as driver drift. That restore runs under --offline
too, and never overwrites an existing driver.
It prints a JSON report whose status says what to do — next_step carries the
same thing as one sentence:
status | What it means | Go to |
|---|---|---|
ok | Set up and current | Step 6 |
setup_required | No macro container on this machine | Step 1 |
macro_unreachable | Container not open, macros off, or the module was NEVER imported | Steps 1–3 |
powerpoint_not_running | Set up; PowerPoint just isn't open | Step 6 |
macro_stale | The macro answered with an old stamp — setup is done, it needs a re-import | Step 3 (Updating) |
macro_stale_inferred | The OPEN container holds a module with no version macro. Inferred, not observed: disabled macros look identical | Step 3 (Updating), then Step 1 if it persists |
driver_drift | Shipped drivers don't match their mirrors | Read drivers.problems; it names the fix |
probe_failed | The probe could not run, so the state is UNKNOWN | Read live.detail — usually denied Automation consent (Step 4) |
probe_missing | The probe driver or osascript is absent | Reinstall the plugin |
unsupported_platform | Not macOS | No deck build is possible here |
Exit code separates a diagnosis from a non-diagnosis: 0 when a verdict was
reached (setup_required included — that is a finding, not a script failure),
1 when the probe could not run and the state is unknown.
Pass --offline to skip the live PowerPoint probe; you then get setup_required
versus everything-else, and no reading on whether the macro is current. A
container open from a path other than the canonical one still counts as set up —
container.canonical_mismatch reports the difference, and next_step names the
file the user actually has open rather than sending them to create a second one.
Heads-up for installed plugins: tessl install ships only .md/.py/.json/.sh/.txt
and STRIPS .bas/.applescript, so RunDeckOps.bas and the .applescript
drivers aren't on disk after install. The doctor and the .sh wrappers restore
them automatically from their committed .txt mirrors; a dev checkout already
has them. Nothing here requires a manual restore.
Ask the user to open PowerPoint → Settings → Security & Privacy and enable macros ("Enable all macros", or enable + trust). Proceed to Step 2.
DeckOps.pptmThe macro must live in a macro-enabled file, never in a real deck. The container goes at ONE canonical path, reused across every talk — the doctor and every error message look for it there:
{vault_root}/.deckops/DeckOps.pptmCreate the directory yourself first, so the Save dialog has somewhere to land:
mkdir -p "{vault_root}/.deckops"Then ask the user to:
DeckOps.pptm and save it to the path above.
In the Save dialog, ⇧⌘G opens "Go to Folder" — paste the expanded directory
there. A dot-directory does not appear in the file list; ⇧⌘. toggles hidden
files if they need to see it.Gotcha to warn about up front: if PowerPoint ever shows "Visual Basic macros
will be removed if you save the file in this format", the user is saving a
.pptx — tell them to click Cancel and save as .pptm instead. A real
deck must never carry the macro.
First, put the .bas somewhere the user can actually reach it. On an
installed plugin the module lives under a hidden .tessl/ directory, which
PowerPoint's Import panel does not show — so export a copy next to the container:
"{python_path}" "{speaker_toolkit_root}/skills/presentation-creator/scripts/sync-deck-drivers.py" \
export --to "{vault_root}/.deckops"It prints the exported path as JSON ({"exported": "..."}). Materializes the
.bas from its mirror first if the install stripped it, and refreshes a stale
earlier export. Give the user that exact absolute path.
Then ask the user to open the VBA editor (Tools → Macro → Visual Basic Editor,
or ⌥F11), select DeckOps.pptm's project in the left pane, then
File → Import File… and choose the exported RunDeckOps.bas. In that panel
⇧⌘G opens "Go to Folder" and ⇧⌘. toggles hidden files — the .deckops directory
starts with a dot, so one of the two is needed. Save DeckOps.pptm (⌘S).
Updating the macro later. A plugin update ships a new macro; the copy already
imported into DeckOps.pptm keeps running the OLD code, silently, because nothing
can read VBA source back out of a saved .pptm. Two statuses cover this, and they
are not interchangeable:
macro_stale — the macro ANSWERED with an old stamp. Macros are provably on,
so the re-import below is the whole fix.macro_stale_inferred — the open container READS as holding a module with no
version macro, which is what a container imported before the stamp existed looks
like. Disabled macros silence the probe identically, so this one is an inference:
do the re-import, and if it does not clear, confirm macros are enabled (Step 1).How the two are classified is
skills/presentation-creator/scripts/deckops-doctor.py's inspect_container /
verdict.
The re-import, for either: re-run the export command above, then in the VBA
editor right-click the DeckOps module → Remove (No to export) →
Import File… the refreshed .bas → save. Re-run Step 0 to confirm ok.
The first time a script drives PowerPoint, macOS shows an Automation consent
prompt. Tell the user it is GUI-only — it cannot be approved headless — so they
should run the first invocation themselves (or be at the machine to click
OK): System Settings → Privacy & Security → Automation → allow the terminal
to control PowerPoint. After consent is granted once, the agent may run
run-deck-ops.sh itself.
Given the history of lost work with other tools, always test before touching a real deck. One script does the whole thing — it copies the template to a uniquely-named base, builds the shipped 3-slide op sequence (layout 0 and a free text box only, so it runs against any template), and prints where the output landed:
bash "{speaker_toolkit_root}/skills/presentation-creator/scripts/deckops-smoke-test.sh" \
"{template_pptx_path}"{template_pptx_path} is infrastructure.template_pptx_path from the speaker
profile; the template is read-only. An optional second argument sets the output
directory. It prints {"ok":true,"output":"<path>","slides":3,"base":"<path>"}
and exits non-zero when no deck was produced. DeckOps.pptm must be open
(Step 6).
It passes when all four hold:
{"ok":true,...}.Anything else: re-run Step 0 and follow next_step. Only after a clean pass run
the real edit. Delete the throwaway files.
Run this after any change to inject-notes.applescript, against a copy of the
smoke-test output. Every path below is absolute: {check_dir} is a new,
empty directory inside PowerPoint's container, for example the expanded
$HOME/Library/Containers/com.microsoft.Powerpoint/Data/notes-check.
Copy the smoke-test output to {check_dir}/notes-check-base.pptx.
Save this Unicode sentinel map, with notes for slides 1 and 3 and none for
slide 2, as {check_dir}/notes.json:
{"0": "PORT → Factories ⚡ “quoted” — café 日本", "2": "second note"}Record shasum -a 256 "{check_dir}/notes-check-base.pptx", then run:
bash "{speaker_toolkit_root}/skills/presentation-creator/scripts/inject-notes.sh" \
"{check_dir}/notes-check-base.pptx" "{check_dir}/notes-check-out.pptx" \
"{check_dir}/notes.json"It passes when all five hold:
Notes applied: 2.second note, and slide 2's are unchanged.Then check the failure paths. Run each case on its own fresh copy of the smoke-test output, so no earlier case's open presentation or output can affect it, and start each with only the presentations you had open before the check. Cases A, C and D must exit non-zero and must not create their own output file; case B and case E must succeed.
Case A — note past the deck.
Copy the smoke-test output to {check_dir}/case-a-base.pptx.
Save {"9": "x"} as {check_dir}/case-a.json.
Run:
bash "{speaker_toolkit_root}/skills/presentation-creator/scripts/inject-notes.sh" \
"{check_dir}/case-a-base.pptx" "{check_dir}/case-a-out.pptx" \
"{check_dir}/case-a.json"It passes when it fails before PowerPoint is touched, with notes for 0-based slide 9 target slide 10, but the deck has 3 slides, and
{check_dir}/case-a-out.pptx does not exist.
Case B — base already open. PowerPoint opens a run-unique copy, so an open base no longer blocks the run.
Copy the smoke-test output to {check_dir}/case-b-base.pptx and open it in
PowerPoint.
Run:
bash "{speaker_toolkit_root}/skills/presentation-creator/scripts/inject-notes.sh" \
"{check_dir}/case-b-base.pptx" "{check_dir}/case-b-out.pptx" \
"{check_dir}/notes.json"It passes when it exits 0 and writes {check_dir}/case-b-out.pptx with the
sentinel notes, case-b-base.pptx is still open in PowerPoint with no notes
added, and no other presentation is left open. Then close case-b-base.pptx
without saving.
Case C — marker write fails after the copy opens.
Copy the smoke-test output to {check_dir}/case-c-base.pptx. Confirm no
presentation named case-c-base.pptx is open.
Run the driver directly with a marker path in a folder that does not exist:
osascript "{speaker_toolkit_root}/skills/presentation-creator/scripts/inject-notes.applescript" \
"{check_dir}/case-c-base.pptx" "{check_dir}/case-c-out.pptx" \
case-c-base.pptx case-c-out.pptx \
"{check_dir}/missing/opened" "{check_dir}/case-c-stage" "note" "" "note"It passes when it exits non-zero with inject-notes failed at [open]: this run opened case-c-base.pptx but could not record that, the close-without-saving
repair and osascript's original File Manager error (-43, folder not found),
case-c-base.pptx is open in PowerPoint, and {check_dir}/case-c-out.pptx
does not exist. Then close case-c-base.pptx without saving.
Case D — stage write fails.
Copy the smoke-test output to {check_dir}/case-d-base.pptx. Confirm no
presentation named case-d-base.pptx is open.
Run the driver directly with a stage path in a folder that does not exist:
osascript "{speaker_toolkit_root}/skills/presentation-creator/scripts/inject-notes.applescript" \
"{check_dir}/case-d-base.pptx" "{check_dir}/case-d-out.pptx" \
case-d-base.pptx case-d-out.pptx \
"{check_dir}/case-d-opened" "{check_dir}/missing/stage" "note"It passes when it exits non-zero with inject-notes failed at [open]: could not record that stage at {check_dir}/missing/stage, the free-space-or-writable
repair and the shell's original status (1), no presentation named
case-d-base.pptx is open, and {check_dir}/case-d-out.pptx does not exist.
Case E — two runs with one output name at once.
Copy the smoke-test output to {check_dir}/case-e-base.pptx, and create
the folders {check_dir}/e1 and {check_dir}/e2.
Start both runs together:
S="{speaker_toolkit_root}/skills/presentation-creator/scripts"
bash "$S/inject-notes.sh" "{check_dir}/case-e-base.pptx" "{check_dir}/e1/deck.pptx" "{check_dir}/notes.json" &
bash "$S/inject-notes.sh" "{check_dir}/case-e-base.pptx" "{check_dir}/e2/deck.pptx" "{check_dir}/notes.json" &
waitIt passes when both exit 0, both e1/deck.pptx and e2/deck.pptx carry the
sentinel notes, and no presentation from either run is left open.
Steps 1–4 are one-time per machine; this is what happens on EVERY real deck build:
DeckOps.pptm first and keep it open for the entire build. Every macro
pass (BuildDeck, ExpandBuilds, backgrounds, QR) drives a macro that lives in
this file; the scripts call it in the running PowerPoint instance. The
speaker-notes pass runs no macro: it writes through PowerPoint's AppleScript
object model.
If DeckOps.pptm is closed (or PowerPoint quits) mid-build, the next pass fails
with a macro-not-found error — reopen it and re-run that pass.ok. It is the cheap way to
catch a closed container or a stale import before a 40-slide build half-finishes.ExpandBuilds → speaker notes → backgrounds → QR.
ExpandBuilds renumbers later slides, so it runs before the by-index passes
(see rules/deck-editing-rules.md).If decks live in a Google Drive "My Drive" folder (the macOS Google Drive
File-Provider mount), sandboxed PowerPoint can OPEN/read them but cannot
create a new file there via VBA — SaveCopyAs fails with E_FAIL
(-2147467259). run-deck-ops.sh works around this by saving to a local
staging folder (~/.deckops-staging/) and then moving the result into the Drive
destination with the shell (which writes to Drive normally). Keep using
uniquely-named copies for the base/import/output so PowerPoint's
filename-keyed open-deck cache never hands back the wrong deck.
This applies to DeckOps.pptm itself only for creation: saving it into a Drive
vault in Step 2 is a normal GUI save, which Drive accepts. The macro never writes
to its own container.
Sandboxed PowerPoint shows a Powerbox "grant access" / "select file" prompt
whenever a VBA macro touches a file OUTSIDE its container — opening a Google-Drive
base deck or template (Presentations.Open), reading an illustration
(UserPicture), or writing the output (SaveCopyAs to a Drive folder E_FAILs, and
to a local ~/.deckops-staging subdir prompts every run). On a 40-slide deck that
is dozens of clicks.
Every deck-ops wrapper avoids this by routing ALL macro file I/O through
PowerPoint's own sandbox container (~/Library/Containers/com.microsoft.Powerpoint/Data/.deckops-stage/<pid>/),
which a sandboxed app reads and writes with no prompt:
container-stage.sh (sourced by every wrapper) provides stage_base — it copies
the base deck / template / QR image into the container and opens them from there.stage-images-into-container.py stages illustration backgrounds the same way.OUT_STAGE_DIR), then the shell (not
sandboxed) moves it to the Drive destination.container-stage.sh removes the per-run staging dir;
wrappers must NOT set their own EXIT trap (it would override that one and leak
copies). A wrapper that needs more cleanup registers a function with
container_stage_on_exit <function>; the helper's trap runs it first and warns,
without changing the exit status, if it fails.So a full build runs with ZERO prompts and no Full Disk Access grant. If the container is absent (PowerPoint never launched), the wrappers fall back to the original paths and a local output dir — prompts return, but nothing breaks.
Mac PowerPoint VBA has no Application.FileDialog, so a "grant one folder" macro is
impossible; container-staging is the supported no-prompt path.
.tessl-plugin
rules
skills
cfp-writer
references
illustrations
presentation-builder
presentation-creator
references
patterns
build
deliver
prepare
scripts
presentation-publisher
screencast-editor
screencast-recorder
shownotes-publisher
vault-clarification
vault-ingress
references
scripts
vault-profile