How to Integrate PI Desktop with Other Systems: A Complete Plugin Development Guide

PI Desktop integrates with external systems through its extensible plugin architecture and host-exposed API, enabling secure bidirectional communication via commands, agent tools, background services, and the message bus.

The vastsa/PI-Desktop repository provides a modular, Electron-based host that treats external integrations as first-class plugins. By packaging your integration logic as a directory with a manifest.json and leveraging the stable pi.* namespace, you can connect PI Desktop toREST APIs, local services, or custom workflows while the host enforces strict permission boundaries.

Understanding the PI Desktop Plugin Architecture

PI Desktop employs a layered architecture that isolates third-party code while exposing a rich API surface for system integration.

Host Layer and Plugin Management

The Host Main layer manages the entire plugin lifecycle. According to the PI Desktop source code, the apps/desktop/electron/main/plugin-manager.ts file handles installation, validation, and runtime monitoring, while apps/desktop/electron/main/plugin-runtime.ts implements the JSON-RPC broker that mediates all communication between the host and sandboxed plugins. This broker ensures that only whitelisted pi.* APIs are accessible to external code.

Plugin Sandbox and Process Isolation

Each plugin runs inside an isolated utilityProcess spawned via apps/desktop/electron/main/plugin-host-process.mjs. The sandbox prevents direct access to Node.js or Electron internals, forcing all interactions through the typed RPC layer defined in plugin-runtime.ts. The plugin's UI renders inside a sandboxed BrowserWindow (or iframe) with a fixed 46 px drag band, communicating with the host through a secure preload bridge.

The Host API Surface (pi.*)

External systems interact with PI Desktop through the stable pi.* namespace. Key methods include:

  • pi.app.getVersion() – Retrieve host version information
  • pi.ui.openPanel() – Launch plugin UI panels
  • pi.fs.readText() / pi.fs.writeText() – File system operations (requires explicit permissions)
  • pi.net.fetch() – Network requests (requires net.fetch permission)
  • pi.agent.registerTool() – Expose capabilities to the AI Agent
  • pi.services.register() – Run background workers
  • pi.bus.publish() / pi.bus.subscribe() – Decoupled message passing

The complete API specification and permission matrix are documented in docs/spec/07-plugins/01-plugin-system.md.

Integration Points and Mechanisms

PI Desktop offers seven primary integration vectors for connecting with external systems. Each mechanism routes through the Permission Gateway, ensuring least-privilege access control.

Command Palette Extensions

Use pi.commands.register(command) to inject custom commands into the global palette. This allows users to trigger external webhooks or API calls via keyboard shortcuts or slash commands.

Agent Tools Integration

Register external capabilities as Agent tools using pi.agent.registerTool(toolSpec). Once registered, the tool becomes callable from the AI Agent or other plugins, enabling scenarios like forwarding queries to external LLM services or specialized data processors.

Background Services

Long-running integrations (such as local caches or message queues) run as background services via pi.services.register({id, start, stop}). These services persist across UI interactions and can expose internal APIs to other plugins.

Message Bus Architecture

The event-driven pi.bus system decouples components using pi.bus.publish(topic, payload) and pi.bus.subscribe(pattern, handler). For example, a CI/CD integration plugin can publish build status updates while a UI plugin subscribes to display real-time notifications.

File System and Network Access

Direct system integration requires explicit permissions in manifest.json:

  • File system: pi.fs.readText(path) and pi.fs.writeText(path, content) require fs.read and fs.write permissions
  • Network: pi.net.fetch(request) requires the net.fetch permission for REST API communication

Native Notifications

Trigger OS-level alerts using pi.ui.showNativeNotification({title, body}) to alert users when external jobs complete or require attention.

Building an Integration Plugin: End-to-End Example

The following demonstrates a complete integration with an external REST API. This pattern applies to any external system: package the logic as a plugin directory, declare capabilities in manifest.json, and implement the runtime in main.js.

Plugin Directory Structure

my-integration/
├── manifest.json          # Plugin metadata and permissions

├── main.js                # Runtime logic (command/tool registration)

└── renderer/
    └── index.html         # UI panel displayed in sandboxed window

Step 1: Define the Manifest

The manifest.json declares the plugin identity, entry point, contributions, and required permissions. This file is validated by plugin-manager.ts before the sandbox spawns.

