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 informationpi.ui.openPanel()– Launch plugin UI panelspi.fs.readText()/pi.fs.writeText()– File system operations (requires explicit permissions)pi.net.fetch()– Network requests (requiresnet.fetchpermission)pi.agent.registerTool()– Expose capabilities to the AI Agentpi.services.register()– Run background workerspi.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)andpi.fs.writeText(path, content)requirefs.readandfs.writepermissions - Network:
pi.net.fetch(request)requires thenet.fetchpermission 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
- Load: Install via Plugins → Load Development Plugin and select the
my-integrationfolder - Validate: The host validates
manifest.jsonagainst the schema inplugin-manager.ts - Spawn: A sandboxed
utilityProcesslaunches viaplugin-host-process.mjsto executemain.js - Register: The
onLoadfunction registers the command and tool with the JSON-RPC broker - Interact: Users invoke commands from the palette, or the Agent calls the tool, triggering
pi.net.fetchand bus updates - Render: The UI panel receives data via
window.pluginBridge.bus.subscribeand 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– Core lifecycle management (install, enable, disable, unload)apps/desktop/electron/main/plugin-runtime.ts– JSON-RPC broker forpi.*call mediationapps/desktop/electron/main/plugin-host-process.mjs– Sandbox process spawner usingutilityProcessdocs/spec/07-plugins/01-plugin-system.md– Authoritative specification of the permission model and API surfaceexamples/plugins/hello/– Minimal working template referenced by thenpm create pi-desktop-pluginscaffolding
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.jsondefines 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →