← Commit history

Initial commit: app/adom-tts

Kyle Bergstedt ·844301415c ·4mo ago
2 files changed +164
README.mdadded+110
@@ -0,0 +1,110 @@+# adom-tts++Thin Rust CLI + webview playground for [service-tts](/apps/service-tts) — Adom's shared edge-tts wrapper with pronunciation overrides + source-hash cache.++## What's new in 0.1.2 — built-in auto-player++The CLI now plays its own mp3s. **Stop authoring custom autoplay HTML or `python3 -m http.server` workarounds.**++```bash+# One shot: synthesize and auto-play. CLI returns immediately (detached).+adom-tts say "Read me your latest answer, I'm driving." --out /tmp/x.mp3 --play++# Replay the newest history clip.+adom-tts play last++# Replay any history clip by full id or unique substring of its hash.+adom-tts play 5f79f0+```++**How it works.** `play` (and `say --play`) spins up a one-shot tiny_http server bound to a 127.0.0.1 ephemeral port, serving the mp3 + a small autoplay page. The container's `$VSCODE_PROXY_URI` wraps the port into a Cloudflare-routable URL. The chosen surface — Hydrogen webview tab (default) or pup browser window — navigates to that URL. Same plumbing both surfaces, no port mappings, no clip-size limits, no base64 URL bloat.++**Tab/window auto-closes** when the audio's `ended` event fires (or a 5-min cap). Programmatic, not AI-driven — the binary handles cleanup. Override with `ADOM_TTS_KEEP_OPEN=1` if you want the player to linger.++**Surface precedence:** `--surface hydrogen|pup` flag → `$ADOM_TTS_SURFACE` env → `~/.adom/tts/config.toml` (`[play] surface = "..."`) → `"hydrogen"` default.++**Self-teaching hints.** `say` without `--play` emits stderr hints pointing the AI at the auto-player and warning against the anti-patterns we've seen sessions fall into (custom HTML, port-add, etc.). Silenceable via `ADOM_TTS_NO_HINTS=1`.++## Hands-free trigger phrases++When the user says "I'm driving", "read it to me", "can't read the screen", "in the car", "narrate the answer" — they CANNOT see your text response. Synthesize and `--play`. The skill front-matter lists the full trigger catalog.++## Playground webview (unchanged from 0.1.1)++`adom-tts serve` opens a Hydrogen webview panel. Type what you want to hear, pick a voice, hit Enter. Every clip you generate (or that any other tool drops via `adom-tts say --history` / `adom-tts push`) is saved to `~/.adom/tts/history/` and replayable from the list. Download, copy shareable URLs, delete — all per-clip.++**Pronunciation authoring.** A live view of the global pronunciation table sits next to the compose area. When your input contains a term that already has an override, a hint appears: "*adom-tsci* will be voiced as `adom t s c i` · global override". An Add form lets you propose new entries, with a **Preview** button that synthesizes raw + phoneticized forms back-to-back, and a **Propose** button that writes straight into `gallia/skills/tts-pronunciation/pronunciations.json`.++**Everything is AI-drivable.** Every UI action has an HTTP equivalent — `POST /synth`, `GET /history`, `DELETE /history/:id`, `GET /pronunciations`, `POST /pronunciation/propose`, `GET /state`, `GET /console`, `POST /shutdown`.++## Install (Tier B)++Paste-into-Claude:++```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+```++`adom-tts install` deploys the SKILL.md + `adom-tts-build` sibling skill + bash completions. Gallia's 30-min refresh hook keeps the installed version in sync with the wiki's `pub_version`.++## CLI++```bash+# Synthesis + auto-play (the common case)+adom-tts say "Hello from adom-tsci" --out hello.mp3 --play+adom-tts say "narration" --out n.mp3 --voice en-US-AriaNeural --play++# Replay+adom-tts play last+adom-tts play /tmp/x.mp3 --wait      # block until ended (max 5 min)+adom-tts play /tmp/x.mp3 --surface pup++# Synthesis only (rare — when you genuinely just need the file)+adom-tts say "for later" --out later.mp3++# Playground+adom-tts serve [--port 8795]+adom-tts push path/to/clip.mp3++# Pronunciations+adom-tts pron list+adom-tts pron add "adom-tsci" "adom t s c i" --reason "letter-by-letter forces 4-letter reading"++# Introspection+adom-tts voices | jq '.count'+adom-tts pronunciations | jq '.count'+adom-tts health+adom-tts config            # current service URL + bake/env source+adom-tts config --show     # also dump resolved play surface + config path+```++`$ADOM_TTS_API` overrides the baked-in service URL. Default voice is `en-US-AndrewNeural` — the house voice for every Adom demo.++## Anti-patterns the new auto-player exists to prevent++- ❌ `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` for a permanent `*.adom.cloud` subdomain. Total overkill for a one-shot.+- ✅ Just use `--play`. The binary handles surface, URL, autoplay, and cleanup.++## Who uses this++- `demo-recording` / `voiceover` / `tour` — everyone that renders narration+- `aci voice` — backend precedence `$ACI_VOICE_API → adom-tts CLI → edge-tts local fallback`+- Drive-mode users via Claude RC — synthesize + auto-play means hands-free answers+- Any new tool that needs spoken output should shell out to `adom-tts say` rather than calling edge-tts directly++## Exit codes++| code | meaning |+|---|---|+| `0` | ok |+| `1` | invalid input (empty text, missing `--out`) |+| `2` | service unreachable — check `adom-tts config` or set `$ADOM_TTS_API` |+| `4` | service-side error (edge-tts failed, etc.) |++Source (private): [`adom-inc/adom-tts`](https://github.com/adom-inc/adom-tts). Service backend: [service-tts](/apps/service-tts). Pronunciation source of truth: `gallia/skills/tts-pronunciation/pronunciations.json`.
page.jsonadded+54
@@ -0,0 +1,54 @@+{+  "schema_version": 1,+  "type": "app",+  "slug": "adom-tts",+  "title": "adom-tts — TTS Playground + Auto-Player",+  "brief": "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",+  "version": "0.1.2",+  "tags": [],+  "license": "MIT",+  "sample_prompts": [+    {+      "label": "Drive mode",+      "prompt": "Read me your latest answer, I'm driving"+    },+    {+      "label": "Synthesize and play",+      "prompt": "Synthesize a 30-second walkthrough of this design and play it for me"+    },+    {+      "label": "Replay last",+      "prompt": "Play the last TTS clip again"+    },+    {+      "label": "Open playground",+      "prompt": "Open the TTS Playground webview"+    },+    {+      "label": "Add pronunciation",+      "prompt": "Teach the TTS service that aci is pronounced A-C-I"+    }+  ],+  "install": {+    "binary_name": "adom-tts",+    "install_dir": "",+    "install_hint": "",+    "version_cmd": ""+  },+  "readme": "# adom-tts\n\nThin Rust CLI + webview playground for [service-tts](/apps/service-tts) — Adom's shared edge-tts wrapper with pronunciation overrides + source-hash cache.\n\n## What's new in 0.1.2 — built-in auto-player\n\nThe CLI now plays its own mp3s. **Stop authoring custom autoplay HTML or `python3 -m http.server` workarounds.**\n\n```bash\n# One shot: synthesize and auto-play. CLI returns immediately (detached).\nadom-tts say \"Read me your latest answer, I'm driving.\" --out /tmp/x.mp3 --play\n\n# Replay the newest history clip.\nadom-tts play last\n\n# Replay any history clip by full id or unique substring of its hash.\nadom-tts play 5f79f0\n```\n\n**How it works.** `play` (and `say --play`) spins up a one-shot tiny_http server bound to a 127.0.0.1 ephemeral port, serving the mp3 + a small autoplay page. The container's `$VSCODE_PROXY_URI` wraps the port into a Cloudflare-routable URL. The chosen surface — Hydrogen webview tab (default) or pup browser window — navigates to that URL. Same plumbing both surfaces, no port mappings, no clip-size limits, no base64 URL bloat.\n\n**Tab/window auto-closes** when the audio's `ended` event fires (or a 5-min cap). Programmatic, not AI-driven — the binary handles cleanup. Override with `ADOM_TTS_KEEP_OPEN=1` if you want the player to linger.\n\n**Surface precedence:** `--surface hydrogen|pup` flag → `$ADOM_TTS_SURFACE` env → `~/.adom/tts/config.toml` (`[play] surface = \"...\"`) → `\"hydrogen\"` default.\n\n**Self-teaching hints.** `say` without `--play` emits stderr hints pointing the AI at the auto-player and warning against the anti-patterns we've seen sessions fall into (custom HTML, port-add, etc.). Silenceable via `ADOM_TTS_NO_HINTS=1`.\n\n## Hands-free trigger phrases\n\nWhen the user says \"I'm driving\", \"read it to me\", \"can't read the screen\", \"in the car\", \"narrate the answer\" — they CANNOT see your text response. Synthesize and `--play`. The skill front-matter lists the full trigger catalog.\n\n## Playground webview (unchanged from 0.1.1)\n\n`adom-tts serve` opens a Hydrogen webview panel. Type what you want to hear, pick a voice, hit Enter. Every clip you generate (or that any other tool drops via `adom-tts say --history` / `adom-tts push`) is saved to `~/.adom/tts/history/` and replayable from the list. Download, copy shareable URLs, delete — all per-clip.\n\n**Pronunciation authoring.** A live view of the global pronunciation table sits next to the compose area. When your input contains a term that already has an override, a hint appears: \"*adom-tsci* will be voiced as `adom t s c i` · global override\". An Add form lets you propose new entries, with a **Preview** button that synthesizes raw + phoneticized forms back-to-back, and a **Propose** button that writes straight into `gallia/skills/tts-pronunciation/pronunciations.json`.\n\n**Everything is AI-drivable.** Every UI action has an HTTP equivalent — `POST /synth`, `GET /history`, `DELETE /history/:id`, `GET /pronunciations`, `POST /pronunciation/propose`, `GET /state`, `GET /console`, `POST /shutdown`.\n\n## Install (Tier B)\n\nPaste-into-Claude:\n\n```bash\ncurl -fsSL https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-tts/adom-tts \\\n  -o /tmp/adom-tts && chmod +x /tmp/adom-tts \\\n  && sudo install -m 0755 /tmp/adom-tts /usr/local/bin/adom-tts \\\n  && adom-tts install \\\n  && adom-tts health\n```\n\n`adom-tts install` deploys the SKILL.md + `adom-tts-build` sibling skill + bash completions. Gallia's 30-min refresh hook keeps the installed version in sync with the wiki's `pub_version`.\n\n## CLI\n\n```bash\n# Synthesis + auto-play (the common case)\nadom-tts say \"Hello from adom-tsci\" --out hello.mp3 --play\nadom-tts say \"narration\" --out n.mp3 --voice en-US-AriaNeural --play\n\n# Replay\nadom-tts play last\nadom-tts play /tmp/x.mp3 --wait      # block until ended (max 5 min)\nadom-tts play /tmp/x.mp3 --surface pup\n\n# Synthesis only (rare — when you genuinely just need the file)\nadom-tts say \"for later\" --out later.mp3\n\n# Playground\nadom-tts serve [--port 8795]\nadom-tts push path/to/clip.mp3\n\n# Pronunciations\nadom-tts pron list\nadom-tts pron add \"adom-tsci\" \"adom t s c i\" --reason \"letter-by-letter forces 4-letter reading\"\n\n# Introspection\nadom-tts voices | jq '.count'\nadom-tts pronunciations | jq '.count'\nadom-tts health\nadom-tts config            # current service URL + bake/env source\nadom-tts config --show     # also dump resolved play surface + config path\n```\n\n`$ADOM_TTS_API` overrides the baked-in service URL. Default voice is `en-US-AndrewNeural` — the house voice for every Adom demo.\n\n## Anti-patterns the new auto-player exists to prevent\n\n- ❌ `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.\n- ❌ Custom HTML with base64-inlined mp3 + `data:` URL. Past 2 MB this hits Chrome's URL length limit.\n- ❌ `adom-cli carbon containers port-add` for a permanent `*.adom.cloud` subdomain. Total overkill for a one-shot.\n- ✅ Just use `--play`. The binary handles surface, URL, autoplay, and cleanup.\n\n## Who uses this\n\n- `demo-recording` / `voiceover` / `tour` — everyone that renders narration\n- `aci voice` — backend precedence `$ACI_VOICE_API → adom-tts CLI → edge-tts local fallback`\n- Drive-mode users via Claude RC — synthesize + auto-play means hands-free answers\n- Any new tool that needs spoken output should shell out to `adom-tts say` rather than calling edge-tts directly\n\n## Exit codes\n\n| code | meaning |\n|---|---|\n| `0` | ok |\n| `1` | invalid input (empty text, missing `--out`) |\n| `2` | service unreachable — check `adom-tts config` or set `$ADOM_TTS_API` |\n| `4` | service-side error (edge-tts failed, etc.) |\n\nSource (private): [`adom-inc/adom-tts`](https://github.com/adom-inc/adom-tts). Service backend: [service-tts](/apps/service-tts). Pronunciation source of truth: `gallia/skills/tts-pronunciation/pronunciations.json`.\n",+  "author": {+    "id": "695820315b5f1e4db2fcf602",+    "name": "Kyle Bergstedt",+    "email": "[email protected]"+  },+  "visibility": {+    "public": true+  },+  "hero": null,+  "discovery_triggers": [],+  "discovery_pitch": null,+  "metadata": {},+  "created_at": "2026-05-28T05:29:06.606Z",+  "updated_at": "2026-05-28T05:29:06.606Z",+  "skills": []+}\ No newline at end of file