{
  "schemaVersion": 1,
  "id": "example.integration",
  "name": "External-Integration Demo",
  "version": "0.1.0",
  "main": "main.js",
  "ui": {
    "panel": "renderer/index.html",
    "width": 480,
    "height": 360
  },
  "contributes": {
    "commands": [
      {
        "id": "integration.fetch",
        "title": "Fetch Remote Data"
      }
    ],
    "agentTools": [
      {
        "name": "integration.fetchRemote",
        "description": "Call external API",
        "risk": "low",
        "schema": {
          "type": "object",
          "properties": {}
        }
      }
    ]
  },
  "permissions": [
    "net.fetch",
    "ui.panel",
    "agent.tool.register"
  ]
}

Step 2: Implement the Main Runtime

The main.js file exports an onLoad function that receives the pi API object. Here, you register commands, tools, and service logic.

export async function onLoad({ pi }) {
  // Register a command that opens the integration panel
  pi.commands.register({
    id: "integration.fetch",
    title: "Fetch Remote Data",
    handler: () => pi.ui.openPanel({ 
      url: "pi-plugin://example.integration/panel" 
    })
  });

  // Register a tool callable by the Agent or other plugins
  pi.agent.registerTool({
    name: "integration.fetchRemote",
    description: "Calls an external REST endpoint",
    handler: async () => {
      const resp = await pi.net.fetch("https://api.example.com/status");
      const data = await resp.json();
      
      // Publish results to the message bus for UI consumption
      pi.bus.publish("integration/status", data);
      return data;
    }
  });
}

Step 3: Create the Renderer UI

The renderer/index.html file runs in a sandboxed BrowserWindow and communicates via the preload bridge.

<!DOCTYPE html>
<html>
<head>
  <meta name="pi-plugin-chrome" content="v2">
</head>
<body>
  <h2>Remote Status</h2>
  <pre id="output">Loading...</pre>
  
  <script>
    // Subscribe to bus events published by the main process
    window.pluginBridge.bus.subscribe('integration/status', (payload) => {
      document.getElementById('output').textContent = 
        JSON.stringify(payload, null, 2);
    });
  </script>
</body>
</html>

Deployment and Execution Flow

  1. Load: Install via Plugins → Load Development Plugin and select the my-integration folder
  2. Validate: The host validates manifest.json against the schema in plugin-manager.ts
  3. Spawn: A sandboxed utilityProcess launches via plugin-host-process.mjs to execute main.js
  4. Register: The onLoad function registers the command and tool with the JSON-RPC broker
  5. Interact: Users invoke commands from the palette, or the Agent calls the tool, triggering pi.net.fetch and bus updates
  6. Render: The UI panel receives data via window.pluginBridge.bus.subscribe and displays results

Key Source Files and References

The following files in the vastsa/PI-Desktop repository define the integration mechanics:

Summary

Integrating PI Desktop with external systems follows a secure, modular pattern:

  • Plugin Architecture: External integrations run as sandboxed plugins with isolated processes and controlled API access
  • Declarative Manifest: The manifest.json defines permissions, commands, tools, and UI panels upfront
  • Host API: The pi.* namespace provides typed methods for network requests, file system access, agent tool registration, and inter-plugin messaging
  • Permission Gateway: All sensitive operations require explicit permission declarations enforced by the host runtime
  • Bidirectional Communication: The message bus (pi.bus) and preload bridge enable real-time data flow between external APIs and the PI Desktop UI

Frequently Asked Questions

What file permissions are required to integrate PI Desktop with local file systems?

To read or write files, your manifest.json must include the fs.read and/or fs.write permissions in the permissions array. At runtime, use pi.fs.readText(path) and pi.fs.writeText(path, content) within your plugin's main.js. The host validates these calls against the declared permissions in plugin-runtime.ts before allowing file system access.

Can PI Desktop plugins communicate with each other directly?

Plugins do not communicate directly; they use the pi.bus API for decoupled messaging. One plugin publishes events using pi.bus.publish(topic, payload), and other plugins (or UI panels) subscribe via pi.bus.subscribe(pattern, handler). This pattern prevents tight coupling and respects the sandbox boundaries enforced by plugin-host-process.mjs.

How does PI Desktop handle security for external API calls?

All network requests route through pi.net.fetch(), which requires the net.fetch permission in the manifest. The host intercepts these calls in plugin-runtime.ts to enforce CORS policies and prevent access to internal network resources unless explicitly allowed. Additionally, plugins run in a utilityProcess sandbox with no direct Node.js access, ensuring that malicious code cannot bypass the permission gateway.

Where can I find a starter template for PI Desktop plugin development?

The repository includes a minimal working example at examples/plugins/hello/, containing a basic manifest.json and main.js. For new projects, use the scaffolding command npm create pi-desktop-plugin, which generates the directory structure, TypeScript configuration, and boilerplate code necessary to integrate PI Desktop with your specific external systems.

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 →