Toadie

Z Claude Code

Czyli jak stworzyłem swojego personalnego
multi-device bota, bez czytania i pisania kodu


Michal Franc

O mnie

  • Staff/Principal Engineer
  • 15+ lat w branży, 12+ lat dla rynku UK/US, firmy produktowe
  • Obecnie Affirm (BNPL fintech), tech lead tech leadów, 30+ inżynierów
  • Impact oriented, wysoki lvl individual contributor
  • AWS, Kubernetes, Terraform, Go, Python, C#, Kotlin
  • Reliability, observability, cost optimization
  • M.Sc. Politechnika Wrocławska
  • Kiedyś współzałożyciel dotnetconf.pl i grupa .NET Wrocław na PWr
  • mfranc.com · @francmichal

Polyglot programmer, ale Toadie to mój pierwszy projekt gdzie nie napisałem ani linijki kodu

Psycho fan vima i i3wm · hobbystycznie pisze grę · maluje figurki Warhammera

Co zbudowałem

  • Aplikacja na Galaxy Watch, Kotlin, Wear OS
  • Aplikacja na telefon Android, z botem Tamagotchi
  • Serwer Python, HTTP, WebSocket, STT, TTS
  • Dashboard webowy, Vue.js, timeline w real-time
  • System uprawnień, hooks, polling, zatwierdzanie z zegarka
  • Integracja z Tailscale, weryfikacja peerów, telefon jako relay

Pokażę jak to wszystko działa...

...i dlaczego Kotlin w ogóle pojawił się na grupie .NET

SPOILER: bo to możliwe że już nie ma znaczenia

SPOILER2: pogadamy też o przyszłości naszej branży trochę

Demo, edycja notatek przez Toadie


Demo na żywo

"Hey Toadie, dodaj notatkę że prezentacja idzie naprawdę dobrze"


[ screen share: phone + obsidian ]

Problem

Chciałem Superwhisper ale na Linuxie


  • Wciśnij klawisz → zacznij dyktować
  • Tekst pojawia się w aktywnym oknie (streaming)
  • Działa wszędzie, terminal, przeglądarka, Obsidian
  • Bonus: przekieruj tekst do Claude jako komendy

Skrypt dyktowania

Emulacja doświadczenia dyktowania jak na Macu. Python orkiestrujący przechwytywanie audio, wywołanie API i xdotool.


  • Przechwycenie audio z mikrofonu
  • Wysyłka do Deepgram (STT streaming)
  • Tekst trafia do aktywnego okna przez xdotool

Testowałem też: Whisper (lokalny), Google Cloud STT

Dlaczego Deepgram?

Zabezpieczenia przed kosztami

  • Auto-stop po ciszy, skrypt kończy po 10s bez mowy
  • Crontab, co 5 minut zabija procesy dyktowania
  • Polybar, pokazuje czy dyktowanie jest włączone

Dyktowanie do instancji Claude Code

Dyktowanie do aktywnego okna jest fajne, ale czasem chcę po prostu pogadać z Claude. Wciśnij przycisk, powiedz coś, tekst ląduje w Claude Code.


  • Nowy skrypt, spawnuje instancję Claude Code w i3wm
  • Transkrypcja pipe'owana bezpośrednio do procesu
  • Działa z mikrofonu słuchawek, z dala od biurka
  • Szybka myśl lub komenda głosem
  • Nie trzeba siadać do komputera
  • Wciśnij przycisk → mów → Claude dostaje

Problem: za każdym razem nowy proces, nowy kontekst...

Dyktowanie do persistent sesji Claude

Poprzedni skrypt za każdym razem startował nowy proces Claude, nowy kontekst. A ja chcę mówić do istniejącej sesji.


  • Claude CLI działa w tmux z nazwaną sesją
  • Transkrypcja buforowana i wysyłana przez tmux send-keys -t session:pane
  • Kontekst zostaje między komendami
  • Długie sesje z pełnym kontekstem projektu
  • Mów z mikrofonu słuchawek, z dala od biurka
  • Głos trafia do tego samego procesu co klawiatura

