Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/npateriya/LocalVoiceAI/llms.txt

Use this file to discover all available pages before exploring further.

Building LocalVoiceAI from source gives you full control over the binary — you can modify key bindings, swap Whisper models, patch the CGo layer, or pin to a specific commit. The build is a standard go build invocation with CGo enabled and a set of Homebrew-provided libraries linked in. The entire process takes a few minutes on a fresh machine.

Prerequisites

Before building, make sure you have the following on your Apple Silicon Mac:

Apple Silicon Mac (M1+)

Required for the Metal GPU backend used by ggml-metal. Intel Macs are not supported.

macOS 14 Sonoma or later

Required for the CGEventTap API behaviour and LaunchAgent compatibility used by LocalVoiceAI.

Go 1.25.5+

Install with brew install go. The module requires go 1.25.5 as declared in go.mod.

Xcode Command Line Tools

Provides clang and the macOS SDK headers needed by CGo. Run xcode-select --install.
Homebrew is also required — it installs portaudio, whisper-cpp, and pkg-config, and all Homebrew-provided libraries are expected at /opt/homebrew/ (the default ARM64 prefix).

Build and Install

1

Clone the repository

git clone https://github.com/npateriya/LocalVoiceAI.git
cd LocalVoiceAI
2

Install Homebrew dependencies

make setup
This runs:
brew install pkg-config portaudio whisper-cpp
After installing, make setup also creates three symlinks in /opt/homebrew/lib/ that the linker expects:
ln -sf /opt/homebrew/lib/libggml.dylib /opt/homebrew/lib/libggml-cpu.dylib
ln -sf /opt/homebrew/lib/libggml.dylib /opt/homebrew/lib/libggml-metal.dylib
ln -sf /opt/homebrew/lib/libggml.dylib /opt/homebrew/lib/libggml-blas.dylib
Homebrew’s whisper-cpp formula ships ggml as a single consolidated libggml.dylib. The CGO_LDFLAGS in the Makefile link against named per-backend libraries (-lggml-cpu, -lggml-metal, -lggml-blas), so these symlinks are necessary for the linker to resolve all -l flags without errors.
3

Build and install

make install
This single target:
  1. Compiles the binary with go build -o localvoice ./cmd/localvoice/
  2. Ad-hoc codesigns the local artifact: codesign -s - --force localvoice
  3. Copies it to ~/.local/bin/localvoice and re-signs the installed copy
  4. Generates ~/Library/LaunchAgents/com.localvoiceai.localvoice.plist from the template (substituting the install path)
  5. Loads the LaunchAgent with launchctl load
After make install completes, follow the on-screen instructions to grant Accessibility and Input Monitoring permissions in System Settings → Privacy & Security. Both must be enabled before starting the service.
Do not skip the permissions step. Without Accessibility, CGEventPost cannot simulate Cmd+V. Without Input Monitoring, CGEventTapCreate fails immediately and the process exits with [ERROR] Event tap failed — missing permissions.
4

Start the service

make start
This runs launchctl start com.localvoiceai.localvoice. The service starts as a LaunchAgent — independent of any terminal session. The plist sets RunAtLoad=false, so the service does not start automatically after launchctl load or after a reboot; you must start it explicitly with make start each time. Logs are written to /tmp/localvoice.log:
tail -f /tmp/localvoice.log

CGo Build Flags Explained

The Makefile sets these environment variables for every go build invocation:
CGO_CFLAGS  = -I/opt/homebrew/include
CGO_LDFLAGS = -L/opt/homebrew/lib \
              -lwhisper -lggml -lggml-base -lggml-cpu -lggml-metal -lggml-blas \
              -framework Accelerate -framework Metal -framework Foundation -framework CoreGraphics
