/audio-dashboard
Use when building audio narration dashboards that require STT-after-TTS exact word-timing, a real word-click-seek read-along dashboard, the AfterCode workflow, or publish-to-tailnet delivery.
$ golems-cli skills install audio-dashboardUpdated yesterday
Purpose
This is the one canonical golem-powers skill for audio narration dashboards, and
it is self-contained and transferable: the whole engine is vendored under
vendor/ and driven by skill-local scripts under scripts/. It does NOT clone or
reach into ~/Gits/narrationlayer, ~/Gits/agent-html, or ~/Gits/skill-creator.
The only external requirements are machine-local binaries/daemon (verified by
scripts/bootstrap.mjs) and an OPTIONAL tailnet publish step.
The invariant: never ship read-along timing from WPM math, line duration, fake
words, placeholder recap text, or old GolemPlaylist generators. A correct
dashboard has real words.json entries from STT-after-TTS alignment, real
transcript text, and word-click seek to the clicked timestamp.
When To Use
Use this for:
- AfterCode read-along dashboards.
- Narration dashboards that need exact word highlights or word-click seeking.
- Regenerating a broken audio dashboard that drifted, restarted, or showed placeholder/meta transcript text.
- Publishing an audio dashboard to the tailnet hub.
Do not use this for listen-only podcasts with no teleprompter requirement.
Vendored Engine (what makes it portable)
Everything the pipeline needs is inside the skill:
| Concern | Skill-local file |
|---|---|
TTS runner (fail-closed voice-profile gate, qwen3 daemon, splitForBreathing) | vendor/narrationlayer/local-tts-runner.ts (+ profiles.ts, text-normalize.ts) |
| STT-after-TTS word timing | vendor/narrationlayer/word-timings.ts (runWhisperCliWordTimings) |
| DP alignment / repair to script tokens | vendor/narrationlayer/word-timing-repair.ts (normalizeWordTimingsForScript) |
Cinema renderer (Q/A, Play-All, teleprompter, .note-area) | vendor/agent-html/lib/render-v4.mjs + vendor/agent-html/templates/v4-story-mode/ |
| Cinema QA | vendor/qa/verify-cinema.mjs |
| Evidence gate | src/audio-dashboard-evidence.mjs |
| Orchestration | scripts/synth-segments.mjs, scripts/build-dashboard.mjs, scripts/bootstrap.mjs, scripts/validate-evidence.mjs, scripts/audio-dashboard-generator.mjs |
Full SKILL.md source — includes LLM directives, anti-patterns, and technical instructions stripped from the Overview tab.
Purpose
This is the one canonical golem-powers skill for audio narration dashboards, and
it is self-contained and transferable: the whole engine is vendored under
vendor/ and driven by skill-local scripts under scripts/. It does NOT clone or
reach into ~/Gits/narrationlayer, ~/Gits/agent-html, or ~/Gits/skill-creator.
The only external requirements are machine-local binaries/daemon (verified by
scripts/bootstrap.mjs) and an OPTIONAL tailnet publish step.
The invariant: never ship read-along timing from WPM math, line duration, fake
words, placeholder recap text, or old GolemPlaylist generators. A correct
dashboard has real words.json entries from STT-after-TTS alignment, real
transcript text, and word-click seek to the clicked timestamp.
When To Use
Use this for:
- AfterCode read-along dashboards.
- Narration dashboards that need exact word highlights or word-click seeking.
- Regenerating a broken audio dashboard that drifted, restarted, or showed placeholder/meta transcript text.
- Publishing an audio dashboard to the tailnet hub.
Do not use this for listen-only podcasts with no teleprompter requirement.
Vendored Engine (what makes it portable)
Everything the pipeline needs is inside the skill:
| Concern | Skill-local file |
|---|---|
TTS runner (fail-closed voice-profile gate, qwen3 daemon, splitForBreathing) | vendor/narrationlayer/local-tts-runner.ts (+ profiles.ts, text-normalize.ts) |
| STT-after-TTS word timing | vendor/narrationlayer/word-timings.ts (runWhisperCliWordTimings) |
| DP alignment / repair to script tokens | vendor/narrationlayer/word-timing-repair.ts (normalizeWordTimingsForScript) |
Cinema renderer (Q/A, Play-All, teleprompter, .note-area) | vendor/agent-html/lib/render-v4.mjs + vendor/agent-html/templates/v4-story-mode/ |
| Cinema QA | vendor/qa/verify-cinema.mjs |
| Evidence gate | src/audio-dashboard-evidence.mjs |
| Orchestration | scripts/synth-segments.mjs, scripts/build-dashboard.mjs, scripts/bootstrap.mjs, scripts/validate-evidence.mjs, scripts/audio-dashboard-generator.mjs |
Canonical Pipeline
Run these skill-local entries for the AfterCode read-along path (all relative to this skill dir):
- Bootstrap —
bun scripts/bootstrap.mjsverifies bun, ffmpeg/ffprobe, whisper-cli + model, and the qwen3 daemon. It FAILS LOUD (never silent) if a hard dependency is missing. - Synthesize + time —
bun scripts/synth-segments.mjs --spec <job.json>. For each scene it runs the vendoredlocal-tts-runner.ts(splitForBreathingcadence, fail-closed voice-profile gate) to produce the WAV, transcodes an mp3, runsrunWhisperCliWordTimingson the WAV, then DP-aligns the audio words to the script tokens withnormalizeWordTimingsForScript. This is the exact STT-after-TTS step. It writes a realwords.json({index,word,start,end}) per segment and refuses to ship estimated/even-split timing (exits nonzero if alignment matches zero script words, or whisper returns nothing). BYO-audio: a scene may setaudioWavto an existing real speech WAV to skip TTS (no daemon needed) and still get real STT timing. - Render —
bun scripts/build-dashboard.mjs --spec <job.json>renders the vendoredrender-v4.mjscinema-with-answers (Q/A sections, Play-All, read-along teleprompter, per-section.note-arearesponse boxes) with real per-word timing (realWordTiming: true), embedding each mp3 as a data URI, and writes the durable HTML tospec.outputPath(a repodocs.local/dashboards/*.htmlsource). It refuses to write into adashboards-servetree. - QA —
bun vendor/qa/verify-cinema.mjs <html>reports scene/word coverage, monotonicity, unfilled tokens. - Evidence gate —
bun scripts/validate-evidence.mjs --spec <job.json> [--published "<sync cmd>"]runssrc/audio-dashboard-evidence.mjsagainst the real rendered dashboard + job dir. Note: the gate only returns PASS once publish evidence is supplied via--published(the tailnet-sync invariant); without it the gate correctly reportsREJECTED: MISSING_TAILNET_SYNCwith every other invariant passing. Run it after step 6, or pass--publishedwith the sync command you will run / did run. - Publish (optional, env-specific) — write stays in the repo
docs.local/dashboards/, then run the tailnet sync ($ORCHESTRATOR_ROOT/scripts/sync-tailnet-dashboards.mjs) or wait for the launchd sync. Never copy intodashboards-servedirectly.
The reference interaction shape is the cinema-with-answers lineage (Q/A section
structure, Play-All, read-along teleprompter, .note-area response textareas with
localStorage persistence so Wispr/VoiceBar can dictate into the focused input).
AfterCode Workflow
Dry-run the exact skill-local plan:
bun scripts/audio-dashboard-generator.mjs --workflow aftercode --dry-runRun it (only when bun, whisper CLI, the qwen3 daemon, and the job spec are ready):
bun scripts/audio-dashboard-generator.mjs --workflow aftercode --run --spec <job.json>The wrapper stays thin. It delegates to the vendored engine + skill-local
orchestration scripts, never to ~/Gits/{narrationlayer,agent-html,skill-creator}.
Job spec
--spec points at a JSON job (see examples/job.json):
{
"id": "my-dashboard",
"title": "…", "kicker": "…", "heading": "…", "subtitle": "…",
"outputPath": "~/Gits/<repo>/docs.local/dashboards/my-dashboard.html",
"scenes": [
{ "id": "c1q", "domain": "comms-layer", "title": "Q1 · …",
"reference": "host-voice", "role": "host", "script": "…" },
{ "id": "c1a", "domain": "overview", "title": "…",
"reference": "expert-voice", "role": "expert", "script": "…" }
]
}reference is a registered voice profile name OR a direct .wav reference clip
path. A scene may also carry audioWav (existing WAV) for a daemon-free BYO run.
Bootstrap
Three dependency classes cannot travel inside a skill and must exist on the host.
scripts/bootstrap.mjs verifies them and FAILS LOUD with install hints — it never
silently falls back to estimated timing or system TTS.
HARD (required for any real dashboard — STT + render):
bun— runs the vendored.tsengine (curl -fsSL https://bun.sh/install | bash)ffmpeg+ffprobe— transcode / duration (brew install ffmpeg)whisper-cli— STT word timings (brew install whisper-cpp)- a whisper model — e.g.
~/.cache/whisper/ggml-large-v3-turbo.bin(or setNARRATIONLAYER_WHISPER_MODEL)
TTS-only (required only to synthesize NEW audio; BYO-audio needs none):
- a reachable qwen3 TTS daemon (default
http://127.0.0.1:8880) - the daemon auth token (
~/.voicelayer/daemon.secret) - voice profiles — set
NARRATIONLAYER_PROFILES_FILE(aprofiles.local.yaml) orNARRATIONLAYER_ROOT, OR pass a direct.wavreference(no profiles needed). Voice profiles + their reference clones are inherently machine-local and are NOT vendored.
bun scripts/bootstrap.mjs # exit nonzero if any HARD dep missing
bun scripts/bootstrap.mjs --require-tts # also gate the daemon/tokenAcceptance Gate
Before calling a dashboard done, verify these conditions (enforced by
src/audio-dashboard-evidence.mjs via scripts/validate-evidence.mjs):
- The deliverable uses the current canonical cinema-with-answers path
(
render-v4.mjsviabuild-dashboard.mjs), with Q/A section structure, Play-All, the read-along teleprompter, and.note-arearesponse textareas with local persistence. Missing answer-writing areas is a hard rejection for the cinema-with-answers ask. Do not generate multiple replacement designs unless Etan explicitly asks for design exploration. - The HTML includes per-section response capture:
.note-areatextareas,data-note/data-titlemetadata, localStorage save/restore, and copy/export. words.jsonexists per segment and every entry has a non-empty stringwordplus finite numericstartandend; word timings are monotonic.- The dashboard declares real word timing (
realWordTiming: true). - Clicking a word seeks audio to that word's timestamp (must not restart the segment or reload the audio).
- The visible transcript is the real script/transcript, not placeholder text.
- The durable output path is a repo
docs.local/dashboards/*.htmlfile. - Tailnet publication happens through sync, not direct writes to
dashboards-serve.
Replay the eval (deterministic, no binaries needed):
bun test evals/audio-dashboard.test.mjsReject any dashboard with WPM/even-split/estimated timing, placeholder recap
copy, build-aftercode-cinema.mjs, golemplaylist-base.js, direct
dashboards-serve output, empty words, missing word-click seek, or missing
tailnet sync evidence.
Transfer & Update (getting this skill to another Mac)
This skill travels as files only — do NOT clone narrationlayer / agent-html
/ skill-creator onto the target Mac. Use the golems skill-sync path (NOT
/sync-to-mac, which is for the notarized Layers app):
- Transfer — the skill lives at
~/Gits/golems/skills/golem-powers/audio-dashboard/. Getting it onto another Mac = get that directory there, by whichever golems mechanism is in use:gitthegolemsrepo on the target (the skill is committed under it), orrsync -a ~/Gits/golems/skills/golem-powers/audio-dashboard/ <mac>:~/Gits/golems/skills/golem-powers/audio-dashboard/for a repo-free copy.
- Register — committed ≠ installed. Run
golem-install/setup-symlinkson the target (it auto-discoversgolem-powers/*/and symlinks each into~/.claude/skills,~/.claude/commands,~/.agents/skills,~/.codex/skills). Then verifyaudio-dashboardappears in the skill list of a fresh session. - Bootstrap — run
bun scripts/bootstrap.mjson the target; install any missing HARD deps it reports. For TTS, start the qwen3 daemon or use BYO-audio / a direct.wavreference. - Update — re-transfer the directory (step 1) and re-run the eval
(
bun test evals/audio-dashboard.test.mjs). Because the engine is vendored, updating the skill = updating these files; there is no separate engine repo to keep in sync. Re-vendor from source only when the upstreamnarrationlayer/agent-htmlengine changes and you deliberately pull it in.
Supersedes
This skill is the one canonical read-along audio-dashboard path. It supersedes the following classes of prior artifact (do not resurrect them):
- GolemPlaylist V1 / multi-audio players — e.g.
golemplaylist-base.jsand the old per-episode learning dashboards +build-static-site.mjs. - V4/AfterCode builders without real whisper timing — e.g.
build-aftercode-cinema.mjsand any cinema built from WPM/estimated timing. - External one-off synth/render scripts the vendored engine now replaces:
aftercode-tonight-synth.ts,regen-*-real-word-timings.ts, andbuild-aftercode-tonight.mjs. - Podcast/card/research dashboards that are not read-along, narrationlayer forks, and VoiceBar player UIs — out of scope.
The exhaustive per-file list of superseded artifacts (episode-specific) lives in git history of this file, not here — this section stays pattern-level and portable.
Best Pass Rate
95%
Sonnet 4.6
Assertions
19
7 models tested
Avg Cost / Run
$0.1914
across models
Fastest (p50)
2.5s
Kiro
Behavior Evals
Phase 2 baseline — skill quality on ClaudeBehavior Baseline
Adapter Evals
Phase 2C — cross-AI portabilityAdapter Portability
| Assertion | Opus 4.6 | Sonnet 4.6 | Haiku 4.5 | Codex | Gemini 2.5 | Cursor | Kiro | Consensus |
|---|---|---|---|---|---|---|---|---|
| requires-real-words-json | 5/7 | |||||||
| requires-nonempty-words | 6/7 | |||||||
| requires-real-timing-status | 5/7 | |||||||
| requires-word-click-seek | 6/7 | |||||||
| requires-real-transcript | 7/7 | |||||||
| requires-docslocal-publish-source | 5/7 | |||||||
| requires-tailnet-sync | 7/7 | |||||||
| rejects-wpm | 6/7 | |||||||
| rejects-realwordtiming-false | 6/7 | |||||||
| transcript-does-not-rescue-timing | 6/7 | |||||||
| rejects-placeholder-copy | 5/7 | |||||||
| words-do-not-rescue-placeholder | 5/7 | |||||||
| requires-script-binding | 5/7 | |||||||
| rejects-golemplaylist-base | 6/7 | |||||||
| rejects-cue-for-wpm | 5/7 | |||||||
| requires-current-engine | 5/7 | |||||||
| rejects-serve-tree-output | 5/7 | |||||||
| requires-docslocal-output | 7/7 | |||||||
| requires-tailnet-sync | 5/7 |
Token Usage
Cost per Run
| Model | Input Tokens | Output Tokens | Cost / Run | Cost / 1K Runs |
|---|---|---|---|---|
| Opus 4.6 | 9,164 | 10,361 | $0.9145 | $914.50 |
| Sonnet 4.6 | 5,666 | 4,096 | $0.0784 | $78.40 |
| Haiku 4.5 | 2,896 | 3,092 | $0.0046 | $4.60 |
| Codex | 5,960 | 6,738 | $0.1646 | $164.60 |
| Gemini 2.5 | 2,366 | 2,330 | $0.0292 | $29.20 |
| Cursor | 5,396 | 5,432 | $0.1085 | $108.50 |
| Kiro | 3,353 | 2,505 | $0.0401 | $40.10 |
Response Time (p50)
Response Time (p95)
| Model | p50 | p95 | Overhead |
|---|---|---|---|
| Opus 4.6 | 5.5s | 9.0s | +65% |
| Sonnet 4.6 | 3.9s | 7.4s | +87% |
| Haiku 4.5 | 3.9s | 6.4s | +65% |
| Codex | 3.1s | 4.7s | +49% |
| Gemini 2.5 | 4.7s | 8.2s | +74% |
| Cursor | 3.0s | 4.2s | +42% |
| Kiro | 2.5s | 4.1s | +62% |
Last evaluated: 2026-03-12 · Data is generated from skill assertions (real cross-model benchmarks coming soon)
Changelog entries are derived from eval runs and skill version updates. Full cascading changelog (Phase 4D) coming soon.
Best Pass Rate
95%
Assertions
19
Models Tested
7
Evals Run
5
- +Initial release to Golems skill library
- +19 assertions across 5 eval scenarios
- +Eval fixtures included