Skip to content

Repository files navigation

Dictate

Dictate is a local-first macOS dictation product. It lets you speak into any app, transcribes on-device with configurable MLX-backed models, and pastes the result back where you were writing.

The goal is a fast, private, customizable dictation workflow for people who write a lot: notes, messages, docs, prompts, email, and code-adjacent text.

What it includes

  • A menu-bar dictation workflow powered by Hammerspoon.
  • A native macOS companion app for history and settings.
  • A small Python CLI transcription engine.
  • Configurable local models, with Qwen ASR as the default.
  • Local transcript history with latency and model metadata.

Requirements

  • macOS on Apple Silicon.
  • Python 3.12 via uv.
  • ffmpeg.
  • Hammerspoon for the current global tray/hotkey workflow.

Install system tools:

brew install ffmpeg
brew install --cask hammerspoon

Install uv if needed:

curl -LsSf https://astral.sh/uv/install.sh | sh

New laptop setup

Clone the repo:

git clone git@Github.com:ash-ishh/dictate.git
cd dictate

Create the Dictate config:

uv run --python 3.12 dictate init-config

Confirm models are available:

uv run --python 3.12 dictate models

Build the native companion app:

cd macos/DictateApp
./package-app.sh
open .build/Dictate.app

Set up Hammerspoon:

mkdir -p ~/.hammerspoon
nano ~/.hammerspoon/init.lua

Add this, replacing the path with your checkout:

hs.allowAppleScript(true)
if hs.ipc then hs.ipc.cliInstall() end

local ok, err = pcall(function()
  dofile("/path/to/dictate/hammerspoon.lua")
end)
if not ok then
  hs.alert.show("Dictate failed to load: " .. tostring(err))
  print("Dictate failed to load: " .. tostring(err))
end

Open or reload Hammerspoon:

open -a Hammerspoon

If Hammerspoon is already running:

hs -c 'hs.reload()'

Grant permissions:

System Settings → Privacy & Security → Accessibility → Hammerspoon
System Settings → Privacy & Security → Microphone → Hammerspoon

The native app may also request permissions if you use native recording later:

System Settings → Privacy & Security → Accessibility → Dictate
System Settings → Privacy & Security → Microphone → Dictate

Usage

Click into any text input, then press:

Cmd + S

Speak, then press again:

Cmd + S

Dictate records, transcribes locally, and pastes into the app that was focused when recording started.

Tray menu options include:

  • Start or stop recording.
  • Switch model.
  • Open app.
  • Play last recording.
  • Copy recent transcripts.

The native app shows transcript history and settings.

Manual transcription test

uv run --python 3.12 dictate transcribe /path/to/audio.wav --model mlx_audio_qwen3_asr

Write outputs to files:

uv run --python 3.12 dictate transcribe /path/to/audio.wav \
  --model mlx_audio_qwen3_asr \
  --output-json /tmp/dictate.json \
  --output-text /tmp/dictate.txt

Model configuration

Models are configured in:

~/.dictate/config.json

Qwen ASR is the default model. You can switch models from the tray menu or by editing the config.

Example:

{
  "default_model": "mlx_audio_qwen3_asr",
  "python": "3.12",
  "models": {
    "mlx_audio_qwen3_asr": {
      "backend": "mlx_audio_stt",
      "model_name": "mlx-community/Qwen3-ASR-0.6B-8bit",
      "language": "English",
      "extra_args": []
    }
  }
}

Some legacy Whisper presets use an insanely-fast-whisper checkout. If needed, set it up next to this repo or set DICTATE_IFW_REPO before running dictate init-config.

git clone https://github.com/Vaibhavs10/insanely-fast-whisper.git
cd insanely-fast-whisper
git fetch origin pull/273/head:pr-273
git checkout pr-273

Packaging the app

Build a local app bundle:

cd macos/DictateApp
./package-app.sh

The app is written to:

macos/DictateApp/.build/Dictate.app

For GitHub releases, zip the built app instead of committing .build:

cd macos/DictateApp
ditto -c -k --keepParent .build/Dictate.app Dictate.app.zip

Upload Dictate.app.zip to a GitHub Release.

Local data

Dictate stores local configuration and history under:

~/.dictate/config.json
~/.dictate/history.jsonl
~/.dictate/native-settings.json
~/.dictate/native-history.json
~/.dictate/recordings/

Architecture

Menu bar / Hammerspoon
  ├─ records microphone audio with ffmpeg
  ├─ remembers the focused app
  ├─ writes local history
  └─ calls the Dictate CLI
        ↓
dictate transcribe <audio> --model <model-key>
        ↓
backend adapter
  ├─ mlx-audio STT models
  ├─ Qwen ASR default
  └─ legacy insanely-fast-whisper MLX presets
        ↓
plain transcript
        ↓
Paste into the original app
        ↓
Native Dictate app reads history and settings

To add a backend, implement it in:

src/dictate/cli.py

Then add a model entry in ~/.dictate/config.json with a distinct backend value.

Roadmap

  • Native global hotkey support, eventually replacing the Hammerspoon dependency.
  • Model manager: show available models, downloaded status, download/remove actions, and warm-up controls.
  • LLM pass to refine dictated input before paste.
  • Personalized dictionary for names, product terms, acronyms, and writing style.
  • Per-app formatting rules.
  • Better onboarding and permissions flow.
  • Signed and notarized macOS releases.

About

Offline Diction app for Mac.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages