# Project AIRI Admin & Developer Guide

This guide is the architectural and operational reference for **Project AIRI** — an open-source, self-hostable AI companion ("cyber living being" / AI VTuber) built by [@moeru-ai](https://github.com/moeru-ai). It is written for the people who run, deploy, extend, and contribute to AIRI: operators, backend engineers, frontend/desktop developers, plugin authors, and architects.

AIRI is **not a single application**. It is a large pnpm monorepo that ships several products sharing one core: a browser app, an Electron desktop companion, a Capacitor mobile app, a cloud backend, a fleet of chat-platform bot services, and a plugin ecosystem. The most important mental model for working on AIRI is therefore *which surface am I in, and which shared package owns this logic?*

> [!NOTE]
> This guide documents commit `19e5020` (release line `v0.10.2`). AIRI evolves quickly; treat version-specific details (dependency versions, file paths) as a snapshot. The always-current version lives at [moeru-ai/airi](/guides/moeru-ai/airi/).

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [System Architecture](#2-system-architecture)
3. [Monorepo Layout](#3-monorepo-layout)
4. [The Three Client Surfaces](#4-the-three-client-surfaces)
5. [Shared Packages: the Stage Stack](#5-shared-packages-the-stage-stack)
6. [The Cloud Backend (`apps/server`)](#6-the-cloud-backend-appsserver)
7. [The Server Channel & SDK](#7-the-server-channel--sdk)
8. [The Plugin System](#8-the-plugin-system)
9. [Bot Services](#9-bot-services)
10. [Rendering, Audio & Memory Subsystems](#10-rendering-audio--memory-subsystems)
11. [Requirements & Toolchain](#11-requirements--toolchain)
12. [Development Workflow](#12-development-workflow)
13. [Building & Deployment](#13-building--deployment)
14. [Configuration Reference](#14-configuration-reference)
15. [Security Analysis](#15-security-analysis)
16. [Observability & Operations](#16-observability--operations)
17. [Code Quality & Conventions](#17-code-quality--conventions)
18. [Testing Strategy](#18-testing-strategy)
19. [Troubleshooting (Operators & Developers)](#19-troubleshooting-operators--developers)
20. [Potential Improvements](#20-potential-improvements)
21. [Quick Reference](#21-quick-reference)

---

## 1. Executive Summary

| Aspect | Detail |
|---|---|
| **What it is** | Self-hostable AI companion platform — animated character (Live2D/VRM) driven by user-supplied LLM/TTS/STT/vision providers. |
| **Repository** | [`moeru-ai/airi`](https://github.com/moeru-ai/airi), ~3,900 files, monorepo. |
| **License** | MIT. |
| **Primary language** | TypeScript (Vue 3 SFCs), with Kotlin/Swift (mobile shells) and GDScript (experimental Godot stage). |
| **Package manager** | pnpm (workspaces + catalog), via Corepack. |
| **Build orchestration** | Turborepo (`turbo.json`), Vite / electron-vite / VitePress, `tsdown`. |
| **Client surfaces** | Web (`stage-web`), Desktop (`stage-tamagotchi`, Electron), Mobile (`stage-pocket`, Capacitor). |
| **Backend** | `apps/server` — Hono + Drizzle/Postgres + Redis + better-auth + Stripe; deployed on Railway. |
| **AI integration** | `xsai` multi-provider client; 40+ LLM providers; local inference via candle (CUDA/Metal). |
| **Key cross-cutting libs** | `@moeru/eventa` (IPC/RPC), `injeca` (DI), `valibot` (validation), UnoCSS + `reka-ui` (UI), `@moeru/std` (utilities). |

**Architectural thesis.** AIRI is "web-first but not web-limited." Everything is built on web platform APIs (WebGPU, WebAudio, Web Workers, WebAssembly, WebSocket) so a single codebase runs in browsers, Electron, and mobile webviews. The desktop surface then escapes the browser sandbox to add native GPU inference, OS-level screen capture, and raw TCP — capabilities (Discord voice, Minecraft, local models) that browsers forbid.

**Operational thesis.** Two very different things can be "deployed":
1. The **client apps** are shipped as static web bundles / signed desktop installers / mobile packages. They hold user config locally and need no server to function with cloud AI providers.
2. The **cloud backend** (`apps/server`) is an optional, separately deployed service that powers accounts, the hosted web experience, billing ("Flux"), and an LLM gateway. Self-hosters who only want the companion do **not** need it.

---

## 2. System Architecture

```
                          ┌─────────────────────────────────────────────┐
                          │             AI PROVIDERS (BYO key)           │
                          │  OpenAI · Anthropic · Gemini · DeepSeek ·    │
                          │  Ollama/vLLM (local) · ElevenLabs · Azure ·  │
                          │  Kokoro (local TTS) · image-gen services     │
                          └───────────────▲─────────────────────────────┘
                                          │ xsai (multi-provider client)
                                          │
   ┌──────────────┐   ┌──────────────┐    │    ┌──────────────────────────┐
   │  stage-web   │   │stage-tamagotchi│  │    │       stage-pocket       │
   │  (browser)   │   │  (Electron)    │  │    │   (Capacitor iOS/Android)│
   │              │   │ main │ renderer│  │    │                          │
   └──────┬───────┘   └───┬──────┬─────┘  │    └────────────┬─────────────┘
          │               │      │        │                 │
          │   packages/stage-ui (shared components, stores, composables) │
          │   packages/stage-shared · stage-ui-{live2d,spine,three} · ui │
          └───────────────┴──────┴────────┴─────────────────┘
                                   │ WebSocket "server channel"
                                   │ (@proj-airi/server-sdk ⇄ server-runtime)
                  ┌────────────────┼─────────────────────────┐
                  │                │                          │
        ┌─────────▼───────┐  ┌─────▼──────────┐     ┌─────────▼──────────┐
        │ services/*      │  │ plugins/*      │     │  apps/server       │
        │ discord/telegram│  │ claude-code    │     │  Hono + Drizzle    │
        │ minecraft/x/... │  │ home-assistant │     │  Postgres + Redis  │
        │ computer-use-mcp│  │ web-extension  │     │  better-auth+Stripe│
        └─────────────────┘  └────────────────┘     └────────────────────┘
```

**Data-flow summary.**
- The user configures providers and a character in a client; that config lives **locally** (Pinia stores + browser storage / app data dir).
- The client calls AI providers directly via `xsai` for chat, voice, and vision.
- Optional integrations (bots, plugins, other surfaces) connect to a running AIRI instance over the **WebSocket server channel** (`server-runtime` ⇄ `server-sdk`).
- The optional **cloud backend** provides accounts, the hosted web app's persistence, billing, and a server-side LLM gateway. **Postgres is the single source of truth** for account/billing state; Redis is cache/KV/pub-sub only.

---

## 3. Monorepo Layout

AIRI uses pnpm workspaces. The top-level directories define the architecture.

| Directory | Contains | Role |
|---|---|---|
| `apps/` | Deployable applications | The products you ship/run. |
| `packages/` | Shared libraries | Reusable code consumed by apps, services, and plugins. |
| `services/` | Bot/backend services | Chat-platform integrations and the computer-use bridge. |
| `plugins/` | First-party plugins | Optional capability extensions loaded by the plugin host. |
| `integrations/` | External-tool integrations | VS Code extension + host plugin. |
| `engines/` | Rendering engines | Experimental `stage-tamagotchi-godot` (GDScript + VRM/MToon). |
| `docs/` | VitePress documentation site | Public docs + multilingual content + blog/DevLogs. |
| `crates/` | **Legacy** Rust (Tauri) | The old desktop shell; superseded by Electron. |
| `nix/`, `patches/`, `scripts/`, `bucket/` | Tooling | Nix packaging, pnpm patches, build scripts, Scoop manifest. |

### Key `apps/`

| App | Package name | Description |
|---|---|---|
| `apps/stage-web` | `@proj-airi/stage-web` | Browser app served at airi.moeru.ai. |
| `apps/stage-tamagotchi` | `@proj-airi/stage-tamagotchi` | Electron desktop companion (main + renderer + preload). |
| `apps/stage-pocket` | `@proj-airi/stage-pocket` | Capacitor mobile app (iOS Swift / Android Kotlin shells). |
| `apps/server` | `@proj-airi/server` | Cloud backend (accounts, billing, gateway). |
| `apps/ui-admin` | `@proj-airi/ui-admin` | Admin SPA for the backend. |
| `apps/ui-server-auth` | `@proj-airi/ui-server-auth` | Auth/login SPA served by the backend. |
| `apps/component-calling` | — | Component-calling playground (e.g. weather widget demo). |

### Key `packages/`

`stage-ui` (the heart — shared components, stores, composables), `stage-shared`, `stage-pages`, `stage-ui-live2d`, `stage-ui-spine`, `stage-ui-three`, `stage-layouts`, `ui` (reka-ui primitives), `i18n`, `audio` / `audio-pipelines-transcribe` / `pipelines-audio`, `core-agent`, `core-character`, `memory-pgvector`, `plugin-sdk` / `plugin-protocol` / `plugin-sdk-tamagotchi`, `server-runtime` / `server-sdk` / `server-shared` / `server-schema` / `server-sdk-shared`, `model-driver-lipsync` / `model-driver-mediapipe`, `electron-eventa` / `electron-vueuse` / `electron-screen-capture`, `cap-vite`, `airi-screenshot`, `stream-kit`, `unocss-preset-fonts`, plus several font packages.

> [!NOTE]
> Many sub-projects spun out into the [@proj-airi](https://github.com/proj-airi) organization (the `xsai` LLM client, DuckDB-WASM drivers, RAG/memory experiments, Live2D utilities). When a capability seems to "come from nowhere," it is often one of these external packages.

---

## 4. The Three Client Surfaces

All three surfaces are Vue 3 + Vite + TypeScript + Pinia + VueUse + UnoCSS, sharing `packages/stage-ui`. They differ in their host runtime and entry points.

### 4.1 Web (`apps/stage-web`)

- Pure browser SPA; Vue Router; router config and build via `vite.config.ts`.
- Pages under `src/pages`; settings layout at `src/layouts/settings.vue`.
- PWA-installable; the canonical hosted deployment.
- Backend is optional and WIP for the self-host case; the hosted version uses `apps/server`.

### 4.2 Desktop (`apps/stage-tamagotchi`, Electron)

The most architecturally involved surface. Built with **electron-vite** (`electron.vite.config.ts`) and split into the standard Electron process model:

| Process | Path | Responsibility |
|---|---|---|
| **Main** | `src/main/` | App lifecycle, windows, tray, native services. Composition root at `src/main/index.ts` (uses `injeca` DI). |
| **Preload** | `src/preload/` | Safe bridge exposing main APIs to the renderer. |
| **Renderer** | `src/renderer/` | The Vue UI (pages, stores, components, devtools). |
| **Shared** | `src/shared/` | Eventa IPC/RPC contracts shared across processes. |

Notable main-process services live under `src/main/services/airi/`: a local **HTTP server** (with auth, static-asset serving, server-manager), an **MCP servers** manager, the **plugin host**, **onboarding**, **channel-server** and **godot-stage** bridges, and **widget/provider** services (e.g. image-gen providers ComfyUI / Nano Banana / Replicate). Each window (`main`, `chat`, `settings`, `dashboard`, `caption`, `desktop-overlay`, `inlay`, `about`, `onboarding`, `widgets`, `devtools`, `beat-sync`, `notice`) has its own module under `src/main/windows/` with an Electron RPC channel.

> [!IMPORTANT]
> The desktop app runs a **local HTTP/WebSocket server** to host the server channel and serve assets. This is the primary attack surface to reason about when binding it to anything other than localhost. See [Security](#15-security-analysis).

### 4.3 Mobile (`apps/stage-pocket`, Capacitor)

- Vue 3 + Capacitor; native iOS (Swift, `ios/App`) and Android (Kotlin, `android/app`) shells.
- Build glue lives in `packages/cap-vite` (a Vite wrapper that drives Capacitor sync; configurable via `CAP_VITE_*` and `VITE_CAP_SYNC_IOS_AFTER_BUILD` env vars).
- Connects to a desktop "server channel" over (secure) WebSocket for shared-brain use.

---

## 5. Shared Packages: the Stage Stack

`packages/stage-ui` is where most product behavior actually lives — both `stage-web` and `stage-tamagotchi` are thin shells over it.

| Area (under `stage-ui/src`) | What it owns |
|---|---|
| `stores/providers.ts`, `stores/providers/` | Standardized **provider definitions** (the 40+ LLM/TTS/STT services and their config shape). |
| `stores/modules/` | AIRI **orchestration modules** — the building blocks behind hearing, vision, etc. |
| `stores/settings/`, `stores/character/`, `stores/chat/`, `stores/ai/models/` | Pinia state for settings, the active character, chat sessions, and model catalogs. |
| `composables/` | Business-oriented Vue composables (audio, vision, etc.). |
| `components/`, `components/scenarios/` | UI components; `scenarios/` holds page/use-case-specific pieces (chat, connection, providers, onboarding dialogs…). |
| `libs/providers/providers/*` | Per-provider client adapters (anthropic, openai, ollama, bedrock, …). |
| `services/` | Front-end service clients (e.g. `characters.ts`, `inference-service-providers.ts`) that talk to the backend. |
| `libs/workers/` | Web Worker code (e.g. `AutomaticSpeechRecognitionPipeline` for client-side STT). |

Supporting packages:

- **`packages/ui`** — standardized primitives (inputs, buttons, layout) built on **reka-ui**; minimal business logic. Its API is documented in `docs/ai/context/ui-components.md`.
- **`packages/stage-shared`** — logic shared across stage-ui, stage-ui-three, and the apps (auth, beat-sync, versioned local-storage composables, global shortcuts, WebGPU helpers).
- **`packages/stage-pages`** — shared page bases (settings, devtools).
- **`packages/i18n`** — all translations (managed via Crowdin); never scatter i18n into apps.

---

## 6. The Cloud Backend (`apps/server`)

`apps/server` is the hosted backend behind airi.moeru.ai. Self-hosters of the companion do not need it, but operators of a hosted instance do. Its own engineering notes live in `apps/server/docs/ai-context/`.

### 6.1 Stack and assembly

- **Framework:** Hono (HTTP + WebSocket).
- **DI:** `injeca`. The single application-assembly entry point is `apps/server/src/app.ts`.
- **Persistence:** Postgres via Drizzle ORM (`drizzle.config.ts`, schemas under `src/schemas/**`, migrations bundled via an unplugin transform in `@proj-airi/server-schema`). **Postgres is the sole source of truth for all balance and billing state.**
- **Cache / pub-sub:** Redis (KV, caching, and cross-instance WebSocket broadcast). The billing path does **not** use Redis Streams.
- **Auth:** `better-auth` with an OIDC provider, email auth (Resend), and a role-based `admin` plugin (ban/unban, balance adjustment).
- **Billing:** Stripe, with a "Flux" credit ledger. Stripe Product/Price is the single source of truth for top-up pricing; the ledger enforces idempotency.

The route layer is deliberately thin; business logic lives in `src/services/`. The server runs as a single `api` role — there are **no background loops**; admin grants, Stripe webhooks, and event dispatch all run synchronously in the request thread.

### 6.2 HTTP / WebSocket surface

Routes are mounted under `src/routes/` and include: `auth`, `openai/v1/*` (an OpenAI-compatible gateway: chat-completions, speech-catalog, speech-generation), `providers`, `characters`, `chats`, `chat-ws` (chat WebSocket), `audio-speech-ws`, `flux`, `stripe` (webhooks), `voice-packs`, and `admin/*` (config router, flux-grants, users, voice-packs).

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/ws/chat` | Chat sync WebSocket. |
| `GET` | `/api/v1/audio/speech/ws` | Streaming speech WebSocket. |
| `POST` | `/openai/v1/chat/completions` | OpenAI-compatible chat gateway (forwards to a configured upstream, meters Flux). |

> [!NOTE]
> The server's "LLM capability" is **not local inference** — it forwards to a configured gateway and debits Flux by usage / fallback rate. WebSocket is used only for chat sync; cross-instance broadcast relies on Redis pub/sub.

### 6.3 Flux billing model

"Flux" is the platform credit. Key properties operators must understand:
- Balances and the credit ledger are **Postgres truth**, with Stripe idempotency on top-ups.
- Sub-services (TTS/STT/embeddings) meter against a **debt ledger** ("flux-meter").
- An **admin grant** path issues Flux synchronously (no batch table, no background worker), guarded by a role-based `adminGuard` and an optional `idempotencyKey`.
- The `ai-context` notes document a historical unbilled-usage exploit and its fix — relevant when auditing the billing path.

---

## 7. The Server Channel & SDK

The "server channel" is AIRI's internal real-time bus connecting surfaces, services, and plugins.

| Package | Role |
|---|---|
| `packages/server-runtime` | The WebSocket **server** implementation (`/ws`), event routing, config. |
| `packages/server-sdk` | The **client** (`Client` class) that connects to the runtime: connect/auth/reconnect, status tracking, typed messages. |
| `packages/server-shared` | Shared types, including the WebSocket message contracts. |
| `packages/server-sdk-shared`, `packages/server-schema` | Shared SDK helpers and DB schema (used by the cloud backend). |

The desktop app embeds a server-runtime instance; bot services and the mobile app use `server-sdk` to connect to it. The SDK surfaces explicit error states operators will see in logs: `Client is closed`, `Client is not connected`, `Connection timed out after <n>ms`, `Connection aborted`, `Received invalid websocket message`, and `Authentication failed`.

---

## 8. The Plugin System

AIRI's plugin system (`packages/plugin-sdk`, `packages/plugin-protocol`, `packages/plugin-sdk-tamagotchi`) lets third parties extend the companion. It is the most security-relevant extension point.

### 8.1 Model

- A plugin is described by a **`plugin.airi.json`** manifest (`ManifestV1`) that declares the **protocol permissions** it needs (e.g. *invoke* `capabilities:wait`, *invoke* `resources:providers:list-providers`, *read* the provider resource).
- Plugins run inside a **plugin host** with runtime backends for `node`, `web`, and shared services (`plugin-sdk/src/plugin-host/runtimes/`). The host exposes a client API (`apis.providers.listProviders()`, kits, tools, resources, bindings).
- A set of **registry services** govern the lifecycle: `BindingsRegistryService`, `DependencyService`, `KitRegistryService`, `PermissionService`, `ResourceService`, `PluginSessionService`, `ToolRegistryService`.

### 8.2 Permission enforcement

`PermissionService` (`plugin-sdk/src/plugin-host/runtimes/shared/services/permissions.ts`) is the gate. It throws on misuse — e.g. `Cannot declare permissions for unknown plugin` / `Cannot grant permissions to unknown plugin` — and a plugin's manifest must declare every protocol capability it invokes. Sessions advance through phases (`loaded` → `ready`) and expose their granted capability list.

> [!WARNING]
> Plugins execute code with the host's privileges. Treat third-party plugins like any code you run: review the manifest's declared permissions, and only load plugins from trusted sources. The **Plugin Host Inspector** (`/devtools/plugin-host`) is the tool for validating a plugin's lifecycle and capabilities before relying on it.

### 8.3 First-party plugins

`airi-plugin-claude-code` (with its own CLI), `airi-plugin-homeassistant`, `airi-plugin-bilibili-laplace`, `airi-plugin-web-extension`, and `airi-plugin-game-chess`. The VS Code integration (`integrations/vscode/`) ships both a host plugin (`airi-plugin-vscode`) and the published extension (`vscode-airi`).

---

## 9. Bot Services

Services under `services/` connect AIRI to chat platforms and the host computer. They are run independently (not bundled in the installer), configured by copying `.env` → `.env.local`, and started with a pnpm workspace filter.

| Service | Package | Notes |
|---|---|---|
| Discord | `@proj-airi/discord-bot` | Joins servers + voice channels; uses Opus decoding + an audio monitor. |
| Telegram | `@proj-airi/telegram-bot` | **Requires Postgres** (`docker compose up -d`); Drizzle migrations via `db:generate` + `db:push`; embeddings stored (pgvector). |
| Minecraft | `@proj-airi/minecraft-bot` | Mineflayer-based agent with a cognitive layer (perception/action/skills) and a debug web UI. |
| Twitter/X | `services/twitter-services` | Browser automation (Browserbase/Playwright) to read/post tweets; fully env-var configured. |
| Satori | `@proj-airi/satori-bot` | Connects via the Satori chat protocol. |
| Computer-use | `services/computer-use-mcp` | Desktop-grounding MCP: observes the screen and drives real OS-level input (macOS CGEvent), with a **read-only** Chrome DOM bridge extension. |

Generic run pattern:

```shell
cd services/<name>
cp .env .env.local        # edit credentials
pnpm -F @proj-airi/<name> start
```

> [!CAUTION]
> `services/computer-use-mcp` can control the user's machine (mouse, keyboard) via real OS input events. Run it only on machines where you intend AIRI to have that control, and understand the blast radius before enabling it.

---

## 10. Rendering, Audio & Memory Subsystems

### 10.1 Character rendering

| Package | Format | Engine |
|---|---|---|
| `stage-ui-live2d` | Live2D `.moc3` | Live2D Cubism; loads models from ZIP (`live2d-zip-loader.ts`), OPFS caching (`opfs-loader.ts`). |
| `stage-ui-three` | VRM (3D) | Three.js + `@pixiv/three-vrm`; VRM composables, animations, MToon. |
| `stage-ui-spine` | Spine | Spine runtime; loads `.skel`/`.json` + `.atlas` from ZIP (`spine-zip-loader.ts`). |
| `engines/stage-tamagotchi-godot` | VRM/MToon | **Experimental** Godot renderer (GDScript). |

All renderers provide auto-blink, idle eye movement, and look-at. Lip-sync is handled by `model-driver-lipsync`; face/pose tracking by `model-driver-mediapipe`.

### 10.2 Audio pipelines

`packages/audio`, `packages/pipelines-audio` (LLM-streaming control + parsers), and `packages/audio-pipelines-transcribe` implement the voice loop: capture → VAD (voice-activity detection web worker) → STT → LLM → TTS → playback, with beat-sync visualization (`stage-shared/beat-sync`).

### 10.3 Memory

`packages/memory-pgvector` plus in-browser databases (DuckDB WASM and `pglite`, exposed through `@proj-airi/drizzle-duckdb-wasm`) keep conversation memory local to the device. A long-term "Memory Alaya" system is WIP.

---

## 11. Requirements & Toolchain

### 11.1 Development prerequisites

| Tool | Version | Notes |
|---|---|---|
| Git | any recent | — |
| Node.js | **23+** | Server Dockerfile uses Node 24. |
| Corepack | bundled with Node | Enables pnpm. |
| pnpm | latest (via Corepack) | `corepack enable && corepack prepare pnpm@latest --activate`. |
| Rust toolchain | stable | Only needed for `crates/` (legacy Tauri) or native desktop bits. |

Platform extras:

=== "Windows"

    Install Visual Studio with the Windows SDK and C++ build tools; install `git`, `nodejs`, and `rustup` (with `rust-msvc`) via Scoop.

=== "macOS"

    `brew install git node`, then enable pnpm via Corepack.

=== "Linux (desktop dev)"

    ```shell
    sudo apt install libssl-dev libglib2.0-dev libgtk-3-dev \
       libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
    ```

=== "NixOS"

    Electron needs shared libraries outside standard paths — use the FHS shell from `flake.nix`:

    ```shell
    nix develop .#fhs
    pnpm dev:tamagotchi
    ```

### 11.2 Runtime dependencies (cloud backend)

Postgres (with pgvector), Redis, and outbound access to your LLM gateway, Stripe, Resend, and (optionally) an OTLP endpoint and/or Langfuse.

---

## 12. Development Workflow

### 12.1 Bootstrapping

```shell
corepack enable
pnpm install
# optional, only for Rust/native work:
cargo fetch
```

### 12.2 Running each surface

| Command | Starts |
|---|---|
| `pnpm dev` | Web (`stage-web`) dev server (the default). |
| `pnpm dev:tamagotchi` | Desktop (Electron) dev. |
| `pnpm dev:pocket:ios --target <DEVICE_OR_SIM>` | Mobile dev on iOS (list devices with `pnpm exec cap run ios --list`). |
| `pnpm dev:docs` | VitePress documentation site. |

> [!TIP]
> Installing [`@antfu/ni`](https://github.com/antfu-collective/ni) lets you use `nr <script>` regardless of package manager. The repo recommends it throughout its docs.

### 12.3 Scoping work with workspace filters

Use pnpm filters to target one workspace:

```shell
pnpm -F @proj-airi/stage-tamagotchi typecheck   # tsc + vue-tsc
pnpm -F @proj-airi/stage-tamagotchi build        # typecheck + electron-vite build
pnpm exec vitest run path/to/file.test.ts        # one test file
pnpm -F @proj-airi/stage-tamagotchi exec vitest run
```

### 12.4 Before committing

```shell
pnpm lint && pnpm typecheck     # formatting is via ESLint; lint:fix applies it
```

Branch naming: `username/feat/short-name`; Conventional Commits (e.g. `feat(stage-ui): add reconnect backoff`); **gitmoji is prohibited**; rebase pulls. Each `packages/` and `apps/` entry should carry a structured `README.md` (what it does, how/when to use it, when not to).

---

## 13. Building & Deployment

### 13.1 Client builds

- **Web:** `pnpm -F @proj-airi/stage-web build` produces a static bundle; deploy to any static host / CDN. The hosted version is served by the backend.
- **Desktop:** `pnpm -F @proj-airi/stage-tamagotchi build` runs typecheck + electron-vite build; packaging produces signed installers per platform. Distribution channels: GitHub releases, winget (`MoeruAI.AIRI`), Scoop (`bucket/`), Homebrew Cask.
- **Mobile:** Vite build + Capacitor sync (`packages/cap-vite`), then Xcode/Android Studio packaging.

### 13.2 Auto-updates (desktop)

Built on `electron-updater`. Channels: `stable`, `beta`, `alpha`, `nightly`, selectable via `AIRI_UPDATE_CHANNEL`; `UPDATE_SERVER_URL` overrides the feed (used by the local test harness in `apps/stage-tamagotchi/scripts/update-test/`). Verify via **About → Check for updates**, and inspect updater diagnostics under **Settings → System → Developer**.

### 13.3 Cloud backend deployment (Railway)

The backend ships as a Dockerfile (`apps/server/production/railway/Dockerfile`) and a `railway.toml`:

```toml
[build]
builder = "DOCKERFILE"
dockerfilePath = "/apps/server/production/railway/Dockerfile"

[deploy]
startCommand = "pnpm -F @proj-airi/server start"
```

Build stages: build the auth + admin UIs, then build `server-schema`, `server-sdk-shared`, and the server. OpenTelemetry is preloaded via `tsx --import ./instrumentation.ts` **before** any app module so the http/pg/ioredis instrumentation hooks install first.

> [!IMPORTANT]
> The server's Dockerfile contains a hard guard that **fails the build** if `server-schema`'s `dist/index.mjs` still references a `virtual:` migration specifier (an intermittent rolldown/tsdown transform failure). If a server build fails with that error, it caught a real packaging bug — do not bypass it.

### 13.4 Self-hosting observability stack (optional)

`apps/server/docker-compose.otel.yml` brings up an OpenTelemetry Collector, Prometheus, Loki, Tempo, and Grafana for local/self-hosted observability (Grafana on host port `3001`).

---

## 14. Configuration Reference

There are **two distinct configuration worlds**; conflating them is the most common source of confusion.

### 14.1 Client app configuration (no env vars)

The web, desktop, and mobile apps are configured **entirely through the in-app Settings UI**, persisted in Pinia stores + local storage (and, on desktop, the app data folder). There is no `.env` for end users. See the [User Guide](/guides/moeru-ai/airi/user-guide/) settings reference for every pane.

### 14.2 Cloud backend & services (env vars)

Environment variables apply to `apps/server` and the `services/*` bots, loaded from `.env.local` (the backend uses `dotenvx`).

**Backend / observability:**

| Variable | Purpose |
|---|---|
| `DATABASE_URL` | Postgres connection (Drizzle). |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint; enables Grafana-pipeline traces/metrics/logs. |
| `OTEL_EXPORTER_OTLP_HEADERS` | `key=value,key2=value2` auth headers for OTLP. |
| `OTEL_SERVICE_NAME` / `OTEL_SERVICE_NAMESPACE` | Service identity (default `server` / `airi`). |
| `OTEL_TRACES_SAMPLING_RATIO` | Head-based trace sampling (0–1, default 1). |
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Defaults to `http` (stable HTTP semconv). |
| `RAILWAY_REPLICA_ID` / `SERVER_INSTANCE_ID` | Per-replica metric identity (avoid series churn). |
| `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` / `LANGFUSE_BASE_URL` | Independent LLM-native tracing export. |
| `NODE_ENV` | Deployment environment label. |

Additional backend integrations (from the `ai-context` docs): Stripe keys (billing), Resend (email auth), and better-auth/OIDC client config.

**Twitter/X service (`services/twitter-services`):**

| Variable | Purpose |
|---|---|
| `TWITTER_API_KEY` / `TWITTER_API_SECRET` | Twitter API credentials. |
| `TWITTER_ACCESS_TOKEN` / `TWITTER_ACCESS_TOKEN_SECRET` | OAuth tokens. |
| `BROWSER_HEADLESS`, `BROWSER_USER_AGENT`, `BROWSER_VIEWPORT_WIDTH/HEIGHT`, `BROWSER_TIMEOUT`, `BROWSER_REQUEST_TIMEOUT`, `BROWSER_REQUEST_RETRIES` | Browser automation tuning. |
| `BROWSERBASE_API_KEY` | Browserbase remote browser. |
| `AIRI_URL` / `AIRI_TOKEN` / `ENABLE_AIRI` | Connect back to an AIRI server channel. |
| `MCP_PORT` / `ENABLE_MCP` | MCP server exposure. |
| `CONCURRENCY` / `CONFIG_PATH` | Worker concurrency / config file path. |

**Computer-use service (`services/computer-use-mcp`):** `COMPUTER_USE_EXECUTOR`, `COMPUTER_USE_REMOTE_SSH_{HOST,USER,PORT}`, `COMPUTER_USE_REMOTE_RUNNER_COMMAND`, `COMPUTER_USE_REMOTE_DISPLAY_SIZE`, `COMPUTER_USE_REMOTE_OBSERVATION_{BASE_URL,TOKEN}`, `COMPUTER_USE_ALLOWED_BOUNDS`, `COMPUTER_USE_BROWSER_DOM_BRIDGE_{PORT,TIMEOUT_MS}`.

**Build-time (`packages/cap-vite`, mobile):** `CAP_VITE_BASE_CONFIG`, `CAP_VITE_CAP_ARGS_JSON`, `CAP_VITE_CONFIG_LOADER`, `CAP_VITE_ROOT`, `VITE_SKIP_MKCERT`, `VITE_CAP_SYNC_IOS_AFTER_BUILD`.

> [!NOTE]
> Several of the env keys above were extracted from `*.config.test.ts` and `*.types.ts` files — they are the real, code-referenced variable names, but always confirm the canonical set against each service's `.env` template before deploying.

---

## 15. Security Analysis

### 15.1 Client trust model

- **API keys and personal config are stored locally** (browser storage / desktop app data dir), not on a central server (for the self-hosted/local case). This is a privacy strength but also means **the device is the security boundary** — anyone with access to the machine can read stored credentials.
- The desktop app runs a **local HTTP/WebSocket server** (`src/main/services/airi/http-server/`) with its own auth layer. Binding it beyond `localhost` exposes that surface to the network; the secure-WebSocket mode exists for the device-to-device (Pocket↔desktop) case and should be used deliberately.
- **Screen capture / vision** is opt-in and gated behind an explicit developer toggle plus OS-level permission grants. Treat it as sensitive: AIRI sees whatever you let it capture, and frames may be sent to a cloud vision model.

### 15.2 Plugin & service privilege

- **Plugins** declare permissions in `plugin.airi.json` and are gated by `PermissionService`, but a loaded plugin runs with host privileges. The manifest is an audit surface, not a sandbox guarantee — vet plugins before loading.
- **`computer-use-mcp`** controls real OS input. Its bundled Chrome extension is deliberately **read-only** (no DOM mutation, no `eval`, no external network, no `chrome.scripting`) — but the executor itself performs real clicks/keystrokes via CGEvent.
- **Bot services** hold platform credentials (Discord/Telegram/Twitter tokens) in `.env.local`. Keep those files out of version control and off shared machines.

### 15.3 Cloud backend

- **better-auth** provides OIDC, email auth, and a role-based admin plugin; ban/unban and balance changes are admin-gated (`adminGuard`), with hot-path enforcement via `resolveRequestAuth` + a userinfo guard so OIDC JWTs take effect immediately.
- **Billing integrity:** Postgres is the source of truth; Stripe top-ups are idempotent; admin Flux grants accept an `idempotencyKey`. The team has already remediated one unbilled-usage exploit (documented in `ai-context`) — audit the metering path when changing it.
- **Observability isolation:** the Langfuse exporter runs on an isolated tracer provider so prompt/completion text is **not** leaked into the Grafana/Tempo OTLP stream — preserve that separation if you touch instrumentation.

### 15.4 Error surface (operational signals)

The codebase throws named errors operators should monitor: server-SDK connection errors (`Authentication failed`, `Connection timed out`), plugin permission errors (`Cannot grant permissions to unknown plugin`), model-loader errors (`Textures not found`, `Expected exactly one moc file`), and provider/character service failures (`Failed to fetch providers`, `Failed to create character`). An `ApiError` class (`apps/server/src/utils/error.ts`) standardizes backend error responses.

---

## 16. Observability & Operations

The backend has a mature OpenTelemetry setup (`apps/server/instrumentation.ts`):

- **Traces** fan out to two independent pipelines: OTLP → Tempo (Grafana), and (optionally) Langfuse for LLM-native generation spans. Langfuse uses an isolated provider with `AlwaysOnSampler` so generations are captured regardless of head-sampling.
- **Metrics & logs** are OTLP-only (15s export interval), instrumenting HTTP (via `@hono/otel` for inbound, `HttpInstrumentation` for outbound), Postgres (`pg`), Redis (`ioredis`), Node runtime, and `undici` (fetch).
- **Self-hosted stack:** `docker-compose.otel.yml` (Collector + Prometheus 7d retention + Loki + Tempo + Grafana).
- **Graceful shutdown:** SIGTERM/SIGINT flush both SDK and Langfuse exporters before exit.

Operator notes from `ai-context`: metric ownership is split (Grafana for system metrics, PostHog for product events, Postgres for truth); set `SERVER_INSTANCE_ID`/`RAILWAY_REPLICA_ID` per replica to avoid metric series churn; grep logs for `[otel-preload]` to confirm the instrumentation preload ran.

---

## 17. Code Quality & Conventions

AIRI ships an unusually detailed `AGENTS.md` that governs contributions. Highlights:

- **IPC** via `@moeru/eventa` only; centralize contracts (e.g. `apps/stage-tamagotchi/src/shared`).
- **DI** via `injeca`, **only at real external boundaries** (DB, model runtime, queue, cache, FS, network, clock, env, feature gates) — not for internal helpers.
- **Validation** via `valibot`, schemas kept near consumers.
- **Error handling** via `@moeru/std` (`errorMessageFrom(error)` instead of manual `instanceof Error` checks).
- **Styling** via UnoCSS (preferred over Tailwind); primitives from `@proj-airi/ui` (reka-ui); grouped class arrays over long inline strings; Iconify icon sets, not bespoke SVGs.
- **Module design:** prefer deep modules over shallow ones; don't split by execution order; don't extract one-call helpers just to name a step; favor functional patterns, classes only for runtime/browser APIs or stateful domain boundaries.
- **No backward-compatibility guards** — refactor forward instead, with refactor docs.
- **Utilities:** prefer `es-toolkit`; never add general-purpose utility libs without explicit maintainer sign-off.
- **JSDoc** required on public/exported APIs (with `Use when` / `Expects` / `Returns`); CLI/runner entry points require ASCII call-stack diagrams; normalizers require before/after examples.
- **File names** camelCase; mark follow-ups/concerns/hacks with `// TODO:` / `// REVIEW:` / `// NOTICE:`.
- Documented past fixes live in `docs/solutions/` (YAML frontmatter: `module`, `tags`, `problem_type`).

---

## 18. Testing Strategy

- **Framework:** Vitest, per project; keep runs targeted (`pnpm exec vitest run <file>`). Root `vitest.config.ts` registers the projects.
- **Discipline:** reproduce a bug with a failing test *before* patching; name regression tests with the tracker id (`Issue #<number>` or the Linear key) and link the report in a comment above the test; include a `ROOT CAUSE:` block.
- **Mocking:** mock IPC/services with `vi.fn`/`vi.mock`; do **not** rely on a real Electron runtime; prefer Vitest **browser mode** for DOM/Web Platform APIs over hard-mocking globals. Do not use mocks/`as unknown as`/dynamic imports to mask real import-boundary problems — fix the boundary.
- **Coverage:** the **cloud backend enforces 100%** lines/functions/branches/statements (`apps/server/vitest.config.ts`). Other packages grow coverage progressively.
- **Desktop updater** has a dedicated local matrix harness (`scripts/update-test/`) exercising lane × runtime-feed × bundle-version combinations.

> [!NOTE]
> Reported confidence for this analysis was 71/100, with gaps concentrated in test-coverage breadth and the very large number of shallow leaf folders — expected for a monorepo of this size. The architecture-bearing modules are well covered.

---

## 19. Troubleshooting (Operators & Developers)

### Error: `ERR_UNSUPPORTED_ESM_URL_SCHEME` / `virtual:` specifier in server-schema

- **Cause:** The `unplugin-drizzle-orm-migrations` transform intermittently no-ops (observed with tsdown 0.21.9 + rolldown 1.0.0-rc.16), shipping a `dist/index.mjs` that re-exports a literal `virtual:` import.
- **Solution:** The Railway Dockerfile already forces a clean rebuild and asserts the transform ran. Reproduce locally with `rm -rf packages/server-schema/dist && pnpm -F @proj-airi/server-schema run build`.
- **Prevention:** Don't cache `server-schema/dist` across builds; keep the build-time guard in place.

### Error: Electron won't start on Linux/NixOS (missing shared libraries)

- **Cause:** Electron needs WebKitGTK/GTK libraries absent from standard paths.
- **Solution:** Install the `libwebkit2gtk-4.1` / `libgtk-3` / `libglib2.0` / `libssl` / `libjavascriptcoregtk-4.1` dev packages; on NixOS use `nix develop .#fhs` then `pnpm dev:tamagotchi`.
- **Prevention:** Document the FHS shell for NixOS contributors.

### Error: Telegram bot fails to start

- **Cause:** Missing Postgres or un-run migrations.
- **Solution:** `docker compose up -d` in `services/telegram-bot`, then `pnpm -F @proj-airi/telegram-bot db:generate && db:push`. Confirm `EMBEDDING_DIMENSION` is supported (the code throws `Unsupported embedding dimension`).
- **Prevention:** Provision the DB before first run.

### Error: `Authentication failed` / `Received invalid websocket message` from server-sdk

- **Cause:** Bad/missing server-channel token, version skew between SDK and runtime, or connecting to the wrong address.
- **Solution:** Verify `AIRI_URL`/`AIRI_TOKEN`, ensure the desktop server channel is running and reachable, and that SDK/runtime versions match (monorepo `workspace:` pins).
- **Prevention:** Pin versions via the workspace; surface connection status in service logs.

### Issue: Metrics series churn / undercounting after each deploy

- **Cause:** No stable per-replica `service.instance.id`; falls back to `randomUUID()`.
- **Solution:** Set `SERVER_INSTANCE_ID` (or rely on `RAILWAY_REPLICA_ID`).
- **Prevention:** Make instance id part of the deploy template.

### Issue: A plugin loads but has no capabilities / can't call an API

- **Cause:** The plugin's `plugin.airi.json` didn't declare the required protocol permission, so `PermissionService` blocks the call.
- **Solution:** Add the needed permissions to the manifest; verify via **Devtools → Plugin Host Inspector** that the session reaches `ready` and lists the capability.
- **Prevention:** Treat the manifest's permission list as the contract; test plugins in the inspector first.

---

## 20. Potential Improvements

Observations for maintainers, grounded in the source and the project's own notes:

- **Provider-integration drift.** Several provider config screens lag behind upstream API changes; the in-app advice is "use OpenAI-compatible." A conformance test matrix against each provider adapter in `stage-ui/src/libs/providers/providers/*` would catch regressions earlier.
- **`services/computer-use-mcp/src` is a large module (41 files, ~360 KB).** It is the heaviest service by far; splitting its session/state/transcript/workflow concerns and growing test coverage would reduce risk for a feature that drives real OS input.
- **Two desktop renderers in flight.** The legacy `crates/` (Tauri) shell and the experimental `engines/stage-tamagotchi-godot` both coexist with the active Electron app. A clear deprecation/EOL note in-repo would prevent contributor confusion.
- **Memory is partly WIP.** Short-term and long-term memory ("Memory Alaya") are unreleased; the in-browser DB path (DuckDB WASM / `pglite`) is the only shipping memory. Document the supported-vs-planned boundary in-product.
- **Community manual gaps.** The user-facing manual has many "editor hasn't tested / details omitted" sections (hearing, artistry, VRM tuning). These are the highest-value areas for first-party documentation.
- **i18n via AI translation.** Non-Chinese manuals are AI-translated with light correction; a glossary/terminology lock in Crowdin would improve consistency for UI strings.

---

## 21. Quick Reference

### 21.1 Dev commands

| Command | Purpose |
|---|---|
| `corepack enable && pnpm install` | Bootstrap. |
| `pnpm dev` | Web dev server. |
| `pnpm dev:tamagotchi` | Desktop (Electron) dev. |
| `pnpm dev:pocket:ios --target <id>` | Mobile (iOS) dev. |
| `pnpm dev:docs` | Docs site. |
| `pnpm lint && pnpm typecheck` | Pre-commit checks. |
| `pnpm -F <pkg> build` | Build one workspace. |
| `pnpm exec vitest run <file>` | Run one test file. |
| `npx bumpp --no-commit --no-tag` | Bump monorepo version. |

### 21.2 Service start pattern

```shell
cd services/<name> && cp .env .env.local   # edit credentials
pnpm -F @proj-airi/<name> start
```

### 21.3 Tech stack at a glance

| Layer | Technology |
|---|---|
| UI | Vue 3, Vue Router, Pinia, VueUse, UnoCSS, reka-ui |
| Desktop | Electron, electron-vite, electron-updater |
| Mobile | Capacitor (Swift/Kotlin), cap-vite |
| Build | pnpm workspaces + catalog, Turborepo, Vite, tsdown, VitePress |
| AI | xsai (40+ providers), candle (local CUDA/Metal), Kokoro TTS, transformers.js |
| Rendering | Live2D Cubism, Three.js + three-vrm, Spine, (experimental) Godot |
| Backend | Hono, Drizzle + Postgres (pgvector), Redis, better-auth, Stripe, Resend |
| Cross-cutting | @moeru/eventa (IPC), injeca (DI), valibot (validation), @moeru/std |
| Observability | OpenTelemetry, Prometheus, Loki, Tempo, Grafana, Langfuse, PostHog |
| Data (client) | DuckDB WASM, pglite, OPFS |

### 21.4 Map of the repo

| Path | Owns |
|---|---|
| `apps/stage-web` · `stage-tamagotchi` · `stage-pocket` | Client surfaces |
| `apps/server` · `ui-admin` · `ui-server-auth` | Cloud backend + its UIs |
| `packages/stage-ui` (+ `stage-shared`, `ui`, `stage-pages`) | Shared product logic |
| `packages/server-runtime` · `server-sdk` · `server-shared` | Server channel |
| `packages/plugin-sdk` · `plugin-protocol` | Plugin system |
| `packages/stage-ui-{live2d,spine,three}` · `model-driver-*` | Rendering |
| `packages/audio*` · `pipelines-audio` | Voice loop |
| `packages/memory-pgvector` | Memory |
| `services/*` | Bots + computer-use |
| `plugins/*` · `integrations/vscode` | Plugins + editor integration |
| `docs/` | VitePress site |
| `crates/` | Legacy Tauri (deprecated) |

### 21.5 Links

- Repository: <https://github.com/moeru-ai/airi>
- Docs site: <https://airi.moeru.ai/docs/en/>
- Sub-project org: <https://github.com/proj-airi>
- `xsai` LLM client: <https://github.com/moeru-ai/xsai>
- Discord: <https://discord.gg/TgQ3Cu2F7A>

---

_Generated June 2026 from moeru-ai/airi at 19e5020. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
