agentleFS
bisque-cloudbisque-cloud/presenter/skills/present/SKILL.md

present

Skill40 starsChanged 9 days agoMITView on GitHub

Quickly turns a design doc, a pull request, a website, or anything the agent has in its context into a watchable presentation at a shareable URL. Use when asked to make a presentation, slides, a video, a deck, a slideshow, a narrated briefing, or a talk — or when stacked after another skill's report, as in /retro /present.

  • Pipes a download into a shell
  • Reads credentials

12 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use tdd.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • GoogleSkill

    tdd

    Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

22 rules. 0 are common, 7 are distinctive, and 15 are very specific to this project.

  • 0

    Common

    Most of the 10 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 15

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read spec.md and follow it exactly.

    line 115
  2. Very specific:

    Use the command rather than curl.

    line 132
  3. Very specific:

    Write index.html per the spec, in its own directory.

    line 140
  4. Very specific:

    Read each respelled value against how the word should sound.

    line 187
  5. Distinctive:

    When you publish, list the words you marked, in one line.

    line 211
  6. Very specific:

    When you stop before publish and a later step publishes the folder (a CI job, inside anthropics/claude-code-action), narrate it yourself …

    line 215
  7. Very specific:

    Use the voice the task names, else kokoro:af_heart.

    line 223
  8. Very specific:

    If the user asks to publish for their team or company, add --org <slug> (their organization's slug on bisque.team).

    line 242
  9. Very specific:

    Add --engine/--align when more than one is installed, --speed (default 1.0, valid range 0.7–1.2), --handle, --slug, --design, …

    line 273
  10. Very specific:

    When the engine to synthesize with is not installed on this machine — typically because it was picked in the welcome flow on bisque.cloud — …

    line 287
  11. Distinctive:

    Don't rebuild the slides from the transcript or from memory:

    line 321
  12. Distinctive:

    Don't re-narrate: the audio is already published and the renderer downloads it.

    line 340
  13. Distinctive:

    Keep it; every later command names it.

    line 358
  14. Very specific:

    Not every account can put a question on a slide yet.

    line 383
  15. Very specific:

    If your session has to end while a question is still open, leave the presentation open.

    line 404
  16. Distinctive:

    When the work is done, end the session.

    line 408
  17. Distinctive:

    If you are asked to continue a live presentation that another session started and never finished, resume it by its id.

    line 415
  18. Very specific:

    If all you have is the watch URL, because the first publish stopped before it printed the id, pass the URL in place of the id.

    line 423
  19. Very specific:

    If the server has no live presentation API yet, publish --keep-open says so and stops.

    line 440
  20. Distinctive:

    Never silently move a user from free local narration to billed cloud narration.

    line 448
  21. Very specific:

    Don't inline the format spec, and don't hand-edit presentation.json — the server assembles it so every producer computes cues the same way.

    line 453
  22. Very specific:

    Never finish a live presentation while a wait is still open or the person is still answering.

    line 455

“Common” is measured: kept by at least 6 in 10 of the 10 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Present

You author one index.html, bisque-voice narrates it on this machine, and Bisque publishes it. Free, unlimited, identical on macOS, Linux and Windows.

scripts/present.mjs in this skill directory does the mechanical half — narration extraction, synthesis, publish, upload, complete. Run it with node (18+) or bun; below, present.mjs means that file's absolute path.

1. Check the machine

node present.mjs doctor

It prints the bisque-voice path, which engines/aligners are installed, whether credentials resolve, the account behind them (username, tier, and any account settings from bisque.cloud), and — when an engine is installed — runs a one-word smoke synthesis that also checks the audio is audible, so a broken or silent engine is caught here rather than at publish time. Fix whatever it reports, in this order:

No bisque-voice. It lives at ~/.bisque/bin/bisque-voice (%USERPROFILE%\.bisque\bin\bisque-voice.exe), which is usually not on PATH — never probe with command -v/where. Tell the user you are about to download it (~13 MB, ~30 MB installed) and what it is, then:

curl -fsSL https://download.bisque.today/bisque-voice/install.sh | sh   # macOS, Linux
irm https://download.bisque.today/bisque-voice/install.ps1 | iex        # Windows

No engine installed. There is deliberately no default speech model and no default voice — the user picks. Run ~/.bisque/bin/bisque-voice engines --json and present each entry's real trade-offs (summary, downloadBytes, languages, voiceCount, parameters) in a sentence each, then ask which to install; more than one is fine. An entry carrying an unsupported field cannot run in this build (kokoro on Intel Macs, for example) — relay its message and offer the rest. bisque-voice install <id> installs. Then pick a voice: if doctor printed a settings line with a voice, that saved setting is the default and publish uses it automatically — only ask the user when there is none. A channel can carry its own voice, in which case that line says which channel it came from and publish narrates in it whenever it targets that channel (--handle, or the account's default handle). When asking, use the engine's own quality metadata: each entry in engines --json may carry a voices array (id, grade, recommended — recommended first). Offer the recommended voices with their grades, suggesting kokoro:af_heart (grade A) as the default. Only if the user wants more options show the rest, grades attached — "not recommended" means the voice has audible quality issues (upstream trained it on very little data), not a licensing matter. The Studio named voices (qwen3-voices-*: serena, vivian, uncle_fu, ryan, aiden, ono_anna, sohee, eric, dylan) exist alongside cloning — nine ready-made voices, no recording needed, ten languages; they carry no upstream grades (grade is "—") and all nine are recommended, with the accent field flagging the two Chinese dialect voices (eric, dylan — best in Chinese) and the Japanese/Korean-leaning ones. Any voice the user names explicitly with --voice works regardless. doctor lists installed voice ids where the pack exposes them, otherwise any wrong --voice prints the engine's speaker list.

