Native macOS menu bar application for reading text and URLs aloud using Apple's native text-to-speech.
- Native macOS text-to-speech using AVSpeechSynthesizer
- Menu bar interface with SwiftUI
- URL content extraction with HTML parsing
- Customizable voice and playback speed
- Real-time playback progress with word highlighting
- Playback controls: play/stop (input window), pause/resume/stop (menu bar)
- Structured logging using OSLog
- macOS 14.0+
- Xcode 15.0+ (for development)
- Swift 5.9+
- Accessibility permissions (optional, only required for global keyboard shortcuts)
Note: This app is not code-signed (no Apple Developer ID). On first launch, macOS Gatekeeper will show a security warning. To bypass: right-click the app → Open. You only need to do this once.
- Download the latest release from the Releases page
- Drag
Speakeasy.appto your Applications folder - First launch: Right-click → Open (to bypass Gatekeeper)
- Launch normally from Applications or Spotlight
# Clone the repository
git clone https://github.com/minac/speakeasy-mac.git
cd speakeasy-mac
# Build and create app bundle
./create-app-bundle.sh release
# Install to Applications
cp -r build/release/Speakeasy.app /Applications/- Click the speaker icon in the menu bar
- Select "Read Text..."
- Enter text or paste a URL
- Click "Play" to start playback (button changes to "Stop")
- Input Window: Play/Stop toggle button
- Menu Bar: Pause, Resume, and Stop buttons appear during playback with progress indicator
- Voice: Choose from available macOS system voices
- Speed: Adjust playback speed (0.5x - 2.0x)
For more natural-sounding free voices for macOS:
- Open System Settings > Accessibility > Spoken Content
- Click System Voice > Manage Voices...
- Download Enhanced or Premium versions
# Debug build (for development)
./create-app-bundle.sh debug
open build/debug/Speakeasy-build.appswift test --package-path SpeakeasyOr in Xcode: Cmd+U
Speakeasy/
├── Speakeasy/
│ ├── SpeakeasyApp.swift # App entry point
│ ├── Core/
│ │ ├── AppState.swift # Central state management
│ │ ├── SpeechEngine.swift # TTS engine wrapper
│ │ ├── TextExtractor.swift # URL/HTML processing
│ │ └── ShortcutManager.swift # Global keyboard shortcuts
│ ├── Models/
│ │ ├── SpeechSettings.swift # Settings model
│ │ ├── Voice.swift # Voice wrapper
│ │ └── PlaybackState.swift # Playback states
│ ├── Services/
│ │ ├── SettingsService.swift # UserDefaults persistence
│ │ └── VoiceDiscoveryService.swift # System voice enumeration
│ ├── Utilities/
│ │ ├── Logger.swift # OSLog structured logging
│ │ ├── PermissionsManager.swift # Accessibility permissions
│ │ └── Extensions.swift # String extensions
│ ├── Views/
│ │ ├── MenuBarView.swift # Menu bar interface
│ │ ├── InputWindow.swift # Text input window
│ │ ├── SettingsWindow.swift # Settings interface
│ │ └── Components/
│ │ ├── VoicePicker.swift
│ │ ├── SpeedSlider.swift
│ │ └── HighlightedTextView.swift
│ ├── ViewModels/
│ │ ├── InputViewModel.swift
│ │ └── SettingsViewModel.swift
│ └── Resources/
│ └── Info.plist
└── Tests/
├── CoreTests/
├── ServicesTests/
└── ViewModelTests/
- SwiftSoup - HTML parsing
The project uses Swift Package Manager with a custom build script to create proper macOS .app bundles.
Why the custom script?
Swift Package Manager executables don't generate proper .app bundles with Info.plist by default. The create-app-bundle.sh script:
- Builds the executable with
swift build - Creates proper
.appbundle structure - Copies
Info.plistwith bundle identifier - Sets correct permissions
Make sure you're running the app as a proper .app bundle (not via swift run), as terminal-launched apps capture keyboard input.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests first (TDD)
- Implement feature
- Run tests (
swift test) - Commit your changes
- Push to the branch
- Open a Pull Request
MIT
- Built with Claude Code



