litter is a native iOS + Android client for Codex.
| Dark (Default) | Light |
|---|---|
![]() |
![]() |
| Accessibility XXL Text | Dark + High Contrast |
|---|---|
![]() |
![]() |
apps/ios: iOS app (Litterscheme)apps/android: Android appapp: Compose UI shell, app state, server manager, SSH/auth flowscore/bridge: native bridge bootstrapping and core RPC clientcore/network: discovery services (Bonjour/Tailscale/LAN probing)docs/qa-matrix.md: Android parity QA matrix
shared/rust-bridge/codex-mobile-client: single shared Rust mobile client crate and UniFFI surfaceshared/rust-bridge/codex-ios-audio: iOS-only audio/AEC crateshared/third_party/codex: upstream Codex submodulepatches/codex: local Codex patch settools/scripts: cross-platform helper scripts
Generated iOS build artifacts under apps/ios/GeneratedRust/ and packaged frameworks under apps/ios/Frameworks/ are not stored in git.
Build everything with:
make ios # full package lane + simulator build
make ios-device # full package lane + device build
make ios-device-fast # fast raw-staticlib device lane
make ios-sim # full package lane + simulator build-
Xcode.app (full install, not only CLT)
-
Rust + iOS targets:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
-
xcodegen(for regeneratingLitter.xcodeproj):brew install xcodegen
Use this flow to make Codex sessions from your Mac visible in the iOS/Android app.
- Enable SSH on the Mac.
- Preferred (UI):
System Settings->General->Sharing-> enableRemote Login. - CLI option:
sudo systemsetup -setremotelogin on sudo systemsetup -getremotelogin
- If you get
setremotelogin: Turning Remote Login on or off requires Full Disk Access privileges, grant Full Disk Access to your terminal app in:System Settings->Privacy & Security->Full Disk Access, then fully restart terminal and retry.
- Verify SSH and Codex binaries from a non-interactive SSH shell.
ssh <mac-user>@<mac-host-or-ip> 'echo ok'
ssh <mac-user>@<mac-host-or-ip> 'command -v codex || command -v codex-app-server'If the second command prints nothing, install Codex and/or fix shell PATH startup files (.zprofile, .zshrc, .profile).
- Connect from the Litter app.
- Keep phone and Mac on the same LAN (or same Tailnet if using Tailscale).
- In Discovery:
- If host shows
codex running, tap to connect directly. - If host shows
SSH, tap and enter SSH credentials; Litter will start remote server via SSH and connect.
- If host shows
- Fallback: run app-server manually on Mac and add server manually in app.
codex app-server --listen ws://0.0.0.0:8390Then in app choose Add Server and enter <mac-ip> + 8390.
- Session visibility note.
Thread/session listing is cwd-scoped. If expected sessions are missing, choose the same working directory used when those sessions were created.
This repo now vendors upstream Codex as a submodule:
shared/third_party/codex->https://github.com/openai/codex
Current local Codex patch set:
patches/codex/ios-exec-hook.patchpatches/codex/realtime-transcript-deltas.patchpatches/codex/client-controlled-handoff.patch
Sync/apply patch (idempotent):
./apps/ios/scripts/sync-codex.shThis preserves the current shared/third_party/codex checkout by default, applies the full local patch set, and fails if any patch no longer matches that checkout cleanly.
Pass --recorded-gitlink if you explicitly want to reset the submodule to the commit recorded in the superproject.
./apps/ios/scripts/build-rust.shUseful modes:
./apps/ios/scripts/build-rust.sh --fast-device
./apps/ios/scripts/build-rust.sh--fast-devicebuilds the raw device staticlib and generated headers only.- Default/package mode builds device + Apple Silicon simulator slices and also creates
apps/ios/Frameworks/codex_mobile_client.xcframework. - Pass
--with-intel-simonly if you need an Intel Mac simulator slice too.
This script:
- Preserves the current
shared/third_party/codexcheckout by default, applies the local Codex patch set for the build, and restores the prior patch state afterward - Regenerates UniFFI Swift bindings when public Rust boundary inputs change
- Builds
shared/rust-bridge/codex-mobile-clientfor device and/or simulator targets - Writes raw artifacts to:
apps/ios/GeneratedRust/Headersapps/ios/GeneratedRust/ios-device/libcodex_mobile_client.aapps/ios/GeneratedRust/ios-sim/libcodex_mobile_client.a
- In package mode, also repackages
apps/ios/Frameworks/codex_mobile_client.xcframework
Debug/device Xcode builds link the raw .a from apps/ios/GeneratedRust/ios-device/, not the xcframework.
Regenerate project if apps/ios/project.yml changed:
./apps/ios/scripts/regenerate-project.shOpen in Xcode:
open apps/ios/Litter.xcodeprojCLI build example:
xcodebuild -project apps/ios/Litter.xcodeproj -scheme Litter -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 17 Pro' buildPrerequisites:
- Java 17
- Android SDK + build tools for API 35
- Gradle 8.x (or use
apps/android/gradlew)
Open in Android Studio (macOS):
open -a "Android Studio" apps/androidRebuild and reopen Android project:
./apps/android/scripts/rebuild-and-reopen.shBuild Android flavors:
gradle -p apps/android :app:assembleOnDeviceDebug :app:assembleRemoteOnlyDebugRun Android unit tests:
gradle -p apps/android :app:testOnDeviceDebugUnitTest :app:testRemoteOnlyDebugUnitTestStart emulator and install on-device debug build:
ANDROID_SDK_ROOT=/opt/homebrew/share/android-commandlinetools \
$ANDROID_SDK_ROOT/emulator/emulator -avd litterApi35
adb -e install -r apps/android/app/build/outputs/apk/onDevice/debug/app-onDevice-debug.apk
adb -e shell am start -n com.sigkitten.litter.android/com.litter.android.MainActivityBuild Android Rust JNI libs (optional bridge runtime step):
./tools/scripts/build-android-rust.sh- Authenticate
asconce with your App Store Connect API key:
asc auth login \
--name "Litter ASC" \
--key-id "<KEY_ID>" \
--issuer-id "<ISSUER_ID>" \
--private-key "$HOME/AppStore.p8" \
--network- Bootstrap TestFlight defaults (internal group, optional review contact metadata):
APP_BUNDLE_ID=<BUNDLE_ID> \
./apps/ios/scripts/testflight-setup.sh- Build and upload to TestFlight:
APP_BUNDLE_ID=<BUNDLE_ID> \
APP_STORE_APP_ID=<APP_STORE_CONNECT_APP_ID> \
TEAM_ID=<APPLE_TEAM_ID> \
ASC_KEY_ID=<KEY_ID> \
ASC_ISSUER_ID=<ISSUER_ID> \
ASC_PRIVATE_KEY_PATH="$HOME/AppStore.p8" \
MARKETING_VERSION=1.0.0 \
./apps/ios/scripts/testflight-upload.shNotes:
testflight-upload.shauto-increments build number from the latest App Store Connect build.- It archives, exports an IPA, uploads via
asc builds upload, and assigns the build toInternal Testersby default. - Override
SCHEMEif needed (default isLitter).
apps/ios/project.yml: source of truth for Xcode project/schemesshared/rust-bridge/codex-mobile-client/: single Rust mobile client crate and UniFFI surfaceshared/rust-bridge/codex-ios-audio/: iOS-only audio/AEC crateshared/third_party/codex/: upstream Codex source (submodule)patches/codex/: local Codex patch set applied to the submodule during Rust/iOS buildsapps/ios/GeneratedRust/: generated UniFFI headers/modulemap and raw iOS staticlibs used by Debug/device buildsapps/ios/Sources/Litter/Bridge/: Swift bridge + JSON-RPC clientapps/android/app/src/main/java/com/litter/android/ui/: Android Compose UI shell and screensapps/android/app/src/main/java/com/litter/android/state/: Android state, transports, session/server orchestrationapps/android/core/bridge/: Android bridge bootstrap and core websocket clientapps/android/core/network/: discovery servicesapps/android/app/src/test/java/: Android unit tests (runtime mode + transport policy scaffolding)apps/android/docs/qa-matrix.md: Android parity checklisttools/scripts/build-android-rust.sh: builds Android JNI Rust artifacts intojniLibsapps/ios/Sources/Litter/Resources/brand_logo.svg: source logo (SVG)apps/ios/Sources/Litter/Resources/brand_logo.png: in-app logo image used byBrandLogoapps/ios/Sources/Litter/Assets.xcassets/AppIcon.appiconset/: generated app icon set
- Home/launch branding uses
BrandLogo(apps/ios/Sources/Litter/Views/BrandLogo.swift) backed bybrand_logo.png. - The app icon is generated from the same logo and stored in
AppIcon.appiconset. - If logo art changes, regenerate icon sizes from
Icon-1024.png(or re-run your ImageMagick resize pipeline) before building.




