A fast, lightweight, and completely local autocorrect daemon for macOS. It monitors your keystrokes globally, detects misspelled words in real-time, and replaces them instantly with their correct spelling.
- Context-aware replacements: Reads the focused field and caret through the macOS Accessibility API, then verifies the exact source text before changing it.
- No trigger/deletion race: AX replacements leave the original trigger untouched; the fallback suppresses and replays it exactly once.
- Newline-aware capitalization: Distinguishes hard
LF/CRLFline breaks from visual line wrapping. - Safe fallback: Uses synchronous keystrokes when a field does not expose Accessibility text. If context is stale or unsafe, the correction is skipped instead of guessing.
- Optimized event path: Skips unnecessary Accessibility work, reads bounded text around the caret when supported, and caches verified fallback capability per field.
- Selection and password safety: Never edits secure text fields or an active text selection.
- Modifier and shortcut protection: Queries macOS modifier flags on each physical keypress and clears pending context whenever
Command,Option, orControlis active. - Asynchronous Log Rotation: Moves file writes off the keyboard callback and rotates logs at 5MB with three backups.
- Bypass App Nap & Throttling: Asserts a latency-critical activity reservation via Cocoa
NSProcessInfooptions (NSActivityUserInitiated | NSActivityLatencyCritical) to prevent the macOS kernel from pausing or throttling the daemon's event loop. - Singleton Process Lock: Uses Unix file locking (
fcntlflock on~/.autocorrect.lock) to guarantee that only one instance of the daemon executes at any time. This prevents event-loop conflict cascades and key-spamming if the app is launched multiple times (e.g., by both launchd and macOS Login Items). - macOS TCC Compatibility: Can be packaged as a background
.appbundle helper to satisfy macOS Privacy & Security (Accessibility) requirements permanently.
git clone https://github.com/AndrewMason7/autocorrect-mac.git
cd autocorrect-macInstall the keyboard, Cocoa, and Accessibility bindings:
pip3 install -r requirements.txtAn application bundle gives macOS a stable identity for Accessibility permission and background launching.
Run the following commands to create the bundle structure in your Applications folder:
# Create the directory structure
mkdir -p /Applications/Autocorrect.app/Contents/MacOS
# Create the wrapper script
cat << 'EOF' > /Applications/Autocorrect.app/Contents/MacOS/Autocorrect
#!/bin/bash
exec /usr/bin/env python3 "$HOME/autocorrect-mac/autocorrect.py"
EOF
# Make it executable
chmod +x /Applications/Autocorrect.app/Contents/MacOS/Autocorrect
# Create the Info.plist configuration
cat << 'EOF' > /Applications/Autocorrect.app/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>Autocorrect</string>
<key>CFBundleIdentifier</key>
<string>com.local.autocorrect.app</string>
<key>CFBundleName</key>
<string>Autocorrect</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSUIElement</key>
<true/>
</dict>
</plist>
EOF- Open System Settings → Privacy & Security → Accessibility.
- Click the
+(plus) button. - Select
/Applications/Autocorrect.appto add it to the list. - Toggle the switch to ON.
You can easily customize, add, or remove autocorrect mappings directly at the top of the autocorrect.py script:
- Contractions & Grammar (
grammardictionary): Handles words like contractions (e.g.dont->don't). The engine preserves your typing case dynamically (e.g., typingDontcorrects toDon't, andDONTcorrects toDON'T). - Proper Nouns & Shortcuts (
shortcutsdictionary): Handles word expansions and capitalizations (e.g.,apple watch->Apple Watchandgithub->GitHub). - Multi-Word Phrases: You can define multi-word autocorrect expansions (e.g.,
'united states': 'United States'). The engine automatically scans up to the last 3 typed words to perform multi-word replacements.
Important
- Store lookup keys in both dictionaries in lowercase because typed words are normalized before lookup.
- After modifying rules in
autocorrect.py, restart the daemon to apply changes:launchctl kickstart -k gui/$(id -u)/com.local.autocorrect
For each possible correction, the daemon first asks the focused text field for its text and insertion-point range. It applies the change only when the exact typed source is immediately before a collapsed caret. Secure fields, active selections, and changed/moved carets are left untouched.
Some apps and custom editors do not expose editable text through Accessibility. In those fields, the daemon falls back to its local keystroke history. The fallback deletes only the source text; the original trigger is suppressed only after the replacement has been posted. Navigation, app switching shortcuts, and stale context clear phrase history.
Safari and other apps may expose text but ignore direct Accessibility writes. After a verified no-op, the daemon temporarily remembers that field and moves straight to the validated keystroke fallback on subsequent corrections.
The ordinary character path does not query Accessibility. Trigger processing uses a bounded context window when the focused field supports range reads and falls back to the full value only when needed. Internal history is bounded, AX capability caches are size-limited and expiring, and logging uses a background queue.
On the development Mac, the launch agent measured 0.0% CPU while idle and
about 25 MB resident memory. Actual usage varies by macOS version, Python
version, browser, and typing activity.
The daemon is configured to start automatically at login and keep itself alive via a launch agent plist.
To register the launch agent:
# Copy the plist file into your LaunchAgents directory
mkdir -p ~/Library/LaunchAgents
cp com.local.autocorrect.plist ~/Library/LaunchAgents/
# Load and start the daemon
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.local.autocorrect.plistRestart the currently registered launch agent without disabling it:
launchctl kickstart -k gui/$(id -u)/com.local.autocorrectConfirm that it is running:
launchctl print gui/$(id -u)/com.local.autocorrectlaunchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.local.autocorrect.plistRun the launchctl bootstrap command from the installation section to enable
it again.
Monitor corrections and activity in real-time:
tail -f ~/autocorrect.logRun the context, newline, range, replacement, and performance regressions with:
python3 -m unittest discover -s tests -v