A local-first macOS menu-bar writing assistant for system-wide spelling, grammar, and rewrite workflows.
WriteAssist lives in your menu bar, monitors typing across apps with Accessibility permission, runs passive spelling and grammar checks locally, and can optionally generate AI rewrites when you explicitly ask for them.
- Local-first passive checks — background spelling and grammar detection stays on-device.
- System-wide monitoring — works in Mail, Notes, Safari, VS Code, Slack, and other text apps.
- Inline suggestion HUD — a floating panel appears near the caret when local issues are detected.
- Explicit AI rewrites — cloud or Ollama rewrites run only after you click a rewrite control.
- Loopback-only Ollama — local model traffic is restricted to
localhost/ loopback. - Menu bar sidebar — inspect issues, preview text, view stats, and request suggestions from the popover.
| Requirement | Minimum |
|---|---|
| macOS | 15 Sequoia |
| Swift toolchain | 6.0 (Xcode 16+) |
| Accessibility permission | Required — see Granting Accessibility Access |
No external package dependencies. The project uses Swift Package Manager with a core library target plus a thin app target.
git clone <repo-url> && cd WriteAssist
swift build
swift run- Open the
WriteAssistdirectory in Xcode. - Let Xcode resolve
Package.swift. - Select the
WriteAssistscheme and run.
Because the app uses .accessory activation policy, it does not appear in the Dock. Look for the pencil icon in the menu bar after launch.
WriteAssist needs Accessibility access to:
- monitor keystrokes globally
- inspect focused text fields and selections
- apply corrections directly in the target app
- Launch the app from
swift runor Xcode. - Click the menu bar icon.
- Use the Enable Access button in the popover.
- In System Settings → Privacy & Security → Accessibility, enable WriteAssist.
- Return to the app. It detects permission changes automatically.
If permission is revoked later, monitoring stops automatically.
When running with
swift run, the entry in System Settings may appear as the Swift toolchain binary instead ofWriteAssist.
- GlobalInputMonitor captures keystrokes into a rolling buffer for passive local checks.
- DocumentViewModel debounces changes and runs local spell / grammar detection.
- SpellCheckService wraps
NSSpellCheckerfor local spell checking. - ErrorHUDPanel surfaces local issues inline near the caret.
- SelectionSuggestionPanel appears when you select enough text, but it does not call AI automatically. A rewrite request is sent only after you click a rewrite style.
- DocumentViewModel applies accepted changes with Accessibility APIs first and falls back to clipboard + synthetic paste only when direct replacement fails.
- Passive spelling and grammar checks stay on-device.
- Selected text is sent to the configured AI provider only when you explicitly request an AI rewrite or suggestion.
- API keys for Anthropic and OpenAI are stored in macOS Keychain.
- Cloud AI traffic uses TLS certificate pinning for supported providers.
- Ollama traffic is allowed only to loopback addresses such as
localhostand127.0.0.1. - Clipboard fallback may temporarily place replacement text on the macOS pasteboard if AX replacement fails.
- Personal dictionary entries, ignore rules, provider/model preferences, and writing stats are stored locally in
UserDefaults. - The app suppresses capture and AX inspection for secure input contexts such as password fields.
WriteAssist/
├── Sources/
│ ├── App/ # app entry point
│ ├── AXHelper.swift # accessibility helpers + secure-context gating
│ ├── DocumentViewModel.swift # local checks, correction flow, clipboard fallback
│ ├── GlobalInputMonitor.swift # system-wide keystroke capture
│ ├── SelectionSuggestionPanel.swift
│ ├── SpellCheckService.swift
│ └── WritingRules/
├── Tests/WriteAssistTests/
├── Package.swift
└── LICENSE
swiftlintSee LICENSE for details.