
TL;DR
Voice Inbox to lokalny daemon na Maca, który poluje Linear (+ gotowe pod Slack) i czyta mi głosem nowe zadania, komentarze, zmiany statusu. Raz na godzinę Haiku streszcza mi ostatnią godzinę w pięciu-sześciu punktach. Issues z priority Urgent/High dostają wyraźniejszy ton — intonacja, nie inny głos.
Repo: github.com/stroniarz/voice-inbox Licencja: Apache 2.0 (do dodania)
Problem
Prowadzę kilkanaście projektów naraz. Linear, Slack, Gmail, czasem SMS — informacje rozpierzchają uwagę. Przez ostatnie miesiące złapałem się na tym, że co pięć minut przełączam się między kartami, żeby sprawdzić czy nic nie wpadło. Efekt: rozpraszam się, tracę flow, a i tak 80% powiadomień to nic ważnego.
Chciałem czegoś przeciwnego — żeby informacja sama do mnie przychodziła, głosowo, w tle, ale tylko ta istotna. Żebym mógł dalej robić swoje i wiedzieć, co się dzieje bez otwierania czegokolwiek.
Rozwiązanie
Python daemon na Macu, który:
- Co minutę pyta Linear API o nowe zdarzenia (issues, komentarze, zmiany statusu)
- Archiwizuje wszystko w lokalnej SQLite (dedup + historia)
- Live — natychmiast czyta głosem krótki komunikat („Linear, nowe zadanie: header sklepu”) bez kosztu LLM
- Digest — co godzinę Haiku streszcza ostatnie 60 minut do max 6 zdań, naturalnym ciągłym tekstem („Raport z ostatniej godziny. Po pierwsze…, dalej…, na koniec…“)
- Issues z priority Urgent/High czyta tym samym głosem, ale z bardziej ekspresyjną intonacją (ElevenLabs
stability: 0.3) i prefixem „Uwaga, pilne!”
Kolejka wypowiedzi jest jednokanałowa — nic się nie nakłada.
Co ciekawego pod spodem
Abstrakcja LLM — provider-agnostic
Na początku wpisałem na sztywno Anthropic. Po godzinie pomyślałem, że to głupie — mam klucze do kilku innych providerów, a na OpenRouter mam dostęp do wszystkiego za jeden klucz. Zrobiłem więc protokół LLMClient:
class LLMClient(Protocol): def chat(self, system: str, user: str, max_tokens: int = 600) -> str: ...
I dwa adaptery: natywny Anthropic SDK oraz OpenAI-compatible (pokrywa OpenAI, OpenRouter, DeepSeek, Ollama — to samo API, inne base_url).
Config:
llm: provider: anthropic # anthropic | openai | openrouter | deepseek | ollama model: claude-haiku-4-5-20251001 api_key_env: ANTHROPIC_API_KEY
Przełączenie na lokalną Gemma przez Ollama — jedna linia:
llm: {provider: ollama, model: gemma3:27b}
I już mam darmowe, offline LLM do streszczeń.
Abstrakcja TTS — intonacja zamiast drugiego głosu
Początkowo pilne eventy dostawały inny głos (męski Josh zamiast domyślnej Sary). Po dwóch dniach mnie to zaczęło wkurzać — za dużo „aktorów”. Zmieniłem na jeden głos, dwa profile:
tts: default: voice_id: EXAVITQu4vr4xnSDxMaL # Sarah stability: 0.5 # neutralnie speed: 1.2 critical: voice_id: EXAVITQu4vr4xnSDxMaL # ta sama Sarah stability: 0.3 # bardziej ekspresyjnie speed: 1.2
Plus prefix tekstowy: "Uwaga, pilne! {treść}". ElevenLabs parsuje wykrzykniki i słowa-wzmacniacze, więc intonacja faktycznie się zmienia. Mózg rozpoznaje „pilne” w ułamku sekundy bez przestawiania się na inny głos.
i18n od razu, nie na koniec
Popełniłem błąd — najpierw wpisałem polskie teksty na sztywno w kod (prompty, templates short form). Po dwóch godzinach chciałem przejść na angielski, żeby sprawdzić jak brzmi. Przepisanie zajęło 30 minut. Po 30 minutach user mnie złapał: „Wybór języka musi być opcją, nie kasowaniem pliku”. Miał rację.
Zrobiłem i18n.py z prostym słownikiem per-locale:
MESSAGES = { "pl": { "linear_new_task": "nowe zadanie: {title}", "urgent_prefix": "Uwaga, pilne! ", "digest_system": "...pełny prompt po polsku...", }, "en": { ... }, }
I language: pl w configu. Jedna linia zmienia wszystko — short forms, prefixy, prompt do digestu. Nowy język = dodanie wpisu do słownika.
SQLite jako persistance layer
Trzy tabele:
seen— deduplikacja (source + external_id + timestamp)cursor— ostatni timestamp pollowania per-sourceevents— archiwum eventów z treścią, do digestu
Żadnego ORM. Sześć funkcji. Cały persistence to 80 linii Pythona.
Koszty
Bo to pewnie ciekawe — a da się zrobić kilka wariantów, od „zero złotówek” po „premium”.
Wolumen przy moim użyciu (30-50 eventów dziennie, digest co godzinę = 24/dzień):
- Live — ~50 wypowiedzi × ~75 znaków = ~110 tys. znaków/mies
- Digest — 24 × ~600 znaków = ~430 tys. znaków/mies
- Razem TTS — ~540 tys. znaków/mies
- Haiku — 720 wywołań digestu, ~3-4M tokenów input miesięcznie
Wariant 1: zero złotówek (100% lokalnie)
llm: provider: ollama model: gemma3:27b # lub mniejszy jeśli masz mało RAM tts: provider: say voice: Zosia # macOS system voice, PL
- LLM: Ollama z Gemma 3 (lub Llama 3, Qwen 2.5) — darmowe, lokalne, prywatne. Na MacBooku M1/M2/M3 z 16+ GB RAM działa płynnie.
- TTS: macOS-owe
say -v Zosia— 0 zł, brzmi jak syntezator z 2005 roku, ale czytelnie. - Linear: free plan (do 10 użytkowników, 250 issues).
Koszt: 0 zł. Dane nie wychodzą poza Twój Mac. Jakość streszczeń gorsza niż Haiku (Gemma streszcza ok, ale bez takiej finezji), głos syntetyczny.
Wariant 2: hybryda — tanio i ładnie (~$25/mies)
llm: {provider: anthropic, model: claude-haiku-4-5-20251001} tts: live: {provider: say, voice: Zosia} # live — darmowo digest: {provider: elevenlabs, voice_id: ...} # digest — ElevenLabs
- Live przez
say— krótkie komunikaty, brzmią jako tako i tak są krótkie - Digest przez ElevenLabs Creator ($22/100k znaków) — jakość tam, gdzie naprawdę warto (długie, spójne raporty)
- Haiku do streszczeń — ~$3-15/mies
Koszt: ~$25/mies. Najlepszy stosunek jakość/cena moim zdaniem.
Wariant 3: premium (~$100/mies)
llm: {provider: anthropic, model: claude-haiku-4-5-20251001} tts: default: {provider: elevenlabs, voice_id: ...} critical: {provider: elevenlabs, voice_id: ..., stability: 0.3}
Wszystko przez ElevenLabs Pro ($99/500k znaków) — najwyższa jakość, priority routing przez voice_settings. To u mnie.
Koszt: ~$100-115/mies.
Na horyzoncie
- Kokoro TTS (open source, self-host) — byłaby darmowa alternatywa dla
sayna English. Polski na razie nieobsługiwany, trzeba czekać. - Fish Audio — alternatywa dla ElevenLabs, pay-as-you-go, ~$15/1M UTF-8 bajtów. Dla polskiego wolumenu ~$5-8/mies.
Architektura
voice_inbox/ ├── adapters/ # polling (Linear GraphQL, Slack Web API) ├── llm/ # LLMClient + anthropic / openai_compat ├── tts/ # TTSClient + say / elevenlabs / openai + kolejka ├── i18n.py # PL/EN teksty + prompty digestu ├── dedup.py # SQLite archiwum + cursors ├── summarize.py # generator digestu ├── config.py └── main.py # orchestrator + digest worker (w osobnym wątku)
Każdy kawałek jest ~50-150 linii. Nic nie jest genialne, wszystko jest po prostu proste.
Status
MVP działający na Linear. W planach:
- Slack adapter — kod gotowy, nieprzetestowany (trzeba założyć Slack App żeby dostać user tokena z odpowiednimi scope’ami)
- Gmail adapter — będzie wymagał filtrowania („tylko od X, nie newslettery”)
- SMS / iMessage — bezpośredni odczyt z
~/Library/Messages/chat.db - Menubar toggle — SwiftBar plugin do on/off + per-source
- Grupowanie — 5 akcji na tym samym issue w 2 min = 1 wypowiedź („trzy komentarze na STR-165”)
- Voice clone — własny głos przez ElevenLabs (dlaczego nie)
Dlaczego open source
Bo to jest rodzaj narzędzia, które albo sobie sam postawisz (wtedy chcesz czytelny kod i pełną kontrolę), albo nie zainstalujesz wcale. Nie ma sensu sprzedawać tego jako SaaS — użytkownik musi podać tokeny do Linear, Slack, Gmail, ElevenLabs. Lokalny daemon to jedyna rozsądna forma.
Jeśli komuś się to do czegoś przyda albo ktoś zrobi adapter do Jiry — świetnie.
Link
Repo: github.com/stroniarz/voice-inbox
Jakby ktoś próbował u siebie i się przyciął — issues lub do mnie na michal@stroniarz.pl.