
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ą (ElevenLabsstability: 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 dlasayna 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.