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
v1.9.13 — three-tier skills (user/dev/publish), new hero (Adom TTS — Text to Speech), MIT LICENSE, env self-heal + hotspot-proof player
4 files changed
+241−16
LICENSEadded+21@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2026 Adom Industries, Inc.++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
dev-skills/adom-tts-dev/SKILL.mdadded+121@@ -0,0 +1,121 @@+---+name: adom-tts-dev+description: >-+ Develop and debug adom-tts from source: run the build, add features to the+ player/queue, and — critically — the hard-won engineering rules for a shared+ audio system that many AI threads drive at once. Read before touching+ src/play.rs, src/player.html, or the drainer. Covers: the single-server/+ single-drainer architecture (multiple AIs talk to ONE adom-tts player),+ never letting audio drop (buffering, retries, hotspot-proof blob playback),+ double-checking audio actually PLAYED (verdicts, --confirm, check),+ the shared queue (byte-snapshots, FIFO, position feedback), and returning+ good hints to every calling AI. Triggers: build adom-tts, debug adom-tts,+ adom-tts dev cycle, silent clips, no-playback, clips play twice, drainer,+ player.html, queue debugging, playback verdict, run the tests.+---++# adom-tts — development cycle & engineering rules++## Dev cycle++```bash+cargo test --release # 6 unit tests — MUST pass before any ship+./build.sh # cargo build --release + refresh bin/adom-tts (never bare cargo build)+./target/release/adom-tts --version && ./target/release/adom-tts health+adom-tts say "smoke" --out /tmp/s.mp3 --thread dev --play --confirm # end-to-end proof+```++Install locally + make every copy on the container identical (byte-compare —+version strings can't catch same-version drift):++```bash+install -m 0755 target/release/adom-tts ~/.local/bin/adom-tts+NEW=$(pwd)/target/release/adom-tts+for f in $(find /home/adom /usr/local/bin /tmp -maxdepth 4 -name adom-tts -type f 2>/dev/null); do+ case "$f" in */.bash_completion.d/*) continue;; esac+ cmp -s "$NEW" "$f" || install -m 0755 "$NEW" "$f"+done+```++## The five engineering rules (each one paid for in real outages)++### 1. Multiple AIs talk to ONE server — keep it that way+Every thread's `say --play` feeds one shared queue (`~/.adom/tts/queue/`);+ONE drainer (lock = `~/.adom/tts/drainer.lock` = `{pid}\n{version}`) runs ONE+persistent HTTP server on ONE port; the webview loads once and swaps clips via+`/poll` — never a re-navigation. A NEWER-version drainer takes over the lock;+the old one notices within 250ms, abandons its clip, and exits. Two drainers+alive = clips flip/overlap (users hear it immediately). Never kill drainers+manually — deploy a new version and let supersede do the handoff. If you must+(last resort), match argv exactly via `/proc/<pid>/cmdline` NUL-split — a+`grep`-kill matches its own shell and kills your session.++### 2. Never let audio drop+- Clips **buffer** (up to 10 min) until the player page shows a *sustained*+ live audience — 3 consecutive polls. One fresh poll is NOT alive (a+ disconnected/backgrounded page still trickles heartbeats).+- `no-playback` clips are **retried 3×**, never silently consumed.+- The page **downloads the whole mp3 to a local blob before playing** —+ a cellular-hotspot drop mid-clip cannot stall audio (the listener is often+ tethered while driving).+- The drainer **adopts surface env** from `~/.adom/tts/env.json` at startup —+ a `say` from a sanitized shell (cron/hook) once spawned a drainer with no+ `VSCODE_PROXY_URI`, which silently killed ALL playback for every thread.+- Never reopen the tab for a clip that already started (`reached > 0`) — a+ reopen reloads the page and REPLAYS the clip (users heard 5× repeats).++### 3. Double-check the audio actually PLAYED+Synthesis success ≠ playback. The page reports telemetry (beacons + piggybacked+on `/poll` so a connection gap can't fake a failure); the drainer logs a+`clip_end` verdict (`ok/partial/no-playback/error`) per clip to+`~/.adom/tts/events.jsonl`. `--confirm` and `check` read that log — they never+touch the contended surface, which is why they're trustworthy. Any new playback+feature MUST keep the verdict truthful; a false "ok" is the worst bug this+system can have (a driving user silently misses answers).++### 4. The queue is sacred+Entries byte-snapshot the mp3 at enqueue (callers reuse names like `reply.mp3`;+without the snapshot a later overwrite corrupts/duplicates a queued clip) and+carry name/label/words metadata. FIFO by timestamped filename. Every enqueue+answers with the clip's queue position.++### 5. Hints back to the AI on every command+Every command output teaches the calling AI the next correct step: `ACCEPTED —+position N` on enqueue, `QUEUED/PLAYING/PLAYED/FAILED` from `check` (with+"do NOT re-send a buffered clip — it'd play twice"), `⚠ AUDIENCE OFFLINE` at+say-time when nobody is listening, and non-`ok` verdicts explain recovery+(`logs` → `status` → `reopen` → one careful `--confirm` retry). When adding a+command, add its hints; when changing behavior, update the hints in the same+commit. `ADOM_TTS_NO_HINTS=1` silences them for stable scripts.++## Debugging playbook++- Start: `adom-tts logs` (verdict summary + anomaly hints) and `adom-tts status`+ (lock pid/version, current clip, queue). Raw: `~/.adom/tts/events.jsonl`.+- "Clip played twice / flipping" → two drainers or a reopen-after-start. Check+ lock vs live `__drain` processes and `player_reopen` events.+- "Synth ok, verdict no-playback" → page not connected. Check+ `open_surface_failed`. If `adom-cli hydrogen workspace get` works in YOUR+ shell but the drainer logs failures → env poisoning: diff+ `tr '\0' '\n' < /proc/<pid>/environ` against `env` (look for+ `VSCODE_PROXY_URI`). `drain_env_missing` in the log = snapshot empty too.+- "Same clip again later" → check clip IDs/durations first: two different clips+ named `reply.mp3` from different threads LOOK like a repeat; `clip_retry`+ events show real retries.+- Intermittent long-say failures with healthy `/health` → duration-correlated+ proxy timeout: keep synth chunks small (≤360 chars, sequential) — don't raise+ timeouts.+- UI changes: ralph-test headless (`--window-size=500,820` — headless Chrome+ clamps min width to 500, so probe `innerWidth`/`scrollWidth` in-page instead+ of trusting narrow screenshots). Media queries go LAST in the stylesheet.+ Exercise real event paths (`el.dispatchEvent(new Event('playing'))`) — the+ play/pause icon was broken for 9 versions because nothing fired the event.+- Don't churn the player while the user is listening. Diagnose from logs; the+ system self-heals. Manual mid-playback intervention has itself caused+ user-audible chaos.++## Service coupling++`adom-tts` is a client of `service-tts` (shared container — NEVER install a+local TTS engine as a fallback; report outages instead). Client-side synth+robustness lives in `synth_chunk`/`split_into_chunks` in `src/main.rs`.
page.json+3−16@@ -2,7 +2,7 @@ "schema_version": 1, "type": "app", "slug": "adom-tts",- "title": "adom-tts — your AI's voice",+ "title": "Adom TTS — Text to Speech", "brief": "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.", "version": "1.9.13", "license": "MIT",@@ -12,14 +12,13 @@ "audio", "demo", "accessibility",- "cli", "open-source", "mit" ], "hero": { "type": "image",- "path": "screenshots/hero-v2.png",- "thumbnail": "screenshots/hero-v2-thumb.png"+ "path": "screenshots/hero-v3.png",+ "thumbnail": "screenshots/hero-v3-thumb.png" }, "discovery_pitch": "Hands-free in the car? Say 'read me your last answer, I'm driving' and adom-tts speaks it aloud. Making a demo video? Feed it your script and get a perfectly-timed narration track in the house voice — no recording booth, no retakes. One shared edge-tts service with Adom pronunciation overrides and an instant source-hash cache.", "discovery_triggers": [@@ -65,17 +64,5 @@ ], "install": { "binary_name": "adom-tts"- },- "description": "Adom's shared text-to-speech CLI + Playground. Reads your AI answers back to you while driving, and renders perfectly-timed voiceover tracks for demo videos.",- "dependencies": {},- "scripts": {- "install": "./install.sh",- "uninstall": "./uninstall.sh"- },- "needs_sudo": false,- "org": "adom",- "author": {- "name": "John Lauer",- "email": "[email protected]" } }
publish-skills/adom-tts-publish/SKILL.mdadded+96@@ -0,0 +1,96 @@+---+name: adom-tts-publish+description: >-+ Cut and publish a new adom-tts release to THE Adom Wiki. Single source of+ truth for WHERE this app lives (https://wiki.adom.inc/adom/adom-tts — org+ `adom`, slug `adom-tts`) and HOW to ship it with the adom-wiki CLI (adompkg+ and all *.adom.cloud slug hosts are DEPRECATED — never use them). Covers the+ version bump, the test gate, packaging, `adom-wiki pkg publish`, `repo push`+ for source + dev/publish skills, keeping the hero linked, deprecating a bad+ version, and the mandatory verify-in-pup step. Triggers: publish adom-tts,+ release adom-tts, ship adom-tts, bump adom-tts, push adom-tts to the wiki,+ update the adom-tts page, re-link the adom-tts hero.+---++# Publish adom-tts to the wiki++**Where this app lives (memorize):**+- Page: `https://wiki.adom.inc/adom/adom-tts` (org **adom**, slug **adom-tts**, type app)+- Page title: **Adom TTS — Text to Speech** (the hero headline matches it verbatim)+- CLI: **`adom-wiki`** — the ONLY wiki tool. `adompkg`, "wiki v1/v2", and any+ `*.adom.cloud` slug host are DEPRECATED; if a doc says otherwise it is stale.+- The wiki has TWO unsynced layers: the **package registry** (`pkg publish` /+ `pkg install` — what installers get) and the **git page repo** (`repo push` —+ the Files tab, README, dev-skills/, publish-skills/, screenshots). Ship BOTH.++## Release steps++1. **Bump every version file** (any code change = new version; versions are+ immutable on the registry):+ ```bash+ cd /home/adom/project/adom-tts+ echo "X.Y.Z" > VERSION+ sed -i 's/^version = .*/version = "X.Y.Z"/' Cargo.toml+ sed -i 's/"version": "[0-9.]*"/"version": "X.Y.Z"/' package.json page.json+ ```++2. **Gate on tests, build, deploy locally** (see `dev-skills/adom-tts-dev` for+ the full dev cycle and the byte-compare sweep):+ ```bash+ cargo test --release 2>&1 | grep -q "test result: ok" || { echo "TESTS FAILED"; exit 1; }+ ./build.sh && install -m 0755 target/release/adom-tts ~/.local/bin/adom-tts+ ```++3. **Publish the package** (bundles `skills/` + install scripts; auto-excludes+ `dev-skills/` and `publish-skills/` from the tarball):+ ```bash+ adom-wiki pkg publish --org adom+ ```++4. **Push the source** — README, src/, screenshots, page.json, and the dev ++ publish skills (they reach the page ONLY via repo push; the Skills tab shows+ them under its Dev / Publish sub-tabs):+ ```bash+ adom-wiki repo push adom/adom-tts -m "vX.Y.Z — <changelog>"+ ```++5. **Keep the hero linked.** The hero lives in `page.json` ONLY (never embed it+ in the README):+ ```json+ "hero": { "type": "image", "path": "screenshots/hero-v3.png" }+ ```+ If a publish/step ever clobbers page metadata, re-push `page.json` LAST and+ re-check the page header. Regenerate the hero itself with the+ `adom-wiki-hero` skill (fresh REAL player screenshot, bleed-bottom/midnight,+ title = the page title verbatim).++6. **Verify live — in pup, always** (never claim published without looking):+ ```bash+ adom-desktop --target <laptop> browser_open_window '{"sessionId":"wiki-check","url":"https://wiki.adom.inc/adom/adom-tts?cb=<epoch>"}'+ adom-desktop --target <laptop> browser_screenshot '{"sessionId":"wiki-check"}' # read the PNG it saves+ ```+ Check: version, hero renders, Skills tab shows User + Dev + Publish tiers,+ README intact, Install command correct.++7. **Prove playback end-to-end** (the release isn't done until a human can+ hear it):+ ```bash+ adom-tts say "release X.Y.Z verification" --out /tmp/rel.mp3 --thread release --play --confirm+ # must print: ✓ CONFIRMED+ ```++## If a bad version ships++Fix → bump → `adom-wiki pkg publish --org adom` → then:+```bash+adom-wiki page deprecate adom/[email protected] --reason "superseded by X.Y.Z+1 (<why>)"+```+(verify the exact deprecate verb with `adom-wiki --help`; never delete the page+— soft-deleted pages block re-creation).++## Distribution notes++- Other containers pick the release up via their wiki auto-update hook; inside+ THIS container run the byte-compare sweep (dev skill) so every copy matches.+- The running drainer self-supersedes to the new version on the next+ `say --play` — do not kill it.