oppi-server
v0.44.0
Published
Self-hosted server for the Oppi mobile coding agent
Maintainers
Readme
oppi-server
Server for Oppi. Embeds the Pi SDK for server-owned sessions and supports terminal-owned mirror sessions through the oppi-mirror Pi extension.
Quickstart
npm global install
npm install -g oppi-server
oppi --version
oppi serveOn first serve, Oppi creates ~/.config/oppi/, generates owner credentials,
bootstraps local HTTPS/WSS with tls.mode=self-signed, and prints a pairing QR
plus invite link for the iPhone/iPad app. Use oppi pair later to generate a
fresh single-use invite.
Upgrade or uninstall the global CLI with npm:
npm install -g oppi-server@latest
npm uninstall -g oppi-serverSource checkout install
Use a source checkout only for development or unreleased server changes. For regular use, prefer the npm global install above.
git clone https://github.com/duh17/oppi.git && cd oppi/server
npm install
npm run build
node dist/src/cli.js serveIf you prefer the repo bootstrapper from outside the repo, use:
curl -fsSL https://raw.githubusercontent.com/duh17/oppi/main/install.sh | bashEquivalent explicit steps:
git clone https://github.com/duh17/oppi.git
cd oppi
bash install.shUse this command only if you need to switch back to self-signed later:
oppi config set tls '{"mode":"self-signed"}'Optional: enable Tailscale HTTPS/WSS (Let's Encrypt cert via tailscale cert):
oppi config set tls '{"mode":"tailscale"}'Create a workspace in the app and start a session.
Requirements
- Node.js 23.6+
- Pi runtime dependency, installed automatically with the npm package
- At least one Pi provider configured with
pi author an API key such asANTHROPIC_API_KEY - macOS or Linux
- OpenSSL on PATH for
tls.mode=self-signedcertificate generation
Docker (skills-ready compose setup)
A containerized setup is included in this directory:
Dockerfiledocker-compose.ymldocker/entrypoint.sh
The container runs oppi serve, persists state in Docker volumes, and seeds Pi auth and skills from your host on first start. Mounting the Docker socket is optional and only needed for Docker-backed skill wrappers.
Quick start:
cd server
# Optional: host/ip or tailnet host encoded into pairing links
export OPPI_PAIR_HOST=$(ipconfig getifaddr en0 || ipconfig getifaddr en1)
# export OPPI_PAIR_HOST=<machine>.<tailnet>.ts.net
# Optional: choose container/server port (default 7750 to avoid host conflicts)
export OPPI_PORT=7750
# Optional: host-side SearXNG endpoint for search skill
export SEARXNG_URL=http://host.docker.internal:8888
# Optional: override host paths
# export PI_AGENT_DIR="$HOME/.pi/agent"
# export DOTFILES_DIR="$HOME/.config/dotfiles"
docker compose up -d --buildWhat it does:
- runs
node dist/src/cli.js serveas PID 1 in container - auto-restarts via
restart: unless-stopped - binds host
${OPPI_PORT:-7750}to the same in-container port - persists server state in Docker volume
oppi-data(/data/oppi) - persists runtime Pi state in Docker volume
pi-agent-data(/data/pi-agent) - seeds Pi auth/skills/extensions from host
${PI_AGENT_DIR}into container (copy-onceby default) - exposes host-side SearXNG via
SEARXNG_URL(default:http://host.docker.internal:8888) - mounts Docker socket so in-session wrappers can reach sibling containers (e.g.
web-toolkit)
Important security note:
- Mounting
/var/run/docker.sockgives the container root-equivalent host control. - Keep this only if you need Docker-backed skill wrappers (
web-nav,web-eval,web-screenshot, etc.).
Useful commands:
# Logs (watch startup + pairing hints)
docker compose logs -f oppi-server
# Health
curl -sk "https://127.0.0.1:${OPPI_PORT:-7750}/health"
# Verify SearXNG reachability from inside container
docker compose exec oppi-server curl -sS "$SEARXNG_URL/healthz"
# Generate pairing QR/deep link explicitly
docker compose exec oppi-server node dist/src/cli.js pair --host <your-lan-host-or-ip>
# Force resync Pi seed from host on next start
PI_AGENT_SYNC_MODE=always docker compose up -d
# Stop / start
docker compose stop
docker compose startCommands
Use oppi ... for npm/global installs. In a source checkout before linking, use
node dist/src/cli.js ... from the server/ directory.
oppi serve [--host <h>] # start server
oppi init # interactive first-time setup
oppi pair [--host <h>] # regenerate pairing QR + invite link
oppi status # server, network, and pairing status
oppi doctor # security and environment diagnostics
oppi workspace ... # list/inspect/create/update/delete workspaces
oppi worktree ... # list/create/open/preview/remove worktrees
oppi session ... # create/send/watch/wait/inspect/resume/fork/delete sessions
oppi agent ... # manage saved Agents
oppi schedule ... # manage schedules and run history
oppi server ... # install/status/restart/stop/uninstall launchd service
oppi config ... # show/get/set/validate config
oppi token rotate # rotate owner bearer token
oppi update # update mutable runtime dependencies only
oppi update --self # show how to update the server installSession history reads are consolidated under oppi session inspect. Start with its default turn outline, use --view summary for counts, and request --turns <spec> --view messages|tools only for the smallest relevant turn set.
Saved Agents and schedules
Saved Agents store reusable Agent definitions. Launch inputs such as workspace, worktree, prompt, and session name stay on oppi session create, so the same Agent definition can run in different workspaces.
Schedules store a trigger plus an action. oppi schedule create accepts --at, --every, or --cron; actions can start a new session in a workspace or send input to an existing session. The background schedule runner materializes due runs, dispatches active schedules, and records run history. Pause or archive a schedule to stop future automatic runs.
Install and update modes
- App-managed runtime: Oppi.app owns server code and seeds
~/.config/oppi/server-runtime.oppi updateupdates mutable runtime dependencies only; update Oppi.app to update server code. - npm global install: npm owns server code. Use
oppi update --selffor upgrade instructions,npm install -g oppi-server@latestto upgrade, andnpm uninstall -g oppi-serverto remove.oppi updateupdates mutable runtime dependencies only. - Git/bootstrap install: git owns server code. Use
git pull && npm install && npm run buildto upgrade a checkout.
Extensions
Oppi uses Pi's extension system and adds mobile rendering for standard extension UI requests. Extension approval behavior lives in Pi extensions, not in server config.
See Oppi extension behavior for workspace allowlists and mobile rendering. Use Oppi Mirror mode for terminal-owned sessions.
Server stats API
GET /server/stats?range=7|30|90&tz=<offset> returns aggregate session counts, cost, token usage, model breakdown, workspace breakdown, and daily trends. GET /server/stats/daily/YYYY-MM-DD?tz=<offset> returns an hourly breakdown and session list for a single day. Both the iOS and Mac apps consume these endpoints for the stats dashboard.
Workspace files API
GET /workspaces/:id/files/<path> serves directory listings and file content over HTTP. GET /workspaces/:id/file-index returns a flat path index for client-side filename search in the iOS file browser.
Configuration
- Config file:
~/.config/oppi/config.json - Data directory:
~/.config/oppi/ - Override both with
OPPI_DATA_DIRor--data-dir
Key config sections:
| Section | What it controls |
| -------- | --------------------------------------------------------------------------- |
| tls | HTTPS mode: self-signed, tailscale, manual, or explicit insecure HTTP |
| asr | Dictation pipeline: STT backend endpoint |
| images | Image attachment preprocessing before upload |
Model routing and API keys are managed by pi (pi auth), not the oppi config.
Unknown config keys are ignored on startup and reported by oppi config validate.
Quick inspection:
cat ~/.config/oppi/config.json | jq . # raw config
cat ~/.config/oppi/config.json | jq '.asr' # single section
node dist/src/cli.js config show # formatted overview
node dist/src/cli.js config get asr # top-level key
node dist/src/cli.js config set images.autoResize false
node dist/src/cli.js config set tls '{"mode":"self-signed"}'For unsupported nested keys, edit config.json directly and restart the server.
See config-schema.md for full reference.
Development
Dependency installation and script runtimes
CI, Docker builds, and the development checks use npm; package-lock.json is
the authoritative dependency lockfile for those flows. Use npm ci when you
need the same dependency tree as CI. Bun is otherwise used only to execute the
TypeScript repository scripts referenced by package.json.
The source-checkout bootstrapper is currently an exception: root install.sh
delegates to server/setup.sh, which prefers Bun when it is installed and runs
bun install --ignore-scripts. That documented flow consumes bun.lock, so the
Bun lockfile cannot be removed until the bootstrapper installs dependencies
with npm. Its dependency tree can diverge from the npm/CI tree in the meantime.
npm test # vitest unit tests
npm run test:coverage # coverage gate
npm run check # typecheck + lint + dead-code + format check
npm run dev # watch mode
npm run test:e2e # Docker E2E harness
npm run test:e2e:pairing # pairing flow only
npm run test:e2e:session # paired session flow only
E2E_NATIVE=1 npm run test:e2e # native E2E harness without Docker
npm run bench:correctness # check + test before perf measurements
npm run bench # correctness + perf regression gate
npm run telemetry:review # telemetry summary
npm run diagnostics:review # diagnostics summaryBenchmark conventions, baselines, and comparison workflow live in bench/README.md.
Local release telemetry dashboard (SQLite + Grafana)
This stack builds directly on telemetry JSONL files written by oppi-server at:
${OPPI_DATA_DIR:-~/.config/oppi}/diagnostics/telemetry/*.jsonl
1) Start telemetry stack (auto-import + Grafana)
cd server
npm run telemetry:grafana:upThis starts two services:
telemetry-importer: watches telemetry JSONL and keeps SQLite in sync.grafana-telemetry: serves the dashboard.
Importer behavior:
- reads telemetry JSONL from
${OPPI_DATA_DIR:-~/.config/oppi}/diagnostics/telemetry/*.jsonl - writes SQLite into a Docker-managed volume mounted at
/var/lib/oppi-telemetry-db/telemetry.db - runs one import immediately on startup
- runs SQLite integrity checks on startup/recovery only by default, not on every watch tick
- continues in watch mode (poll interval:
OPPI_TELEMETRY_IMPORT_INTERVAL_MS, default15000) - ingests incrementally for append-only daily JSONL files instead of reimporting the whole hot file each cycle
- normalizes source file keys so Docker and host imports target the same rows
- flattens common server-ops tags (
path,type,level,lane,ring,code,outcome) for split-stream Grafana panels - keeps at most
OPPI_TELEMETRY_BROKEN_DB_KEEP_COUNTmalformed-db backups (default1) - uses a short-lived lock file so overlapping importer runs skip instead of clobbering each other
Open:
http://localhost:13001- default login:
admin/admin
The datasource and dashboard are provisioned automatically:
- datasource:
Oppi Telemetry SQLite - dashboards:
Oppi Release PreflightandOppi Server Health(folder:Oppi)
2) Stop telemetry stack
cd server
npm run telemetry:grafana:downOptional manual import commands
Use these if you want to import without Docker:
cd server
npm run telemetry:import
npm run telemetry:import:watchNotes:
- Services are defined in
server/docker-compose.telemetry.yml. - The Docker stack keeps SQLite inside a named volume instead of the host-mounted telemetry directory. This avoids SQLite corruption on macOS bind mounts while still reading host JSONL input files. Grafana opens this database read-write because SQLite WAL-mode readers may need sidecar shared-memory files even for read queries.
- Manual
telemetry:importruns still write${OPPI_DATA_DIR:-~/.config/oppi}/diagnostics/telemetry/telemetry.dbon the host, share the same normalized file keys as the Docker watcher, and skip if another importer run currently holds the lock. - Importer integrity-check mode is
startupby default; use--integrity-check alwaysfor old every-run checking or--integrity-check neverfor trusted throwaway databases. - If you use a non-default data dir, export
OPPI_DATA_DIRbefore running commands.
License
MIT
