refactor: switch the whole project to English
talk is going to be published. Code comments, CLI output, error messages,
daemon log lines, MCP descriptions and results, test names and assertions,
README, spec, skill, build scripts and flake are now English. Only the
spoken announcement texts stay bilingual: the wrap result and the test
sentence are German or English depending on the detected language.
Language fallback is now configurable: `lang = "en" | "de"` (env
TALK_LANG, default en) is used when detection cannot tell (numbers, single
words, ties) and for talk's own announcements. langdetect.Detect takes the
fallback as an argument instead of hard-coding German. `talk setup` asks for
it; `talk doctor` and `talk status` show it.
Vocabulary pinned across packages so the process-boundary tests observe
stable strings: chain log lines ("chain: tier X skipped (not available)",
"falling back to the next tier", "spoken via X"), queue lines ("discarded:
quiet hours ... active", "replaced by toast", "max_age"), CLI notes
("speaking directly", "daemon started at", "no daemon", "nothing to cancel"),
status block with aligned English labels, MCP messages, origin guard 403.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d30bd95ad
commit
85fa64cfe6
95 files changed
+4707
-4854
No files matched your search
+99
-99
@@ -1,133 +1,133 @@
|
||||
# Spec: talk 2.0 — Cross-Platform Voice Notifications für KI-Agenten
|
||||
# Spec: talk 2.0: Cross-Platform Voice Notifications for AI Agents
|
||||
|
||||
Status: ready-for-agent
|
||||
Created: 2026-10-18
|
||||
Source: Grilling-Session 2026-10-18 (shared understanding, alle Äste besucht)
|
||||
Source: grilling session 2026-10-18 (shared understanding, all branches visited)
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
KI-Agenten (Coding-Agenten wie opencode/Claude Code, Firmen-Chats, Hintergrund-Jobs) können mich heute nur visuell erreichen — Terminal-Output, Toast, push notification. Wenn ich weg vom Bildschirm bin (Kaffee, anderes Fenster, Meeting am zweiten Monitor), verpasse ich, dass ein Build fertig ist, ein Agent auf meine Freigabe wartet oder ein Job gecrasht ist.
|
||||
Today, AI agents (coding agents such as opencode/Claude Code, company chats, background jobs) can only reach me visually: terminal output, toast, push notification. When I am away from the screen (coffee, another window, a meeting on the second monitor), I miss that a build has finished, that an agent is waiting for my approval, or that a job has crashed.
|
||||
|
||||
Der bestehende talk/crunch-Stack löst das prinzipiell, ist aber unbrauchbar als Basis: bash + mpv + notify-send + agenix = Linux-Desktop-only, ElevenLabs-only (Key pro Host, in der Praxis nur auf einem bestätigt funktionierend), kein natives Tool-Interface für Agenten (nur Shell-Umwege), keine Queue (Audio überlappt), flüchtiger Mute-Toggle, und null Geschichte auf dem Windows-Fleet der Firma.
|
||||
The existing talk/crunch stack solves this in principle but is unusable as a foundation: bash + mpv + notify-send + agenix means Linux desktop only, ElevenLabs only (key per host, in practice confirmed working on just one), no native tool interface for agents (shell workarounds only), no queue (audio overlaps), a volatile mute toggle, and no story at all for the company's Windows fleet.
|
||||
|
||||
## Solution
|
||||
|
||||
**talk 2.0**: Ein cross-platform Werkzeug (Windows/macOS/Linux, amd64+arm64) in Go als statische Binaries — `talk` (CLI mit Bubble-Tea-TUI) und `talkd` (Daemon mit Queue). Jeder KI-Agent, der ein Programm ausführen kann, kann damit sprechen: `talk "Build fertig"`. Lokale MCP-Clients bekommen zusätzlich einen nativen MCP-Endpoint auf localhost. Die TTS-Qualität skaliert mit verfügbaren Keys über eine Vier-Stufen-Fallback-Kette bis hinunter zu kostenlosen/offline-Stimmen — Stimme funktioniert also überall ab Minute eins. Sprechen ist striktes Opt-in durch den Nutzer; der Daemon erzwingt Rate-Limit, Mute und Ruhezeiten als Backstop unter allem Prompt-Verhalten.
|
||||
**talk 2.0**: a cross-platform tool (Windows/macOS/Linux, amd64+arm64) written in Go as static binaries: `talk` (CLI with a Bubble Tea TUI) and `talkd` (daemon with queue). Any AI agent that can run a program can speak with it: `talk "Build done"`. Local MCP clients additionally get a native MCP endpoint on localhost. TTS quality scales with the available keys through a four-stage fallback chain down to free/offline voices, so voice works everywhere from minute one. Speaking is strictly opt-in by the user; the daemon enforces rate limit, mute and quiet hours as a backstop beneath all prompt behaviour.
|
||||
|
||||
Verteilung: Gitea-Releases nach agent-lib-Muster (Asset-Schema `talk_<os>_<arch>` + sha256); Firma: interne Spiegelung + Fleet-Role (Ansible, Program Files + PATH); privat: Nix-Flake. Skill lebt im Repo und rollt in der Firma über az-agent-defaults/agent-lib aus.
|
||||
Distribution: Gitea releases following the agent-lib pattern (asset schema `talk_<os>_<arch>` + sha256); company: internal mirror + fleet role (Ansible, Program Files + PATH); private: Nix flake. The skill lives in the repo and rolls out in the company via az-agent-defaults/agent-lib.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. Als **Nutzer** will ich `talk "Text"` aufrufen können, damit ich sofort weiß, wie die Schnittstelle funktioniert, ohne Doku zu lesen.
|
||||
2. Als **Nutzer** will ich, dass der Aufruf non-blocking zurückkehrt, damit mein Skript/Agent nicht auf das Audio wartet.
|
||||
3. Als **Skript-Autor** will ich `talk "Text" --wait`, damit Cron-Jobs erst nach der Ansage weiterlaufen bzw. enden.
|
||||
4. Als **Nutzer** will ich `talk wrap -- <befehl>` nutzen können, damit mir Dauer + Erfolg/Misserfolg eines beliebigen Befehls angesagt werden.
|
||||
5. Als **KI-Agent** will ich das Programm mit Argument oder via stdin füttern können, damit ich es aus jeder Umgebung heraus aufrufen kann.
|
||||
6. Als **KI-Agent (MCP-fähig)** will ich einen lokalen MCP-Endpoint mit einem speak-Tool nutzen können, damit ich Voice nativ als Tool habe statt Shell-Umwege.
|
||||
7. Als **Browser-KI-Chat (später)** will ich `POST /speak` auf localhost mit CORS-Erlaubnis rufen können, damit eine Web-UI (z. B. LibreChat) mich ansprechen kann, ohne dass talk dafür geändert werden muss.
|
||||
8. Als **Nutzer mit ElevenLabs-Key** will ich die Premium-Stimme bekommen, damit Ansagen so klingen wie gewohnt.
|
||||
9. Als **Nutzer mit nur einem OpenAI-Key** will ich OpenAI-TTS als Provider nutzen können, damit ich keinen zweiten Account brauche.
|
||||
10. Als **Nutzer ohne jeden Key** will ich automatisch Edge-TTS (kostenlos) bekommen, damit Stimme out-of-the-box funktioniert.
|
||||
11. Als **Offline-Nutzer** will ich auf OS-eingebaute TTS-Stimmen (say/SAPI/espeak) zurückfallen, damit auch ohne Netz/Key gesprochen wird.
|
||||
12. Als **Nutzer** will ich, dass die Provider-Kette automatisch durchfällt (Key ungültig, Kontingent leer, Netz weg), damit nie stumm bleibt, wenn nicht sein muss.
|
||||
13. Als **Nutzer** will ich die Sprache des Texts automatisch erkannt bekommen (de/en), damit englische Agent-Antworten nicht mit deutscher Stimme verhackstückt werden.
|
||||
14. Als **Nutzer** will ich pro Provider und Sprache eine Stimme in der config.toml setzen, damit meine gewohnte Stimme überall gleich klingt.
|
||||
15. Als **Nutzer** will ich `talk setup` als geführten Wizard (Stimmen probehören, Key testen, Config schreiben), damit ich ohne Terminal-Erfahrung in 2 Minuten eingerichtet bin.
|
||||
16. Als **Nutzer** will ich `talk status` als Live-Dashboard (Queue, Mute, aktiver Provider, Version), damit ich immer weiß, was das Tool gerade tut.
|
||||
17. Als **Nutzer** will ich `talk voices` als interaktive Liste mit Vorhör-Preview, damit ich Stimmen ohne IDs auswendig zu lernen vergleichen kann.
|
||||
18. Als **Nutzer** will ich `talk mute` / `talk unmute` als persistenten Schalter, damit alle Agenten und Skripte still sind, bis ich es erlaube.
|
||||
19. Als **Nutzer** will ich konfigurierbare Ruhezeiten (`quiet_hours = "18:00-08:00"`), damit 23:47-Uhr-Cron-Jobs mich nicht wecken.
|
||||
20. Als **Nutzer** will ich `talk cancel`, damit eine laufende oder geplante Ansage sofort stoppt.
|
||||
21. Als **Nutzer** will ich `--priority`, damit Dringendes die Queue vorn überspringt und Gedrängel bei Rate-Limit verworfen werden darf.
|
||||
22. Als **Nutzer** will ich, dass sich Audio-Nachrichten nie überlappen (Queue serialisiert), damit bei drei gleichzeitigen Agenten alles verständlich bleibt.
|
||||
23. Als **Nutzer** will ich ein Rate-Limit als Daemon-Gesetz (Standard ~1 Ansage/30 s), damit kein Agent-Spam möglich ist, egal was im Prompt steht.
|
||||
24. Als **Agent-nutzender Nutzer** will ich Opt-in-Etikette im Skill dokumentiert: Agent spricht nur auf meine Anweisung oder Standing-Order in der AGENTS.md, session-sticky bis Widerruf, damit keine Agentin von sich aus plappert.
|
||||
25. Als **Agent-nutzender Nutzer** will ich, dass die Ansage nützlich ist (Status / nächster Schritt / wofür ich gebraucht werde), nicht die vorgelesene Langantwort, damit 10 Sekunden Ansage reichen.
|
||||
26. Als **Fleet-Administrator** will ich statische Binaries von Gitea-Releases versionpinnt + sha256-verifiziert ausrollen (agent-lib/officecli-Muster), damit das Ansible-Rollout deterministisch ist.
|
||||
27. Als **Fleet-Administrator** will ich Keys user-spezifisch als Env-Variablen (`TALK_*`) ausrollen nach unserem AZ_FLEET-Muster, damit kein Secret in Config-Dateien landet.
|
||||
28. Als **Fleet-Administrator** will ich `talk doctor` als Diagnose-Output, damit „kein Ton bei Kollege X" in 5 Sekunden selbsterklärend ist.
|
||||
29. Als **Fleet-Benutzer ohne Adminrechte** will ich, dass talk ohne Runtime-Abhängigkeiten (kein ffmpeg, kein Player, kein Python) läuft, damit es auf jedem Firmenrechner einfach funktioniert.
|
||||
30. Als **Nutzer auf einem Rechner ohne laufenden Daemon** will ich einen Degraded Mode (Lock-File, sequenziell), damit talk auch ohne talkd nie kaputt ist.
|
||||
31. Als **Privatnutzer mit NixOS-Hosts** will ich einen Flake-Output im Repo, damit meine Hosts talk wie gewohnt deklarativ beziehen.
|
||||
32. Als **OS-Nutzer** will ich eine Desktop-Notification parallel zum Audio mit Abbrechen-Button, wo das OS es hergibt, damit ich stummschalten kann, ohne ein Terminal zu öffnen.
|
||||
33. Als **Nicht-Terminal-Nutzer** will ich, dass TUI-Befehle ohne TTY auf plain Output zurückfallen, damit Pipes und Automatisierung niemals brechen.
|
||||
34. Als **Projekt-Betreuer** will ich PR-getriebene Tests + Release-Matrix in Gitea Actions, damit jeder Tag automatisch konsistent gebaute Artefakte für alle Plattformen produziert.
|
||||
1. As a **user** I want to call `talk "text"` so that I immediately know how the interface works without reading docs.
|
||||
2. As a **user** I want the call to return non-blocking so that my script/agent does not wait for the audio.
|
||||
3. As a **script author** I want `talk "text" --wait` so that cron jobs only continue or exit after the announcement.
|
||||
4. As a **user** I want to use `talk wrap -- <command>` so that the duration and success/failure of any command are announced to me.
|
||||
5. As an **AI agent** I want to feed the program via argument or stdin so that I can call it from any environment.
|
||||
6. As an **AI agent (MCP-capable)** I want a local MCP endpoint with a speak tool so that I have voice natively as a tool instead of shell workarounds.
|
||||
7. As a **browser AI chat (later)** I want to call `POST /speak` on localhost with CORS permission so that a web UI (e.g. LibreChat) can address me without talk having to change.
|
||||
8. As a **user with an ElevenLabs key** I want the premium voice so that announcements sound the way I am used to.
|
||||
9. As a **user with only an OpenAI key** I want to use OpenAI TTS as a provider so that I do not need a second account.
|
||||
10. As a **user without any key** I want to get Edge TTS (free) automatically so that voice works out of the box.
|
||||
11. As an **offline user** I want to fall back to OS built-in TTS voices (say/SAPI/espeak) so that speech works even without network or key.
|
||||
12. As a **user** I want the provider chain to fall through automatically (invalid key, quota exhausted, network gone) so that it never stays silent when it does not have to.
|
||||
13. As a **user** I want the language of the text detected automatically (de/en) so that English agent replies are not mangled by a German voice.
|
||||
14. As a **user** I want to set a voice per provider and language in config.toml so that my familiar voice sounds the same everywhere.
|
||||
15. As a **user** I want `talk setup` as a guided wizard (preview voices, test key, write config) so that I am set up in 2 minutes without terminal experience.
|
||||
16. As a **user** I want `talk status` as a live dashboard (queue, mute, active provider, version) so that I always know what the tool is doing.
|
||||
17. As a **user** I want `talk voices` as an interactive list with audio preview so that I can compare voices without memorising IDs.
|
||||
18. As a **user** I want `talk mute` / `talk unmute` as a persistent switch so that all agents and scripts stay silent until I allow it.
|
||||
19. As a **user** I want configurable quiet hours (`quiet_hours = "18:00-08:00"`) so that 23:47 cron jobs do not wake me.
|
||||
20. As a **user** I want `talk cancel` so that a running or scheduled announcement stops immediately.
|
||||
21. As a **user** I want `--priority` so that urgent items skip to the front of the queue and crowding under the rate limit may be discarded.
|
||||
22. As a **user** I want audio messages to never overlap (the queue serialises) so that everything stays intelligible with three agents at once.
|
||||
23. As a **user** I want a rate limit as daemon law (default ~1 announcement/30 s) so that agent spam is impossible, whatever the prompt says.
|
||||
24. As an **agent-using user** I want opt-in etiquette documented in the skill: the agent speaks only on my instruction or a standing order in AGENTS.md, session-sticky until revoked, so that no agent chatters on its own.
|
||||
25. As an **agent-using user** I want the announcement to be useful (status / next step / what I am needed for), not the long answer read aloud, so that 10 seconds of announcement are enough.
|
||||
26. As a **fleet administrator** I want to roll out static binaries from Gitea releases, version-pinned and sha256-verified (agent-lib/officecli pattern), so that the Ansible rollout is deterministic.
|
||||
27. As a **fleet administrator** I want to roll out keys per user as environment variables (`TALK_*`) following our AZ_FLEET pattern so that no secret ends up in config files.
|
||||
28. As a **fleet administrator** I want `talk doctor` as diagnostic output so that "no sound at colleague X" is self-explanatory in 5 seconds.
|
||||
29. As a **fleet user without admin rights** I want talk to run without runtime dependencies (no ffmpeg, no player, no Python) so that it simply works on every company machine.
|
||||
30. As a **user on a machine without a running daemon** I want a degraded mode (lock file, sequential) so that talk is never broken even without talkd.
|
||||
31. As a **private user with NixOS hosts** I want a flake output in the repo so that my hosts pull talk declaratively as usual.
|
||||
32. As an **OS user** I want a desktop notification alongside the audio with a cancel button, where the OS allows it, so that I can silence it without opening a terminal.
|
||||
33. As a **non-terminal user** I want TUI commands to fall back to plain output without a TTY so that pipes and automation never break.
|
||||
34. As a **project maintainer** I want PR-driven tests + a release matrix in Gitea Actions so that every tag automatically produces consistently built artifacts for all platforms.
|
||||
|
||||
## Implementation Decisions
|
||||
|
||||
- **Sprache/Laufzeit**: Go; statische Binaries für win/mac/linux × amd64/arm64. Keine Runtime-Abhängigkeiten.
|
||||
- **Binaries**: `talk` (CLI) + `talkd` (Daemon). Repo: `code.m3ta.dev/m3tam3re/talk`, MIT-Lizenz.
|
||||
- **Daemon**: talkd besitzt die Queue (Serialisierung, Prioritäten, Rate-Limit, Verwerfen bei Gedrängel), den persistenten Mute-State, die Ruhezeiten-Fenster (lokale Zeit, still verwerfen oder toast-only, konfigurierbar), die HTTP-API (`POST /speak` auf 127.0.0.1, CORS-Domains konfigurierbar) und einen MCP-Endpoint (Streamable HTTP, localhost).
|
||||
- **Degraded Mode**: ohne laufenden talkd fällt das CLI auf Lock-File-Sequential zurück; nichts bricht still.
|
||||
- **Audio**: embedded Playback (oto) — kein mpv/ffmpeg nötig. Audio-Sink wählbar inkl. `null` (CI, Server, doctor).
|
||||
- **Notifications**: best effort über beeep (notify-send / macOS Notification Center / Windows Toast); Cancel-Button, wo das OS ihn hergibt.
|
||||
- **Provider-Kette V1** (Auto-Fallback in dieser Reihenfolge, überschreibbar per Config/Flag): ElevenLabs → OpenAI TTS → Edge-TTS (frei, kein Key) → OS built-in (macOS say / Windows SAPI / Linux espeak).
|
||||
- **Keys/Config**: Env-first (`TALK_ELEVENLABS_KEY`, `TALK_OPENAI_KEY`, …) plus optionale `config.toml` im platform-nativen Config-Dir (`~/.config/talk` / `~/Library/Application Support/talk` / `%APPDATA%\talk`). Provider-Endpunkte konfigurierbar (Testbarkeit, Firmenserver später). Per-User-Keys in V1; zentraler Firmenserver/Gateway bewusst später.
|
||||
- **Sprache/Stimmen**: de/en-Heuristik (reine Funktion, kein ML); `voice.<provider>.<lang>`-Mapping in der config.toml; persönliche ElevenLabs-Stimme (KXxZ…) als privater Default, neutrale Defaults im Repo.
|
||||
- **CLI-Verhalten**: non-blocking Default; Flags `--wait`, `--now` (Queue-Überprung für Dringendes), `--priority`, `--voice`, `--lang`, `--provider`. Befehle: say, wrap, mute, unmute, status, cancel, voices, test, doctor, setup.
|
||||
- **TUI (Bubble Tea)**: interaktiv = setup (Wizard), status (Live-Dashboard), voices (Picker mit Preview); plain & scriptbar = say, wrap, mute, cancel, test, doctor; kein TTY → immer plain.
|
||||
- **Etikette (Skill-Policy)**: striktes Opt-in — Sprechen nur auf Nutzer-Trigger des Skills oder Standing-Order in der AGENTS.md; session-sticky bis Widerruf (Prompt-Level, kein Daemon-Session-State); Ansageinhalt = Status/nächster Schritt/wofür der Nutzer gebraucht wird, nie die Langantwort. Daemon-Backstop: Rate-Limit + Mute + Ruhezeiten gelten unabhängig vom Prompt.
|
||||
- **Verteilung**: GoReleaser → Gitea-Releases, Asset-Schema `talk_<os>_<arch>` + sha256 (agent-lib-Vertrag); Firma spiegelt intern (pin_mirror-Muster) + Fleet-Role nach agent-lib/officecli-Vorbild (Program Files + Machine-PATH, admin-geschützte ProgramData-Config); Skill-Rollout Firma via az-agent-defaults → agent-lib Three-State-Sync; privat via flake.nix.
|
||||
- **CI**: Gitea Actions — PR → Tests, Tag → Release-Matrix.
|
||||
- **Legacy-Abriss**: alte voice-notify/crunch-jobs-Skills (AGENTS-Repo) werden Redirect-Stubs; nixpkgs-PRs #24/#7 werden als superseded geschlossen; m3ta-nixpkgs talk/crunch nach Migration deprecatet; ElevenLabs-Key (exponiert in altem Prototyp-Skript) wird rotiert, danach das Skript gelöscht.
|
||||
- **Naming**: Name bleibt `talk` (akzeptierte Kollision mit historischem Unix-talk; `say` vermieden wegen macOS).
|
||||
- **Language/runtime**: Go; static binaries for win/mac/linux x amd64/arm64. No runtime dependencies.
|
||||
- **Binaries**: `talk` (CLI) + `talkd` (daemon). Repo: `code.m3ta.dev/m3tam3re/talk`, MIT licence.
|
||||
- **Daemon**: talkd owns the queue (serialisation, priorities, rate limit, discarding under crowding), the persistent mute state, the quiet-hours windows (local time, discard silently or toast-only, configurable), the HTTP API (`POST /speak` on 127.0.0.1, CORS domains configurable) and an MCP endpoint (Streamable HTTP, localhost).
|
||||
- **Degraded mode**: without a running talkd the CLI falls back to lock-file sequential; nothing breaks silently.
|
||||
- **Audio**: embedded playback (oto), no mpv/ffmpeg needed. Audio sink selectable, including `null` (CI, server, doctor).
|
||||
- **Notifications**: best effort via beeep (notify-send / macOS Notification Center / Windows toast); cancel button where the OS allows it.
|
||||
- **Provider chain V1** (auto-fallback in this order, overridable via config/flag): ElevenLabs -> OpenAI TTS -> Edge TTS (free, no key) -> OS built-in (macOS say / Windows SAPI / Linux espeak).
|
||||
- **Keys/config**: env-first (`TALK_ELEVENLABS_KEY`, `TALK_OPENAI_KEY`, ...) plus an optional `config.toml` in the platform-native config dir (`~/.config/talk` / `~/Library/Application Support/talk` / `%APPDATA%\talk`). Provider endpoints configurable (testability, company server later). Per-user keys in V1; a central company server/gateway deliberately later.
|
||||
- **Language/voices**: de/en heuristic (pure function, no ML); `voice.<provider>.<lang>` mapping in config.toml; personal ElevenLabs voice (KXxZ...) as private default, neutral defaults in the repo.
|
||||
- **CLI behaviour**: non-blocking by default; flags `--wait`, `--now` (queue bypass for urgent items), `--priority`, `--voice`, `--lang`, `--provider`. Commands: say, wrap, mute, unmute, status, cancel, voices, test, doctor, setup.
|
||||
- **TUI (Bubble Tea)**: interactive = setup (wizard), status (live dashboard), voices (picker with preview); plain and scriptable = say, wrap, mute, cancel, test, doctor; no TTY -> always plain.
|
||||
- **Etiquette (skill policy)**: strict opt-in. Speaking only on a user trigger of the skill or a standing order in AGENTS.md; session-sticky until revoked (prompt level, no daemon session state); announcement content = status / next step / what the user is needed for, never the long answer. Daemon backstop: rate limit + mute + quiet hours apply regardless of the prompt.
|
||||
- **Distribution**: GoReleaser -> Gitea releases, asset schema `talk_<os>_<arch>` + sha256 (agent-lib contract); the company mirrors internally (pin_mirror pattern) + fleet role modelled on agent-lib/officecli (Program Files + machine PATH, admin-protected ProgramData config); company skill rollout via az-agent-defaults -> agent-lib three-state sync; private via flake.nix.
|
||||
- **CI**: Gitea Actions: PR -> tests, tag -> release matrix.
|
||||
- **Legacy teardown**: the old voice-notify/crunch-jobs skills (AGENTS repo) become redirect stubs; nixpkgs PRs #24/#7 are closed as superseded; m3ta-nixpkgs talk/crunch deprecated after migration; the ElevenLabs key (exposed in the old prototype script) is rotated, then the script is deleted.
|
||||
- **Naming**: the name stays `talk` (accepted collision with the historical Unix talk; `say` avoided because of macOS).
|
||||
|
||||
## Testing Decisions
|
||||
|
||||
- **Guter Test** testet ausschließlich extern beobachtbares Verhalten an der Schnittstelle, nie Implementationsdetails.
|
||||
- **Primäre Seam (eine)**: die Prozessgrenze von talkd — Daemon + CLI als Black Box, mit zwei über die Config injizierbaren Fakes: (1) Provider-Endpunkte zeigen im Test auf einen Fake-HTTP-Server mit Audio-Bytes; (2) `audio.sink = "null"` schaltet echtes Playback ab. An dieser Seam werden getestet: Queue-Serialisierung/Priorität/Rate-Limit/Verwerfen, Mute-Persistenz, Ruhezeiten, Fallback-Kette (Fake-Provider 500 → nächster Provider), non-blocking + `--wait`, Degraded Mode, HTTP-`/speak`-Vertrag inkl. CORS, MCP-Endpoint-Grundvertrag, status/cancel/doctor-Output.
|
||||
- **Zwei reine Neben-Seams** (tischgesteuerte Unit-Tests, keine Mocks): Spracherkennung (de/en) und Config-Auflösung (Env + TOML-Merge).
|
||||
- **Bewusst ungetestet** (hardware-/OS-gebunden): echtes Audio-Rendering und OS-Toasts — abgedeckt durch `talk doctor` als manuelles Diagnosewerkzeug.
|
||||
- **Vorläufer**: keine — grüne Wiese. CI läuft von Ticket 1 an in Gitea Actions mit.
|
||||
- **A good test** tests only externally observable behaviour at the interface, never implementation details.
|
||||
- **Primary seam (one)**: the process boundary of talkd. Daemon + CLI as a black box, with two fakes injectable via config: (1) provider endpoints point to a fake HTTP server serving audio bytes in tests; (2) `audio.sink = "null"` disables real playback. Tested at this seam: queue serialisation/priority/rate limit/discarding, mute persistence, quiet hours, fallback chain (fake provider 500 -> next provider), non-blocking + `--wait`, degraded mode, HTTP `/speak` contract including CORS, MCP endpoint basic contract, status/cancel/doctor output.
|
||||
- **Two pure side seams** (table-driven unit tests, no mocks): language detection (de/en) and config resolution (env + TOML merge).
|
||||
- **Deliberately untested** (hardware/OS-bound): real audio rendering and OS toasts, covered by `talk doctor` as a manual diagnostic tool.
|
||||
- **Predecessors**: none, greenfield. CI runs in Gitea Actions from ticket 1 onwards.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- LibreChat-Frontend-Anbindung (CORS-Tür wird gebaut, Integration selbst nicht)
|
||||
- Browser-Extension als Brücke
|
||||
- Zentraler Company-Gateway-Server (ein Key für alle)
|
||||
- Timer/Scheduling (`crunch at|in|daily` bleibt OS-nativ: cron/systemd/Task Scheduler)
|
||||
- Lokale Neural-Stimmen (Piper/Kokoro) — späterer Ausbau
|
||||
- Kalenderbewusste Ruhezeiten (nur statische Zeitfenster in V1)
|
||||
- Homebrew/Scoop/AUR-Feeds — später optional
|
||||
- Mehrnutzer-Maschinen (Ein-Nutzer-pro-Host wie im Fleet)
|
||||
- LibreChat frontend integration (the CORS door is built, the integration itself is not)
|
||||
- Browser extension as a bridge
|
||||
- Central company gateway server (one key for everyone)
|
||||
- Timers/scheduling (`crunch at|in|daily` stays OS-native: cron/systemd/Task Scheduler)
|
||||
- Local neural voices (Piper/Kokoro), later expansion
|
||||
- Calendar-aware quiet hours (static time windows only in V1)
|
||||
- Homebrew/Scoop/AUR feeds, optional later
|
||||
- Multi-user machines (one user per host as in the fleet)
|
||||
|
||||
## Further Notes
|
||||
|
||||
- **Pending (Nutzer)**: ElevenLabs-Key-Rotation im Dashboard VOR Löschung des alten Prototyp-Skripts.
|
||||
- Persönlicher Default-Voice: ElevenLabs `KXxZd16DiBqt82nbarJx` (aus altem Stack übernommen als privater Default, nicht Repo-Default).
|
||||
- Session-Stickiness der Etikette ist Prompt/Skill-Level — der Daemon kennt keine Agent-Sessions.
|
||||
- Offene Später-Entscheidung: LibreChat-Anbindung via Frontend-Patch vs. Browser-Extension — blockiert nichts, Tür steht mit CORS-fähiger HTTP-API.
|
||||
- Verwandte Firmen-ADRs: 0009 (agent-lib), 0012/0013 (Secrets-Env-Bridge/Machine-Env-Keys), 0016/0017 (user-scharfe Keys, MCP-Audience-Scoping) — talk folgt diesen Mustern.
|
||||
- **Pending (user)**: rotate the ElevenLabs key in the dashboard BEFORE deleting the old prototype script.
|
||||
- Personal default voice: ElevenLabs `KXxZd16DiBqt82nbarJx` (carried over from the old stack as private default, not the repo default).
|
||||
- Session stickiness of the etiquette is prompt/skill level; the daemon knows nothing about agent sessions.
|
||||
- Open decision for later: LibreChat integration via frontend patch vs. browser extension. Blocks nothing; the door is open with the CORS-capable HTTP API.
|
||||
- Related company ADRs: 0009 (agent-lib), 0012/0013 (secrets env bridge / machine env keys), 0016/0017 (user-scoped keys, MCP audience scoping). talk follows these patterns.
|
||||
|
||||
---
|
||||
|
||||
## Nachtrag 2026-10-24 — Entscheidungen nach Architektur-Review
|
||||
## Addendum 2026-10-24: decisions after the architecture review
|
||||
|
||||
Diese Punkte ersetzen die entsprechenden Aussagen oben (Grilling-Stand
|
||||
These points replace the corresponding statements above (grilling state of
|
||||
2026-10-18):
|
||||
|
||||
- **Ein Binary statt `talk` + `talkd`.** Der Daemon ist `talk daemon run`;
|
||||
`talk say`/`talk wrap` starten ihn beim ersten Sprechbefehl selbst im
|
||||
Hintergrund (`daemon.autostart`, Default an; `talk daemon start|stop`
|
||||
explizit). Der Degraded Mode mit Lock-Datei entfällt; ohne erreichbaren
|
||||
oder startbaren Daemon spricht talk direkt (unserialisiert, mit Hinweis).
|
||||
- **CLI ist die Agenten-Schnittstelle.** MCP bleibt als Tür bestehen, ist
|
||||
aber kein Fokus mehr; Browser-Zugriff ist serverseitig auf POST /speak
|
||||
von gelisteten Origins beschränkt (Origin-Guard, nicht nur CORS).
|
||||
- **`now:true` bricht bewusst aus der Ruhezeit aus** (bestätigt).
|
||||
- **Zwei weitere Gesetze:** `queue.max_text` (400 Zeichen, Kürzung an der
|
||||
Wortgrenze) und `queue.max_age` (2 min, veraltete Stau-Ansagen
|
||||
verfallen).
|
||||
- **Zielbild Veröffentlichung:** talk soll perspektivisch öffentlich
|
||||
nutzbar sein. Oberstes Kriterium ist Usability für Nicht-Terminal-
|
||||
Nutzer; die Bubble-Tea-TUI ist kein Selbstzweck — einfache, klare
|
||||
Befehle (`doctor`, `setup`, `test`, `voices`) haben Vorrang.
|
||||
- **Release ohne GoReleaser:** `scripts/release.sh` baut die sechs Targets
|
||||
mit `go build`; `.gitea/workflows/release.yml` legt bei Tag `v*` das
|
||||
Gitea-Release an. Asset-Vertrag unverändert (`talk_<os>_<arch>[.exe]` +
|
||||
`checksums.txt`). `flake.nix` liefert Paket, App, Overlay und devShell.
|
||||
- **Notifications ohne beeep:** Desktop-Toast über die Bordmittel des OS
|
||||
(notify-send / osascript / PowerShell), exec-basiert wie osbuiltin;
|
||||
zunächst nur für `quiet_mode = "toast"`. Cancel-Button: später.
|
||||
- **One binary instead of `talk` + `talkd`.** The daemon is `talk daemon run`;
|
||||
`talk say`/`talk wrap` start it themselves in the background on the first
|
||||
speak command (`daemon.autostart`, on by default; `talk daemon start|stop`
|
||||
explicitly). The degraded mode with a lock file is dropped; without a
|
||||
reachable or startable daemon, talk speaks directly (unserialised, with a
|
||||
notice).
|
||||
- **The CLI is the agent interface.** MCP remains as a door but is no longer
|
||||
a focus; browser access is restricted server-side to POST /speak from
|
||||
listed origins (origin guard, not just CORS).
|
||||
- **`now:true` deliberately breaks out of quiet hours** (confirmed).
|
||||
- **Two more laws:** `queue.max_text` (400 characters, truncated at a word
|
||||
boundary) and `queue.max_age` (2 min, stale backlog announcements expire).
|
||||
- **Target picture for publication:** talk should eventually be usable
|
||||
publicly. The top criterion is usability for non-terminal users; the
|
||||
Bubble Tea TUI is not an end in itself. Simple, clear commands (`doctor`,
|
||||
`setup`, `test`, `voices`) take precedence.
|
||||
- **Release without GoReleaser:** `scripts/release.sh` builds the six targets
|
||||
with `go build`; `.gitea/workflows/release.yml` creates the Gitea release
|
||||
on tag `v*`. Asset contract unchanged (`talk_<os>_<arch>[.exe]` +
|
||||
`checksums.txt`). `flake.nix` provides package, app, overlay and devShell.
|
||||
- **Notifications without beeep:** desktop toast via the OS's own tools
|
||||
(notify-send / osascript / PowerShell), exec-based like osbuiltin;
|
||||
initially only for `quiet_mode = "toast"`. Cancel button: later.
|
||||
Reference in new issue
Block a user