Galaxy Watch

Pewnej nocy mózg zdecydował: zegarek ma mikrofon... czemu nie gadać z Claude z nadgarstka?


  • Galaxy Watch, klient Kotlin Wear OS, nagrywa audio, wyświetla odpowiedzi, odtwarza głos
  • Serwer Python, lokalnie na maszynie, odbiera audio, wysyła do Deepgram, rozmawia z Claude
  • Deepgram, STT + TTS, audio in → tekst out, tekst in → audio out
  • Claude Code CLI, wrapper obsługuje input/output i lifecycle procesu, JSON streaming zamiast scrapowania tmux pane
  • Sesja tmux, wrapper loguje wszystko do sesji claude-watch, daje widoczność w to jak Claude pracuje

Demo, Galaxy Watch



Głos z zegarka → odpowiedź z Claude

Architecture

Permission Hooks

Server Dashboard

Z zegarkiem, serwerem, WebSocketami i permission hooks setup robi się skomplikowany. Potrzebowałem widoczności.


  • Timeline z wszystkimi akcjami serwera i timestampami
  • Settings, zmiana języka, modelu
  • Chat, wizualizacja konwersacji w real-time
  • Vue.js, single-page, WebSocket live updates
  • Pomaga debugować problemy i rozumieć flow
  • Dostępny przez Cloudflare Tunnel

Demo, Server Dashboard


Demo na żywo

1. Timeline, settings, chat w akcji
2. Wysyłamy komendę i obserwujemy flow


[ screen share: browser ]

Aplikacja na telefon

Mam zegarek... czemu nie telefon? Więcej ekranu, a może nawet trochę osobowości?

Tak narodził się Toadie.


  • Tamagotchi, zielony troll z animowanymi stanami (idle, słuchanie, myślenie, mówienie, spanie)
  • Kolejna apka Kotlin, chat, wiadomości głosowe i tekstowe
  • Z punktu widzenia serwera, klient jest abstrakcją
  • Telefon jako relay dla zegarka, Watch → Phone (DataLayer) → Server
  • Wizualizacja mockup stworzona w Remotion + Claude
  • Ten sam protokół WebSocket co zegarek

Demo, Aplikacja na telefon

Wiadomość tekstowa + głosowa → odpowiedź TTS

Architecture, pełny obraz

Telefon i dashboard to po prostu kolejni klienci. Dashboard też łączy się przez WebSocket.

Tailscale

Wszystkie urządzenia udają że są w jednej prywatnej sieci, nawet jeśli są w różnych miejscach.


  • WireGuard, szyfrowany tunel
  • Tailscale orkiestruje: tożsamość, zarządzanie kluczami, discovery
  • Każde urządzenie dostaje IP 100.x.x.x
  • It JUST works
  • UDP hole punching, urządzenia za NAT (normalnie niedostępne publicznie) rozmawiają ze sobą
  • Jeśli hole punching nie działa, DERP relay server przejmuje
  • Świetny UX, zero konfiguracji

Prywatny dostęp, Tailscale

Tailscale mesh, each device gets a 100.x.x.x address

Problem: zegarek nie ma Tailscale

Galaxy Watch nie wspiera Tailscale. Rozwiązanie: telefon jako relay.


  • Watch → Phone → Server (przez Tailscale)
  • Telefon działa jako transparentny proxy
  • Android Wearable DataLayer do komunikacji watch↔phone
  • Protokół sygnałowy zapewnia stabilne połączenie
  • Zegarek nie musi być w publicznej sieci
  • Dużo bardziej niezawodne połączenie niż bezpośrednie

Wake Word

