How to Integrate moeru-ai/airi with Other Tools: The Complete Plugin SDK Guide

To integrate external tools with moeru-ai/airi, implement a plugin using the @proj-airi/plugin-sdk, define a ManifestV1 schema, and load it via the PluginHost class which manages lifecycle states and transport-agnostic communication through the Eventa channel.

The moeru-ai/airi repository provides a runtime-agnostic plugin SDK that enables external tools—such as Discord bots, Minecraft clients, VS Code extensions, or custom WebSocket services—to hook into the core platform without tightly coupling to its UI layers. This integration leverages a finite-state machine lifecycle and type-safe RPC capabilities to ensure reliable communication between the host application and your custom implementations.

Understanding the AIRI Plugin Architecture

The integration architecture centers on three core abstractions that isolate plugin logic from transport mechanisms.

Core Components and Source Locations

The PluginHost class, defined in packages/plugin-sdk/src/plugin-host/core.ts, manages plugin sessions and enforces lifecycle transitions. It creates an isolated Eventa channel (via @moeru/eventa) for each plugin, enabling multiple plugins to coexist in the same process (in-memory transport) or across processes (WebSocket, worker threads).

The FileSystemLoader class, located in the same core.ts file, resolves runtime-specific entrypoints from a ManifestV1 declaration. It handles entrypoint selection logic for default, electron, node, and web runtimes, dynamically importing the correct module based on the host's environment.

Lifecycle State Machine

Every plugin session follows a strict finite-state machine with the following states: loading → loaded → authenticating → authenticated → announced → preparing → prepared → configuration-needed → configured → ready. Invalid transitions raise errors via assertTransition. This machine ensures that capabilities are only accessible after the plugin reaches the ready state.

Step-by-Step Integration Guide

1. Install the Plugin SDK

Add the official package to your project. The SDK requires no additional runtime dependencies for in-memory transport.

pnpm add @proj-airi/plugin-sdk

2. Create the Plugin Manifest

Define a manifest.json file that conforms to manifestV1Schema. This declares your plugin name and entrypoint paths for each supported runtime.

{
  "apiVersion": "v1",
  "kind": "manifest.plugin.airi.moeru.ai",
  "name": "my-integration-tool",
  "entrypoints": {
    "default": "src/index.ts",
    "node": "src/node.ts",
    "web": "src/web.ts"
  }
}

Save this file in your plugin directory. The PluginHost validates this automatically against manifestV1Schema during the load phase.

3. Implement the Plugin Logic

Export a plain Plugin object exposing init and optionally setupModules methods. Alternatively, use the definePlugin helper for lazy-loading scenarios.

Plain object implementation:

// src/index.ts
import type { Plugin } from '@proj-airi/plugin-sdk/plugin'

export const myPlugin: Plugin = {
  async init({ channels, apis }) {
    channels.host.on('module:ready', () => console.log('AIRI host ready'))
    
    apis.myTool = {
      async ping() {
        return 'pong'
      }
    }
  },
  
  async setupModules({ apis }) {
    apis.publishCapability('my-tool:available')
  }
}

Lazy loading with definePlugin:

import { definePlugin } from '@proj-airi/plugin-sdk/plugin'

export default definePlugin(async () => ({
  init: async ({ channels }) => {
    channels.host.emit('my-tool:started')
  }
}))

4. Load and Initialize via PluginHost

Instantiate the host, load the manifest, and initialize the session. This pattern mirrors the desktop app implementation found in apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts.

import { PluginHost } from '@proj-airi/plugin-sdk/plugin-host'
import { safeParse } from 'valibot'
import { manifestV1Schema } from '@proj-airi/plugin-sdk/plugin-host'
import manifest from './manifest.json'

async function bootstrap() {
  // Validate manifest structure
  const valid = safeParse(manifestV1Schema, manifest).success
  if (!valid) throw new Error('Invalid plugin manifest')

  // Create host with appropriate runtime: 'electron', 'node', or 'web'
  const host = new PluginHost({ runtime: 'node' })
  
  // Load manifest and resolve entrypoints
  const session = await host.load(manifest, { cwd: __dirname })
  
  // Initialize with optional capability requirements
  await host.init(session.id, {
    requiredCapabilities: ['discord:bot']
  })
  
  console.log('Plugin active, session:', session.id)
}
bootstrap()

5. Interact with Capabilities and Providers

The SDK injects a bound API object (session.apis) containing protocol helpers. Capability waiting allows plugins to depend on services provided by other plugins.

import { protocolCapabilityWait } from '@proj-airi/plugin-sdk/plugin/apis/protocol'

