A voice assistant that fits in one bash file.
Claude as its brain, the shell as its hands, and a pixel-art Babel fish as its face.
Record → transcribe → think → speak runs on any Linux box: PipeWire, Python 3, voxtype, Claude Code, Piper. Only his hands and his face need Omarchy.
SUPER + ALT + J press to speak, press again to answer
« Hey Jarvis » the same, without touching the keyboard
SUPER + ALT + ESCAPE stop whatever is in flight
Say « mets le thème clair » and the theme changes. Ask « pourquoi ma dernière commande a échoué ? » and he reads your shell history and tells you. Say « corrige le bug de scroll dans mon projet web » and he dispatches a background coding session and reports back when it lands.
He answers in the language you spoke. He remembers yesterday. When he cannot do something he writes down why, and later — while idle — he reads those notes back and distils them into lessons he keeps.
Even if you never run it, four things in this repository are designed to be read and taken:
-
A state machine that cannot drift from its documentation. Eight states, nine events, 38 of the 72 pairs legal — written out in prose above the code in
bin/jarvis-fsm.shand frozen into a test fixture, so the table and the paragraph explaining it fail together or not at all. Drive it in your browser. -
Cancelling that actually cancels. The state file records what the machine is doing; a record beside it records which attempt is doing it. Every stage carries the epoch it started on and goes quiet if that moved. Each external stage runs in its own process group, because killing the group is what reaches the subprocesses
claudespawned for its own tools — a bare pid never does, which is why aborting used to do nothing at all. -
A memory that consolidates itself. Failures are written down as they happen. When he is idle, a separate session reads them back and distils them into lessons that become part of his persona. Interruptions go in a different file on purpose: one line in the failure log was enough to schedule a session that rewrites his personality, so pressing a key was manufacturing permanent character changes.
-
A test suite for a voice assistant. More than 250 assertions, entirely offline: a fake brain, a fake voice, a fake microphone that really dies on a signal. No quota, no network, no sound, no microphone taken.
Honestly: partly, unless your machine looks a lot like mine. Jarvis is three layers, and they fail independently, so it is worth knowing which one you are getting.
| Layer | What it gives you | What it needs |
|---|---|---|
| The voice loop | record → transcribe → think → speak, the whole conversation | Linux, PipeWire, voxtype, Claude Code, Piper, Python 3 — portable |
| The hands | changing your theme, brightness, network, windows, launching apps | Omarchy and Hyprland — its CLI is his hands |
| The body | the fish in the corner, the speech bubble, the typed prompt bar | Quickshell via omarchy-shell |
Layer one runs anywhere with a microphone. Layers two and three are written
against a specific desktop and will not pretend otherwise: every call into
them is || true, so on a plain Linux box Jarvis still listens, thinks and
speaks — he just has nothing to touch and no face to make.
The reference machine is an M2 MacBook Pro running Asahi Linux under
macarchy (Omarchy plus a macOS-shaped layer).
bootstrap.sh fetches an arm64 or amd64 Piper build depending on where
you run it.
You also need a Claude account: the brain is claude -p, invoked once per
exchange. Nothing else in the pipeline leaves the machine.
git clone https://github.com/macarchy/jarvis.git ~/Work/jarvis
cd ~/Work/jarvis
./bootstrap.sh # Piper, its voices, the Whisper model, the wake venv
./install.sh # the CLI symlink, the mascot plugin, the user units
omarchy-jarvis doctorbootstrap.sh downloads ~300 MB that the repository deliberately does not
carry (see THIRD_PARTY.md); it is resumable and running it
twice fetches nothing. install.sh is idempotent too — re-running it is the
supported way to update after a git pull.
doctor is the answer to "is it working?": every dependency, green or red,
no guessing. Whatever it says is red, docs/troubleshooting.md
explains.
Install claude and voxtype yourself — they have accounts and configuration
of their own and are not Jarvis's to manage.
This matters more than the feature list, so it is here and not in an appendix. The long version is docs/privacy.md.
- Your speech is transcribed locally. Whisper runs on your CPU. Audio never leaves the machine and the recording is overwritten by the next one.
- Your transcript goes to Anthropic, because the brain is Claude by default — unless you point him at a local model, see "The local brain" below, in which case nothing leaves at all. So does anything Claude reads while answering you — and that can include a screenshot or your clipboard, but only when you ask for it in that exchange, never on his own initiative, and every use is logged.
- The wake daemon holds the microphone open whenever it runs. It is
optional (
--skip-wake), it matches audio locally against a small model, and it records nothing until it fires. - Everything he does is written down in
memory/trace/— every command, every result. That file is how he answers "why did you do that?". It is also a plain-text log of your day on your own disk.
You can turn off his autonomy entirely by editing SOUL.md: rondes: non
stops the hourly inspection, reves: non stops the memory consolidation,
silence: 23-7 gives him hours where he does nothing unbidden.
Some requests never needed a model. "What time is it" is answered by
date, in two hundred milliseconds, exactly.
memory/REFLEXES.md is a four-column tab-separated table — pattern,
command, slot, spoken sentence — read before the brain. The pattern is a
regular expression anchored on the whole transcript, exactly like
memory/NOISE.md: that anchoring is what keeps "why is the sound so loud?"
away from the volume command.
It exists mostly for one reason. Offline, asked the time, the local model
answered "it is 2:37 pm" at 11 pm, with total confidence, in Jarvis's exact
spoken format. A plausible wrong value is worse than a refusal. What date
or /sys can state exactly, the brain no longer gets to guess.
omarchy-jarvis reflexe? "quelle heure est-il" # which row matches
omarchy-jarvis reflexe? --run "quelle heure est-il" # and what it would say
The file is yours: add, remove, fix. omarchy-jarvis doctor rejects a
malformed table, and a destructive command is refused at load — the table
does not go through .claude/settings.json, this is its only gate.
By default Jarvis thinks with Claude, which needs the network. He can also talk to any OpenAI-compatible server — llama-server, LM Studio, Ollama, LocalAI, vLLM — and so answer you on a plane.
Three lines in SOUL.md, under "Réglages":
- cerveau: auto # auto | nuage | local
- cerveau-url: http://127.0.0.1:8099
- cerveau-modele: Qwen3.5-4B
cerveau-modele carries two roles: it is the id sent to the server —
llama-server ignores it, LM Studio and Ollama need it — and it is also what
Jarvis says out loud when asked which brain he is thinking with. Put your
model's real name in it.
auto follows the cloud's health: Claude while it answers, the local model
once it stops, and a retry towards the cloud every ten minutes
(JARVIS_BRAIN_RETRY). The Control Center carries the same switch under
"Âme", and says where your words are going right now.
What he loses offline. The local brain has no tools at all: no Bash, no
screen reading, no web search. So he can neither act on the machine nor read
the time, the battery or the weather — and memory/OFFLINE_PROMPT.md
explicitly forbids him to invent them, because without that instruction a 4B
model answers "it is 2:37 pm" at 11 pm, with total confidence. What he keeps:
talking, explaining, translating, telling a story, remembering the thread.
Running the server. On this M2 MacBook under Asahi, llama.cpp built with the Vulkan backend (Honeykrisp driver) is about 1.7x faster than CPU at prefill and 1.3x at generation. With a 4B model at Q4_K_M the first spoken sentence lands in one to three seconds.
systemctl --user enable --now jarvis-brain.service
install.sh installs the unit but never enables it for you. --reasoning off
is mandatory there: a thinking model streams its reasoning into
reasoning_content, burns its token budget and never says a word.
One utterance, five stages, each with a handle on disk so it can be stopped:
press ─▶ pw-record ─▶ voxtype (whisper) ─▶ claude -p ─▶ piper ─▶ pw-play
record stt brain voice play
Underneath is an explicit state machine — eight states, nine events, and only
38 of the 72 pairs are legal. It lives in
bin/jarvis-fsm.sh, written out in prose above the code,
and it is frozen into tests/fixtures/transitions.txt so it cannot drift from
its own documentation without turning a test red.
The machine, drivable — press the events, watch the fish change, and watch the illegal pairs be refused with the reason. Thirty seconds there beats reading this section.
The part worth stealing if you build something similar: the state file records
what the machine is doing, and a record beside it records which attempt is
doing it. Every stage remembers the epoch it entered on and goes quiet if it
has moved. That, plus running each external stage in its own process group, is
what makes cancelling real — killing the group is what reaches the
subprocesses claude spawned for its own tools, which a bare pid never does.
Touch Bar — with macarchy-touchbar
installed, the plugin is also a touchbar-module: the fish sits on the bar
itself, where the mic key used to be — tap to press, long-press to open his
Control Center page. The bar is his while he works: it fills with a meter
while he listens (fed by the wake daemon), types the transcript out as he
thinks, and types his reply as it is spoken; a ✕ interrupts. bin/jarvis
and jarvis-wake.py publish over
macarchy-touchbar macarchy.jarvis state|heard|reply|level|emote|abort — no
different from the FSM signalling any other listener. install.sh copies
plugin/touchbar.py with the rest of the plugin, and
the daemon picks it up on macarchy-touchbar reload.
SOUL.md is who he is — tone, humour, whether he uses tu or
monsieur, his language, which of the three French voices speaks, his
quiet hours, and the five pieces his fish is assembled from. Edit it freely; it takes effect on the next conversation.
CLAUDE.md is what he can do — the commands that are his hands,
written as a prompt. Adding a capability usually means one paragraph there and
one line in .claude/settings.json.
« Hey Jarvis » is trained on native English. Say it the English way
(héï djâ-vis); a natural French accent slides under the model. A fresh
clone has no accent verifier, so the threshold defaults to 0.30 — train your
own with wake/train-verifier and it rises to 0.8.
./tests/run # the whole pipeline, fully offlineThe suite stubs claude, piper, voxtype, pw-record, pw-play and
omarchy-shell, and redirects state and memory into a sandbox. It takes no
microphone, makes no sound, spends no tokens and never touches your real
memory. If it does any of those, that is a bug.
CONTRIBUTING.md has the house style and the rules that keep the machine honest.
MIT — see LICENSE. The downloaded pieces have licenses of their own; THIRD_PARTY.md lists them.