Back to Learnings

Local voice AI / 7 guides

The Local voice AI build

Connect dictation, a local language model, and speech output on your Mac, from installation to a working voice workflow.

What you’ll build

Your voice, processed on your Mac.

Use NautPin for dictation, LM Studio or Ollama for text processing, and Kokoro for speech output. Install the parts, connect their local endpoints, and test the result.

Stack
NautPin · Kokoro · LM Studio or Ollama
Hardware
Apple Silicon Mac · macOS 14+ · about 10 GB free
You need
Homebrew, a container runtime, and Xcode for the source build
Format
Written lessons, commands, and checkpoints

7 lessons, in order

  1. 01

    Prepare your Mac

    Understand the components and check what you need before installing.

  2. 02

    Set up the container runtime

    Choose Colima or Docker Desktop and confirm Docker is ready.

  3. 03

    Run local speech output

    Start Kokoro and choose a voice for text-to-speech.

  4. 04

    Run a local language model

    Expose a local chat endpoint with LM Studio or Ollama.

  5. 05

    Install NautPin

    Build the dictation app and grant the permissions it needs.

  6. 06

    Connect and test the voice stack

    Wire transcription, local text processing, and speech output together.

  7. 07

    Keep the stack working

    Diagnose connection problems and recover after a restart.

Optional: let your coding agent help with setup

Use this prompt with an agent that can work on your Mac. Review its proposed actions before it installs or changes anything.

Lazy install · ~1 min

Hand it to your agent

If you use Claude Code, Cursor, Codex, or any other agent CLI — paste the prompt below. It walks the same six phases as the manual guide, asks you to confirm before each one, and tells you what to grant permission to. Follow the lessons above if you prefer the manual setup.

Paste into your agent

You are installing the 48nauts Local Voice Stack on macOS Apple Silicon.

# Goal
A complete local voice stack on this machine:
  - Kokoro TTS (Docker, port 8880)
  - A local LLM via LM Studio or Ollama (OpenAI-compatible API on localhost)
  - NautPin dictation app in /Applications
  - All services talking over 127.0.0.1; nothing on the network

# Prerequisites — confirm with me before starting
  - macOS 14+ on Apple Silicon (M1 or newer)
  - Homebrew installed (https://brew.sh)
  - ~10 GB free disk space
  - I will grant microphone + accessibility permissions when prompted

# Execution rules
  - Execute ONE phase, then summarize what changed and ASK before the next phase
  - If a command fails, diagnose before retrying; do not loop on errors
  - Never bypass macOS permission prompts — pause and let me click through
  - Don't commit anything to my git unless I explicitly ask

# Phase 1 — Docker via Colima (free, lightweight)
  brew install colima docker docker-compose just
  colima start --cpu 2 --memory 4
  docker info   # verify daemon responsive

# Phase 2 — Kokoro TTS container
  docker run -d --name kokoro --restart unless-stopped \
    -p 127.0.0.1:8880:8880 \
    ghcr.io/remsky/kokoro-fastapi-cpu:latest
  curl -s http://127.0.0.1:8880/v1/audio/voices | jq '.voices | length'
  # should return 60+

# Phase 3 — Local LLM
  Ask me whether to install LM Studio (GUI) or Ollama (CLI).
  - LM Studio:
      Open https://lmstudio.ai, drag to /Applications
      Walk me through downloading a compatible instruction-tuned model
      In Developer tab, load the model + start API server on :1234
      Verify: curl http://127.0.0.1:1234/v1/models
  - Ollama:
      brew install ollama
      brew services start ollama
      ollama pull gemma3:4b
      Verify: curl http://127.0.0.1:11434/v1/models

# Phase 4 — NautPin
  git clone https://github.com/48Nauts-Operator/NautPin.git ~/Code/NautPin
  cd ~/Code/NautPin
  just build
  cp -R DerivedData/Build/Products/Debug/NautPin.app /Applications/
  open /Applications/NautPin.app
  Tell me to grant Microphone + Accessibility permissions in
  System Settings → Privacy & Security, then continue.

# Phase 5 — Configure NautPin (walk me through; cannot script)
  Open NautPin → Settings:
  - Speech: Parakeet TDT 0.6B v3 (European multilingual)
  - AI Enhancement → Providers → add Custom (OpenAI-compatible):
      Base URL → http://127.0.0.1:1234/v1   (LM Studio)
              OR http://127.0.0.1:11434/v1   (Ollama)
      API key → any string ("local" works)
      Default model → the model ID from phase 3
  - AI Enhancement → Assignments: set Transcription + Note Enhancement to this provider
  - Voice Output:
      Kokoro server URL → http://127.0.0.1:8880
      Default voice → bf_emma
      Read Selected Text hotkey → ⌘⇧S (or my preference)

# Phase 6 — Smoke test
  Tell me to focus a text field, press the dictation hotkey, speak a sentence,
  press again. Confirm cleaned text appears at my cursor.
  Then highlight text and press the Read hotkey. Confirm Emma reads it aloud.

# Done
  Print a one-line summary of what was installed and where, plus the
  "uninstall to roll back" commands. Link me back to
  https://48nauts.com/learnings/articles/local-voice-stack for context.

The prompt is intentionally cautious — it stops between phases and asks. If you want fully autonomous, add --dangerously-skip-permissions (Claude Code) and tell the agent to skip the confirmation prompts. We don't recommend it for a first run.

Optional extension: spoken replies from a coding agent

This extension needs the separate speak.sh source referenced in the original post. It is not included in this series; the seven core lessons work without it.

If you use Claude Code in the terminal and want voice replies, drop in our Stop hook.

Create ~/.claude/hooks/speak.sh with the contents of speak.sh (the separate hook source is not included here).

Make it executable:

bash

chmod +x ~/.claude/hooks/speak.sh

Register the hook in ~/.claude/settings.json:

~/.claude/settings.json

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "/Users/YOU/.claude/hooks/speak.sh" }
        ]
      }
    ]
  }
}

After obtaining the hook source and configuring it, restart your coding-agent session and verify a short reply is spoken.

Mute temporarily without uninstalling:

bash

touch ~/.claude/voice-off    # mute
rm ~/.claude/voice-off       # unmute

Or use the slash commands /voice and /voice-off that ship with the repo.