Here is what each flag does and why it is required:
FlagReason
-I/opt/homebrew/includeAdds Homebrew’s include path so CGo’s clang invocation can find whisper.h, portaudio.h, and other C headers
-L/opt/homebrew/libTells the linker where to find Homebrew’s .dylib files at link time
-lwhisperLinks libwhisper.dylib — the whisper.cpp C API (used at runtime by whisper-cli)
-lggml -lggml-baseCore ggml tensor library and its base layer
-lggml-cpuCPU backend for ggml
-lggml-metalMetal GPU backend — required for Apple Silicon GPU acceleration
-lggml-blasAccelerate/BLAS-backed matrix operations
-framework AccelerateApple’s high-performance BLAS/LAPACK framework, used by the ggml-blas backend
-framework MetalRequired by the ggml-metal backend to compile and dispatch Metal shaders
-framework FoundationObjective-C runtime used internally by Metal and CoreFoundation
-framework CoreGraphicsRequired by CGEventPost and CGEventTap in the CGo key-monitoring and paste code
Release builds produced by the CI workflow (.github/workflows/release.yml) add an extra linker flag to embed the version string:
go build -ldflags "-X main.version=$VERSION" -o localvoice ./cmd/localvoice/
The regular make build target does not include -ldflags — it compiles without a version string. This is intentional; the embedded version is only meaningful for tagged releases distributed via GitHub Releases.
The CGo source in main.go also directly links three additional frameworks via #cgo LDFLAGS:
#cgo LDFLAGS: -framework ApplicationServices -framework CoreFoundation -framework Carbon
  • ApplicationServices — umbrella framework that includes CGEventTap, CGEventPost, and the Core Graphics event API
  • CoreFoundation — provides CFRunLoop, CFMachPort, and CFRunLoopSource used in the event thread
  • Carbon — provides the kVK_* virtual key constants (e.g., kVK_Command, kVK_ANSI_V) used when synthesizing keystrokes

Codesigning

Every build applies an ad-hoc signature to the binary:
codesign -s - --force localvoice
The -s - flag means “sign with an ad-hoc identity” (no Developer ID certificate required). --force replaces any existing signature. macOS requires a valid code signature on any binary that requests Accessibility or Input Monitoring entitlements. An ad-hoc signature satisfies this requirement without an Apple Developer account. The signature is applied twice per make install: once to the local ./localvoice build artifact, and once to the installed copy at ~/.local/bin/localvoice.
After every make update (which rebuilds and reinstalls the binary), the binary’s cryptographic hash changes. macOS ties both Accessibility and Input Monitoring permission grants to the binary hash, so both permissions are revoked automatically. You must re-add ~/.local/bin/localvoice to both lists in System Settings → Privacy & Security after every rebuild before the service will function.

Go Module

The module is declared as github.com/npateriya/LocalVoiceAI in go.mod and requires Go 1.25.5. LocalVoiceAI has exactly one external Go dependency:
require github.com/gordonklaus/portaudio v0.0.0-20260203164431-765aa7dfa631
Everything else — CGEventTap, CGEventPost, CFRunLoop, pbcopy subprocess, WAV encoding, model download — is implemented using the Go standard library and CGo bindings against macOS system frameworks. There is no Objective-C and no Swift.

Makefile Reference

TargetDescription
make setupInstall Homebrew dependencies (portaudio, whisper-cpp, pkg-config) and create libggml-*.dylib symlinks
make buildCompile the binary only; output is ./localvoice in the repo root
make installBuild + codesign + copy to ~/.local/bin/ + install and load the LaunchAgent
make updateRebuild + codesign + reinstall binary + restart the service (re-grant permissions after)
make startStart the LaunchAgent (launchctl start com.localvoiceai.localvoice)
make stopStop the LaunchAgent (launchctl stop com.localvoiceai.localvoice)
make reloadReload plist from template and restart — use when changing the plist without a binary change
make statusShow LaunchAgent status via launchctl list | grep com.localvoiceai.localvoice
make uninstallUnload and remove the LaunchAgent plist and the installed binary
make cleanRemove the local ./localvoice build artifact
You can override the push-to-talk key at build time without recompiling. Set WHISPER_KEYCODE to any decimal macOS virtual keycode before starting the service:
WHISPER_KEYCODE=101 make start   # use F9 instead of the default F10
Common codes: F8 = 100, F9 = 101, F10 = 109 (default), F11 = 103, F12 = 111.

Build docs developers (and LLMs) love