packages/docs/apps/mobile/build-guide.md
The Eliza mobile app (packages/app) is a Capacitor project that wraps the shared web UI in a native shell. Building it requires three steps: compiling the workspace Capacitor plugins, bundling the Vite web assets, and syncing them into the native iOS or Android project. Distribution builds additionally require code signing — Apple certificates and provisioning profiles for iOS, a keystore for Android.
All build commands are invoked via bun run from inside the packages/app directory.
build:ios) and Android (build:android) that compile plugins, bundle assets, and sync to the native project in one stepplugin:build) for faster iteration when only plugin code has changedcap:open:ios and cap:open:androidPrerequisites by platform:
| Requirement | iOS | Android |
|---|---|---|
| Operating system | macOS only | macOS, Linux, or Windows |
| IDE | Xcode 15+ | Android Studio (recent) |
| SDK | iOS platform tools via Xcode | Android SDK API 35 via SDK Manager |
| Dependency manager | CocoaPods (sudo gem install cocoapods) | JDK 17+ (bundled with Android Studio) |
| Apple Developer account | Required for device/distribution builds | — |
| Keystore file | — | Required for release APK/AAB signing |
Build commands:
# From packages/app — build everything and sync to iOS
bun run build:ios
# Build everything and sync to Android
bun run build:android
# Build custom Capacitor plugins only
bun run plugin:build
# Push already-built web assets to both native projects
bun run cap:sync
# Open native project in IDE
bun run cap:open:ios # Xcode
bun run cap:open:android # Android Studio
iOS signing: Open packages/app-core/platforms/ios/App/App.xcworkspace in Xcode, select the App target, go to Signing & Capabilities, and choose your development team. For App Store distribution, select a distribution certificate and a matching provisioning profile.
Android signing: Create a release keystore and configure it in packages/app-core/platforms/android/app/build.gradle under signingConfigs. Use ./gradlew bundleRelease (AAB for Play Store) or ./gradlew assembleRelease (APK for direct distribution) from the Android platform directory.
The iOS bundle supports three runtime modes. Use the root helper scripts so the Vite environment, native sync, CocoaPods, and Xcode project overlay stay aligned.
The iOS target is one app. The first onboarding screen chooses the connection mode: remote Mac, Eliza Cloud, or Eliza Cloud plus donated phone compute. The mode-specific commands below only pre-seed development builds so Xcode opens with the expected defaults; users can still change the connection mode in onboarding.
Expose the Eliza API on the Mac's LAN address, then build/open the iOS project with the phone pointed at that API:
ELIZA_API_BIND=0.0.0.0 \
ELIZA_API_TOKEN=replace-with-a-short-lived-token \
ELIZA_ALLOWED_ORIGINS=capacitor://localhost,ionic://localhost \
bun run dev
ELIZA_IOS_REMOTE_API_BASE=http://192.168.1.42:31337 \
ELIZA_IOS_REMOTE_API_TOKEN=replace-with-the-same-token \
bun run dev:ios:remote-mac
If ELIZA_IOS_REMOTE_API_BASE is omitted, the helper picks the first non-loopback IPv4 address and port 31337. Run from Xcode with an Apple development team selected to install on a physical phone.
Build the bundled iOS shell with the cloud runtime defaults, then select Eliza Cloud in the first onboarding view:
bun run dev:ios:cloud
Set ELIZA_IOS_CLOUD_BASE or VITE_ELIZA_CLOUD_BASE only when targeting a non-default Eliza Cloud environment.
Cloud-hybrid mode keeps the app on the cloud runtime and starts the existing device bridge so eligible local-inference work can route to the phone through the server-side routing preferences. Select the Cloud + phone compute option in the first onboarding view.
ELIZA_DEVICE_BRIDGE_ENABLED=1 \
ELIZA_DEVICE_PAIRING_TOKEN=replace-with-a-short-lived-token \
bun run dev
ELIZA_IOS_DEVICE_BRIDGE_API_BASE=https://agent-or-tunnel.example.com \
ELIZA_IOS_DEVICE_BRIDGE_TOKEN=replace-with-the-same-token \
bun run dev:ios:cloud-hybrid
ELIZA_IOS_DEVICE_BRIDGE_API_BASE derives wss://.../api/local-inference/device-bridge. Use ELIZA_IOS_DEVICE_BRIDGE_URL when the bridge lives at a different URL. The server still decides which slots use the paired device through Local models routing; the phone does not override cloud routing on its own.
Live reload lets you see web-layer changes on a physical device or simulator without rebuilding native code. Capacitor achieves this by loading the app from your local Vite dev server instead of the bundled assets.
Setup:
Find your machine's local IP address (e.g., 192.168.1.42). On macOS, check System Settings → Wi-Fi → Details → IP Address, or run ipconfig getifaddr en0.
Edit packages/app/capacitor.config.ts and add a server block pointing at the Vite dev server. This repo's Vite UI defaults to port 2138 (or ELIZA_PORT if you override it), not Vite's stock 5173:
const config: CapacitorConfig = {
// ...existing config
server: {
url: "http://192.168.1.42:2138",
cleartext: true, // required for plain HTTP on Android
},
};
bun run dev
bun run cap:sync
bun run cap:open:ios # or cap:open:android
Step-by-step debug build:
bun run build:ios
open packages/app-core/platforms/ios/App/App.xcworkspace
In Xcode, select the App target, go to Signing & Capabilities, and choose your development team.
Select your target device or simulator from the device toolbar.
Press Cmd+R (Product → Run) to build and launch a debug build.
Distribution via TestFlight:
Common issue — "No signing certificate":
This means Xcode cannot find a valid development or distribution certificate. Fix it by navigating to Xcode → Settings → Accounts, selecting your Apple ID, clicking your team, then Manage Certificates. Click the + button to create a new Apple Development certificate. For distribution, create an Apple Distribution certificate through the Apple Developer portal.
Step-by-step debug build:
bun run build:android
# Or use the helper:
bun run cap:open:android
Wait for Gradle sync to complete. Android Studio will download dependencies and index the project. This can take several minutes on first open.
Select your target device or emulator from the device dropdown in the toolbar.
Click Run → Run 'app' (or press Shift+F10) to install and launch a debug build.
Release builds:
For Google Play Store distribution (AAB format):
cd packages/app-core/platforms/android && ./gradlew bundleRelease
For direct distribution (APK format):
cd packages/app-core/platforms/android && ./gradlew assembleRelease
Release signing setup:
keytool -genkey -v -keystore release.keystore -alias eliza -keyalg RSA -keysize 2048 -validity 10000
packages/app-core/platforms/android/app/build.gradle under signingConfigs:android {
signingConfigs {
release {
storeFile file("release.keystore")
storePassword "your-store-password"
keyAlias "eliza"
keyPassword "your-key-password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
Automated builds for both platforms can run in headless environments without opening an IDE.
iOS (macOS CI runners only):
xcodebuild -workspace ios/App/App.xcworkspace \
-scheme App \
-configuration Release \
-archivePath build/App.xcarchive \
archive
Export the archive with an ExportOptions.plist that specifies your distribution method and provisioning profile.
Android:
cd packages/app-core/platforms/android && ./gradlew bundleRelease --no-daemon
The --no-daemon flag ensures Gradle does not leave background processes on ephemeral CI runners.
Secrets management:
build.gradle."Pod install failed"
CocoaPods cannot resolve dependencies, often due to a stale spec repo. Run:
cd packages/app-core/platforms/ios/App && pod install --repo-update
If the issue persists, delete Podfile.lock and the Pods/ directory, then run pod install again.
"Android SDK not found"
The build cannot locate the Android SDK. Set the ANDROID_HOME environment variable:
# macOS / Linux (add to ~/.zshrc or ~/.bashrc)
export ANDROID_HOME="$HOME/Library/Android/sdk"
export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH"
Then restart your terminal and retry the build.
"Plugin build failed"
One or more custom Capacitor plugin TypeScript sources failed to compile. Isolate the error by building plugins separately:
bun run plugin:build
Review the TypeScript compiler output to identify which plugin has the error. Fix the TypeScript issue, then re-run the full build.
"White screen on device"
The native shell launched but no web content is visible. This usually means web assets were not synced to the native project. Run:
bun run cap:sync
Then rebuild and re-run from the IDE. Also verify that the server override in capacitor.config.ts is removed if you previously used live reload.
"Permission denied" on macOS
macOS quarantine flags can prevent Xcode from accessing iOS project files downloaded or generated by tools. Clear them:
xattr -cr packages/app-core/platforms/ios/
Then re-open the workspace in Xcode.