A custom iOS keyboard extension with three operating modes: Quick Actions, Tribunal consensus, and a LEGO-style pipeline builder. Backed by Rhea Tribunal — a multi-model AI gateway.
RheaKeyboard replaces the default iOS keyboard system-wide. In any app — Messages, Mail, Notes, Twitter — the user gets a dark-themed panel with AI capabilities directly in the keyboard area.
Three modes, toggled by a single button in the header:
| Mode | Description |
|---|---|
| Quick | One-tap actions on the text before the cursor: translate, grammar fix, rewrite styles, summarize, explain |
| Tribunal | Submit a claim to multiple models simultaneously; get a consensus reply with an agreement score |
| Builder | Compose a LEGO block chain — Input → Model → Action → Verify → Loop → Output — and run it as a pipeline |
KeyboardViewController (UIInputViewController)
|
+---> KeyboardView (SwiftUI)
|
+---> mode: .actions -- Quick action strip
+---> mode: .tribunal -- Consensus input
+---> mode: .builder -- Block chain editor
|
+---> TribunalClient (async/await API client)
|
+---> POST /keyboard/quick (single model, fast)
+---> POST /dialog (multi-model tribunal)
- No RheaKit dependency (keyboard extensions must stay lean, <30 MB).
- Reads a JWT token from the shared Keychain service
com.rhea.preview, written by the main Rhea app. - Falls back to
X-API-Key: dev-bypassif no token is found (development only). - Base URL:
https://rhea-tribunal.fly.dev
Standard UIInputViewController subclass. Wraps KeyboardView in a UIHostingController. Bridges the textDocumentProxy callbacks (insert text, delete backward, read context) into Swift closures passed to the SwiftUI view.
All UI lives here. SwiftUI, iOS 16+. No external dependencies. Colors are defined inline to match RheaTheme without importing the main app's design system.
[ Translate ] [ Grammar ] [ Rewrite ] [ Summarize ] [ Explain ]
[ Formal ] [ Casual ] [ Shorter ] [ Longer ] [ Friendly ] [ Professional ]
[ Ask anything... ^ ]
- Top row: primary actions operating on the text before the cursor
- Second row: rewrite style variants
- Bottom: freeform text field for arbitrary prompts
- Results appear in a scrollable response area with Insert and Copy buttons
MULTI-MODEL CONSENSUS
[ Enter claim for tribunal... ^ ]
- Submits the query to
/dialog - Response shows: reply text, agreement percentage, model count, elapsed time
- Intended for fact-checking, ambiguous decisions, contested claims
[ INPUT ] > [ Claude ] > [ Rewrite ] > [ >=80% ] > [ Paste ] [+]
[ cheap | mid | frontier ]
[ Running... 2/5 ] [ Run Chain ]
Block types:
| Block | Purpose | Config |
|---|---|---|
| INPUT | Grab text from host app | — |
| MODEL | Pass through AI model | tier: cheap / mid / frontier |
| ACTION | Apply named transformation | translate / rewrite / summarize / extract / grammar |
| VERIFY | Tribunal consensus gate | threshold: 70% / 80% / 90% |
| LOOP | Repeat previous step N times | iterations: 3 / 5 / 10 |
| OUTPUT | Insert result into host app | — |
Tap a block to open its inline config. Long-press to delete. Tap + to add from palette. Tap "Run Chain" to execute all steps in sequence.
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/timelabs-npo/rhea-keyboard.git", from: "1.0.0"),
],
targets: [
.target(name: "YourApp", dependencies: ["RheaKeyboard"]),
]- Add a new target: File > New > Target > Custom Keyboard Extension
- Set the principal class to
RheaKeyboard.KeyboardViewController - Add the three source files directly to the extension target
- Enable App Groups in Capabilities if you want Keychain sharing with the main app
- Set
NSExtensionPrincipalClassinInfo.plist:
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).KeyboardViewController</string>The main app must write the JWT into Keychain under:
service: "com.rhea.preview"
account: "jwt_token"The keyboard extension reads it at launch via the Security framework directly (no third-party dependency).
Your Tribunal backend must implement:
POST /keyboard/quick
{
"text": "...",
"action": "translate|rewrite|grammar|summarize|explain|freeform",
"target_lang": "en",
"style": "formal"
}
-> { "text": "...", "model": "...", "elapsed_s": 1.2, "action": "..." }
POST /dialog
{
"text": "...",
"sender": "keyboard"
}
-> { "reply": "...", "agreement_score": 0.87, "models_responded": 4, "elapsed_s": 3.1 }
- iOS 16+
- Xcode 15+
- Swift 5.9+
- A running Tribunal API instance (see rhea-tribunal)
MIT