Technical
License storage.
Loupe stores your activation license as a flat JSON file in Application Support — not in the macOS Keychain. The Ed25519 signature on the envelope is the integrity boundary, so the file path is bundle-ID keyed and stable across signing-cert rotations, dev rebuilds, and reinstalls.
Where the license lives
The license file is at:
~/Library/Application Support/studio.shyguy.loupe/license.json
It contains the signed activation envelope (Ed25519 over JSON, base64url-encoded), the activation device ID, the original activation timestamp, and a sticky permanently-expired flag. File permissions are 0600 (user read/write only); writes are atomic.
Inspect with cat; delete with rm to deactivate (Loupe re-creates it on the next successful activation).
Why a flat file instead of Keychain
The activation envelope's Ed25519 signature is what proves the license is valid. Loupe verifies it locally against a public key embedded in the binary. Keychain doesn't add meaningful confidentiality on top of that signature — but it does couple the entry to the binary's signing identity.
Coupling caused silent license loss across re-signs: Apple Developer cert rotations, dev rebuilds, app reinstalls. The file path is bundle-ID keyed and stable across all of those events. If you upgrade Loupe across a developer-cert rotation, your trial / paid license survives.
Per-case AES encryption keys are still in Keychain — those genuinely need it. Compromise of a per-case key means the encrypted case file is readable; we want OS-managed access control on those. The license is a different surface: a publicly-distributable signed envelope. Different requirements, different storage.
Migrating between Macs
Two equally valid paths:
- Copy the license file to the new Mac at the same path:
~/Library/Application Support/studio.shyguy.loupe/license.json - Re-activate from the original
loupe://activate?envelope=...URL on the new Mac. Re-activation does not reset the trial clock — the expiry date is signed into the envelope.
Migrating cases between Macs requires both the case files (in Application Support) and the per-case Keychain keys. See the bundled Reference Manual §6 for the full migration recipe.
Reproducibility
Storage code: Sources/Loupe/Services/LicenseStorage.swift. Verify the file exists with ls -la ~/Library/Application\ Support/studio.shyguy.loupe/ after activation. Verify Keychain holds only per-case keys (not the license) with security find-generic-password -s studio.shyguy.loupe -a license — should return "could not be found" on a v1.0+ install. Tests: Tests/LoupeTests/LicenseStorageTests.swift covers the file round-trip + the one-shot Keychain migration for installs that pre-date this storage layout.