Two review-driven fixes, scoped narrowly to the two files the user
flagged. The broader uncommitted changes (Engine mode wiring,
SpeechAnalyzer, locale picker, etc.) belong to a prior agent pass and
are intentionally left out of this commit.
1. Light-mode accent is no longer the dark-mode green (#3AA05A).
Both the system `AccentColor` asset and `Palette.light.accent`
(plus `.accentMuted` / `.accentGlow`) now use Apple system blue
(#007AFF). On a near-white surface the green read as "garden
centre" and clashed with every iOS HIG-styled control. Dark
palette's accent stays green — the keyboard extension is always
dark, and green-on-dark is the more legible pairing for the
polish/active affordance.
2. The keyboard preview's "textbox" is now a real `TextField` and
the recognized text actually lands in it.
Before: `mockTextField` was a static `HStack` (icon + "Type
here…" placeholder). `cyclePhase()` had a comment
"// Local engine skips processing — insert happens
immediately." but the insert was never wired up — the user
tapped the disc, the stub transcript appeared briefly in the
transcript line, then vanished on the next tap with nothing
landing in the top box.
After:
- `mockTextField` is a real `TextField(text: $typedText, axis: .vertical)`
with a clear-X button, vertical growth (1-4 lines), and the
accent-coloured cursor. The user can also type into it directly.
- `cyclePhase()` calls `insertRecognizedText()` on the
recording→idle (local) and processing→idle (cloud) transitions,
so the (mock) recognized transcript appends to the textbox with
a leading space when the existing text doesn't already end in
whitespace — matches what `textDocumentProxy.insertText` does
for the real keyboard.
This only changes the in-app preview; the real keyboard extension
already inserts via `textDocumentProxy` and is untouched.
Build: BUILD SUCCEEDED on iPhone 17 Pro / iOS 26 simulator.
Tests: 21/21 pass (no test changes — visual + App-Group code paths
already covered).
🤖 Generated with Claude Code
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.
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.
- Press and hold the mic key
- Speak naturally
- 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 SFSpeechRecognizer on iOS 18/19; iOS 26+ SpeechAnalyzer planned for the next release). 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 18/19
SFSpeechRecognizer; iOS 26+SpeechAnalyzer+DictationTranscriberplanned) - ✍️ 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+
- XcodeGen:
brew install xcodegen - An OpenAI-compatible API key (e.g. from OpenAI, DeepSeek, or Qwen DashScope)
Build & run
git clone https://github.com/hkgood/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
- Run the app on your device or simulator.
- 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.
- In any text field, tap 🌐 to switch to OSGKeyboard.
- 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
WKWebViewtextareas (iOS limitation). - iOS 18/19 ships with
SFSpeechRecognizerfor on-device ASR. iOS 26+SpeechAnalyzeris planned for the next release — it is significantly faster and supports more locales. - iOS 26+ users in v0.1.1 use the iOS 18
SFSpeechRecognizerpath; the iOS 26SpeechAnalyzeris planned for 0.2.0.
License
MIT — use it, fork it, ship it. No warranty.
Acknowledgements
- Inspired by Typeless and the desktop open-source OpenLess
- Built with XcodeGen
- Powered by Apple's SpeechAnalyzer and SFSpeechRecognizer
Note: the project is published at hkgood/OSGKeyboard; badges and git clone URLs already point there.