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>
Usability-Schicht für Nutzer, die kein Terminal-Handbuch lesen wollen:
- `talk doctor`: eine Zeile je Prüfpunkt mit klarem Satz, was Sache ist und
was zu tun wäre — Config (kaputt → ✘, die übrigen Prüfungen laufen mit
Defaults weiter), Daemon (läuft / startet beim nächsten Sprechbefehl /
fremder Dienst auf dem Port → ✘ mit Port-Hinweis), Audio (oto-Kontext
wirklich initialisieren; null → ⚠), jede Provider-Stufe (Key-Env-Name,
Endpunkt, Default-Stimme; OS-Stimme nennt den gefundenen Mechanismus),
Mute (an → ⚠ mit unmute-Hinweis), Gesetze inkl. gerade aktiver Ruhezeit.
--probe macht echte Synthese-Proben, --json liefert die Liste. Exit 0
ohne ✘. Windows-Konsolen bekommen [OK]/[WARN]/[FAIL].
- `talk test`: Testsatz direkt über den Sprech-Kern (nicht über den
Daemon), nennt die Stufe, die wirklich gesprochen hat, Synthese- und
Wiedergabezeit; Mute/Ruhezeit gelten bewusst nicht, Mute wird genannt.
- `talk voices`: kuratierter Katalog mit Namen (ElevenLabs-Premade, OpenAI,
Edge-Neural de/en-Locales) als Tabelle, „*“ = effektive Default-Stimme
(Config vor Repo-Default), Config-IDs außerhalb des Katalogs als eigene
Zeile, Filter --provider/--lang, --json.
- `talk setup`: wenige Fragen statt Bubble Tea — Keys nur als Hinweis
(ohne Key läuft die Edge-Stimme), Stimme je Sprache aus dem Katalog mit
Probehören, Ruhezeit; schreibt die config.toml zusammengeführt mit
bestehenden Werten (.bak), spricht einen Testsatz. q bricht ab, bevor
etwas geschrieben wird; ohne Terminal keine Fragen, nur die wirksame
Konfiguration.
Unterbau: provider.Catalog/VoiceName/DefaultVoice, Chain.SpeakOutcome
(Stufe, Bytes, Zeiten) durch speak.Engine bis in Daemon-Log und test,
audio.Check (oto-Initialisierung ohne Ton), config.Path,
OSBuiltin.Describe. README: Abschnitt „Erste Schritte“.
Tests: voices-Zeilen (Default-Markierung, Config-Override, Filter, JSON),
doctor (ohne Daemon, Mute/Key via JSON, fremder Dienst → Exit 1, kaputte
Config → ✘ aber vollständiger Bericht), test (fake espeak), setup
(stdin-Antworten → config.toml-Inhalt, Merge mit .bak, Abbruch schreibt
nichts, Pipe stellt keine Fragen), Prozessgrenze: doctor/--probe/voices/
test gegen echten Daemon.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Zwei Binaries erzeugten den halben Komplexitätsballast: Degraded Mode mit
Lock-Datei und Stale-Lock-Race, „läuft talkd?", Autostart-Konfiguration je
OS, doppelte Chain-Konstruktion in cmd/talk und cmd/talkd — und der
Degraded Mode kannte weder Rate-Limit noch Ruhezeiten.
Jetzt:
- internal/speak: der eine Sprech-Kern (Config + Sink + Kette → Ansage);
Daemon und Direkt-Pfad benutzen ihn, es gibt keine zweite
Zusammensetzung der Kette mehr.
- internal/daemon: Run (der bisherige talkd-Kern), Spawn (detached per
Setsid bzw. DETACHED_PROCESS, stdout/stderr → daemon.log), PID-Datei
daemon.pid, Shutdown-Trigger.
- `talk daemon run|start|stop`: Vordergrund (systemd/launchd), expliziter
Hintergrund-Start, Stop per POST /shutdown — stop wartet, bis HTTP weg
und die PID-Datei gelöscht ist (Prozess wirklich beendet).
- Autostart: say/wrap starten den Daemon beim ersten Sprechbefehl selbst
(daemon.autostart, Default an; Env TALK_AUTOSTART). Verliert ein
paralleler Aufruf das Port-Rennen, erkennt er das am Kind-Ende und nutzt
den Daemon des anderen — kein Timeout-Warten.
- Direkt-Pfad statt Degraded Mode: ohne erreichbaren oder startbaren
Daemon spricht talk direkt (Mute respektiert, kein Lock, Hinweis +
Grund + Log-Pfad auf stderr). Steuerbefehle starten den Daemon bewusst
nicht; `talk status` zeigt „läuft nicht" (Exit 1).
- cmd/talkd und cmd/talk/degraded.go entfernt; README-Daemon-Abschnitt
neu.
Tests: Harness baut nur noch talk und startet `talk daemon run`;
TALK_AUTOSTART=false als Grundausstattung aller Tests (kein Test
hinterlässt Daemons), autostart_test.go schaltet es bewusst an: Autostart
beim ersten say, Idempotenz, paralleler Start in EINE Queue, expliziter
start/stop, Port fremd belegt → Direkt-Pfad ohne Timeout. Cross-Compile
aller sechs Targets mit CGO_ENABLED=0 verifiziert.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>