macOS menu bar app + CLI tool (SwiftPM, no Xcode) that reads text aloud using Apple TTS (default) or OpenAI TTS (optional BYOK). Shows a teleprompter-style floating overlay with synchronized word highlighting.
Bundle ID: com.malpern.voxclaw
URL scheme: voxclaw://read?text=...
CLI command: voxclaw
- Single binary, dual-mode: CLI or menu bar app depending on how it's launched (
ModeDetector) - SpeechEngine protocol abstracts Apple TTS (
AppleSpeechEngine) and OpenAI TTS (OpenAISpeechEngine) - Distribution: GitHub Releases (ad-hoc signed, or Developer ID for no-Gatekeeper-warning)
- Packaging:
Scripts/package_app.sh(ad-hoc signed by default)
swift build # Debug build
swift test # 75 tests
Scripts/compile_and_run.sh # Build, package, and launch (dev loop)
Scripts/package_app.sh release # Release build (ad-hoc signed)
APP_IDENTITY="Developer ID Application: ..." Scripts/package_app.sh release # Signed# URL scheme
open "voxclaw://read?text=Hello%20world"
# Services menu
# Select text > right-click > Services > Read with VoxClaw
# Shortcuts CLI
shortcuts run "Read with VoxClaw" --input-string "Hello world"
# Network listener (toggle in Settings or --listen flag)
curl -X POST http://localhost:4140/read -d '{"text":"Hello"}'
curl -X POST http://localhost:4140/read -H 'Content-Type: application/json' -d '{"text":"Hello"}'
curl http://localhost:4140/statusvoxclaw "Hello, world!" # Read text aloud
voxclaw -a "Hello" # Audio only (no overlay)
voxclaw --clipboard # Read from clipboard
voxclaw --file article.txt # Read from file
echo "Hello" | voxclaw # Read from stdin
voxclaw --voice nova "Hello" # OpenAI voice override
voxclaw --rate 1.5 "Hello" # 1.5x speech speed
voxclaw --instructions "Read warmly" "Hello" # OpenAI prosody control
voxclaw --output hello.mp3 "Hello" # Save audio to file (OpenAI)
voxclaw --listen # Start HTTP listener on port 4140
voxclaw --listen --port 8080 # Custom port
voxclaw --send "Hello" # Send text to running listener
voxclaw --status # Check if listener is running- Audio leads, visuals follow. The voice sets the pace and tone of the experience. The overlay exists to support the audio, not the other way around. Never show the overlay before audio is ready to play. Don't let visual elements (loading states, animations, transitions) compete with or distract from the spoken word.
When finishing a non-trivial task in this repo, pipe a 1–2 sentence spoken summary through plugins/voxclaw/scripts/voxclaw-say --kind summary. Lead with the outcome, then verification status, then any blocker. Use prose only — no code, file paths, or command names. Skip for pure Q&A or read-only exploration. The app's agentSpeechMode setting filters whether it actually speaks, so always emit; let the app decide.
- Tests use Swift Testing framework (
@Test,#expect) - Network integration tests are
@Suite(.serialized)to avoid port conflicts - Logging via
os.Loggercategories inLog.swift - API key storage:
KeychainHelper— checksOPENAI_API_KEYenv var first, then~/Library/Application Support/VoxClaw/api-key- File has
0600permissions (owner read/write only) - One-time migration from Keychain happens automatically on first launch after upgrade
- File has
@ObservableonAppStateandSettingsManager— use stored properties withdidSet, not computed, because@Observablecan't track computed properties
log stream --predicate 'subsystem == "com.malpern.voxclaw"' --level debugPush a tag (e.g. v1.0.0) → .github/workflows/release.yml builds, packages, uploads VoxClaw.zip to GitHub Releases.
Sources/
VoxClawCore/ Library target (all logic)
VoxClaw/ Thin executable (main.swift → VoxClawLauncher.main())
Tests/
VoxClawCoreTests/ 75+ tests
Scripts/
compile_and_run.sh Kill → build → package → launch (dev loop)
package_app.sh Build .app bundle
install-cli.sh Symlink voxclaw binary to /usr/local/bin
docs/
privacy.html https://malpern.github.io/VoxClaw/privacy.html
index.html