mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Wave 1 (correctness/trust): - Fix paid large-model dead path: engine reads the persisted selected repo via a thread-safe ModelManager.activeRepo(), so download/load/isReady now agree. - Lemon Squeezy store/product check fails closed in release builds — the placeholder-ID 'any key unlocks paid' bypass can no longer ship. - Replace force-unwrapped Hugging Face URLs with safe URLComponents construction. - Never read/replace/scan secure (password) fields via AX.isSecureTextField. Wave 2 (compounding seam): - Extract AIPromptKit (prompts/temperature/maxTokens/cleanup) out of MLXEngine so every backend produces identical prompts and output. - AIEngine gains protectedTerms (personal dictionary now actually injected into the LLM), warmup(), and runStreaming(); add makeAIEngine() factory. - Honor WritingMode.lengthCap for maxTokens; warm the model on launch/after download. - Guard custom modes against empty prompts and duplicate names. Wave 3: OllamaEngine — a URLSession-only AIEngine backend (cross-platform, opt-in via GRAMMARGEM_AI_BACKEND=ollama). Wave 4: LocalEngineServer — loopback-only, token-authed HTTP bridge (Network.framework, no new deps), off by default, exposing Harper + the LLM + the entitlement gate to a browser extension. Tests: 6 new unit tests for the AIPrompts seam and custom-mode guard (19 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
3.5 KiB
Swift
75 lines
3.5 KiB
Swift
import Foundation
|
|
|
|
/// Compile-time configuration. The Lemon Squeezy identifiers are hard-coded so
|
|
/// the licensing layer can *hard-verify* that a key belongs to GrammarGem (and to
|
|
/// the claimed tier) rather than to some other Lemon Squeezy product.
|
|
/// TODO(real-integration): replace every `TODO_*` value with your real IDs.
|
|
enum AppConfig {
|
|
static let appName = "GrammarGem"
|
|
static let bundleIdentifier = "com.foundergem.grammargem" // TODO(real-integration)
|
|
static let supportEmail = "support@grammargem.com" // TODO(real-integration)
|
|
static let modelPortalURL = URL(string: "https://www.grammargem.com/license")! // TODO
|
|
static let websiteURL = URL(string: "https://www.grammargem.com")!
|
|
static let appVersion = "0.1.0"
|
|
|
|
// Legal entity (shown in About / legal).
|
|
static let companyName = "FounderGem LLC"
|
|
static let companyAddress = "1309 Coffeen Avenue STE 1200, Sheridan, WY 82801, USA"
|
|
static let copyright = "© 2026 FounderGem LLC"
|
|
|
|
/// Lemon Squeezy License API (app talks to it directly — no server of ours).
|
|
enum LemonSqueezy {
|
|
static let apiBase = URL(string: "https://api.lemonsqueezy.com/v1")!
|
|
static let expectedStoreID = "TODO_STORE_ID"
|
|
static let expectedProductID = "TODO_PRODUCT_ID"
|
|
/// One variant per tier; the variant's activation limit is the device cap.
|
|
static let variantID: [Tier: String] = [
|
|
.solo: "TODO_SOLO_VARIANT_ID",
|
|
.personal: "TODO_PERSONAL_VARIANT_ID",
|
|
.studio: "TODO_STUDIO_VARIANT_ID",
|
|
]
|
|
}
|
|
|
|
/// Local LLM weights, pulled from Hugging Face on first run (never bundled).
|
|
enum Model {
|
|
static let defaultRepo = "mlx-community/Qwen2.5-3B-Instruct-4bit" // Apache-2.0
|
|
static let largeRepo = "mlx-community/Qwen2.5-7B-Instruct-4bit" // optional, paid tiers
|
|
static let huggingFaceBase = "https://huggingface.co"
|
|
}
|
|
|
|
/// The cross-platform Layer-2 backend: a locally-running Ollama server. Used
|
|
/// when MLX isn't available (Windows/Linux) or when the user opts in. Inference
|
|
/// stays on-device — Ollama runs the model on the user's own machine.
|
|
enum Ollama {
|
|
static let baseURL = URL(string: "http://127.0.0.1:11434")!
|
|
static let defaultModel = "qwen2.5:3b" // matches the MLX default model family
|
|
}
|
|
|
|
/// Loopback engine server for the browser extension (off by default; see
|
|
/// `LocalEngineServer`). Exposes Harper + the local LLM to the extension over
|
|
/// 127.0.0.1 with a per-install token — no cloud round-trip.
|
|
enum LocalServer {
|
|
static let port: UInt16 = 47800
|
|
static let tokenKey = "GrammarGem.localServerToken"
|
|
static let enableKey = "GrammarGem.enableLocalServer"
|
|
}
|
|
|
|
/// Free-tier limits + license validation cadence (see spec §5/§6).
|
|
enum Limits {
|
|
static let freeDailyAIActions = 10
|
|
static let freeDictionaryCap = 25
|
|
static let validationIntervalDays = 7
|
|
static let offlineGraceDays = 30
|
|
}
|
|
|
|
/// Default global hotkey: ⌘; (Carbon key code 41, Carbon cmdKey mask 256).
|
|
/// Stored as raw integers here to keep this file free of the Carbon import.
|
|
enum Hotkey {
|
|
static let defaultKeyCode: UInt32 = 41 // kVK_ANSI_Semicolon
|
|
static let defaultModifiers: UInt32 = 256 // cmdKey
|
|
// A second hotkey opens the "Ask" popover (default ⌘' = key 39).
|
|
static let askKeyCode: UInt32 = 39 // kVK_ANSI_Quote
|
|
static let askModifiers: UInt32 = 256
|
|
}
|
|
}
|