← 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

John Lauer ·87c6f43332 ·3mo ago ·parent f50cb6b
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]"   } }\ No newline at end of file
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.