diff --git a/README.md b/README.md index 8434689..6de1ed6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,26 @@ Runtime-Abhängigkeiten. Say-Flags (gelten auch für `talk "Text"`): `--wait`, `--now`, `--priority`, `--voice`, `--lang`, `--provider`. +## Installation + +**Release-Binary** (Linux, macOS, Windows; amd64/arm64): von den +[Releases](https://code.m3ta.dev/m3tam3re/talk/releases) das passende +`talk__` laden, Prüfsumme gegen `checksums.txt` vergleichen, als +`talk` in den PATH legen. Keine weiteren Abhängigkeiten. + +```console +curl -LO https://code.m3ta.dev/m3tam3re/talk/releases/latest/download/talk_linux_amd64 +sha256sum -c --ignore-missing <(curl -sL https://code.m3ta.dev/m3tam3re/talk/releases/latest/download/checksums.txt) +install -m 755 talk_linux_amd64 ~/.local/bin/talk +``` + +**Nix** (Flake): `nix run git+https://code.m3ta.dev/m3tam3re/talk -- doctor` +oder in der Konfiguration `inputs.talk.url = "git+https://code.m3ta.dev/m3tam3re/talk";` +und `inputs.talk.packages.${system}.talk` bzw. `inputs.talk.overlays.default`. +Das Paket bringt espeak-ng (OS-Stimme) und libnotify (Toast) mit. + +**Aus dem Quelltext:** `go install code.m3ta.dev/m3tam3re/talk/cmd/talk@latest`. + ## Erste Schritte ```console @@ -150,9 +170,10 @@ sind Opt-in wie Mute). In der Ruhezeit verfällt Audio, auch für high (Burst ist ein Rate-Recht, kein Ruhe-Recht): - `quiet_mode = "discard"` (Default): still verwerfen. -- `quiet_mode = "toast"`: Benachrichtigung statt Audio — derzeit eine - Protokoll-Zeile über den Notification-Pfad (OS-Toast folgt mit der - Notification-Anbindung). +- `quiet_mode = "toast"`: Desktop-Benachrichtigung statt Audio — über die + Bordmittel des Systems (Linux `notify-send`, macOS Notification Center, + Windows Toast), best effort: fehlt das Werkzeug, bleibt es bei der + Protokoll-Zeile, `talk doctor` zeigt es unter „Toast". - `now:true` **bricht auch die Ruhezeit** (bewusster Ausbruch). **Länge und Alter:** Zwei weitere Gesetze gegen das, was Prompts gern tun @@ -350,6 +371,18 @@ talk wrap --wait -- make test label = "Deployment" # Ansage-Label statt Befehls-Basisname (TALK_WRAP_LABEL) ``` +## Für Agenten: Skill und Standing-Order + +[`skills/talk/SKILL.md`](skills/talk/SKILL.md) ist die Etikette als +Skill-Artefakt: **striktes Opt-in** (Sprechen nur auf Aufforderung in der +Session oder Standing-Order in der AGENTS.md, session-sticky bis Widerruf), +Inhalt = Status · nächster Schritt · wofür der Nutzer gebraucht wird, nie +die Langantwort, `muted`/`discarded` respektieren. Der Drei-Zeilen-Block +für Projekt-AGENTS.md steht in +[`skills/talk/AGENTS-snippet.md`](skills/talk/AGENTS-snippet.md). Der +Daemon erzwingt Rate-Limit, Längen- und Alters-Grenze, Mute und Ruhezeit +unabhängig davon, was im Prompt steht. + ## Entwicklung ```console @@ -357,11 +390,21 @@ go build ./... # bauen go test ./... # Tests go vet ./... # statische Analyse gofmt -l . # Formatierung prüfen (leerer Output = ok) +nix develop # devShell mit go, gopls, espeak-ng, libnotify ``` CI: `.gitea/workflows/test.yml` (Gitea Actions) läuft vet, gofmt-Check, Tests und Build bei jedem Push und Pull Request. +**Release:** Tag `v*` pushen → `.gitea/workflows/release.yml` testet, baut +mit `scripts/release.sh` die sechs Targets (statisch, `CGO_ENABLED=0`, +Version per ldflags) und legt ein Gitea-Release mit `talk__[.exe]` +und `checksums.txt` an. Lokal: `scripts/release.sh 1.2.3` → `dist/`. Bewusst +ohne GoReleaser: ein Shell-Skript über `go build` ist dasselbe Artefakt, +lokal wie in CI, ohne zusätzliches Werkzeug. `nix build .#talk` baut das +Nix-Paket (nach Änderungen an go.mod den `vendorHash` in flake.nix +nachziehen — `nix build` nennt den erwarteten Wert). + ## Lizenz MIT — siehe [LICENSE](LICENSE). diff --git a/docs/spec.md b/docs/spec.md index 046d3f2..fd52b6c 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -124,3 +124,10 @@ Diese Punkte ersetzen die entsprechenden Aussagen oben (Grilling-Stand 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__[.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. diff --git a/skills/talk/AGENTS-snippet.md b/skills/talk/AGENTS-snippet.md new file mode 100644 index 0000000..69f876d --- /dev/null +++ b/skills/talk/AGENTS-snippet.md @@ -0,0 +1,13 @@ +# Standing-Order für talk (in die AGENTS.md des Projekts kopieren) + +```markdown +## Voice notifications (talk) +Sprich per `talk "<1–2 Sätze>"`, wenn eine Aufgabe länger als ~30 s lief und +fertig ist, wenn du blockiert bist und mich brauchst (dann `talk --now`), +oder wenn etwas fehlgeschlagen ist. Inhalt: Status, nächster Schritt, wofür +du mich brauchst — nie die Antwort selbst. `muted` und `discarded` sind +meine Entscheidung: respektieren, nicht umgehen. +``` + +Ohne diesen Block (und ohne Aufforderung in der Session) spricht ein Agent +nicht — talk ist striktes Opt-in. diff --git a/skills/talk/SKILL.md b/skills/talk/SKILL.md new file mode 100644 index 0000000..cd79dbe --- /dev/null +++ b/skills/talk/SKILL.md @@ -0,0 +1,86 @@ +--- +name: talk +description: "Use when the user has asked for voice notifications — explicitly in this session or as a standing order in the project's AGENTS.md — and (1) a long-running task finishes (build, tests, deploy, data job), (2) the agent is blocked and needs the user's decision or approval, (3) something failed that the user should know about now. Triggers: talk, say it, sag Bescheid, melde dich, notify me, let me know when done, voice, Ansage. Strict opt-in: never speak without the user's trigger or standing order." +compatibility: opencode, claude-code, codex +--- + +# talk — Voice-Notifications für Agenten + +`talk "Build fertig"` spricht eine kurze Ansage über den lokalen talk-Daemon +(Queue, Rate-Limit, Mute, Ruhezeiten). Ein Binary, startet den Daemon beim +ersten Aufruf selbst. Nichts zu installieren außer `talk` im PATH. + +## When to Use — striktes Opt-in + +Sprich **nur**, wenn eines davon gilt: + +1. Der Nutzer hat es in dieser Session verlangt („sag Bescheid, wenn der + Build durch ist", „melde dich, wenn du mich brauchst"). Das gilt dann + **session-sticky bis Widerruf** — nicht bei jeder Antwort neu fragen. +2. Die AGENTS.md des Projekts enthält eine Standing-Order (siehe + `AGENTS-snippet.md`). + +Dann sprich bei: Abschluss langer Aufgaben (> ~30 s), Blockern, die den +Nutzer brauchen (Entscheidung, Freigabe, Passwort), Fehlern, die sofort +zählen. + +## When NOT to Use + +- Ohne Trigger oder Standing-Order. Nie „von sich aus plappern". +- Für kurze Antworten, Zwischenstände, jede Tool-Ausgabe. +- Um die Langantwort vorzulesen. Der Daemon kürzt bei 400 Zeichen — + aber Ziel sind 1–2 Sätze. +- Wenn `talk` `muted` oder `discarded: quiet_hours` zurückgibt: Das ist + die Entscheidung des Nutzers. Nicht umgehen, nicht `--now` nachschieben. + +## Was eine gute Ansage ist + +**Status · nächster Schritt · wofür der Nutzer gebraucht wird.** Nie die +Antwort selbst. + +| gut | schlecht | +| --- | --- | +| „Tests grün, PR ist bereit zum Review." | „Ich habe die Tests ausgeführt und alle 247 Tests sind erfolgreich durchgelaufen, außerdem habe ich …" | +| „Deploy fehlgeschlagen, Code 1. Ich brauche deine Entscheidung zur Migration." | „Fehler." | +| „Brauche dein sudo-Passwort im Terminal." | (stumm warten) | + +Sprache: in der Sprache des Nutzers schreiben; talk erkennt de/en selbst +(`--lang` erzwingt). + +## Usage + +```bash +talk "Tests grün, PR bereit zum Review." # non-blocking, Item-ID auf stdout +talk --wait "Deploy fertig." # erst zurückkehren, wenn gesprochen +talk --priority high "Build rot, Code 2." # darf das Rate-Limit verletzen +talk --now "Ich brauche dich: Freigabe im Terminal." # vor die Queue, auch in der Ruhezeit — nur für Blocker +echo "Import fertig" | talk # stdin geht auch +talk wrap -- make test # Befehl ausführen, Erfolg/Misserfolg + Dauer ansagen +``` + +Antworten auf stdout: eine Item-ID (angenommen), `muted`, oder +`discarded: rate_limit|quiet_hours`. Exit 0 in allen drei Fällen — ein +Verwurf ist kein Fehler. Exit ≠ 0 nur bei echten Problemen (kein Text, +ungültige Flags, Config kaputt); dann nicht wiederholen, sondern dem +Nutzer sagen. + +## Etikette gegenüber dem Daemon + +- **Rate-Limit** (Default 1 Ansage / 30 s): `normal` parkt, `low` wird + verworfen, `high` darf durch. Nicht alles ist `high`. +- **`--now`** ist für Blocker, die den Nutzer brauchen — nicht für + „fertig". Es bricht aus Ruhezeit und Rate-Limit aus; das ist eine + bewusste Störung. +- **Mute und Ruhezeit** sind Nutzerentscheidungen. Respektieren, nicht + umgehen. Der Daemon erzwingt sie ohnehin. +- Mehrere Dinge gleichzeitig? **Eine** Ansage, die zusammenfasst — nicht + fünf. + +## Pitfalls + +- `talk` nicht gefunden → nicht improvisieren (kein `say`, `espeak`, + `notify-send`). Dem Nutzer sagen, dass talk fehlt. +- Kein Ton beim Nutzer → `talk doctor` schlagen vorschlagen; nicht selbst + an Keys oder Config drehen. +- Keys kommen aus `TALK_ELEVENLABS_KEY` / `TALK_OPENAI_KEY` (Env). Nie in + Dateien schreiben, nie in Ansagen oder Logs ausgeben.