VibeMobile is the Flutter companion for VibeCody — a full phone/tablet/desktop client that pairs with a running VibeCLI or VibeCoder instance, streams AI sessions in real time, and hands the session back and forth between devices.
As of v0.5.5 VibeMobile gained URL-only pairing, zero-config mDNS / Tailscale / ngrok discovery, Apple-Handoff-style continuity with desktop and watch clients, and Google-Docs-style full-content sync (no more 80/512-char truncation).
Platforms: iOS, Android, macOS, Linux, Windows, Web — one Flutter codebase.
What’s new in 0.5.5
| Area | Improvement |
|---|---|
| Pairing | URL-only / URL + Bearer pairing — no QR code or JSON copy required; works on emulators |
| Auth | P-256 ECDSA (Keystore / StrongBox / Secure Enclave) replaces Ed25519 |
| Discovery | mDNS LAN (_vibecli._tcp.local.), Tailscale Funnel, ngrok auto-detect — client races all reachable paths |
| Continuity | Handoff banner auto-appears when desktop or watch opens a session |
| Sync | Google-Docs-style full-content reconciliation with ID-based dedup — no truncation |
| Session tree | Sandbox tab auto-surfaces when a paired watch starts a sandbox session |
Platform requirements
| Target | Minimum |
|---|---|
| iOS | 13.0+ (deployment target raised from 12.0 in v0.5.5) |
| Android | 7.0 / API 24+ |
| macOS | 12.0+ |
| Linux / Windows | GTK 3 / Edge WebView2 |
| Build toolchain | Flutter 3.44.2 (CI-pinned, floor ≥ 3.8.0), Xcode 26 (App Store submissions after 2026-04-28) |
Android build SDK —
compileSdk = 37from v0.5.8.permission_handler_android, pulled in by the voice-input microphone permission, declares an AAR metadata minimum of API 37, and:app:checkReleaseAarMetadatafails the release build against anything lower.compileSdkonly selects which APIs are available at compile time —minSdk(API 24) andtargetSdkare unchanged, so the supported device range is the same. VibeCodyWear is already on 37 for a comparable reason (see Wear OS).
Install
From release artifacts (fastest)
See the Releases page for the current build. For v0.5.5:
| Platform | Artifact |
|---|---|
| iOS | VibeCody-Mobile-v0.5.7-ios.ipa (unsigned — sideload via AltStore / Sideloadly) |
| Android APK | VibeCody-Mobile-v0.5.7-android.apk |
| Android AAB | VibeCody-Mobile-v0.5.7-android.aab |
From source
# Prerequisites: Flutter ≥ 3.2.0, Xcode (iOS/macOS), Android Studio + SDK (Android)
cd vibemobile
flutter pub get
flutter run # launches on the currently-selected device
# Release builds
make -C vibemobile ios-ipa # signed .ipa (needs APPLE_TEAM_ID)
flutter build apk --release # Android APK
flutter build appbundle --release # Android AAB for Play Store
flutter build macos --release # macOS .app
flutter build linux --release # Linux bundle
flutter build windows --release # Windows .exe
flutter build web --release # static web bundle
Pair with VibeCody in 30 seconds
Pairing produces a short-lived challenge, the phone signs it with its Keystore/Secure-Enclave P-256 key, and the daemon returns a JWT.
Path A — QR code (desktop nearby)
- Desktop: start the daemon.
vibecli --serve --port 7879 - Desktop: in the REPL or VibeCoder
Governance → Watch Devices, click Pair Device. - Phone: open VibeMobile → Pair → scan the QR code. Done.
Path B — URL only (new in 0.5.5, works on emulators)
- Desktop: run
/pair --url-onlyin VibeCLI (or click Show URL in VibeCoder). - Phone: VibeMobile → Pair → Paste URL → confirm.
The URL encodes host:port + a one-time bearer token. No clipboard JSON is needed.
Path C — URL + Bearer (manual, air-gapped)
- Desktop:
/pair --show-bearerprints two fields. - Phone: VibeMobile → Pair → Manual → paste URL and bearer.
All three paths produce the same JWT. Sessions authenticate with Authorization: Bearer … afterwards.
Home tabs
| Tab | Purpose |
|---|---|
| Machines | All paired desktops/laptops. Health, CPU/memory, active provider/model, session count |
| Sessions | Live agent + chat sessions across all machines; tap to stream |
| Sandbox | Auto-focused when a paired watch or desktop opens a sandbox session |
| Chat | Direct remote chat with any provider configured on the paired host |
| Settings | Pairing, notifications, mDNS toggle, Tailscale / ngrok preferences |
The Handoff banner appears at the top of any tab when another paired device is active in the same session — tap to pull the session onto the phone.
Connectivity paths
VibeMobile races all reachable paths on every request and picks the fastest one:
- mDNS LAN — discovers
_vibecli._tcp.local.on any IP range; no setup. - Tailscale — if the phone is on your tailnet, the 100.x address is used; if the host has Funnel enabled, a public HTTPS URL is shared.
- ngrok — auto-detected if already running on the host; opt-in auto-start requires an auth token in VibeCLI settings.
- Manual host:port — always available as a fallback under Settings → Manual Connection.
See the full Connectivity guide for firewall / NAT / corporate-network notes.
Chat & sessions
- Streaming. Every message streams token-by-token over Server-Sent Events. Reconnects automatically on flaky networks.
- No truncation. The 0.5.5 sync model reconciles by message ID and keeps the full transcript — even long code blocks survive the round trip.
- Markdown. Fenced code blocks render with syntax highlighting; copy button per block.
- Voice. Tap the microphone on the chat input to dictate. The on-device recogniser runs first (partial text as you speak, nothing leaves the phone); if it is unavailable the clip is recorded and uploaded to the daemon’s
POST /voice/transcribe, which prefers a locally downloaded whisper model and falls back to Groq. Needs microphone permission on both platforms. - Provider picker. Switch providers or models mid-conversation from the session header.
Handoff continuity
When you’re actively using a session on another paired device (desktop, watch, or another phone), VibeMobile surfaces a Handoff chip at the top of the current screen:
↻ ravi's iMac — Session #1287 · "refactor axum routes"
[ Pull session ] [ Mirror ] [ Dismiss ]
- Pull — transfers stream ownership. The other device goes read-only.
- Mirror — keeps both devices writing; the Google-Docs-style sync reconciles edits.
- Dismiss — ignores, but the chip reappears if the other device is still active.
Secure storage
Credentials never touch plaintext on disk:
| Platform | Backing store |
|---|---|
| iOS | Keychain Services (kSecClassGenericPassword, access group per-app) |
| Android | EncryptedSharedPreferences, key material in Android Keystore / StrongBox where available |
| macOS | Keychain |
| Linux | Secret Service (libsecret) |
| Windows | DPAPI via flutter_secure_storage |
| Web | IndexedDB + WebCrypto; clears on cache wipe |
Notifications
| Trigger | Default | How to change |
|---|---|---|
| Agent task complete | On | Settings → Notifications |
| Security alert (policy violation) | On | — |
| Machine goes offline | On | — |
| New message from another device (Handoff) | Off | Toggle on if you want push-style Handoff |
iOS notifications require granting permission on first launch. Android 13+ requires POST_NOTIFICATIONS runtime permission.
Troubleshooting
Pairing QR code won’t scan
- Dim your display, then try again — QR codes need contrast.
- Switch to Path B (URL only) — paste the URL instead.
- Check the phone can reach
host:port(try a browser).
mDNS discovery doesn’t find the host
- Some corporate networks block multicast. Fall back to manual host:port.
- Verify
vibecli --serveis running and bound to0.0.0.0(not127.0.0.1).
“Session desynced” banner
This is the reconciliation catch-up signaling a missed event. Tap Reload — the full transcript is fetched and merged.
Wear OS / watchOS continuity not working
The watch must be paired to the same host as the phone. Check Governance → Watch Devices on the desktop.
API endpoints
All calls require Authorization: Bearer <jwt> after pairing. See VibeCLI Server Mode for the full reference.
| Endpoint | Method | Purpose |
|---|---|---|
/health |
GET | Liveness |
/status |
GET | Active provider/model |
/pair/challenge |
POST | Issue pairing nonce |
/pair/confirm |
POST | Submit signed attestation, receive JWT |
/mobile/beacon |
POST | Heartbeat + presence for Handoff routing |
/sessions |
GET | List sessions across all machines |
/sessions/{id}/stream |
GET (SSE) | Stream messages |
/sessions/{id}/reply |
POST | Append a user reply |
/chat |
POST | One-shot chat |
Related reading
- VibeCLI reference — full REPL + server mode
- Connectivity guide — mDNS, Tailscale, ngrok
- Watch Integration — how VibeMobile relays watch sessions
- Apple Watch guide · Wear OS guide