export async function init({ apis }) {
  // Block until Discord bot capability is ready
  const discord = await apis.waitForCapability('discord:bot')
  
  // Invoke RPC methods exposed by the Discord plugin
  await apis.discord.sendMessage({
    channelId: '123456',
    text: 'Hello from integrated tool'
  })
}

Enumerate available external services using apis.providers.listProviders, which returns providers for platforms like Telegram, Discord, or custom WebSocket services.

Real-World Integration Patterns

Minimal Node.js Plugin Example

manifest.json:

{
  "apiVersion": "v1",
  "kind": "manifest.plugin.airi.moeru.ai",
  "name": "hello-world",
  "entrypoints": { "default": "src/index.ts" }
}

Plugin implementation:

import type { Plugin } from '@proj-airi/plugin-sdk/plugin'

export const helloWorld: Plugin = {
  async init({ channels }) {
    console.log('Hello-World plugin initialized')
    channels.host.emit('hello-world:ready')
  }
}

Host bootstrap:

import { PluginHost } from '@proj-airi/plugin-sdk/plugin-host'
import manifest from './manifest.json'

async function start() {
  const host = new PluginHost({ runtime: 'node' })
  const session = await host.start(manifest, { cwd: __dirname })
  console.log('Session ready:', session.id)
}
start()

Publishing Custom Capabilities

If your tool exposes a service other plugins might consume, announce it during the setupModules phase:

export async function setupModules({ apis }) {
  apis.publishCapability('weather:service')
  apis.markCapabilityReady('weather:service')
}

Consumers then await this capability using apis.waitForCapability('weather:service') before invoking your exposed methods.

Advanced Lifecycle Management

The PluginHost provides granular control over plugin states beyond simple initialization.

Stop a plugin: Call host.stop(sessionId) to transition the session to the stopped state and release resources.

Reload a plugin: Use host.reload(sessionId, options) to create a fresh session while preserving the original manifest configuration. This is useful for hot-reloading during development.

Configuration workflows: Plugins can signal configuration requirements by emitting module:configuration:needed. The host supplies configuration data via host.applyConfiguration(sessionId, configEnvelope), transitioning the state to configured and then ready.

The desktop application's devtools expose these actions through the Plugin Host Debug bridge located in packages/stage-ui/src/stores/devtools/plugin-host-debug.ts.

Summary

  • Declare a ManifestV1 schema describing your plugin name and runtime-specific entrypoints.
  • Implement the Plugin interface with init and optional setupModules methods, or use definePlugin for lazy loading.
  • Instantiate PluginHost with the correct runtime context (electron, node, or web).
  • Load manifests using host.load() and start sessions via host.init() or the convenience host.start().
  • Communicate through the Eventa channel using apis.waitForCapability() and apis.publishCapability() for dependency management.
  • Reference production implementations in services/telegram-bot/src/index.ts and services/minecraft/README.md for platform-specific integration patterns.

Frequently Asked Questions

What is the PluginHost class in moeru-ai/airi?

The PluginHost class, located in packages/plugin-sdk/src/plugin-host/core.ts, is the central orchestrator for plugin lifecycle management. It creates isolated execution contexts, validates manifest schemas, manages the finite-state machine transitions (from loading to ready), and provides the load(), init(), start(), stop(), and reload() methods for session control.

How does the AIRI plugin SDK handle communication between plugins?

The SDK uses the Eventa channel (@moeru/eventa), a transport-agnostic RPC and event bus. Each plugin receives an isolated channel instance, enabling type-safe communication via channels.host.emit() and channels.host.on(). The Capability Registry allows plugins to announce availability (publishCapability) and wait for dependencies (waitForCapability), ensuring services are only consumed after they are fully initialized.

Can I integrate AIRI with Discord or Minecraft using this SDK?

Yes. The repository includes reference implementations for both platforms. The Telegram bot service (services/telegram-bot/src/index.ts) and Minecraft integration (services/minecraft/README.md) demonstrate how to expose platform-specific actions through the plugin SDK. You can consume these capabilities using apis.waitForCapability('discord:bot') or apis.waitForCapability('minecraft:client') after loading the respective service plugins.

What runtime environments does the AIRI plugin SDK support?

The SDK supports Node.js, Electron, and Web (browser) runtimes. The FileSystemLoader class automatically resolves the correct entrypoint based on the runtime parameter passed to PluginHost ('node', 'electron', or 'web'), allowing the same plugin codebase to operate across desktop applications, CLI tools, and browser-based interfaces without modification to the host logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →