I've been using Mistral's Vibe CLI for a while now. It's a coding agent that lives in your terminal: you point it at a project and it starts working — the same loop as Claude Code or Codex, but Mistral's take on it.
The pattern that kept biting me: the agent runs for a long time. It plans, edits a file, runs the test suite, hits a permission prompt, waits. Sometimes I'm at my desk and that's fine. Often I'm not — I stepped out for coffee, I'm cooking, I'm halfway across the room — and the agent is just sitting there, blocked on me, until I come back.
Anthropic shipped Dispatch — pair your phone to your desktop Claude session, hand off tasks, watch them run. OpenAI has the Codex web companion. Cursor, GitHub, Replit — every US lab has wired their CLI agent to a phone or a browser so you can babysit it from anywhere.
Mistral hasn't. Mistral is the biggest AI company in Europe and the one I actually want to root for from where I sit, and on the mobile side there's nothing. So I built Vibe Mobile.
What it does
You open the app on your phone, scan a QR code printed by a small desktop agent running next to your Vibe CLI session, and you're paired. From the phone you can:
Send messages into the running session, watch tool calls stream in with collapsible detail cards, approve or deny permission prompts, and get a push notification when the agent is blocked on you. The desktop session keeps running where it always did. The phone is just a remote control for it.
How it's wired up
There are four pieces, in a single pnpm monorepo:
iPhone ← Socket.IO → Relay Server ← Socket.IO → Desktop Agent → vibe-acp
packages/desktop-agent is a tiny Node CLI you start in your project directory. It spawns vibe-acp (Mistral's agent over the Agent Client Protocol) and forwards everything to the relay. packages/relay-server is a Fastify + Socket.IO server with PostgreSQL behind it — the only piece with a public address, so the phone and desktop don't need to find each other on the network. packages/mobile is the Expo iOS app. packages/wire is the shared TypeScript package that defines every message that flies over the wire so the three runtimes can't disagree about the schema.
I'll be honest: the boring reason for splitting it like this is that Expo's Metro bundler and a Node CLI have nothing in common, and forcing them to share a single tsconfig was making my life worse than the duplication ever could. Three runtimes, one shared types package, one source of truth.
The part I actually care about: the relay sees nothing
The interesting design question on something like this isn't "how do you relay messages." It's "what does the relay get to know."
A naive version of this app would push your code, your prompts, and your tool calls through a server I run. You'd have to trust me, my server, my database, my backups, and any future intern with read access. That trust is hard to earn and very easy to lose.
So Vibe Mobile uses a blind relay. The server forwards opaque blobs and stores opaque blobs. It never sees plaintext.
The scheme is the standard hybrid one, but worth describing because it's the bit that decides whether the whole project is actually trustworthy:
When the desktop agent starts a session it generates a random 32-byte AES key — the data key. This is the only key that can read the session's messages. The phone has its own X25519 keypair stored on-device; its public key reaches the desktop agent through the QR code at pairing time. The desktop then wraps the data key for the phone using libsodium's crypto_box_seal — a sealed-box construction over X25519 — and the wrapped key travels through the relay to the phone, which unwraps it locally with its secret key.
From then on, every message in either direction is AES-256-GCM with that data key, with a fresh 12-byte random nonce per message. The relay sees version || nonce || ciphertext+tag, base64'd, and nothing else.
The whole thing is about thirty lines of cryptographic code, almost all of it from libsodium and Web Crypto. It's intentionally boring. The relay's database can leak tomorrow and the messages stay opaque, because the data key never sat in it.
The surface that remains is metadata — session IDs, timestamps, message sizes. That's the honest answer. If you need even that surface gone, the relay is small enough that you can self-host it on Fly or Railway, set DATABASE_URL and AUTH_SECRET, and call it a day.
Where it is right now
The iOS app is in review with Apple as I write this. I'll edit this post with a TestFlight or App Store link the moment it lands.
The repo lives at github.com/catancs/vibe-mobile. If you have Node 22+, pnpm, and Postgres, you can clone it and run the relay and desktop agent locally today.
pnpm install
pnpm --filter relay-server db:push
pnpm dev:relay # in one terminal
pnpm dev:agent /path/to/your/project # in another
That second command prints a session ID and a QR code. Once the iOS app is live, you scan it and you're connected.
Why I'm sharing this
The story of CLI-first AI tooling has been written almost entirely in California — the companions, the dashboards, the mobile apps, the cloud-coding web UIs all came from labs in San Francisco, all aimed at their own models. I think it's worth one of those existing for Mistral too, ideally written by someone who actually uses Vibe and lives a flight away from Paris.
If you use Vibe CLI, this is for you. If you don't, but you've ever wanted to approve a permission prompt from your phone, this might still be for you. It's open source, it's MIT, and the encryption story above is the only thing I'm asking you to trust.
If you want to check it out, the code is on GitHub.