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

> Learn to integrate PI Desktop with external systems using its plugin architecture and host API. Develop secure bidirectional communication for commands, agent tools, and more.

- Repository: [Lan/PI-Desktop](https://github.com/vastsa/PI-Desktop)
- Tags: how-to-guide
- Published: 2026-09-12

---

**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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/apps/desktop/electron/main/plugin-manager.ts) file handles installation, validation, and runtime monitoring, while [`apps/desktop/electron/main/plugin-runtime.ts`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json), and implement the runtime in [`main.js`](https://github.com/vastsa/PI-Desktop/blob/main/main.js).

### Plugin Directory Structure

```text
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`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) declares the plugin identity, entry point, contributions, and required permissions. This file is validated by [`plugin-manager.ts`](https://github.com/vastsa/PI-Desktop/blob/main/plugin-manager.ts) before the sandbox spawns.

```json
{
  "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`](https://github.com/vastsa/PI-Desktop/blob/main/main.js) file exports an `onLoad` function that receives the `pi` API object. Here, you register commands, tools, and service logic.

```javascript
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`](https://github.com/vastsa/PI-Desktop/blob/main/renderer/index.html) file runs in a sandboxed `BrowserWindow` and communicates via the preload bridge.

```html
<!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`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) against the schema in [`plugin-manager.ts`](https://github.com/vastsa/PI-Desktop/blob/main/plugin-manager.ts)
3. **Spawn**: A sandboxed `utilityProcess` launches via `plugin-host-process.mjs` to execute [`main.js`](https://github.com/vastsa/PI-Desktop/blob/main/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:

- [`apps/desktop/electron/main/plugin-manager.ts`](https://github.com/vastsa/PI-Desktop/blob/main/apps/desktop/electron/main/plugin-manager.ts) – Core lifecycle management (install, enable, disable, unload)
- [`apps/desktop/electron/main/plugin-runtime.ts`](https://github.com/vastsa/PI-Desktop/blob/main/apps/desktop/electron/main/plugin-runtime.ts) – JSON-RPC broker for `pi.*` call mediation
- `apps/desktop/electron/main/plugin-host-process.mjs` – Sandbox process spawner using `utilityProcess`
- [`docs/spec/07-plugins/01-plugin-system.md`](https://github.com/vastsa/PI-Desktop/blob/main/docs/spec/07-plugins/01-plugin-system.md) – Authoritative specification of the permission model and API surface
- `examples/plugins/hello/` – Minimal working template referenced by the `npm create pi-desktop-plugin` scaffolding

## 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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/main.js). The host validates these calls against the declared permissions in [`plugin-runtime.ts`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) and [`main.js`](https://github.com/vastsa/PI-Desktop/blob/main/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.