feat: initial release v0.1.0

- Custom Keyboard Extension with push-to-talk UI
- iOS 26 SpeechAnalyzer + DictationTranscriber (iOS 18 SF fallback)
- OpenAI-compatible LLM client (4 built-in providers + custom)
- 3-page onboarding flow + provider config UI
- App Group shared storage for cross-process config
- 8s LLM timeout with raw-transcript fallback
- App Store privacy manifests for both targets
- SwiftLint + XcodeGen + GitHub Actions CI
- Unit tests (4/4 passing)
This commit is contained in:
2026-06-17 23:08:58 +08:00
commit 07067ca6c5
45 changed files with 3072 additions and 0 deletions
@@ -0,0 +1,45 @@
// AppGroupStore.swift
// OSGKeyboard · Shared
//
// Convenience wrapper around App Group UserDefaults for non-Published reads.
// Used by the keyboard extension (no SwiftUI) to read config without
// instantiating an ObservableObject.
import Foundation
public struct AppGroupStore: @unchecked Sendable {
public let defaults: UserDefaults
public init(defaults: UserDefaults = AppGroup.defaults) {
self.defaults = defaults
}
public var providerId: String {
defaults.string(forKey: "config.providerId") ?? "openai"
}
public var baseURL: String {
defaults.string(forKey: "config.baseURL") ?? LLMProvider.provider(id: "openai").defaultBaseURL
}
public var apiKey: String {
defaults.string(forKey: "config.apiKey") ?? ""
}
public var model: String {
defaults.string(forKey: "config.model") ?? LLMProvider.provider(id: "openai").defaultModel
}
public var systemPrompt: String {
defaults.string(forKey: "config.systemPrompt")
?? "You are a voice-input polishing assistant. Rewrite the user's dictation as clean written text. Preserve intent. Add punctuation and structure. Do not invent facts. Output in the same language as the input."
}
public func makeClient() -> LLMClient {
OpenAICompatibleClient(
baseURL: baseURL,
apiKey: apiKey,
model: model
)
}
}