mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Native SwiftUI menu-bar app: global hotkey → capture selected text (AX API / clipboard fallback) → grammar (Harper) / local AI (MLX) → replace in place. Free/paid entitlement gate + Lemon Squeezy device-cap licensing, $0 marginal cost per user. Heavyweight engines behind protocol stubs so it builds out of the box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
1.9 KiB
Swift
71 lines
1.9 KiB
Swift
import Foundation
|
|
|
|
/// The locally-stored license record (Keychain-persisted).
|
|
struct LicenseRecord: Codable, Equatable {
|
|
var key: String
|
|
var instanceID: String // Lemon Squeezy activation instance id
|
|
var tier: Tier
|
|
var lastValidated: Date
|
|
var activatedDevices: Int
|
|
}
|
|
|
|
// MARK: - Lemon Squeezy API response shapes (subset we rely on)
|
|
|
|
struct LSActivateResponse: Codable {
|
|
let activated: Bool
|
|
let instance: LSInstance?
|
|
let license_key: LSLicenseKey?
|
|
let meta: LSMeta?
|
|
let error: String?
|
|
}
|
|
|
|
struct LSValidateResponse: Codable {
|
|
let valid: Bool
|
|
let license_key: LSLicenseKey?
|
|
let meta: LSMeta?
|
|
let error: String?
|
|
}
|
|
|
|
struct LSDeactivateResponse: Codable {
|
|
let deactivated: Bool
|
|
let error: String?
|
|
}
|
|
|
|
struct LSInstance: Codable {
|
|
let id: String
|
|
let name: String
|
|
}
|
|
|
|
struct LSLicenseKey: Codable {
|
|
let status: String // "active", "expired", "disabled", …
|
|
let activation_limit: Int?
|
|
let activation_usage: Int?
|
|
}
|
|
|
|
/// `meta` carries the store/product/variant identity we *hard-verify* against
|
|
/// AppConfig so a key from another Lemon Squeezy product can't unlock GrammaGem.
|
|
struct LSMeta: Codable {
|
|
let store_id: Int?
|
|
let product_id: Int?
|
|
let variant_id: Int?
|
|
let variant_name: String?
|
|
}
|
|
|
|
enum LicenseError: LocalizedError, Equatable {
|
|
case network(String)
|
|
case invalidKey
|
|
case wrongProduct
|
|
case activationLimitReached(Int)
|
|
case serverMessage(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .network(let m): return "Couldn't reach the license server: \(m)"
|
|
case .invalidKey: return "That license key isn't valid."
|
|
case .wrongProduct: return "That key belongs to a different product."
|
|
case .activationLimitReached(let n): return "This plan is active on \(n) Macs — deactivate one or upgrade."
|
|
case .serverMessage(let m): return m
|
|
}
|
|
}
|
|
}
|