app
Adom TTS: Text to Speech
Public Made by Adomby adom
Adom's shared voice service. Reads your AI's answers back to you while you drive, and renders perfectly-timed voiceover tracks for your demo videos, so you never record your own voice.
← Commit history
Add adompkg installer files
4 files changed
+235
SKILL.mdadded+205@@ -0,0 +1,205 @@+---+name: adom-tts+description: >+ Thin Rust CLI for service-tts — Adom's shared edge-tts wrapper with+ pronunciation overrides + source-hash cache. Every Adom tool that+ renders narration (demo-recording, aci voice, any walkthrough/tour)+ should shell to `adom-tts say` instead of calling edge-tts directly,+ so Adom terms like adom-tsci come out phonetically right and repeat+ synthesis hits the cache.+ Trigger words: adom-tts, text to speech, tts, narration, voiceover,+ synthesize speech, edge-tts, neural voice, andrew neural,+ pronunciation override, demo narration, walkthrough narration,+ say this, speak this, make audio from text, render narration,+ aci voice, voice service, service-tts,+ play this audio, play the audio file, auto-play tts, narrate hands-free,+ drive mode, drive-mode tts, read this to me, play it back, replay last tts.+ HANDS-FREE TRIGGERS — when the user says ANY of these, ALWAYS reach for+ `adom-tts say --play` instead of writing a long text response:+ "I'm driving", "I am driving", "while I drive", "while driving",+ "can't read the screen", "can't see the screen", "can't look at the screen",+ "read it to me", "read this to me", "read it out loud", "read me your answer",+ "read me back", "read me the", "narrate the answer", "say it out loud",+ "tell me out loud", "speak the answer", "audio answer please",+ "I'm in the car", "in the car right now", "on my commute", "hands-free".+ These phrases mean the user CANNOT see your text response — synthesize it+ and auto-play. Default surface is hydrogen webview; if the user seems to be+ on a phone via adom-rc, hydrogen still works (audio plays through phone).+---++# adom-tts — shared TTS CLI, Tier B++`adom-tts` is the Adom-wide text-to-speech CLI. It shells every synthesis+call through the `service-tts` container (edge-tts + `en-US-AndrewNeural`+by default) so:++1. **Adom pronunciation overrides auto-apply server-side.** Terms like+ `adom-tsci`, `aci`, `instapcb`, `kicad` come out phonetically correct+ without the caller pre-phoneticizing. 18 overrides loaded from+ [tts-pronunciation](../tts-pronunciation/SKILL.md).+2. **Source-hash cache.** Repeat synthesis of the same `(text, voice, rate)`+ returns cached bytes instantly. Demo iteration loops save real time.+3. **Adom-owned URL.** Backend (edge-tts → piper → Azure direct → ElevenLabs)+ is swappable without any caller changes.++Default voice is `en-US-AndrewNeural` per approval. Service lives at+`https://adom-tts-f84dy9x2ezq9.adom.cloud`; CLI bakes it in at build time.++## When to reach for this skill (besides obvious "make me an mp3" asks)++**Hands-free / driving mode.** If the user says any of "I'm driving", "can't read the screen", "read it to me", "read it out loud", "read me your answer", "I'm in the car", "narrate the answer", "tell me out loud", "while I drive" — they CANNOT see your text response. The right move is:++1. Compose what you would have written as a normal reply (concise, ~30–60 seconds spoken).+2. Pipe it straight into `adom-tts say "..." --out /tmp/answer.mp3 --play`.+3. The reply plays automatically; you don't need a separate "here's a summary" text response on top of it (the user can't see it anyway).++This is the canonical drive-mode pattern. Memorize it. The user's literal words won't always say "TTS" — they'll say "I'm driving, just read me your answer." That IS the trigger.++## Use++**If the user wants to HEAR the audio, use `--play`. Don't author a custom autoplay HTML, don't `python3 -m http.server`, don't create a port mapping. `adom-tts` ships a built-in auto-player.**++```bash+# Synthesize and AUTO-PLAY (the common case).+# Detaches; CLI returns immediately. Plays in a Hydrogen webview tab by default.+adom-tts say "Read me the answer." --out /tmp/x.mp3 --play++# --surface pup if the user prefers a desktop browser window instead.+adom-tts say "..." --out /tmp/x.mp3 --play --surface pup++# Replay the newest clip from ~/.adom/tts/history/ (e.g. user said "play that again").+adom-tts play last++# Synthesize without playing (rare — only when you genuinely just need the file).+adom-tts say "Hello from adom-tsci" --out hello.mp3++# Pipe from stdin for long narration.+cat narration.txt | adom-tts say - --out narration.mp3 --play++# Different voice / speed.+adom-tts say "Announcement" --out a.mp3 --voice en-US-AriaNeural --play+adom-tts say "Quickly" --out q.mp3 --rate +15% --play++# Raw text (no pronunciation overrides — rare, for non-Adom narration).+adom-tts say "Generic sentence" --out g.mp3 --no-pronunciations --play+```++**Anti-patterns (have caused real spin-loops in past sessions):**+- ❌ `python3 -m http.server <port>` to serve the mp3 — pup runs on the user's *desktop*, container localhost is unreachable. Hydrogen webviews have the same constraint.+- ❌ Custom HTML with base64-inlined mp3 + `data:` URL — past 2 MB this hits Chrome's URL length limit.+- ❌ `adom-cli carbon containers port-add` — provisions a permanent `*.adom.cloud` subdomain for a one-shot, total overkill.+- ✅ Just use `--play`. The binary handles the surface, the URL, and cleanup.++`say` emits `→ <path> (<bytes>, cache=hit|miss, hash=<prefix>)` — scripts+can parse `cache=hit` to detect re-renders.++When `--play` is omitted, `say` also emits stderr hints reminding the AI of the auto-player. Silence them with `ADOM_TTS_NO_HINTS=1` for already-stable scripts.++Other commands:++```bash+adom-tts voices # GET /tts/voices (edge-tts list as JSON)+adom-tts pronunciations # GET /tts/pronunciations (the override table)+adom-tts health # service + edge-tts version+adom-tts config # which URL is this binary hitting?+adom-tts config --show # also dump the resolved play surface + config path+```++## Drive mode (hands-free auto-player)++You wrote an mp3 — now play it. Don't author a custom autoplay HTML.++```bash+# One shot: synthesize and auto-play. Detaches; CLI returns immediately.+adom-tts say "Read me back the answer." --out /tmp/x.mp3 --play++# Replay the most recent clip from ~/.adom/tts/history/.+adom-tts play last++# Replay a specific clip by full id or any unique substring of its hash.+adom-tts play 20260506-104555-c55f79f0+adom-tts play 5f79f0++# Block until playback finishes (--wait, max 5 min) — useful for shell scripts+# that need to chain after the audio ends.+adom-tts play /tmp/x.mp3 --wait+```++**How it works.** `play` (and `say --play`) spins up a one-shot HTTP+server bound to a 127.0.0.1 ephemeral port, serving the mp3 + a tiny+autoplay page. The container's `$VSCODE_PROXY_URI` wraps that port into+a Cloudflare-routable URL. The chosen surface (Hydrogen webview tab or+pup browser window) navigates to that URL — same plumbing, no port+mappings, no clip-size limits, no base64 URL bloat.++**Surface (Hydrogen vs pup).** Picked from this precedence chain:++```+--surface hydrogen|pup → $ADOM_TTS_SURFACE → ~/.adom/tts/config.toml → "hydrogen" (default)+```++Pin a default in `~/.adom/tts/config.toml`:++```toml+[play]+surface = "pup" # or "hydrogen"+pup_session = "adom-tts"+pup_profile = "adom-tts"+```++Default is `hydrogen` (always available inside the container; pup needs+a desktop Chrome session). Per the pup skill's "pick ONE surface" rule,+there is intentionally no `both` option.++**Tab name.** Hydrogen tabs are titled `"TTS Player"` (distinct from the+playground's `"TTS Playground"`). Repeat `play` calls open new tabs;+close stale ones with `adom-cli hydrogen workspace close-panel <id>`.++**For AIs.** When the user says "read me X", "play this", "make audio+and play it" — go straight to `adom-tts say "X" --out /tmp/x.mp3 --play`.+Don't write an autoplay HTML file. Don't create a port mapping. The+detached child handles the surface and self-shuts down on the audio's+`ended` event (or after a 5-minute cap).++## Install (Tier B via wiki)++```bash+curl -fsSL https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-tts/adom-tts \+ -o /tmp/adom-tts && chmod +x /tmp/adom-tts \+ && sudo install -m 0755 /tmp/adom-tts /usr/local/bin/adom-tts \+ && adom-tts install \+ && adom-tts health+```++## Who should use this (and how)++- **demo-recording** — replace `edge-tts --voice ...` invocations with+ `adom-tts say`. Cache hit + phonetic-correct Adom terms for free.+- **aci voice** — backend precedence was updated to prefer `adom-tts` over+ direct `edge-tts`. Users don't have to do anything; `aci voice say`+ routes through `adom-tts` if it's on PATH.+- **any future tour/walkthrough/voiceover** — same pattern: `adom-tts say`,+ don't invoke edge-tts directly.++## Why not invoke edge-tts directly?++Because:++- Every consumer has to know every pronunciation override (or get them+ wrong). Server-side application fixes this once.+- Repeat renders cost real time (+ Microsoft endpoint bandwidth). A+ source-hash cache makes the 2nd + N-th call free.+- When Microsoft changes the edge-tts endpoint (they have; will again),+ every consumer breaks in parallel. One server-side swap fixes everyone.+- Voice consistency: Andrew Neural is approved once in the `Adom` context;+ every call through adom-tts gets that default.++## Related skills++- [tts-pronunciation](../tts-pronunciation/SKILL.md) — the canonical+ phonetic-override table (loaded by service-tts at startup).+- [demo-recording](../demo-recording/SKILL.md) — top consumer.+- [service-kicad](../service-kicad/SKILL.md),+ [step2glb](../step2glb/SKILL.md) — sibling Tier B services + CLIs.++Canonical source: `adom-inc/service-tts` repo + `adom-inc/adom-tts` repo.
install.shadded+15@@ -0,0 +1,15 @@+#!/bin/bash+set -e+PKG_DIR="$(cd "$(dirname "$0")" && pwd)"+SKILLS_DIR="$HOME/.claude/skills"+mkdir -p "$SKILLS_DIR/adom-tts"+if [ -f "$PKG_DIR/SKILL.md" ]; then+ cp "$PKG_DIR/SKILL.md" "$SKILLS_DIR/adom-tts/SKILL.md"+fi+for skill_dir in "$PKG_DIR/skills"/*/; do+ [ -d "$skill_dir" ] || continue+ skill_name=$(basename "$skill_dir")+ mkdir -p "$SKILLS_DIR/$skill_name"+ cp -r "$skill_dir"* "$SKILLS_DIR/$skill_name/"+done+echo "Installed adom-tts"
package.jsonadded+11@@ -0,0 +1,11 @@+{+ "slug": "adom-tts",+ "version": "1.0.0",+ "type": "app",+ "description": "Adom's shared edge-tts wrapper with pronunciation overrides + source-hash cache, now with a built-in auto-player. Pass --play to 'say' (or run 'play' on a path/id/last) to auto-play synthesized clips",+ "dependencies": {},+ "scripts": {+ "install": "./install.sh",+ "uninstall": "./uninstall.sh"+ }+}
uninstall.shadded+4@@ -0,0 +1,4 @@+#!/bin/bash+set -e+rm -rf "$HOME/.claude/skills/adom-tts"+echo "Uninstalled adom-tts"