Zhongshu 2e2d8e33b3 [P0-③] API Key data flow fix
- AppGroup.defaults: in DEBUG, missing App Group is a hard fatalError
  with a precise remediation message (was a soft print + .standard
  fallback, which desynced the keyboard extension from the main App).
  Release keeps the fallback + NSLog so end-users still get a usable app.
- KeyboardViewController.loadPersistedLocale now prints a masked DEBUG
  view of the live App Group config (provider, baseURL, masked key,
  model, mode, locale) so the extension's view is visible in the
  device console.
- KeyboardViewController.handleFinalTranscript now routes by typed error:
    noAPIKey  → red error '未配置 API Key · 请在主 App 设置中填写'
    http 401  → red error 'API Key 无效 (401) · 请检查主 App 设置'
    http 429  → red error 'API 限流 (429) · 请稍后再试'
    other     → insert raw transcript + generic error badge
- APISettingsCard gains a 'Test connection' button that runs a single
  client.polish('ping') round-trip and surfaces the typed result inline.
- PolishingService.timeout raised 12s → 15s to match LLMClient.request
  timeout (was racing and discarding successful responses in 12–15s).
- Tests: 4 new cases (HTTP 429, transport timeout, App Group cross-process,
  AppGroupStore→LLMClient noAPIKey). All 8 tests pass on iPhone 16e sim.

xcodebuild iOS Simulator: SUCCEEDED
xcodebuild test: 8/8 passed
2026-06-18 10:50:02 +08:00
2026-06-17 23:08:58 +08:00
2026-06-17 23:08:58 +08:00
2026-06-17 23:08:58 +08:00
2026-06-17 23:08:58 +08:00

OSGKeyboard

Hold a key, speak, release — AI-polished text appears at your cursor in any app. An open-source, custom-keyboard-based voice input tool for iOS 18+, inspired by Typeless and OpenLess.

Platform Swift License CI

中文 README


What is it?

OSGKeyboard is a free, open alternative to commercial voice-input tools. It runs as a Custom Keyboard Extension on iOS, so you can use it in any app — Messages, Notes, Mail, ChatGPT, Claude, Cursor, you name it.

  1. Press and hold the mic key
  2. Speak naturally
  3. Release — the AI polishes your words into clean text and inserts it at the cursor

The audio stays on-device (transcribed by Apple's on-device SpeechAnalyzer on iOS 26+, or SFSpeechRecognizer on iOS 18/19). Only the polished transcript is sent to your chosen cloud LLM. No audio ever leaves your phone.


Features

  • 🎙 Push-to-talk with a Typeless-style circular mic button
  • 🧠 On-device ASR (iOS 26 SpeechAnalyzer + DictationTranscriber; iOS 18 SFSpeechRecognizer fallback)
  • ✍️ AI polishing — adds structure, punctuation, fixes grammar, optionally produces lists
  • 🔌 Bring-your-own API — works with any OpenAI-compatible endpoint (OpenAI, DeepSeek, Qwen DashScope, your own self-hosted server, …)
  • 🔒 Privacy first — audio never leaves your device; transcripts only sent to the LLM you choose
  • 🎨 Native SwiftUI — dark theme, frosted glass, ~2000 lines of Swift
  • 🪶 Zero dependencies — no SwiftPM packages, no CocoaPods, no Carthage

Quick start

Requirements

  • macOS with Xcode 16+ (Xcode 26 recommended)
  • iPhone running iOS 18.0+ (iOS 26+ for the best on-device ASR)
  • XcodeGen: brew install xcodegen
  • An OpenAI-compatible API key (e.g. from OpenAI, DeepSeek, or Qwen DashScope)

Build & run

git clone https://github.com/<OWNER>/OSGKeyboard.git
cd OSGKeyboard
xcodegen generate          # produces OSGKeyboard.xcodeproj
open OSGKeyboard.xcodeproj # or build via CLI:
xcodebuild -project OSGKeyboard.xcodeproj -scheme OSGKeyboard \
  -destination 'generic/platform=iOS Simulator' build

Enable the keyboard in iOS

  1. Run the app on your device or simulator.
  2. Follow the 3-step onboarding: enable the keyboard in iOS Settings, then allow Full Access (required for the mic and LLM calls), then paste your API key.
  3. In any text field, tap 🌐 to switch to OSGKeyboard.
  4. Press and hold the mic, speak, release. ✨

"Allow Full Access" is required. Without it, iOS blocks the keyboard from using the microphone and from making network requests. We never log, store, or transmit your keystrokes — see PrivacyInfo.xcprivacy.


Architecture

OSGKeyboard/
├── OSGKeyboard/                 # Main iOS app (settings, onboarding)
│   ├── Views/                   # SwiftUI screens
│   ├── OSGKeyboardApp.swift     # @main entry
│   ├── PrivacyInfo.xcprivacy    # Required privacy manifest
│   └── OSGKeyboard.entitlements # App Group declaration
├── OSGKeyboardExt/              # Custom Keyboard Extension
│   ├── KeyboardViewController.swift   # Principal class
│   ├── Services/
│   │   ├── AudioCaptureService.swift  # AVAudioEngine → 16 kHz PCM
│   │   ├── ASRService.swift           # iOS 26 + iOS 18 ASR
│   │   └── PolishingService.swift     # LLM call with timeout
│   └── Views/                   # RecordButton, Waveform, KeyboardRootView
├── OSGKeyboardShared/           # Framework shared by app + extension
│   ├── Models/                  # ProviderConfig, LLMRequest, LLMProvider
│   ├── Services/                # LLMClient (OpenAI-compatible)
│   └── Constants/               # AppGroup identifier
├── OSGKeyboardTests/            # XCTest unit tests
├── project.yml                  # XcodeGen project definition
└── .github/workflows/ci.yml     # Lint + build CI

Data flow

[Long-press mic] → AudioCaptureService → AudioBufferSnapshot (16 kHz mono)
                                          ↓
                                  ASRService.transcribe()
                                          ↓
                                ASREvent.final(rawTranscript)
                                          ↓
                              PolishingService.polish()
                                          ↓
                            LLMClient (OpenAI-compatible)
                                          ↓
                          textDocumentProxy.insertText(polished)

Adding a new LLM provider

Open OSGKeyboardShared/Models/LLMProvider.swift and append a new LLMProvider to the presets array. The default OpenAICompatibleClient handles any endpoint that speaks the POST /chat/completions protocol.

LLMProvider(
    id: "groq",
    name: "Groq",
    defaultBaseURL: "https://api.groq.com/openai/v1",
    defaultModel: "llama-3.1-70b-versatile",
    apiKeyURL: URL(string: "https://console.groq.com/keys")
)

That's it. No other code changes required.


Limitations

  • iOS sandboxes keyboard extensions: ~60 MB memory cap, Full Access required.
  • The keyboard does not work in password fields or some WKWebView textareas (iOS limitation).
  • iOS 18 uses SFSpeechRecognizer instead of SpeechAnalyzer; on iOS 26, SpeechAnalyzer is significantly faster and supports more locales.

License

MIT — use it, fork it, ship it. No warranty.


Acknowledgements


Note: replace <OWNER> in badges and the git clone URL with your GitHub username.

S
Description
No description provided
Readme
87 MiB
Languages
Swift 84.9%
Python 13.1%
HTML 0.8%
Shell 0.7%
Objective-C++ 0.3%
Other 0.1%