Chat-first remote control for Codex, Claude Code, and soon Gemini on any server.
Run AI coding agents over SSH from your phone, keep sessions alive on the host, and switch between chat, diffs, terminal, and file browsing without leaving the app.
Remote hosts | Chat UX | Diff viewer | Terminal | File browser
OpenVide is a React Native mobile app for running AI coding CLIs on remote machines over SSH. A lightweight daemon runs on each host to relay commands and persist sessions so work survives reconnects, backgrounding, and network drops.
- Connect to remote hosts over SSH (password or key-based auth)
- Detect which AI CLIs are installed on the host (Claude, Codex, Gemini)
- Install, update, or remove supported CLIs directly from the app
- Run multi-turn AI sessions — send prompts, stream output live, resume conversations
- Persist sessions on the remote host so work isn't lost when you disconnect
- Push notifications when sessions complete (even if the app is killed)
- Store credentials securely on-device (SSH keys via expo-secure-store)
- Node.js >= 18
- Yarn 4 (the repo uses Corepack — run
corepack enableif needed) - EAS CLI for cloud builds:
npm install -g eas-cli - Xcode (iOS) or Android Studio (Android) for local builds
- An Expo account (free) at expo.dev
git clone https://github.com/open-vide/openvide.git
cd openvide
yarn installThen follow the setup steps below to configure your local environment before building.
openvide/
apps/
app/ React Native mobile app (Expo SDK 54)
daemon/ openvide-daemon (Node.js relay, runs on remote machines)
package.json Yarn workspaces + Turborepo root
Several files are gitignored because they contain personal identifiers (bundle IDs, EAS project IDs, Firebase keys). You need to create them before building.
The app supports two build variants: production and development. Each needs a config.json with your own bundle identifiers.
cd apps/app
# Copy the example configs
cp variants/production/config.example.json variants/production/config.json
cp variants/development/config.example.json variants/development/config.jsonEdit each config.json with your values:
{
"displayName": "OpenVide",
"iosBundleIdentifier": "com.yourorg.openvide",
"androidPackage": "com.yourorg.openvide",
"scheme": "openvide",
"splashBackgroundColor": "#FFFFFF"
}You also need to provide icon and splash assets for each variant:
variants/production/icon.png(1024x1024 app icon)variants/production/splash.png(splash screen image)variants/production/splash-animation.json(Lottie animation for animated splash)- Same for
variants/development/
cp apps/app/.env.example apps/app/.envEdit apps/app/.env:
# Apple Development Team ID (required for iOS builds)
APP_DEVELOPMENT_TEAM=YOUR_TEAM_ID
# EAS project ID (fallback — prefer app.json, see step 3)
EXPO_PROJECT_ID=
# Set to "1" to enable push notifications on iOS (requires paid Apple Developer account).
# Leave unset for free/personal accounts — the app builds and works without push.
# Android push is enabled automatically when google-services.json is present.
# ENABLE_PUSH_NOTIFICATIONS=1Find your Apple Team ID in Xcode → Settings → Accounts, or at developer.apple.com.
The app uses EAS (Expo Application Services) for cloud builds, OTA updates, and push notifications. Each contributor needs their own EAS project.
cd apps/app
# Create or link an EAS project (select your account/org when prompted)
eas initThis creates apps/app/app.json with your project's owner, name, slug, and projectId. This file is gitignored — see app.json.example for the format.
If eas init fails to write to the dynamic config, create app.json manually:
{
"expo": {
"owner": "your-expo-username-or-org",
"name": "Your App Name",
"slug": "your-app-slug",
"extra": {
"eas": {
"projectId": "your-eas-project-id"
}
}
}
}Push notifications are entirely optional. The app builds and works without them. Skip this step if you don't need notifications when sessions complete.
Android — requires Firebase Cloud Messaging:
- Create a project at Firebase Console
- Add Android apps with your package names (e.g.
com.yourorg.openvideandcom.yourorg.openvide.dev) - Download
google-services.jsonand place it atapps/app/google-services.json - In Firebase → Project Settings → Service accounts → Generate new private key (downloads a JSON file)
- Upload the service account key to your Expo project:
cd apps/app eas credentials --platform android # Select your build profile → Push Notifications → FCM V1 → upload the JSON
Android push is enabled automatically when google-services.json is present. Without it, builds succeed normally — push just won't work.
iOS — requires a paid Apple Developer account:
Free/personal Apple Developer accounts don't support the Push Notifications capability, so it's disabled by default. To enable it, set in your .env:
ENABLE_PUSH_NOTIFICATIONS=1Then prebuild:
ENABLE_PUSH_NOTIFICATIONS=1 yarn prebuild:cleanWithout this flag, the push entitlement is stripped from iOS builds so they work on any Apple Developer account.
Requires Xcode (iOS) or Android Studio (Android) installed locally.
cd apps/app
# Generate native projects
yarn prebuild:clean # production variant
yarn prebuild:dev:clean # development variant
# Run on device/simulator
yarn ios # iOS (production)
yarn ios:dev # iOS (development)
yarn android # Android (production)
yarn android:dev # Android (development)
# Start Metro bundler (if not auto-started)
yarn startBuilds are submitted to Expo's build service. No local Xcode/Android Studio required.
cd apps/app
# Android (outputs .apk for dev/preview, .aab for production)
yarn build:dev # development client
yarn build:preview # internal testing
yarn build:prod # production
# iOS
yarn build:dev:ios
yarn build:preview:ios
yarn build:prod:iosPush JavaScript bundle updates to devices without a full rebuild:
cd apps/app
yarn update:dev # development channel
yarn update:preview # preview channel
yarn update:prod # production channelThe daemon runs on each remote machine you want to control. It needs Node.js >= 18.
# From the repo root
yarn install
yarn daemon:buildBuild output is in apps/daemon/dist. To install globally on a remote machine:
cd apps/daemon && npm install -g .
openvide-daemon health # auto-starts the daemon and returns statusThe app can also install the daemon for you — go to a host's detail screen and tap "Install Daemon".
The app never talks to CLI tools directly. Instead, it communicates with the daemon on each remote machine over SSH:
Mobile App → SSH exec → openvide-daemon CLI → Unix socket IPC → Daemon process
↓
spawns CLI tool
(claude / codex / gemini)
↓
stdout/stderr captured
line-by-line → output.jsonl
↓
App ← SSH stdout ← openvide-daemon session stream --follow ← tails output.jsonl
- The app opens an SSH connection to your host and runs
openvide-daemoncommands. - Sessions are created per workspace and tool, then prompts are sent to the daemon.
- The daemon spawns the CLI tool, captures output line-by-line into JSONL files.
- The app tails streamed output over SSH and renders parsed events in a chat-like UI.
- For multi-turn conversations, the daemon tracks conversation IDs (
session_idfor Claude,thread_idfor Codex) and passes them back on subsequent turns.
The daemon (openvide-daemon) is a zero-dependency Node.js process that runs persistently on each remote machine.
- Self-daemonizes: auto-starts on first CLI invocation, no manual setup needed.
- No open ports: communicates only via a local Unix domain socket (
~/.openvide-daemon/daemon.sock). All app-to-daemon communication goes through CLI commands executed over SSH. - Persistent state: sessions, output logs, and metadata live in
~/.openvide-daemon/. - Heartbeat: PID file is touched every 30s; considered stale after 60s.
- Graceful shutdown: sends SIGTERM to child processes, SIGKILL after 5s timeout.
| Command | Description |
|---|---|
session.create |
Create a session record and output directory |
session.send |
Spawn the CLI tool process, begin capturing output |
session.stream --follow |
Tail output.jsonl in real-time (reads file directly, bypasses IPC) |
session.cancel |
SIGINT → 3s grace → SIGTERM |
session.get/list/remove |
CRUD on session records |
config set-push-token |
Register Expo push token for notifications |
health |
PID, uptime, session counts |
stop |
Graceful shutdown |
Each line is one of three types:
{ t: "o", ts, line }— stdout from the CLI tool{ t: "e", ts, line }— stderr from the CLI tool{ t: "m", ts, event, ... }— meta events:turn_start,turn_end(with exit code),error
# From repo root — checks both app and daemon
yarn checkexpo prebuild fails with missing variant config
You haven't created variants/production/config.json (or development). Copy from the .example.json files — see Setup step 1.
eas build fails with "Cannot automatically write to dynamic config"
Create apps/app/app.json manually — see Setup step 3.
eas build fails with Yarn/Corepack version error
The eas.json sets COREPACK_ENABLE_STRICT=0 to handle this. If you still hit issues, make sure you're using the latest EAS CLI: npm install -g eas-cli@latest.
iOS build fails with "Personal development teams do not support Push Notifications"
Make sure ENABLE_PUSH_NOTIFICATIONS is not set in your .env (or is empty). By default, push is disabled on iOS so free/personal accounts can build. See Setup step 4.
Push notifications return "InvalidCredentials"
The FCM V1 Service Account Key is missing or uploaded to the wrong EAS project. Run eas credentials --platform android and check that FCM V1 is set up under the correct application identifier (matching your androidPackage in the variant config).