Smoke synthesis fails or is silent. doctor prints the remediation with the failure: retry with --device cpu (on macOS the GPU path is the usual culprit), and if that fixes it, pass --device cpu to publish too. A SILENT result means the engine ran but produced inaudible audio — publishing would ship silent narration, so fix it first (CPU retry, or reinstall the engine). --no-smoke skips the check.

Aligners come with the Studio engines, not separately. A forced aligner measures where each word lands instead of inferring it. Every qwen3-* engine declares one as its companion, so bisque-voice install <engine> fetches it too — a 1.05 GB download shared by all of them, on top of the engine's own size. Say both numbers when you offer a Studio engine. Kokoro produces exact timings natively and has no companion. --align none makes synthesis fall back to the engine's own timings; it does not avoid the download, and it is a real downgrade — these engines emit frames rather than word boundaries, so without the aligner cues drift, and the clip can open with untrimmed non-speech that measured timings would have cut. Only pass it if the user accepts that. The publish response says which slides were approximate.

No credentials. node present.mjs login prints a URL and a pairing code; have the user open the URL and approve, and it saves the key to ~/.bisque/config.json under the profile present. (BISQUE_API_KEY + BISQUE_USER_ID in the environment win over the file.)

Ambiguous credentials. With several accounts configured, resolution refuses to guess rather than publish to the wrong one — ask the user which, then pass --profile <name> to whichever command reported it. Every command takes it: doctor, plan, publish, claim-username, and login, where it names the profile to write so a second account can be added without displacing the first.

No username. A presentation is published under one, and doctor reports when the account has none. Handle it now, not at publish time: ask the user what handle they want (3–30 chars, lowercase letters/digits/hyphens), then

node present.mjs claim-username <handle>

publish makes the same check itself and stops before synthesizing anything when the username is missing, so nothing is wasted either way.

2. Fetch the format spec — never write it from memory

node present.mjs spec --out spec.md

Read spec.md and follow it exactly; it is the renderer's own contract, which is why it is fetched rather than repeated here.

What you fetched is the core spec. Its "Capability modules" index names the deep modules — design, charts, tables, dither, motion, code-walkthrough, cues-advanced — and the condition under which each is needed. Once you know what the presentation contains, fetch every module it calls for and read it before composing any slide that uses the capability:

node present.mjs spec --part charts --out spec-charts.md

A module you have not read is a capability you must not use. When unsure whether a module applies, fetch it — one module is cheap. --part format fetches the whole spec with every module inlined, if you prefer one file.

