spych
Spych
Spych (pronounced "speech"): Talk with your computer like it's your personal assistant without sending your voice to the cloud.
A lightweight, fully offline Python toolkit for wake word detection, audio transcription, spoken AI responses, and AI integrations. Built on faster-whisper, PvRecorder, and Kokoro.
API Docs: https://connor-makowski.github.io/spych/spych.html
Installation
Recommended: uv
uv tool install spych
Alternative: pip
pip install spych
TTS Extras
By default, Spych automatically installs the right TTS backend for your Python version. You can also install explicitly:
uv tool install "spych[kokoro]" # Fast, lightweight (Python < 3.13 recommended)
uv tool install "spych[chatterbox]" # High-quality voice cloning (Python >= 3.13 required)
Quick Start
# Navigate to your project directory first
cd ~/my_project
# Voice-control Claude Code β say "hey claude" to trigger
spych claude
# Use a personality preset β say "hey jarvis" to trigger
spych claude --personality jarvis
# Voice-control a local Ollama model β say "hey llama" to trigger
spych ollama --model llama3.2:latest
π‘ Pro tip: Saying "Hey Claude" or "Hey Llama" tends to trigger more reliably than the bare wake word.
Say "terminate" (or press Ctrl+C) to stop any session.
CLI
Available Agents
All agents require their respective CLI tool to be installed and authenticated before use.
| Command | Alias | Description | Default wake words |
|---|---|---|---|
spych claude_code_cli |
β | Voice-control Claude Code via the CLI | claude, clod, cloud, clawed |
spych claude_code_sdk |
spych claude |
Voice-control Claude Code via the Agent SDK | claude, clod, cloud, clawed |
spych codex_cli |
spych codex |
Voice-control the OpenAI Codex agent | codex |
spych gemini_cli |
spych gemini |
Voice-control the Google Gemini agent | gemini, google |
spych opencode_cli |
spych opencode |
Voice-control the OpenCode agent | opencode, open code |
spych ollama |
β | Talk to a local Ollama model | llama, ollama, lama |
Available Utilities
The following utilities are also available as CLI commands. They don't use wake words, but serve various auxiliary functions like live transcription and voice profiling.
| Command | Description |
|---|---|
spych --version |
Print the version number and exit |
spych --help |
Show detailed usage instructions and exit |
spych live |
Continuous speech-to-text transcription to file |
spych live-translation |
(Beta Mode) Continuous speech transcription + translation (bilingual input and output) |
spych multi |
Run multiple agents simultaneously |
spych users |
Manage user profiles and global settings |
spych profile_my_voice |
Record a voice sample for TTS cloning |
Global Flags
These must be placed before the agent name:
spych --theme light claude
| Flag | Options | Default | Description |
|---|---|---|---|
--theme |
dark, light, solarized, mono |
dark |
Terminal colour theme |
π‘ TUI Dashboard: Spych launches a rich terminal interface by default. Use the
--verboseflag (e.g.,spych --verbose claude) to switch to a simpler, non-interactive scrollable output.
Common Flags
All agent subcommands accept these flags:
| Flag | Default | Description |
|---|---|---|
--personality NAME |
β | Apply a named preset (sets wake words, voice, name, style) |
--name NAME |
(agent default) | Custom display name shown in the terminal |
--wake-words WORD [...] |
(agent default) | One or more words that trigger the agent |
--terminate-words WORD [...] |
terminate |
Words that stop the listener |
--listen-duration SECONDS |
0 (VAD auto) |
Seconds to record after wake word |
--follow-up-listen-duration SECONDS |
0 |
Seconds to listen for a follow-up answer |
--inactivity-timeout SECONDS |
4.0 |
Seconds of silence before returning to wake word |
--use-speaker BOOL |
true |
Speak responses aloud via TTS |
--speaker-voice VOICE |
af_heart |
Voice name for spoken responses |
--speaker-backend BACKEND |
(auto) | chatterbox or kokoro |
--response-style STYLE |
β | Style preset or custom instruction for spoken output |
--intermediate-responses BOOL |
true |
Enable intermediate response chaining for long-running tasks |
Coding agents (claude, codex, gemini, opencode) also accept:
| Flag | Default | Description |
|---|---|---|
--continue-conversation BOOL |
true |
Resume the most recent session |
--show-tool-events BOOL |
true |
Print live tool start/end events |
Agent-specific flags:
| Agent | Flag | Default | Description |
|---|---|---|---|
ollama |
--model |
llama3.2:latest |
Ollama model name |
ollama |
--history-length |
10 |
Past interactions to include in context |
ollama |
--host |
http://localhost:11434 |
Ollama instance URL |
opencode_cli |
--model |
β | Model in provider/model format |
claude_code_sdk |
--setting-sources |
user project local |
Claude Code settings sources |
Personalities
Personalities are named presets that bundle a wake word list, voice, display name, and response style into a single flag. Any explicit flag overrides the preset.
spych claude --personality jarvis
# equivalent to:
spych claude --name "JARVIS" --wake-words jarvis jarves --speaker-voice bm_george --use-speaker true --response-style jarvis
| Name | Wake words | Voice | Style |
|---|---|---|---|
assistant |
assistant, helper, computer |
af_heart |
assistant β helpful, precise, informative |
friend |
friend, buddy, pal |
af_amy |
friendly β warm and simple |
jarvis |
jarvis, jarves, jargus, jervis |
bm_george |
jarvis β precise, dry wit, "sir" |
pirate |
blackbeard, pirate, ahoy |
am_michael |
pirate β pirate speak, colorful |
news_anchor |
bella, news anchor, anchor |
af_bella |
news_anchor β professional broadcast tone |
robot |
rob, robot |
am_adam |
robot β monotone, literal |
caveman |
er, ur, caveman, cave man |
am_onyx |
caveman β very simple, direct |
User Management
Spych supports multiple user profiles, allowing agents to provide more personalized responses based on your name, age, and other context.
# Launch the interactive user management menu
spych users
The users utility allows you to:
- Create, edit, and delete user profiles.
- Set a default user for all agents.
- Change the global terminal theme (
dark,light,solarized,mono).
You can also specify a user for a specific session:
spych claude --user Connor
Response Styles
The --response-style flag shapes how the agent formats its spoken output.
| Style | Description |
|---|---|
assistant |
Helpful and precise, concise and informative |
concise |
Key points only, direct |
friendly |
Warm, approachable, simple language |
military |
Brevity-style, short sentences |
five_year_old |
Simple words, very short |
fast |
As brief as reasonably possible |
pirate |
Pirate speak, colorful |
news_anchor |
Professional broadcast tone |
haiku |
5-7-5 haiku form |
shakespearean |
Elizabethan English |
robot |
Monotone, literal |
caveman |
Very simple, direct |
yoda |
Inverted sentence structure |
jarvis |
JARVIS from Iron Man β precise, dry wit, addresses user as "sir" or "ma'am" |
You can also pass any custom instruction string directly: --response-style "Reply in exactly one sentence.".
Text-to-Speech & Voices
Spoken responses are enabled by default for personality presets and when --use-speaker true is set.
spych claude --use-speaker true --speaker-voice bm_george
spych claude --use-speaker true --speaker-backend kokoro
spych claude --use-speaker false # disable TTS
When TTS is active, short responses are spoken verbatim; longer ones use the agent's short summary. If the response ends with a question, Spych automatically listens for a follow-up β no wake word required.
TTS Backends
| Backend | Best for | Python support |
|---|---|---|
| Chatterbox (default priority) | Natural voices, zero-shot voice cloning | 3.11+ (required for 3.13+) |
| Kokoro (lightweight fallback) | Fast, low-resource devices (e.g. Raspberry Pi) | 3.11β3.12 recommended |
Spych tries Chatterbox first, then Kokoro. Use --speaker-backend to force one explicitly.
Available Voices
The same voice names work for both backends.
- Chatterbox wave voices: https://github.com/connor-makowski/spych/tree/main/voices/wave
- Kokoro pt voices (56 total): https://github.com/connor-makowski/spych/tree/main/voices/pt
American English (am_ / af_):
| Voice | Gender | Grade |
|---|---|---|
af_heart |
F | A (default) |
af_bella |
F | A- |
af_nicole |
F | B- |
am_michael |
M | C+ |
am_fenrir |
M | C+ |
am_puck |
M | C+ |
British English (bm_ / bf_):
| Voice | Gender | Grade |
|---|---|---|
bf_emma |
F | B- |
bf_isabella |
F | C |
bm_george |
M | C |
Voice Cloning
Record a 10-second sample of your voice, then use it as the speaker voice. Requires the Chatterbox backend.
# Step 1: record your profile
spych profile_my_voice --name my_voice
# Step 2: use it
spych claude --use-speaker true --speaker-voice my_voice --speaker-backend chatterbox
# Or use any .wav file directly
spych claude --use-speaker true --speaker-voice /path/to/my_voice.wav --speaker-backend chatterbox
Live Transcription
spych live continuously records from the microphone using VAD and writes the transcript to disk in real time. No wake word required β it transcribes everything until stopped.
CLI
spych live # writes transcript.srt
spych live --output-path meeting --output-format both
spych live --terminate-words "stop recording"
spych live --no-timestamps --whisper-model medium.en
Stop by pressing the stop key (default: q + Enter), saying a terminate word, or pressing Ctrl+C.
Parameters
| Flag | Default | Description |
|---|---|---|
--output-path PATH |
transcript |
Base output file path without extension |
--output-format FORMAT |
srt |
txt, srt, or both |
--no-timestamps |
false | Omit timestamps from terminal and .txt output |
--stop-key KEY |
q |
Key (then Enter) to stop the session |
--terminate-words WORD [...] |
β | Spoken words that stop the session |
--device-index N |
-1 |
Microphone device index; -1 uses system default |
--whisper-model MODEL |
base.en |
faster-whisper model name |
--whisper-device DEVICE |
cpu |
cpu or cuda |
--whisper-compute-type TYPE |
int8 |
int8, float16, or float32 |
--no-speech-threshold FLOAT |
0.3 |
Whisper segments above this no_speech_prob are dropped |
--speech-threshold FLOAT |
0.5 |
VAD speech onset probability |
--silence-threshold FLOAT |
0.35 |
VAD silence probability during speech |
--silence-frames N |
20 |
Consecutive silent frames to end a segment (~32ms each) |
--speech-pad-frames N |
5 |
Pre-roll frames and onset confirmation count |
--max-speech-duration SECONDS |
30.0 |
Hard cap on a single segment |
--context-words N |
32 |
Trailing words passed as whisper initial_prompt |
Python
from spych.live import SpychLive
SpychLive(
output_format="srt", # "txt", "srt", or "both"
output_path="my_transcript", # written to my_transcript.srt
show_timestamps=True,
stop_key="q", # type q + Enter to stop
terminate_words=["stop recording"],
).start()
SpychLive Parameters
| Parameter | Default | Description |
|---|---|---|
output_format |
"srt" |
Output format(s): "txt", "srt", or "both" |
output_path |
"transcript" |
Base path without extension |
show_timestamps |
True |
Prepend [HH:MM:SS] timestamps to terminal and .txt output |
stop_key |
"q" |
Key (then Enter) to stop the session |
terminate_words |
None |
Spoken words that stop the session |
on_terminate |
None |
No-argument callback executed when a terminate word fires |
device_index |
-1 |
Microphone device index; -1 uses system default |
whisper_model |
"base.en" |
faster-whisper model name |
whisper_device |
"cpu" |
Device for inference: "cpu" or "cuda" |
whisper_compute_type |
"int8" |
Compute precision: "int8", "float16", or "float32" |
no_speech_threshold |
0.4 |
Whisper segments above this are discarded |
speech_threshold |
0.5 |
Silero VAD onset probability |
silence_threshold |
0.35 |
Silero VAD silence probability during speech |
silence_frames_threshold |
20 |
Consecutive silent frames to close a segment |
speech_pad_frames |
5 |
Pre-roll frame count and onset confirmation threshold |
max_speech_duration_s |
30.0 |
Hard cap on a single segment in seconds |
context_words |
32 |
Trailing transcript words passed as initial_prompt |
Live Translation
Currently in beta: expect some rough edges.
spych live-translation starts a bidirectional live translation session between two languages. Either participant can speak in either language β Whisper transcribes each utterance, Ollama detects which language was spoken and translates it to the other, and each segment is shown as two lines in real time. The translated text is also spoken aloud via TTS by default.
Note: This is currently in beta mode and the API may change without a major version bump. Feedback is very welcome!
Requires Ollama running locally with the translation model pulled. By default, Spych uses the llama3.2 model, but you can specify any Ollama model with --ollama-translation-model.
CLI
# English β Spanish conversation
spych live-translation --languages en es
# English β French, disable TTS
spych live-translation --languages en fr --no-speaker
# Use a larger Ollama model and save a bilingual transcript
spych live-translation --languages en de --ollama-translation-model mistral --output-format both
Stop by pressing the stop key (default: q + Enter), saying a terminate word, or pressing Ctrl+C.
Each utterance is shown as two lines β the original and the translation:
[00:00:05](es) Hola, ΒΏcΓ³mo estΓ‘s?
[00:00:05](en) Hello, how are you?
If Ollama is unreachable, the session continues and the translation line shows [translation unavailable] for that segment.
Parameters
| Flag | Default | Description |
|---|---|---|
--languages LANG LANG |
(required) | Two BCP-47 language codes for the conversation pair (e.g. en es) |
--ollama-host URL |
http://localhost:11434 |
Ollama HTTP base URL |
--ollama-translation-model MODEL |
llama3.2 |
Ollama model used for translation |
--no-speaker |
false | Disable TTS β speaker is on by default |
--speaker-voice VOICE |
(model default) | Wave voice name for zero-shot cloning; omit to use the model's built-in default voice |
--output-path PATH |
transcript |
Base output file path without extension |
--output-format FORMAT |
(none) | Save transcript to file: txt, srt, or both; omit for terminal-only output |
--no-timestamps |
false | Omit timestamps from terminal and .txt output |
--stop-key KEY |
q |
Key (then Enter) to stop the session |
--terminate-words WORD [...] |
β | Spoken words that stop the session |
--device-index N |
-1 |
Microphone device index; -1 uses system default |
--whisper-model MODEL |
small |
faster-whisper model name; .en suffix is stripped automatically |
--whisper-device DEVICE |
auto |
auto, cpu, or cuda; auto selects cuda when available on Python β€3.13 |
--whisper-compute-type TYPE |
int8 |
int8, float16, or float32 |
--no-speech-threshold FLOAT |
0.3 |
Whisper segments above this no_speech_prob are dropped |
--speech-threshold FLOAT |
0.5 |
VAD speech onset probability |
--silence-threshold FLOAT |
0.35 |
VAD silence probability during speech |
--silence-frames N |
20 |
Consecutive silent frames to end a segment (~32ms each) |
--speech-pad-frames N |
5 |
Pre-roll frames and onset confirmation count |
--max-speech-duration SECONDS |
30.0 |
Hard cap on a single segment |
Python
from spych.live_translation import SpychLiveTranslation
SpychLiveTranslation(
lang_a="en",
lang_b="es",
output_format="both", # "txt", "srt", or "both"; "" for terminal only
output_path="my_translation", # written to my_translation.txt + .srt
show_timestamps=True,
stop_key="q",
terminate_words=["terminate"],
ollama_host="http://localhost:11434",
ollama_translation_model="llama3.2",
use_speaker=True,
speaker_voice="", # "" uses the model's built-in default voice
).start()
SpychLiveTranslation Parameters
| Parameter | Default | Description |
|---|---|---|
lang_a |
(required) | BCP-47 code of the first language (e.g. "en") |
lang_b |
(required) | BCP-47 code of the second language (e.g. "es") |
output_format |
"" |
Output format(s): "txt", "srt", or "both"; "" disables file output |
output_path |
"transcript" |
Base path without extension |
show_timestamps |
True |
Prepend [HH:MM:SS] timestamps to terminal and .txt output |
stop_key |
"q" |
Key (then Enter) to stop the session |
terminate_words |
None |
Spoken words that stop the session |
device_index |
-1 |
Microphone device index; -1 uses system default |
whisper_model |
"base" |
faster-whisper model name; .en suffix stripped automatically |
whisper_device |
"auto" |
Device for inference: "auto", "cpu", or "cuda" |
whisper_compute_type |
"int8" |
Compute precision: "int8", "float16", or "float32" |
no_speech_threshold |
0.4 |
Whisper segments above this are discarded |
speech_threshold |
0.5 |
Silero VAD onset probability |
silence_threshold |
0.35 |
Silero VAD silence probability during speech |
silence_frames_threshold |
20 |
Consecutive silent frames to close a segment |
speech_pad_frames |
5 |
Pre-roll frame count and onset confirmation threshold |
max_speech_duration_s |
30.0 |
Hard cap on a single segment in seconds |
ollama_host |
"http://localhost:11434" |
Ollama HTTP base URL for translation |
ollama_translation_model |
"llama3.2" |
Ollama model used for translation |
use_speaker |
True |
Speak each translated segment aloud via TTS |
speaker_voice |
"" |
Wave voice name for zero-shot cloning; "" uses the model's built-in default voice |
Multi-agent
Run several agents simultaneously under a single listener, each bound to its own wake words. Say "hey claude" to talk to Claude, "hey llama" to talk to Ollama β all in the same terminal session.
CLI
# Two agents, default wake words
spych multi --agents claude gemini
# Include Ollama with a specific model
spych multi --agents claude ollama --ollama-model llama3.2:latest
# Tune listen duration across all agents
spych multi --agents claude codex --listen-duration 8
Multi-agent CLI Flags
| Flag | Default | Description |
|---|---|---|
--agents AGENT [...] |
(required) | Agents to run: claude (claude_code_cli), claude_sdk (claude_code_sdk), codex (codex_cli), gemini (gemini_cli), opencode (opencode_cli), ollama |
--terminate-words WORD [...] |
terminate |
Words that stop all agents |
--listen-duration SECONDS |
5 |
Seconds to listen after a wake word |
--follow-up-listen-duration SECONDS |
0 |
Seconds to listen for follow-up answers |
--inactivity-timeout SECONDS |
4.0 |
Seconds of silence before returning to wake word |
--continue-conversation BOOL |
true |
Resume the most recent session for each coding agent |
--show-tool-events BOOL |
true |
Print live tool start/end events |
--use-speaker BOOL |
true |
Speak responses aloud via TTS |
--speaker-backend BACKEND |
(auto) | chatterbox or kokoro |
--intermediate-responses BOOL |
true |
Enable intermediate response chaining for long-running tasks |
--ollama-model MODEL |
llama3.2:latest |
Only used when ollama is in --agents |
--ollama-host URL |
http://localhost:11434 |
Only used when ollama is in --agents |
--ollama-history-length N |
10 |
Only used when ollama is in --agents |
--opencode-model MODEL |
β | provider/model format. Only used when opencode_cli is in --agents |
--setting-sources SOURCE [...] |
user project local |
Only used when claude_code_sdk is in --agents |
Python
from spych.core import Spych
from spych.orchestrator import SpychOrchestrator
from spych.agents.claude import LocalClaudeCodeCLIResponder
from spych.agents.ollama import OllamaResponder
spych_object = Spych(whisper_model="base.en")
SpychOrchestrator(
entries=[
{
"responder": LocalClaudeCodeCLIResponder(spych_object=spych_object),
"wake_words": ["claude", "clod", "cloud", "clawed"],
"terminate_words": ["terminate"],
},
{
"responder": OllamaResponder(spych_object=spych_object, model="llama3.2:latest"),
"wake_words": ["llama", "ollama", "lama"],
},
]
).start()
OrchestratorEntry Keys
| Key | Required | Default | Description |
|---|---|---|---|
responder |
β | β | A BaseResponder instance |
wake_words |
β | β | Words that trigger this responder. Must be unique across all entries |
terminate_words |
["terminate"] |
Words that stop the entire orchestrator |
SpychOrchestrator Parameters
| Parameter | Default | Description |
|---|---|---|
entries |
(required) | List of OrchestratorEntry dicts |
spych_wake_kwargs |
None |
Extra kwargs forwarded to SpychWake |
Python β Built-in Agents
The same agents available from the CLI can be used directly from Python.
Claude Code CLI
from spych.agents import claude_code_cli
# Say "hey claude" to trigger
claude_code_cli()
Claude Code SDK
from spych.agents import claude_code_sdk
# Say "hey claude" to trigger
claude_code_sdk()
Codex CLI
from spych.agents import codex_cli
# Say "hey codex" to trigger
codex_cli()
Gemini CLI
from spych.agents import gemini_cli
# Say "hey gemini" to trigger
gemini_cli()
OpenCode CLI
from spych.agents import opencode_cli
# Say "hey opencode" to trigger
opencode_cli()
Ollama
from spych.agents import ollama
# Pull the model first: ollama pull llama3.2:latest
# Say "hey llama" to trigger
ollama(model="llama3.2:latest")
Coding Agent Parameters
| Parameter | claude_code_cli |
claude_code_sdk |
codex_cli |
gemini_cli |
opencode_cli |
Description |
|---|---|---|---|---|---|---|
name |
Claude |
Claude |
Codex |
Gemini |
OpenCode |
Custom display name |
wake_words |
["claude", "clod", "cloud", "clawed"] |
["claude", "clod", "cloud", "clawed"] |
["codex"] |
["gemini", "google"] |
["opencode", "open code"] |
Words that trigger the agent |
terminate_words |
["terminate"] |
["terminate"] |
["terminate"] |
["terminate"] |
["terminate"] |
Words that stop the listener |
model |
β | β | β | β | None |
Model in provider/model format |
listen_duration |
0 |
0 |
0 |
0 |
0 |
Seconds to listen (0 = VAD auto) |
continue_conversation |
True |
True |
True |
True |
True |
Resume the most recent session |
setting_sources |
β | ["user", "project", "local"] |
β | β | β | Claude Code settings sources |
show_tool_events |
True |
True |
True |
True |
True |
Print live tool start/end events |
use_speaker |
False |
False |
False |
False |
False |
Speak responses aloud via TTS |
speaker_voice |
"af_heart" |
"af_heart" |
"af_heart" |
"af_heart" |
"af_heart" |
Voice name for TTS |
response_style |
"" |
"" |
"" |
"" |
"" |
Style preset or custom instruction |
allow_intermediate_responses |
True |
True |
True |
True |
True |
Enable intermediate response chaining |
spych_kwargs |
β | β | β | β | β | Extra kwargs passed to Spych |
spych_wake_kwargs |
β | β | β | β | β | Extra kwargs passed to SpychWake |
Ollama Parameters
| Parameter | Default | Description |
|---|---|---|
name |
"Ollama" |
Custom display name |
wake_words |
["llama", "ollama", "lama"] |
Words that trigger the agent |
terminate_words |
["terminate"] |
Words that stop the listener |
model |
"llama3.2:latest" |
Ollama model name |
listen_duration |
0 |
Seconds to listen (0 = VAD auto) |
history_length |
10 |
Past interactions to include in context |
host |
"http://localhost:11434" |
Ollama instance URL |
use_speaker |
False |
Speak responses aloud via TTS |
speaker_voice |
"af_heart" |
Voice name for TTS |
response_style |
"" |
Style preset or custom instruction |
allow_intermediate_responses |
True |
Enable intermediate response chaining |
spych_kwargs |
None |
Extra kwargs passed to Spych |
spych_wake_kwargs |
None |
Extra kwargs passed to SpychWake |
Python: Building Your Own Agent
Subclass BaseResponder, implement respond, and Spych handles the rest: wake word detection, transcription, spinner UI, timing, TTS, error handling.
respond() must return an AgentResponse. Use self.format_prompt() to inject the JSON schema into your prompt and self.parse_output() to parse the result:
from spych.responders import BaseResponder, AgentResponse
class MyResponder(BaseResponder):
def respond(self, user_input: str) -> AgentResponse:
raw = call_my_llm(self.format_prompt(user_input))
return self.parse_output(raw)
A complete working example with a custom wake word:
from spych import Spych, SpychOrchestrator
from spych.responders import BaseResponder, AgentResponse
class EchoResponder(BaseResponder):
def respond(self, user_input: str) -> AgentResponse:
return AgentResponse(
response=f"'{self.name}' heard: {user_input}",
summary=f"Heard: {user_input}",
requires_user_feedback=False,
)
SpychOrchestrator(
entries=[
{
"responder": EchoResponder(
spych_object=Spych(whisper_model="base.en"),
listen_duration=5,
name="TestResponder",
),
"wake_words": ["test"],
"terminate_words": ["terminate"],
}
]
).start()
You can also subclass a built-in agent. For example, a translation agent that routes to Ollama:
from spych import Spych, SpychOrchestrator
from spych.agents import OllamaResponder
from spych.responders import AgentResponse
class Spanish(OllamaResponder):
def respond(self, user_input: str) -> AgentResponse:
user_input = f"Translate the following to Spanish and return only the translated text: '{user_input}'"
return super().respond(user_input)
class German(OllamaResponder):
def respond(self, user_input: str) -> AgentResponse:
user_input = f"Translate the following to German and return only the translated text: '{user_input}'"
return super().respond(user_input)
spych_object = Spych(whisper_model="base.en")
SpychOrchestrator(
entries=[
{
"responder": Spanish(spych_object=spych_object, name="SpanishTranslator", model="llama3.2:latest"),
"wake_words": ["spanish"],
"terminate_words": ["terminate"],
},
{
"responder": German(spych_object=spych_object, name="GermanTranslator", model="llama3.2:latest"),
"wake_words": ["german"],
"terminate_words": ["terminate"],
},
]
).start()
Think your agent would be useful to others? Open a PR or file a feature request via a GitHub issue.
Python: Lower-Level API
Need more control? Use Spych and SpychWake directly.
Transcription
from spych import Spych
spych = Spych(
whisper_model="base.en", # tiny, small, medium, large β all faster-whisper models work
whisper_device="cpu", # use "cuda" for Nvidia GPU
)
print(spych.listen(duration=5))
See: https://connor-makowski.github.io/spych/spych/core.html
Wake Word Detection
from spych import SpychWake, Spych
spych = Spych(whisper_model="base.en", whisper_device="cpu")
def on_wake():
print("Wake word detected! Listening...")
print(spych.listen(duration=5))
SpychWake(
wake_word_map={"speech": on_wake},
whisper_model="tiny.en",
whisper_device="cpu",
).start()
See: https://connor-makowski.github.io/spych/spych/wake.html
API Reference
Full docs including all parameters and methods: https://connor-makowski.github.io/spych/spych.html
Support
Found a bug or want a new feature? Open an issue on GitHub.
Contributing
Contributions are welcome!
- Fork the repo and clone it locally.
- Make your changes.
- Run tests and make sure they pass.
- Commit atomically with clear messages.
- Submit a pull request.
Virtual environment setup:
python3.11 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
./utils/test.sh
1""" 2# Spych 3[](https://badge.fury.io/py/spych) 4[](https://opensource.org/licenses/MIT) 5[](https://pepy.tech/project/spych) 6 7**Spych** (pronounced "speech"): Talk with your computer like it's your personal assistant without sending your voice to the cloud. 8 9A lightweight, fully offline Python toolkit for wake word detection, audio transcription, spoken AI responses, and AI integrations. Built on [faster-whisper](https://github.com/SYSTRAN/faster-whisper), [PvRecorder](https://github.com/Picovoice/pvrecorder), and [Kokoro](https://github.com/hexgrad/kokoro). 10 11**API Docs**: https://connor-makowski.github.io/spych/spych.html 12 13--- 14 15# Installation 16 17### Recommended: uv 18 19```bash 20uv tool install spych 21``` 22 23### Alternative: pip 24 25```bash 26pip install spych 27``` 28 29### TTS Extras 30 31By default, Spych automatically installs the right TTS backend for your Python version. You can also install explicitly: 32 33```bash 34uv tool install "spych[kokoro]" # Fast, lightweight (Python < 3.13 recommended) 35uv tool install "spych[chatterbox]" # High-quality voice cloning (Python >= 3.13 required) 36``` 37 38--- 39 40# Quick Start 41 42```bash 43# Navigate to your project directory first 44cd ~/my_project 45 46# Voice-control Claude Code β say "hey claude" to trigger 47spych claude 48 49# Use a personality preset β say "hey jarvis" to trigger 50spych claude --personality jarvis 51 52# Voice-control a local Ollama model β say "hey llama" to trigger 53spych ollama --model llama3.2:latest 54``` 55 56> π‘ **Pro tip:** Saying "Hey Claude" or "Hey Llama" tends to trigger more reliably than the bare wake word. 57 58Say **"terminate"** (or press `Ctrl+C`) to stop any session. 59 60--- 61 62# CLI 63 64## Available Agents 65 66All agents require their respective CLI tool to be installed and authenticated before use. 67 68| Command | Alias | Description | Default wake words | 69|---|---|---|---| 70| `spych claude_code_cli` | β | Voice-control Claude Code via the CLI | `claude`, `clod`, `cloud`, `clawed` | 71| `spych claude_code_sdk` | `spych claude` | Voice-control Claude Code via the Agent SDK | `claude`, `clod`, `cloud`, `clawed` | 72| `spych codex_cli` | `spych codex` | Voice-control the OpenAI Codex agent | `codex` | 73| `spych gemini_cli` | `spych gemini` | Voice-control the Google Gemini agent | `gemini`, `google` | 74| `spych opencode_cli` | `spych opencode` | Voice-control the OpenCode agent | `opencode`, `open code` | 75| `spych ollama` | β | Talk to a local Ollama model | `llama`, `ollama`, `lama` | 76 77## Available Utilities 78 79The following utilities are also available as CLI commands. They don't use wake words, but serve various auxiliary functions like live transcription and voice profiling. 80 81| Command | Description | 82|---|---| 83| `spych --version` | Print the version number and exit | 84| `spych --help` | Show detailed usage instructions and exit | 85| `spych live` | Continuous speech-to-text transcription to file | 86| `spych live-translation` | (Beta Mode) Continuous speech transcription + translation (bilingual input and output) | 87| `spych multi` | Run multiple agents simultaneously | 88| `spych users` | Manage user profiles and global settings | 89| `spych profile_my_voice` | Record a voice sample for TTS cloning | 90 91## Global Flags 92 93These must be placed **before** the agent name: 94 95```bash 96spych --theme light claude 97``` 98 99| Flag | Options | Default | Description | 100|---|---|---|---| 101| `--theme` | `dark`, `light`, `solarized`, `mono` | `dark` | Terminal colour theme | 102 103> π‘ **TUI Dashboard:** Spych launches a rich terminal interface by default. Use the `--verbose` flag (e.g., `spych --verbose claude`) to switch to a simpler, non-interactive scrollable output. 104 105--- 106 107## Common Flags 108 109All agent subcommands accept these flags: 110 111| Flag | Default | Description | 112|---|---|---| 113| `--personality NAME` | β | Apply a named preset (sets wake words, voice, name, style) | 114| `--name NAME` | *(agent default)* | Custom display name shown in the terminal | 115| `--wake-words WORD [...]` | *(agent default)* | One or more words that trigger the agent | 116| `--terminate-words WORD [...]` | `terminate` | Words that stop the listener | 117| `--listen-duration SECONDS` | `0` (VAD auto) | Seconds to record after wake word | 118| `--follow-up-listen-duration SECONDS` | `0` | Seconds to listen for a follow-up answer | 119| `--inactivity-timeout SECONDS` | `4.0` | Seconds of silence before returning to wake word | 120| `--use-speaker BOOL` | `true` | Speak responses aloud via TTS | 121| `--speaker-voice VOICE` | `af_heart` | Voice name for spoken responses | 122| `--speaker-backend BACKEND` | *(auto)* | `chatterbox` or `kokoro` | 123| `--response-style STYLE` | β | Style preset or custom instruction for spoken output | 124| `--intermediate-responses BOOL` | `true` | Enable intermediate response chaining for long-running tasks | 125 126Coding agents (`claude`, `codex`, `gemini`, `opencode`) also accept: 127 128| Flag | Default | Description | 129|---|---|---| 130| `--continue-conversation BOOL` | `true` | Resume the most recent session | 131| `--show-tool-events BOOL` | `true` | Print live tool start/end events | 132 133Agent-specific flags: 134 135| Agent | Flag | Default | Description | 136|---|---|---|---| 137| `ollama` | `--model` | `llama3.2:latest` | Ollama model name | 138| `ollama` | `--history-length` | `10` | Past interactions to include in context | 139| `ollama` | `--host` | `http://localhost:11434` | Ollama instance URL | 140| `opencode_cli` | `--model` | β | Model in `provider/model` format | 141| `claude_code_sdk` | `--setting-sources` | `user project local` | Claude Code settings sources | 142 143--- 144 145# Personalities 146 147Personalities are named presets that bundle a wake word list, voice, display name, and response style into a single flag. Any explicit flag overrides the preset. 148 149```bash 150spych claude --personality jarvis 151# equivalent to: 152spych claude --name "JARVIS" --wake-words jarvis jarves \ 153 --speaker-voice bm_george --use-speaker true \ 154 --response-style jarvis 155``` 156 157| Name | Wake words | Voice | Style | 158|---|---|---|---| 159| `assistant` | `assistant`, `helper`, `computer` | `af_heart` | `assistant` β helpful, precise, informative | 160| `friend` | `friend`, `buddy`, `pal` | `af_amy` | `friendly` β warm and simple | 161| `jarvis` | `jarvis`, `jarves`, `jargus`, `jervis` | `bm_george` | `jarvis` β precise, dry wit, "sir" | 162| `pirate` | `blackbeard`, `pirate`, `ahoy` | `am_michael` | `pirate` β pirate speak, colorful | 163| `news_anchor` | `bella`, `news anchor`, `anchor` | `af_bella` | `news_anchor` β professional broadcast tone | 164| `robot` | `rob`, `robot` | `am_adam` | `robot` β monotone, literal | 165| `caveman` | `er`, `ur`, `caveman`, `cave man` | `am_onyx` | `caveman` β very simple, direct | 166 167--- 168 169# User Management 170 171Spych supports multiple user profiles, allowing agents to provide more personalized responses based on your name, age, and other context. 172 173```bash 174# Launch the interactive user management menu 175spych users 176``` 177 178The `users` utility allows you to: 179- Create, edit, and delete user profiles. 180- Set a default user for all agents. 181- Change the global terminal theme (`dark`, `light`, `solarized`, `mono`). 182 183You can also specify a user for a specific session: 184```bash 185spych claude --user Connor 186``` 187 188--- 189 190# Response Styles 191 192The `--response-style` flag shapes how the agent formats its spoken output. 193 194| Style | Description | 195|---|---| 196| `assistant` | Helpful and precise, concise and informative | 197| `concise` | Key points only, direct | 198| `friendly` | Warm, approachable, simple language | 199| `military` | Brevity-style, short sentences | 200| `five_year_old` | Simple words, very short | 201| `fast` | As brief as reasonably possible | 202| `pirate` | Pirate speak, colorful | 203| `news_anchor` | Professional broadcast tone | 204| `haiku` | 5-7-5 haiku form | 205| `shakespearean` | Elizabethan English | 206| `robot` | Monotone, literal | 207| `caveman` | Very simple, direct | 208| `yoda` | Inverted sentence structure | 209| `jarvis` | JARVIS from Iron Man β precise, dry wit, addresses user as "sir" or "ma'am" | 210 211You can also pass any custom instruction string directly: `--response-style "Reply in exactly one sentence."`. 212 213--- 214 215# Text-to-Speech & Voices 216 217Spoken responses are enabled by default for personality presets and when `--use-speaker true` is set. 218 219```bash 220spych claude --use-speaker true --speaker-voice bm_george 221spych claude --use-speaker true --speaker-backend kokoro 222spych claude --use-speaker false # disable TTS 223``` 224 225When TTS is active, short responses are spoken verbatim; longer ones use the agent's short `summary`. If the response ends with a question, Spych automatically listens for a follow-up β no wake word required. 226 227### TTS Backends 228 229| Backend | Best for | Python support | 230|---|---|---| 231| **Chatterbox** (default priority) | Natural voices, zero-shot voice cloning | 3.11+ (required for 3.13+) | 232| **Kokoro** (lightweight fallback) | Fast, low-resource devices (e.g. Raspberry Pi) | 3.11β3.12 recommended | 233 234Spych tries Chatterbox first, then Kokoro. Use `--speaker-backend` to force one explicitly. 235 236### Available Voices 237 238The same voice names work for both backends. 239 240- Chatterbox wave voices: https://github.com/connor-makowski/spych/tree/main/voices/wave 241- Kokoro pt voices (56 total): https://github.com/connor-makowski/spych/tree/main/voices/pt 242 243American English (`am_` / `af_`): 244 245| Voice | Gender | Grade | 246|---|---|---| 247| `af_heart` | F | A (default) | 248| `af_bella` | F | A- | 249| `af_nicole` | F | B- | 250| `am_michael` | M | C+ | 251| `am_fenrir` | M | C+ | 252| `am_puck` | M | C+ | 253 254British English (`bm_` / `bf_`): 255 256| Voice | Gender | Grade | 257|---|---|---| 258| `bf_emma` | F | B- | 259| `bf_isabella` | F | C | 260| `bm_george` | M | C | 261 262### Voice Cloning 263 264Record a 10-second sample of your voice, then use it as the speaker voice. Requires the Chatterbox backend. 265 266```bash 267# Step 1: record your profile 268spych profile_my_voice --name my_voice 269 270# Step 2: use it 271spych claude --use-speaker true --speaker-voice my_voice --speaker-backend chatterbox 272 273# Or use any .wav file directly 274spych claude --use-speaker true --speaker-voice /path/to/my_voice.wav --speaker-backend chatterbox 275``` 276 277--- 278 279# Live Transcription 280 281`spych live` continuously records from the microphone using VAD and writes the transcript to disk in real time. No wake word required β it transcribes everything until stopped. 282 283## CLI 284 285```bash 286spych live # writes transcript.srt 287spych live --output-path meeting --output-format both 288spych live --terminate-words "stop recording" 289spych live --no-timestamps --whisper-model medium.en 290``` 291 292Stop by pressing the stop key (default: `q` + Enter), saying a terminate word, or pressing `Ctrl+C`. 293 294### Parameters 295 296| Flag | Default | Description | 297|---|---|---| 298| `--output-path PATH` | `transcript` | Base output file path without extension | 299| `--output-format FORMAT` | `srt` | `txt`, `srt`, or `both` | 300| `--no-timestamps` | false | Omit timestamps from terminal and `.txt` output | 301| `--stop-key KEY` | `q` | Key (then Enter) to stop the session | 302| `--terminate-words WORD [...]` | β | Spoken words that stop the session | 303| `--device-index N` | `-1` | Microphone device index; -1 uses system default | 304| `--whisper-model MODEL` | `base.en` | faster-whisper model name | 305| `--whisper-device DEVICE` | `cpu` | `cpu` or `cuda` | 306| `--whisper-compute-type TYPE` | `int8` | `int8`, `float16`, or `float32` | 307| `--no-speech-threshold FLOAT` | `0.3` | Whisper segments above this `no_speech_prob` are dropped | 308| `--speech-threshold FLOAT` | `0.5` | VAD speech onset probability | 309| `--silence-threshold FLOAT` | `0.35` | VAD silence probability during speech | 310| `--silence-frames N` | `20` | Consecutive silent frames to end a segment (~32ms each) | 311| `--speech-pad-frames N` | `5` | Pre-roll frames and onset confirmation count | 312| `--max-speech-duration SECONDS` | `30.0` | Hard cap on a single segment | 313| `--context-words N` | `32` | Trailing words passed as whisper `initial_prompt` | 314 315## Python 316 317```python 318from spych.live import SpychLive 319 320SpychLive( 321 output_format="srt", # "txt", "srt", or "both" 322 output_path="my_transcript", # written to my_transcript.srt 323 show_timestamps=True, 324 stop_key="q", # type q + Enter to stop 325 terminate_words=["stop recording"], 326).start() 327``` 328 329### `SpychLive` Parameters 330 331| Parameter | Default | Description | 332|---|---|---| 333| `output_format` | `"srt"` | Output format(s): `"txt"`, `"srt"`, or `"both"` | 334| `output_path` | `"transcript"` | Base path without extension | 335| `show_timestamps` | `True` | Prepend `[HH:MM:SS]` timestamps to terminal and `.txt` output | 336| `stop_key` | `"q"` | Key (then Enter) to stop the session | 337| `terminate_words` | `None` | Spoken words that stop the session | 338| `on_terminate` | `None` | No-argument callback executed when a terminate word fires | 339| `device_index` | `-1` | Microphone device index; `-1` uses system default | 340| `whisper_model` | `"base.en"` | faster-whisper model name | 341| `whisper_device` | `"cpu"` | Device for inference: `"cpu"` or `"cuda"` | 342| `whisper_compute_type` | `"int8"` | Compute precision: `"int8"`, `"float16"`, or `"float32"` | 343| `no_speech_threshold` | `0.4` | Whisper segments above this are discarded | 344| `speech_threshold` | `0.5` | Silero VAD onset probability | 345| `silence_threshold` | `0.35` | Silero VAD silence probability during speech | 346| `silence_frames_threshold` | `20` | Consecutive silent frames to close a segment | 347| `speech_pad_frames` | `5` | Pre-roll frame count and onset confirmation threshold | 348| `max_speech_duration_s` | `30.0` | Hard cap on a single segment in seconds | 349| `context_words` | `32` | Trailing transcript words passed as `initial_prompt` | 350 351--- 352 353# Live Translation 354 355**Currently in beta: expect some rough edges.** 356 357`spych live-translation` starts a **bidirectional** live translation session between two languages. Either participant can speak in either language β Whisper transcribes each utterance, Ollama detects which language was spoken and translates it to the other, and each segment is shown as two lines in real time. The translated text is also spoken aloud via TTS by default. 358 359Note: This is currently in beta mode and the API may change without a major version bump. Feedback is very welcome! 360 361Requires [Ollama](https://ollama.com) running locally with the translation model pulled. By default, Spych uses the `llama3.2` model, but you can specify any Ollama model with `--ollama-translation-model`. 362 363## CLI 364 365```bash 366# English β Spanish conversation 367spych live-translation --languages en es 368 369# English β French, disable TTS 370spych live-translation --languages en fr --no-speaker 371 372# Use a larger Ollama model and save a bilingual transcript 373spych live-translation --languages en de \ 374 --ollama-translation-model mistral --output-format both 375``` 376 377Stop by pressing the stop key (default: `q` + Enter), saying a terminate word, or pressing `Ctrl+C`. 378 379Each utterance is shown as two lines β the original and the translation: 380 381``` 382[00:00:05](es) Hola, ΒΏcΓ³mo estΓ‘s? 383[00:00:05](en) Hello, how are you? 384``` 385 386If Ollama is unreachable, the session continues and the translation line shows `[translation unavailable]` for that segment. 387 388### Parameters 389 390| Flag | Default | Description | 391|---|---|---| 392| `--languages LANG LANG` | *(required)* | Two BCP-47 language codes for the conversation pair (e.g. `en es`) | 393| `--ollama-host URL` | `http://localhost:11434` | Ollama HTTP base URL | 394| `--ollama-translation-model MODEL` | `llama3.2` | Ollama model used for translation | 395| `--no-speaker` | false | Disable TTS β speaker is on by default | 396| `--speaker-voice VOICE` | *(model default)* | Wave voice name for zero-shot cloning; omit to use the model's built-in default voice | 397| `--output-path PATH` | `transcript` | Base output file path without extension | 398| `--output-format FORMAT` | *(none)* | Save transcript to file: `txt`, `srt`, or `both`; omit for terminal-only output | 399| `--no-timestamps` | false | Omit timestamps from terminal and `.txt` output | 400| `--stop-key KEY` | `q` | Key (then Enter) to stop the session | 401| `--terminate-words WORD [...]` | β | Spoken words that stop the session | 402| `--device-index N` | `-1` | Microphone device index; -1 uses system default | 403| `--whisper-model MODEL` | `small` | faster-whisper model name; `.en` suffix is stripped automatically | 404| `--whisper-device DEVICE` | `auto` | `auto`, `cpu`, or `cuda`; `auto` selects `cuda` when available on Python β€3.13 | 405| `--whisper-compute-type TYPE` | `int8` | `int8`, `float16`, or `float32` | 406| `--no-speech-threshold FLOAT` | `0.3` | Whisper segments above this `no_speech_prob` are dropped | 407| `--speech-threshold FLOAT` | `0.5` | VAD speech onset probability | 408| `--silence-threshold FLOAT` | `0.35` | VAD silence probability during speech | 409| `--silence-frames N` | `20` | Consecutive silent frames to end a segment (~32ms each) | 410| `--speech-pad-frames N` | `5` | Pre-roll frames and onset confirmation count | 411| `--max-speech-duration SECONDS` | `30.0` | Hard cap on a single segment | 412 413## Python 414 415```python 416from spych.live_translation import SpychLiveTranslation 417 418SpychLiveTranslation( 419 lang_a="en", 420 lang_b="es", 421 output_format="both", # "txt", "srt", or "both"; "" for terminal only 422 output_path="my_translation", # written to my_translation.txt + .srt 423 show_timestamps=True, 424 stop_key="q", 425 terminate_words=["terminate"], 426 ollama_host="http://localhost:11434", 427 ollama_translation_model="llama3.2", 428 use_speaker=True, 429 speaker_voice="", # "" uses the model's built-in default voice 430).start() 431``` 432 433### `SpychLiveTranslation` Parameters 434 435| Parameter | Default | Description | 436|---|---|---| 437| `lang_a` | *(required)* | BCP-47 code of the first language (e.g. `"en"`) | 438| `lang_b` | *(required)* | BCP-47 code of the second language (e.g. `"es"`) | 439| `output_format` | `""` | Output format(s): `"txt"`, `"srt"`, or `"both"`; `""` disables file output | 440| `output_path` | `"transcript"` | Base path without extension | 441| `show_timestamps` | `True` | Prepend `[HH:MM:SS]` timestamps to terminal and `.txt` output | 442| `stop_key` | `"q"` | Key (then Enter) to stop the session | 443| `terminate_words` | `None` | Spoken words that stop the session | 444| `device_index` | `-1` | Microphone device index; `-1` uses system default | 445| `whisper_model` | `"base"` | faster-whisper model name; `.en` suffix stripped automatically | 446| `whisper_device` | `"auto"` | Device for inference: `"auto"`, `"cpu"`, or `"cuda"` | 447| `whisper_compute_type` | `"int8"` | Compute precision: `"int8"`, `"float16"`, or `"float32"` | 448| `no_speech_threshold` | `0.4` | Whisper segments above this are discarded | 449| `speech_threshold` | `0.5` | Silero VAD onset probability | 450| `silence_threshold` | `0.35` | Silero VAD silence probability during speech | 451| `silence_frames_threshold` | `20` | Consecutive silent frames to close a segment | 452| `speech_pad_frames` | `5` | Pre-roll frame count and onset confirmation threshold | 453| `max_speech_duration_s` | `30.0` | Hard cap on a single segment in seconds | 454| `ollama_host` | `"http://localhost:11434"` | Ollama HTTP base URL for translation | 455| `ollama_translation_model` | `"llama3.2"` | Ollama model used for translation | 456| `use_speaker` | `True` | Speak each translated segment aloud via TTS | 457| `speaker_voice` | `""` | Wave voice name for zero-shot cloning; `""` uses the model's built-in default voice | 458 459--- 460 461# Multi-agent 462 463Run several agents simultaneously under a single listener, each bound to its own wake words. Say "hey claude" to talk to Claude, "hey llama" to talk to Ollama β all in the same terminal session. 464 465## CLI 466 467```bash 468# Two agents, default wake words 469spych multi --agents claude gemini 470 471# Include Ollama with a specific model 472spych multi --agents claude ollama --ollama-model llama3.2:latest 473 474# Tune listen duration across all agents 475spych multi --agents claude codex --listen-duration 8 476``` 477 478### Multi-agent CLI Flags 479 480| Flag | Default | Description | 481|---|---|---| 482| `--agents AGENT [...]` | *(required)* | Agents to run: `claude` (`claude_code_cli`), `claude_sdk` (`claude_code_sdk`), `codex` (`codex_cli`), `gemini` (`gemini_cli`), `opencode` (`opencode_cli`), `ollama` | 483| `--terminate-words WORD [...]` | `terminate` | Words that stop all agents | 484| `--listen-duration SECONDS` | `5` | Seconds to listen after a wake word | 485| `--follow-up-listen-duration SECONDS` | `0` | Seconds to listen for follow-up answers | 486| `--inactivity-timeout SECONDS` | `4.0` | Seconds of silence before returning to wake word | 487| `--continue-conversation BOOL` | `true` | Resume the most recent session for each coding agent | 488| `--show-tool-events BOOL` | `true` | Print live tool start/end events | 489| `--use-speaker BOOL` | `true` | Speak responses aloud via TTS | 490| `--speaker-backend BACKEND` | *(auto)* | `chatterbox` or `kokoro` | 491| `--intermediate-responses BOOL` | `true` | Enable intermediate response chaining for long-running tasks | 492| `--ollama-model MODEL` | `llama3.2:latest` | Only used when `ollama` is in `--agents` | 493| `--ollama-host URL` | `http://localhost:11434` | Only used when `ollama` is in `--agents` | 494| `--ollama-history-length N` | `10` | Only used when `ollama` is in `--agents` | 495| `--opencode-model MODEL` | β | `provider/model` format. Only used when `opencode_cli` is in `--agents` | 496| `--setting-sources SOURCE [...]` | `user project local` | Only used when `claude_code_sdk` is in `--agents` | 497 498## Python 499 500```python 501from spych.core import Spych 502from spych.orchestrator import SpychOrchestrator 503from spych.agents.claude import LocalClaudeCodeCLIResponder 504from spych.agents.ollama import OllamaResponder 505 506spych_object = Spych(whisper_model="base.en") 507 508SpychOrchestrator( 509 entries=[ 510 { 511 "responder": LocalClaudeCodeCLIResponder(spych_object=spych_object), 512 "wake_words": ["claude", "clod", "cloud", "clawed"], 513 "terminate_words": ["terminate"], 514 }, 515 { 516 "responder": OllamaResponder(spych_object=spych_object, model="llama3.2:latest"), 517 "wake_words": ["llama", "ollama", "lama"], 518 }, 519 ] 520).start() 521``` 522 523### `OrchestratorEntry` Keys 524 525| Key | Required | Default | Description | 526|---|---|---|---| 527| `responder` | β | β | A `BaseResponder` instance | 528| `wake_words` | β | β | Words that trigger this responder. Must be unique across all entries | 529| `terminate_words` | | `["terminate"]` | Words that stop the entire orchestrator | 530 531### `SpychOrchestrator` Parameters 532 533| Parameter | Default | Description | 534|---|---|---| 535| `entries` | *(required)* | List of `OrchestratorEntry` dicts | 536| `spych_wake_kwargs` | `None` | Extra kwargs forwarded to `SpychWake` | 537 538--- 539 540# Python β Built-in Agents 541 542The same agents available from the CLI can be used directly from Python. 543 544## Claude Code CLI 545 546```python 547from spych.agents import claude_code_cli 548 549# Say "hey claude" to trigger 550claude_code_cli() 551``` 552 553## Claude Code SDK 554 555```python 556from spych.agents import claude_code_sdk 557 558# Say "hey claude" to trigger 559claude_code_sdk() 560``` 561 562## Codex CLI 563 564```python 565from spych.agents import codex_cli 566 567# Say "hey codex" to trigger 568codex_cli() 569``` 570 571## Gemini CLI 572 573```python 574from spych.agents import gemini_cli 575 576# Say "hey gemini" to trigger 577gemini_cli() 578``` 579 580## OpenCode CLI 581 582```python 583from spych.agents import opencode_cli 584 585# Say "hey opencode" to trigger 586opencode_cli() 587``` 588 589## Ollama 590 591```python 592from spych.agents import ollama 593 594# Pull the model first: ollama pull llama3.2:latest 595# Say "hey llama" to trigger 596ollama(model="llama3.2:latest") 597``` 598 599### Coding Agent Parameters 600 601| Parameter | `claude_code_cli` | `claude_code_sdk` | `codex_cli` | `gemini_cli` | `opencode_cli` | Description | 602|---|---|---|---|---|---|---| 603| `name` | `Claude` | `Claude` | `Codex` | `Gemini` | `OpenCode` | Custom display name | 604| `wake_words` | `["claude", "clod", "cloud", "clawed"]` | `["claude", "clod", "cloud", "clawed"]` | `["codex"]` | `["gemini", "google"]` | `["opencode", "open code"]` | Words that trigger the agent | 605| `terminate_words` | `["terminate"]` | `["terminate"]` | `["terminate"]` | `["terminate"]` | `["terminate"]` | Words that stop the listener | 606| `model` | β | β | β | β | `None` | Model in `provider/model` format | 607| `listen_duration` | `0` | `0` | `0` | `0` | `0` | Seconds to listen (0 = VAD auto) | 608| `continue_conversation` | `True` | `True` | `True` | `True` | `True` | Resume the most recent session | 609| `setting_sources` | β | `["user", "project", "local"]` | β | β | β | Claude Code settings sources | 610| `show_tool_events` | `True` | `True` | `True` | `True` | `True` | Print live tool start/end events | 611| `use_speaker` | `False` | `False` | `False` | `False` | `False` | Speak responses aloud via TTS | 612| `speaker_voice` | `"af_heart"` | `"af_heart"` | `"af_heart"` | `"af_heart"` | `"af_heart"` | Voice name for TTS | 613| `response_style` | `""` | `""` | `""` | `""` | `""` | Style preset or custom instruction | 614| `allow_intermediate_responses` | `True` | `True` | `True` | `True` | `True` | Enable intermediate response chaining | 615| `spych_kwargs` | β | β | β | β | β | Extra kwargs passed to `Spych` | 616| `spych_wake_kwargs` | β | β | β | β | β | Extra kwargs passed to `SpychWake` | 617 618### Ollama Parameters 619 620| Parameter | Default | Description | 621|---|---|---| 622| `name` | `"Ollama"` | Custom display name | 623| `wake_words` | `["llama", "ollama", "lama"]` | Words that trigger the agent | 624| `terminate_words` | `["terminate"]` | Words that stop the listener | 625| `model` | `"llama3.2:latest"` | Ollama model name | 626| `listen_duration` | `0` | Seconds to listen (0 = VAD auto) | 627| `history_length` | `10` | Past interactions to include in context | 628| `host` | `"http://localhost:11434"` | Ollama instance URL | 629| `use_speaker` | `False` | Speak responses aloud via TTS | 630| `speaker_voice` | `"af_heart"` | Voice name for TTS | 631| `response_style` | `""` | Style preset or custom instruction | 632| `allow_intermediate_responses` | `True` | Enable intermediate response chaining | 633| `spych_kwargs` | `None` | Extra kwargs passed to `Spych` | 634| `spych_wake_kwargs` | `None` | Extra kwargs passed to `SpychWake` | 635 636--- 637 638# Python: Building Your Own Agent 639 640Subclass `BaseResponder`, implement `respond`, and Spych handles the rest: wake word detection, transcription, spinner UI, timing, TTS, error handling. 641 642`respond()` must return an `AgentResponse`. Use `self.format_prompt()` to inject the JSON schema into your prompt and `self.parse_output()` to parse the result: 643 644```python 645from spych.responders import BaseResponder, AgentResponse 646 647class MyResponder(BaseResponder): 648 def respond(self, user_input: str) -> AgentResponse: 649 raw = call_my_llm(self.format_prompt(user_input)) 650 return self.parse_output(raw) 651``` 652 653A complete working example with a custom wake word: 654 655```python 656from spych import Spych, SpychOrchestrator 657from spych.responders import BaseResponder, AgentResponse 658 659class EchoResponder(BaseResponder): 660 def respond(self, user_input: str) -> AgentResponse: 661 return AgentResponse( 662 response=f"'{self.name}' heard: {user_input}", 663 summary=f"Heard: {user_input}", 664 requires_user_feedback=False, 665 ) 666 667SpychOrchestrator( 668 entries=[ 669 { 670 "responder": EchoResponder( 671 spych_object=Spych(whisper_model="base.en"), 672 listen_duration=5, 673 name="TestResponder", 674 ), 675 "wake_words": ["test"], 676 "terminate_words": ["terminate"], 677 } 678 ] 679).start() 680``` 681 682You can also subclass a built-in agent. For example, a translation agent that routes to Ollama: 683 684```python 685from spych import Spych, SpychOrchestrator 686from spych.agents import OllamaResponder 687from spych.responders import AgentResponse 688 689class Spanish(OllamaResponder): 690 def respond(self, user_input: str) -> AgentResponse: 691 user_input = f"Translate the following to Spanish and return only the translated text: '{user_input}'" 692 return super().respond(user_input) 693 694class German(OllamaResponder): 695 def respond(self, user_input: str) -> AgentResponse: 696 user_input = f"Translate the following to German and return only the translated text: '{user_input}'" 697 return super().respond(user_input) 698 699spych_object = Spych(whisper_model="base.en") 700 701SpychOrchestrator( 702 entries=[ 703 { 704 "responder": Spanish(spych_object=spych_object, name="SpanishTranslator", model="llama3.2:latest"), 705 "wake_words": ["spanish"], 706 "terminate_words": ["terminate"], 707 }, 708 { 709 "responder": German(spych_object=spych_object, name="GermanTranslator", model="llama3.2:latest"), 710 "wake_words": ["german"], 711 "terminate_words": ["terminate"], 712 }, 713 ] 714).start() 715``` 716 717Think your agent would be useful to others? Open a PR or file a feature request via a [GitHub issue](https://github.com/connor-makowski/spych/issues). 718 719--- 720 721# Python: Lower-Level API 722 723Need more control? Use `Spych` and `SpychWake` directly. 724 725## Transcription 726 727```python 728from spych import Spych 729 730spych = Spych( 731 whisper_model="base.en", # tiny, small, medium, large β all faster-whisper models work 732 whisper_device="cpu", # use "cuda" for Nvidia GPU 733) 734 735print(spych.listen(duration=5)) 736``` 737 738See: https://connor-makowski.github.io/spych/spych/core.html 739 740## Wake Word Detection 741 742```python 743from spych import SpychWake, Spych 744 745spych = Spych(whisper_model="base.en", whisper_device="cpu") 746 747def on_wake(): 748 print("Wake word detected! Listening...") 749 print(spych.listen(duration=5)) 750 751SpychWake( 752 wake_word_map={"speech": on_wake}, 753 whisper_model="tiny.en", 754 whisper_device="cpu", 755).start() 756``` 757 758See: https://connor-makowski.github.io/spych/spych/wake.html 759 760--- 761 762# API Reference 763 764Full docs including all parameters and methods: https://connor-makowski.github.io/spych/spych.html 765 766--- 767 768# Support 769 770Found a bug or want a new feature? [Open an issue on GitHub](https://github.com/connor-makowski/spych/issues). 771 772--- 773 774# Contributing 775 776Contributions are welcome! 777 7781. Fork the repo and clone it locally. 7792. Make your changes. 7803. Run tests and make sure they pass. 7814. Commit atomically with clear messages. 7825. Submit a pull request. 783 784**Virtual environment setup:** 785```bash 786python3.11 -m venv venv 787source venv/bin/activate 788pip install -r requirements.txt 789./utils/test.sh 790``` 791""" 792 793from .core import Spych 794from .wake import SpychWake 795from .orchestrator import SpychOrchestrator