- Swift 97.2%
- Shell 2.4%
- Python 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
CI / build-and-test (push) Has been cancelled
Co-Authored-By: Claude Fable 5.1 <[email protected]> |
||
| .github/workflows | ||
| build | ||
| dist | ||
| Docs | ||
| output/imagegen | ||
| Packaging | ||
| Scripts | ||
| Sources/LocalWhisper | ||
| Tests/LocalWhisperTests | ||
| tools/license | ||
| Vendors | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| DESIGN.md | ||
| Package.resolved | ||
| Package.swift | ||
| PRODUCT.md | ||
| README.md | ||
LocalWhisper
Local-first macOS transcription app built with SwiftUI and whisper.cpp.
Current Status
- Native macOS SwiftUI shell.
- Local file transcription via
whisper-cli(audio conversion uses built-in AVFoundation). - Metal-enabled
whisper.cppbackend. - Meeting detection banner with developer bypass mode.
- Permission state dashboard.
- Model manager shell.
- Queue, history and transcript detail views.
- Swift Testing coverage for parser, diarization mapping and bypass detector.
Build
cd /Users/ai_leed/Documents/Projects/LocalWhisper
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swift build
Test
cd /Users/ai_leed/Documents/Projects/LocalWhisper
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swift test
Run With Developer Permission Bypass
cd /Users/ai_leed/Documents/Projects/LocalWhisper
LOCALWHISPER_BYPASS_PERMISSIONS=1 DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swift run LocalWhisper
Bypass mode only bypasses LocalWhisper's internal workflow gates. It does not grant macOS Microphone, Screen Recording, Accessibility or Calendar permissions.
Build App Bundle
cd /Users/ai_leed/Documents/Projects/LocalWhisper
./Scripts/build_app.sh
The app bundle is created at:
/Users/ai_leed/Documents/Projects/LocalWhisper/dist/LocalWhisper.app
Smoke-run the built app with developer bypass:
cd /Users/ai_leed/Documents/Projects/LocalWhisper
LOCALWHISPER_BYPASS_PERMISSIONS=1 dist/LocalWhisper.app/Contents/MacOS/LocalWhisper
Distribution
Three Key Pairs — Never Mix Them Up
| # | Key pair | Purpose | Private key lives | Public key lives |
|---|---|---|---|---|
| 1 | Apple Developer ID | codesign + Gatekeeper + notarization | macOS Keychain / CI secret CERTIFICATE_P12_BASE64 |
Embedded by Apple in the signed binary (cert chain) |
| 2 | Sparkle EdDSA (ed25519) | Update package integrity — Sparkle verifies each DMG before installing | macOS Keychain (local) / CI secret SPARKLE_ED_PRIVATE_KEY |
SUPublicEDKey key in Packaging/Info.plist |
| 3 | License Ed25519 | Offline license-payload verification | Your server / your machine only — never in the repo | Constant embeddedPublicKeyBase64 in Sources/LocalWhisper/LicenseService.swift |
One-Time Setup (per machine)
# 1. Generate Sparkle EdDSA key pair (run once, store private key in Keychain)
# Sparkle's generate_keys is in .build/artifacts after the first `swift build`
find .build/artifacts -name generate_keys | head -1 | xargs -I{} {}
# → prints public key → paste it as SUPublicEDKey in Packaging/Info.plist
# 2. Generate license signing key pair (run on your server or secure machine)
swift tools/license/license-tool.swift generate-keys
# → prints private key (store securely, never commit) and public key
# → paste public key as embeddedPublicKeyBase64 in Sources/LocalWhisper/LicenseService.swift
# 3. Store notarytool credentials in Keychain
xcrun notarytool store-credentials notarytool-creds \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD"
Signing & Notarization
# Prerequisites: .env filled in from .env.example, app bundle already built
source .env
./Scripts/build_dmg.sh # build → dist/LocalWhisper-Installer.dmg
./Scripts/sign-and-notarize.sh # sign (inside-out, no --deep) → notarize → staple → verify
The script signs in the correct inside-out order:
- dylibs in
Contents/Frameworks/ - Sparkle.framework nested helpers (XPC services, Updater.app, AutoUpdate)
Contents/Resources/whisper.cpp/bin/whisper-cli(stale build-tree rpaths stripped first)Contents/MacOS/LocalWhisper- Outer
.appbundle withbuild/entitlements.plist
Final checks run automatically: codesign --verify --strict, spctl -a -t exec -vvv, xcrun stapler validate.
Full Release (build → sign → notarize → Sparkle sign → upload)
source .env
./Scripts/release.sh 1.2.0 # bumps Info.plist, builds, signs, notarizes,
# signs update with Sparkle EdDSA, generates appcast.xml,
# rsyncs to $UPDATE_HOST_USER@$UPDATE_HOST:$UPDATE_HOST_PATH
CI (GitHub Actions)
Workflow at .github/workflows/release.yml. Trigger: workflow_dispatch with version input.
Secrets to configure in GitHub → Settings → Secrets:
| Secret | Description |
|---|---|
CERTIFICATE_P12_BASE64 |
base64 of Developer ID .p12 (base64 -i cert.p12) |
CERTIFICATE_P12_PASSWORD |
.p12 export password |
DEVELOPER_ID_APP |
"Developer ID Application: Name (TEAMID)" |
APPLE_ID |
Apple ID email |
APPLE_TEAM_ID |
10-char team ID |
APPLE_APP_SPECIFIC_PASSWORD |
app-specific password from appleid.apple.com |
SPARKLE_ED_PRIVATE_KEY |
Sparkle EdDSA private key (base64) |
UPDATE_HOST_USER |
SSH user for rsync upload |
UPDATE_HOST |
hostname for rsync upload |
UPDATE_HOST_PATH |
remote path (e.g. /var/www/updates/) |
SPARKLE_FEED_URL |
Full URL of appcast.xml (e.g. https://updates.localwhisper.dev/appcast.xml) |
Key Rotation
- Apple Developer ID: revoke old cert in Developer portal, issue new one, re-export .p12, update CI secret. No code change needed.
- Sparkle EdDSA: run
generate_keysagain, updateSUPublicEDKeyinPackaging/Info.plist, update CI secret. Old updates already delivered are unaffected; new releases use new key. - License key pair: generate new pair with
tools/license/license-tool.swift generate-keys, updateembeddedPublicKeyBase64inLicenseService.swift, ship a new app version. Old signed licenses signed with the old private key will stop verifying offline — issue replacement licenses to affected customers.
Licensing
Tiers: Free (cloud STT on BYO API key) · Pro (one-time, unlocks local pipeline) · Teams (multi-seat).
Users activate in Settings → License using a Lemon Squeezy license key.
# Issue a license offline (server-side, private key required)
swift tools/license/license-tool.swift issue \
--tier pro --email [email protected] --seats 1 \
--private-key "$LICENSE_SIGNING_PRIVATE_KEY"
Licenses are verified first offline (Ed25519 signature against the embedded public key) then periodically online with a 14-day grace period.
Local Whisper Backend
Expected files:
Vendors/whisper.cpp/build/bin/whisper-cliVendors/whisper.cpp/models/ggml-base.bin
Rebuild backend:
cd /Users/ai_leed/Documents/Projects/LocalWhisper/Vendors/whisper.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_METAL=ON
cmake --build build --config Release -j
./models/download-ggml-model.sh base