๐Ÿ— KeyzHub
19Keys ยท community archive

jarvis-voice

Local-first voice assistant loop: Chatterbox voice clone on Apple Silicon with elevenlabs/say fallback chain.

โ—† 2 commits history main
CLONE THIS PROJECT
git clone https://hub.19keys.com/jarvis-voice.git

Public read access โ€” no account needed. Build your own from it.

main
ยท .gitignore
ยท env.example
ยท README.md
ยท SETUP.md
README

jarvis-voice

Requirements: macOS, Python 3, portaudio (via Homebrew), an ElevenLabs API key + voice ID, and a Telegram bot token/chat ID โ€” full walkthrough in SETUP.md.

A wake-word voice loop for talking to your own AI agent out loud, Jarvis-style. Say "Hey Neo," it answers "Peace god," and from there you talk back and forth until you say "Peace god" again to end the session.

This is the whole thing in one file: neo-voice-assistant.py. No framework, no server โ€” just a while True: loop wired to three pieces:

EARS   โ†’   BRAIN            โ†’   MOUTH
mic/STT    your agent            TTS
(local)    (via Telegram)        (ElevenLabs, falls back to macOS `say`)

Why this matters

Most "voice assistant" tutorials give you a wake word and a canned response. This one routes every question to a real agent โ€” whatever you've already got running that listens on Telegram (a Claude Code agent, an n8n flow, a cron job with a bot token, anything) โ€” and speaks back whatever that agent says. The mic and the speaker are the only new surface area; your actual brain stays wherever it already lives.

How it works

  1. Ears โ€” speech_recognition listens on your Mac's mic, transcribes locally in short chunks, and watches for a wake phrase ("hey neo," "hey nio," a few near-miss variants for STT noise).
  2. Brain โ€” once activated, everything you say gets POSTed to a Telegram bot as [VOICE] <your text>, then the script polls getUpdates for up to 40 seconds waiting for a bot reply in that same chat. Whatever agent is behind that bot token is the actual brain โ€” this script doesn't know or care what it is.
  3. Mouth โ€” the reply is cleaned of markdown, capped to 800 characters, and streamed through ElevenLabs' TTS API in your cloned (or chosen) voice. If the ElevenLabs call fails for any reason, it falls back to macOS's built-in say so the loop never goes silent.

Say "peace god," "goodbye," "shut down," or "go to sleep" to end the conversation and drop back to wake-word listening. Say the wake word again mid-conversation and it just re-acknowledges you instead of restarting.

Quickstart

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

cp env.example .env
# fill in ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, NEO_TELEGRAM_BOT_TOKEN, NEO_TELEGRAM_CHAT_ID

python3 neo-voice-assistant.py

The script refuses to start if any of the four env vars are missing โ€” it fails loud instead of silently falling back to something broken.

You'll need: - A Mac with a working microphone (it auto-picks a "MacBook"/"Built-in" input if one exists, otherwise the system default). - afplay and say, both stock on macOS โ€” no extra install. - An ElevenLabs account and API key, plus a voice ID (clone your own voice in their dashboard, or use any voice from their library). - A Telegram bot (via @BotFather) and the chat ID it talks in, wired to whatever agent you want answering.

Make it your own

  • Swap the brain. The only contract is "POST a message, poll the same chat for a bot reply." Point NEO_TELEGRAM_BOT_TOKEN / NEO_TELEGRAM_CHAT_ID at any bot backed by any agent โ€” the voice loop doesn't need to change.
  • Change the wake word and exit phrases. Edit the WAKE_WORDS and EXIT_WORDS lists at the top of the file.
  • Swap the personality. Change what speak("Peace god...") says on activation and what it says on sign-off โ€” those are the only two hardcoded lines.
  • Go fully local for the mouth. This version calls ElevenLabs (cloud) with a say fallback. If you want the whole loop to run offline, replace the body of speak() with a local TTS call instead of the httpx.post(...) block โ€” Chatterbox (MIT-licensed voice cloning, runs well on Apple Silicon) via mlx-audio is a solid drop-in: clone a voice once, generate audio locally with no API key and no per-character cost, and keep ElevenLabs (or say) as the fallback tier if the local model errors out. That gives you the same three-tier fallback chain โ€” local model, cloud API, OS built-in โ€” with nothing but the OS say command required to always work.
  • Change how it listens. listen() uses Google's free Web Speech API through speech_recognition โ€” swap in a local Whisper model if you want STT offline too.

Files

  • neo-voice-assistant.py โ€” the full loop: ears, brain routing, mouth.
  • requirements.txt โ€” SpeechRecognition, httpx, PyAudio (PyAudio needs portaudio on macOS: brew install portaudio before pip install).
  • env.example โ€” the four env vars this script needs, with placeholders (copy to .env, which is gitignored).