"Hey Toadie"


  • PicoVoice Porcupine, działa lokalnie na telefonie
  • Bez połączenia z serwerem (poza aktywacją klucza)
  • 1 custom wake word za darmo
  • Automatycznie uruchamia nagrywanie
  • W 2-3 tygodnie zbudowałem naprawdę funkcjonalny software z dobrym UX
  • Używam tego codziennie
  • Ciekawe i trochę przerażające co jedna osoba może teraz zbudować

Moment "WOW"

Demo, Wake Word


Demo na żywo

Powiedz "Hey Toadie", wake word uruchamia nagrywanie


[ screen share: phone ]

Demo, Rich Content


Demo na żywo

1. Wyślij screenshot na telefon przez toadie-show
2. Wyślij animację D3.js, interaktywna treść na telefonie


[ screen share: phone + terminal ]


(jak Michał zrobi rich content w webie bo na komórce jest)

Co potrafi Toadie?

  • Czyta cele, listę zadań, kalendarz, maile
  • Dostęp do wszystkich notatek 2nd brain (Obsidian)
  • Steruje monitorami (wejścia, jasność)
  • Włącza TV i uruchamia apki na telewizorze (mam też apkę TV z botem)
  • Dyskutuje pomysły, pisze posty na bloga, tworzy plany
  • Stał się mega użytecznym narzędziem do pracy z notatkami
  • 95% gotowe żeby Toadie zarządzał innymi botami

Następny krok: Toadie jako manager, "hey, popracuj z zespołem na tym repo, sprawdź GitHub issues i po prostu pracuj nad nimi"

Bezpieczeństwo, Defense in Depth

Tailscale + WireGuard

Peer Verification

Tech Stack

Python aiohttp WebSocket Deepgram tmux Claude Code CLI
Kotlin Wear OS Android PicoVoice Tailscale Vue.js

github.com/michal-franc/toadie-personal-assistant

Ile czasu zajęło napisanie tego całego kodu?

Wcale.

Nie napisałem ani linijki kodu


Nie przeczytałem ani linijki kodu


100% wygenerowane przez Claude Code

Co tak naprawdę robiłem

  • Opisywałem czego chcę, naturalnym językiem
  • Testowałem wynik, działa czy nie?
  • Iterowałem, "to nie działa", "zmień to"
  • Decydowałem o architekturze, nie o implementacji

Python, Kotlin, Vue.js, WebSockets, języki których ledwo dotykam

Rozwój oparty na specyfikacji

Zarządzanie projektem przez GitHub

Wszystkie zmiany, plany i prace trzymamy w GitHub. Pomysły, dyskusje i plan pracy.


  • Issues jako backlog pomysłów i bugów
  • Pull Requesty z pełnym opisem zmian i planem testów
  • Dyskusje w komentarzach, iteracje na review
  • Bot tworzy PRy, ja reviewuję i mergeuję
  • Pełna historia decyzji i zmian
  • CI/CD sprawdza linty i testy automatycznie
  • Każda funkcjonalność = branch + PR
  • Transparentność, widać co bot zrobił i dlaczego

Przykłady: PR #74, beta plan testów · PR #49, duża zmiana

Wiele botów, wiele issues naraz

Każdy bot pracuje nad swoim issue w izolacji. Klucz: git worktrees.


  • Każdy bot dostaje własny worktree, osobna kopia repo
  • Brak konfliktów, boty nie nadpisują sobie zmian
  • Każdy bot tworzy branch → PR → review
  • Mogę odpalić 3-4 boty równolegle na różnych taskach
  • Claude Code ma już wbudowane wsparcie dla worktrees
  • Ale polecam samemu zarządzać worktrees z botem
  • Większa kontrola nad tym co bot robi i gdzie
  • git worktree add ./worktrees/feature -b feature

Worktree = tani klon repo bez kopiowania historii. Każdy bot ma swój sandbox.

Workflow: boty → beta → test

Bot Manager (testowane)

Zarządzanie kontekstem to klucz

