mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Installed apps now show an 'Update available' button (menu bar) and can one-click install signed updates. Adds the Sparkle dependency, an UpdaterManager wired into AppState, the menu-bar banner + 'Check for Updates', and Info.plist SUFeedURL (https://grammagem.app/appcast.xml), SUPublicEDKey, and scheduled background checks. Build / release: - build.sh embeds + rpaths Sparkle.framework; sign-app.sh signs it inside-out (XPC services, Updater.app, Autoupdate, framework, then app) for a valid, notarizable signature. - release.sh stamps the version, builds, signs, notarizes, staples, zips, and emits the EdDSA-signed appcast.xml to upload to grammagem.app. - A hidden --update-selftest checks a feed headlessly (CI/build pipeline). Verified: a v0.1.0 build detects a v0.2.0 update against a local signed appcast (UPDATE_FOUND: 0.2.0); the signed app passes codesign --strict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
2.6 KiB
Swift
77 lines
2.6 KiB
Swift
import SwiftUI
|
|
import AppKit
|
|
|
|
/// Process entry point. A hidden `--mlx-selftest` flag loads the on-device model
|
|
/// and runs one real generation (verifies the MLX runtime + metallib headlessly),
|
|
/// then exits. Otherwise the normal menu-bar app launches.
|
|
@main
|
|
enum GrammaGemMain {
|
|
static func main() {
|
|
if CommandLine.arguments.contains("--mlx-selftest") {
|
|
MLXSelfTest.run()
|
|
return
|
|
}
|
|
if let i = CommandLine.arguments.firstIndex(of: "--update-selftest") {
|
|
let feed = i + 1 < CommandLine.arguments.count ? CommandLine.arguments[i + 1] : ""
|
|
UpdaterSelfTest.run(feedURL: feed)
|
|
return
|
|
}
|
|
GrammaGemApp.main()
|
|
}
|
|
}
|
|
|
|
/// Headless verification of the on-device LLM (used by CI / the build pipeline).
|
|
enum MLXSelfTest {
|
|
static func run() {
|
|
let sample = "me and him was going to the store for to buy some milks"
|
|
let engine = MLXEngine(modelDirectoryProvider: {
|
|
ModelManager.completedModelDirectory(repo: AppConfig.Model.defaultRepo)
|
|
})
|
|
guard engine.isReady else {
|
|
FileHandle.standardError.write(Data("SELFTEST_FAIL: model not present\n".utf8))
|
|
exit(2)
|
|
}
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var line = "SELFTEST_FAIL: unknown"
|
|
Task.detached {
|
|
do { line = "SELFTEST_OK: " + (try await engine.run(.rewriteClarity, on: sample)) }
|
|
catch { line = "SELFTEST_FAIL: \(error.localizedDescription)" }
|
|
sem.signal()
|
|
}
|
|
sem.wait()
|
|
print(line)
|
|
exit(line.hasPrefix("SELFTEST_OK") ? 0 : 1)
|
|
}
|
|
}
|
|
|
|
/// App entry point. A menu-bar (`LSUIElement`) SwiftUI app: the only persistent
|
|
/// scene is the `MenuBarExtra`; Settings uses the standard scene; Onboarding and
|
|
/// Ask are AppKit-hosted windows opened on demand (see `AppState`).
|
|
struct GrammaGemApp: App {
|
|
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
|
@StateObject private var app = AppState.shared
|
|
|
|
var body: some Scene {
|
|
MenuBarExtra {
|
|
MenuBarContent()
|
|
.environmentObject(app)
|
|
} label: {
|
|
MenuBarLabel()
|
|
}
|
|
.menuBarExtraStyle(.window)
|
|
|
|
Settings {
|
|
SettingsView()
|
|
.environmentObject(app)
|
|
.frame(width: 560, height: 520)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Runs launch-time wiring (hotkeys, permissions, license validation).
|
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
AppState.shared.start()
|
|
}
|
|
}
|