grammargem/Sources/GrammaGem/Licensing/LicenseModels.swift
BigMarc f5faa27f88 GrammaGem — open-source, on-device macOS writing assistant
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>
2026-06-16 13:56:26 +02:00

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
}
}
}