CLAUDE.md, instrukcje które agent czyta przy każdej sesji


  • Decyzje architektoniczne, ścieżki plików, komendy
  • Konwencje testów, czego NIE robić
  • Info o urządzeniach, portach, zależnościach
  • Auto-pamięć, agent uczy się między sesjami
  • CLAUDE.md rośnie organicznie, staje się nieaktualny
  • context-doctor, linter dla CLAUDE.md
  • Sprawdza złamane referencje, martwe komendy, sprzeczności
  • github.com/michal-franc/context-doctor

Co jest w CLAUDE.md

context-doctor

github.com/michal-franc/context-doctor

Linter dla CLAUDE.md, uruchom, dostań ocenę


$ context-doctor CLAUDE.md

METRICS
Lines: 119 (MODERATE)
Instructions: ~43 (+50 Claude = ~93) (OK)
Progressive Disclosure: YES
Scope Activity: 11 commits since last update (10 days ago)

LENGTH ISSUES
⚠ [CD002] File has more than 100 lines
→ Consider being more concise. Ideal ~60 lines

REFERENCED DOCS
KLAUDIUSH.md (9 days ago)
docs/server-api.md (9 days ago)
docs/features/security.md (23 days ago)

DIMENSION SCORES
Correctness ███████████████████░ 95/100
Style ████████████████████ 100/100
Freshness ██████████████████░░ 90/100

OVERALL SCORE
███████████████████░ 96/100

github.com/michal-franc/context-doctor

Prawdziwe prompty, budowanie funkcjonalności

> "ok now on hey toadie after the wake word is detected it should open the app even if its in lock mode doable?"

> "could we make this to be kind of like a voice sin function?"

> "i also have critical bug now - watch is never refreshing chat even after sending new voice command"

> "ok but we should retry more often instead of dropping to idle timeout it should be exponential retry"

Prawdziwe prompty, debugowanie

> "can you check the logs i can still see flickering"

> "the screen is turning on - but then when i swipe i am not being taken to the app"

> "i now have the app in listening state its blocked on the overlay"

> "can you check request nr 8 from server and tell me why we need 3.3s to capture response?"

Prawdziwe prompty, reszta

> "ok install on the watch"

> "try again"

> "git push"

> "we change the app name you silly fool :D"

> "dude you keep confused we streamlined the name for both watch and phone"

> "ej wez sprawdz logi z zegarka bo cos websockety szwankuja i mi nie laczy"

> "yeah it works!"

Era personal software?

  • Apki budowane wyłącznie dla siebie
  • Nie dopieszczone, nie produkcyjne, ale działają dla Ciebie
  • Musi działać tylko na Twojej maszynie, dla Twojego workflow
  • Jedna osoba może zbudować to co wcześniej wymagało zespołu

Programowanie jako aktywność społeczna? Narzędzia szyte na miarę dla społeczności?

Era CLI

Bez IDE. Bez edytora. Tylko i3wm + tmux + alacritty + polybar

Czasem nawet edytuję notatki przez... Claude. Albo po prostu mówię Toadie żeby to zrobił, z kanapy.

Diagramy, draw.io MCP

Wygenerowane przez Claude przez mcp-drawio, bez ręcznego rysowania


Diagram architektury

Architektura phone relay

Ta prezentacja

Reveal.js + D3.js. Zbudowana w tej sesji z Claude Code.

> "2 slajd o mnie przeczytaj mfranc.com i wrzuc o mnie jakies tam info tez mozesz moje cv sprawdzic"

> "no i tez jest troche bledny popatrz tutaj to jest realny obrazek [screenshot] popraw d3"

> "i ten obrazek zaambedujemy i omowimy https://mfranc.com/images/dictation-architecture-simple.png"

> "albo lepiej zamien ten obrazek ze zrodla w d3.js nowa symulacje pod tytuilem ehh?"

> "przesun resolve hostname o 10px do gory | cache miss 20px na prawo"

> "ok 10 more and we are good :d"

Witamy w nowej erze?



mfranc.com


Q/A