Use the command rather than curl. The endpoint is public and a plain fetch still returns a correct spec — but the command sends the credentials this account already has, and the spec can carry sections that only some accounts are entitled to. Fetched without them, those sections are simply absent and you would never know the capability existed.

3. Author

Write index.html per the spec, in its own directory. Each slide's narration is its <aside class="notes">; a slide without one is silent. Optionally write:

  • context.md — what the presentation can answer viewer questions from. It ships at the presentation's visibility, so nothing more private than the presentation goes in it.
  • design.md — theme tokens in frontmatter.
  • assets/ — every font, image, and other file the slides reference, as the spec requires. publish walks this directory and uploads it with the presentation, so a page that renders locally renders the same once published.

node present.mjs plan --html index.html shows exactly what will be spoken, per slide. Read it before synthesizing.

When the input is another skill's report

Skills stack. Many skills end with a report in the terminal — gstack's /retro, /qa, /autoplan, /cso, and /ship all do — and a user can put /present on the same line: /retro /present presents the retrospective the moment it is written. Invoked this way, your material is the report already in the conversation, not the repository around it. Build the slides from the report; there is nothing extra to gather.

A report like this names its finding somewhere inside it: the ship of the week, the bug that blocks release, the highest-severity issue, the verdict on the plan. Open on that finding, wherever it sits in the report — the first slide states the report's single most important conclusion, named specifically, never the file list, the metrics table, or a description of what was run. Then the reasoning, then the evidence. A viewer who stops after the first slide still leaves with the finding.

Check pronunciations before synthesizing

A presentation narrated by a Kokoro voice MUST run this report before publish, on the final narration, and again after any narration edit. plan is not a substitute: plan shows the words, only the report shows the sounds. If the report cannot run, say so in your reply rather than publishing as if it had.

node present.mjs pronunciation-report --html index.html --voice <engine:voice>

It emits, per slide and per word, what the voice will say: phonemes, a respelled form in ordinary letters, inLexicon, and readings. The report is facts only — it flags nothing, and you judge every word:

  1. Read each respelled value against how the word should sound. Watch names and product words (inLexicon: false means the pronunciation was guessed from the spelling) and words with readings above 1 (a reading was chosen for that occurrence; check it is the one the sentence means). Short capitals read as a word. Kokoro resolves a two- or three-letter all-caps word through its lowercase entry, so US respells as UHS ("us"), ID as IHD ("id"), IT as IHT ("it"), and the report calls each inLexicon: true with one reading. Every such word is a decision you make: letters ([US](U-S), [ID](eye dee), [IT](eye tee)), a name ([HN](Hacker News)), or a word that happens to be capitalised. Only the last stays unmarked.
  2. For a word said wrong, write a marker with your best IPA into the slide's narration — [Kokoro](/kˈOkOɹO/) — and run the report again. The reported respelled for that word is the verdict. The engine's notation differs from standard IPA (O is the "oh" diphthong, ə is schwa), so a first proposal is often wrong and only the re-run confirms it.
  3. Repeat until the respelling matches the intended sound, at most three report runs per word. If three runs do not land it, remove the marker, leave the word as written, and tell the user which word you could not fix.

inspectable: false means the voice has no letters-to-sounds stage to read; the reason field says why. That is not a pass — tell the user no automated check ran for that voice. Markers still work there, judged by ear.

When you publish, list the words you marked, in one line.

Narrate before someone else publishes

When you stop before publish and a later step publishes the folder (a CI job, inside anthropics/claude-code-action), narrate it yourself first, so the narration you judged is the narration that ships. It needs no account:

node present.mjs narrate --html index.html --voice <engine:voice>

Use the voice the task names, else kokoro:af_heart. bisque-cloud/presenter's presentation: input publishes in the voice and speed you narrated in; any other publish narrates again unless it names the same ones. It writes audio/ beside index.html and prints, per slide, what the voice actually produced: durationMs, every word with its start and end in seconds, pronunciationWarnings the engine raised while speaking, and exact (false means the timings are estimates). Judge it:

  1. Any pronunciationWarnings entry is a word to fix with a marker, as above.
  2. A word whose time is out of line with its length — a short word held long, a long name rushed — is one the voice stumbled on. Look it up in the pronunciation report and mark it if it is wrong.
  3. A slide that runs much longer than what it shows needs shorter narration.

Edit, then run pronunciation-report and narrate again; only the slides whose narration changed are synthesized again. Leave audio/ in the folder: a publish of it in the same voice and speed uploads that audio as it is.

Publishing into a company (bisque.team)

If the user asks to publish for their team or company, add --org <slug> (their organization's slug on bisque.team). The presentation then belongs to the company — it lives at bisque.team/{org}/{slug}, only members can open it, and it defaults to members-only visibility. A repository that always publishes to one company can pin it once with {"org": "<slug>"} in .bisque.json. Do not pass --handle with --org.

4. Publish

With a Kokoro voice, publish only after the pronunciation report has run on this exact narration and you have read it.

node present.mjs publish --html index.html \
  --title "Q3 Review" \
  --visibility unlisted \
  --made-with "Claude Opus 5" \
  --context context.md

Pass --made-with with the model you are running as, named the way a person says it ("Claude Opus 5", "GPT-5.4 mini", "Gemini 3 Pro"), not an API id. The narrating voice is added for you. Viewers can see the list in the watch page's ⋯ menu; it is never the focus of the page. If you genuinely do not know what model you are, leave the flag off rather than guessing.

Pass --description description.txt to set the text under the player: a line or two on what the presentation covers, then the links behind it, one per line. Web URLs in it become links, so sources belong there and in context.md, not on a slide. A republish without the flag keeps the description already there.

Add --engine/--align when more than one is installed, --speed (default 1.0, valid range 0.7–1.2), --handle, --slug, --design, --presentation-id, --device. It prints the webUrl — give that to the user. Report any staleSlides or warnings it prints rather than hiding them.

Pass --voice only when the user picked a voice in this conversation (step 1 or by naming one). Otherwise leave it off: publish preflights the account, and when --voice/--engine are omitted the channel's saved voice, else the account's, on bisque.cloud fills them in — it says so when it does. A --voice you add yourself outranks the voice the owner saved, so never pass one by habit. A missing username stops the run before any synthesis (see "No username" above).

When the engine to synthesize with is not installed on this machine — typically because it was picked in the welcome flow on bisque.cloud — publish says what it is about to download and how large (from bisque-voice engines --json), then runs bisque-voice install itself before synthesizing. Relay that message to the user in plain language; there is nothing else to do.

For a clone engine (qwen3-clone-*), publish also makes sure the voice itself exists: if this machine has no cloned voice for that engine, it fetches the account's reference recording (made in the browser during the welcome flow) and runs bisque-voice clone locally, once. If the account has no recording, it stops and says to record one at bisque.cloud/welcome — relay that; never ask the user to produce a recording in the terminal.

5. Edits are cheap — never re-narrate everything

Editing a slide and re-running the same publish command is the whole point:

  1. It publishes without audio first, purely to ask the server what changed. Unchanged slides carry their audio forward; the response's staleSlides names the ones that actually need synthesizing.
  2. It synthesizes only those, then publishes for real.

So a one-slide fix costs one slide of synthesis, not the whole presentation, and an HTML-only edit (layout, colors, a cue marker moved) costs none at all. Pass the same --title/--slug/--presentation-id/--speed as before, or it is a different presentation. A different voice re-narrates every slide on its own: audio is carried forward only in the voice it was spoken in. Audio published before slides recorded their voice is narrated again once, on the next publish, and carried forward after that. Use --all only to deliberately re-synthesize everything.

A presentation from an earlier session

To change a presentation you no longer have the files for, pull its source. Don't rebuild the slides from the transcript or from memory:

node present.mjs pull --presentation https://bisque.today/p/<handle>/<slug> --dir <empty directory>

It takes the watch URL or the id, works only for an account that may republish the presentation, and writes index.html as published, context.md and assets/ into that directory. It prints a republish command with the same id, slug, visibility, voice and speed. Change what you were asked to change and nothing else, then run that command from the directory. The URL stays, the slides you left alone keep their audio, and the replaced version stays in the history. A presentation still being added to is resume, not pull.

6. A video, if they want one

publish prints a watch URL, and that URL is all a video needs. If the user asks for an MP4, a YouTube upload, a Short or a Reel, hand off to the video skill — bisque-video renders any published presentation from its watch URL. Don't re-narrate: the audio is already published and the renderer downloads it.

Rendering is macOS-only. On Linux or Windows, say so and offer the watch URL.

7. An interactive presentation, while the work is still running

A presentation can ask the person watching it a question and hand you the answer. Use this when what you are presenting still has decisions in it: a workflow that stops to ask (an office-hours session, a plan review, a one-question-at-a-time grilling), rather than a finished report. A finished report has nothing left to decide, so present it the ordinary way. You tell the two apart from the material, not from the request. The order of skill names on a line carries no meaning, and "run office hours on this idea, as a presentation" is enough.

Publish what you have so far with --keep-open. The presentation stays open instead of being finished. Its presentationId is printed as soon as the slides are up, before narration starts, and again in the output at the end. Keep it; every later command names it.

node present.mjs publish --html index.html --keep-open --title "Office hours: …"

To add slides, append new sections to the end of index.html and re-run the same command with the id. Never remove, reorder or rewrite an earlier section; the person may already be watching it, and the server refuses the append (LIVE_APPEND_ONLY).

node present.mjs publish --html index.html --keep-open --presentation-id <id>

To ask, put the question on a slide as a bp-survey. Fetch node present.mjs spec --part surveys first and follow it: the same spec in both layout blocks, and a <<fire handoff target=… for=hold>> cue on the last word of the narration that asks the question, so playback stops for the answer. Publish that slide, then wait:

node present.mjs wait --presentation <id> --survey <survey id> --timeout 1500

Not every account can put a question on a slide yet. When that fetch answers Unknown spec part, this account can't: ask the question in the conversation instead, and say what the person chose on the next slide. Resuming changes nothing here. resume prints the answers already given either way; the spec is only for writing a new question.

wait blocks until the person presses Continue under the questions (or play on that slide), prints the answer as JSON (answers, keyed by question id), and exits 0. Use the answer, then keep going: the next slide can say what they chose. Ask one question at a time: publish the next question only after the last one is answered. A person who skips past an unanswered question leaves you waiting on it while they answer the next.

People answer when they get to it, which can be hours later. Your harness probably stops long commands before then (Claude Code stops a background command after two hours at most), so give wait a --timeout shorter than that limit. Exit 2 means the time passed with no answer yet, not that the session is over: run the same wait again. A timeout is never a reason to finish.

If your session has to end while a question is still open, leave the presentation open. Tell the person its id and that any later session can pick it up with resume (below); their answer will be waiting for it.

When the work is done, end the session. That claims the version, and from then on it is an ordinary presentation.

node present.mjs finish --presentation <id>

If you are asked to continue a live presentation that another session started and never finished, resume it by its id. Don't start a new one, and don't rebuild its slides from memory:

node present.mjs resume --presentation <id> --dir <empty directory>

If all you have is the watch URL, because the first publish stopped before it printed the id, pass the URL in place of the id. That works for a presentation published without --presentation-id, from the account that published it.

node present.mjs resume --presentation https://bisque.today/p/<handle>/<slug> --dir <empty directory>

resume writes the slides published so far into that directory as index.html, prints every answer the person has already given, and records where the presentation stands. Don't ask a question again that already has an answer. Add new sections after the last one and continue with publish --keep-open --presentation-id <id> from that directory. If another session appended first, the publish stops and changes nothing; resume again into a new directory and add your sections after theirs. A finished presentation can't be resumed.

If the server has no live presentation API yet, publish --keep-open says so and stops. Ask the questions in the conversation instead, and present the result the ordinary way when the work is done.

Rules

  • Absolute path for bisque-voice. command -v reports "missing" on machines where it is installed.
  • Never silently move a user from free local narration to billed cloud narration.
  • A Kokoro voice is never published without pronunciation-report run on the final narration and read word by word. Short all-caps words (US, ID, IT) read as words unless marked.
  • Don't inline the format spec, and don't hand-edit presentation.json — the server assembles it so every producer computes cues the same way.
  • Never finish a live presentation while a wait is still open or the person is still answering. Finish once the work is done and the last slide is published.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.