How Plugins and Skills Are Managed Through Different API Routes in Pi-Web

Pi-web separates plugins and skills into distinct API route hierarchies—app/api/plugins/route.ts for NPM-based packages and app/api/skills/[action]/route.ts for markdown skill files—each with specialized endpoints, backend logic, and UI integration.

Extension management in Pi-web follows a strict architectural split between plugins (global NPM packages) and skills (individual markdown files). Both mechanisms leverage the Pi SDK's core abstractions, but they expose completely different route structures to match their unique lifecycle requirements.

Plugin Management via /api/plugins

Plugins in Pi-web are NPM packages that provide UI widgets, commands, or language-model integrations. The entire plugin API surface lives in a single consolidated route.

API Route Structure

The plugin handler resides at app/api/plugins/route.ts and implements three HTTP verbs:

  • GET – List currently installed plugins from ~/.pi/agent/settings.json
  • POST – Install a new plugin via DefaultPackageManager.install(name, version)
  • DELETE – Remove a plugin and update the settings file

Backend Implementation

All plugin operations delegate to lib/plugin-manager.ts in the Pi SDK, specifically the DefaultPackageManager class. This abstraction handles:

// Conceptual flow in app/api/plugins/route.ts
import { DefaultPackageManager } from '@pi/core';

// POST handler
const manager = new DefaultPackageManager();
await manager.install(name, version);
// Updates extensions/plugins section of settings.json

The package manager executes npm install commands within an allowed file root, then persists the plugin metadata to the user's agent configuration.

Security Boundary

File-system access is gated by allowFileRoot() in lib/file-access.ts. Only paths belonging to:

  • The current workspace
  • The global ~/.pi directory

are permitted for plugin installation.

Skills Management via /api/skills/*

Skills are markdown files (*.skill.md) containing prompts, themes, or model-invocation configurations. Unlike plugins, skills require a distributed route structure with dedicated endpoints for each operation.

Primary Route: /api/skills

The base route at app/api/skills/route.ts handles:

  • GET – Scan allowed directories and return skill descriptors
  • PATCH – Modify skill metadata (enable/disable model invocation)

Supporting Routes

Route File Path Purpose
Install app/api/skills/install/route.ts Fetch and place new skill files (npx skills add …)
Search app/api/skills/search/route.ts Query public skill registry
Check app/api/skills/check/route.ts Validate skill file integrity
Update app/api/skills/update/route.ts Toggle disable-model-invocation front-matter key

Backend Implementation

Skills use DefaultResourceLoader from the Pi SDK to read/write markdown files under:

  • ~/.pi/agent/skills (global)
  • .agents/skills (project-local)

Enabling or disabling a skill modifies the YAML front-matter:

// In app/api/skills/update/route.ts
// Toggling disable-model-invocation
const skill = await resourceLoader.readSkill(path);
skill.frontMatter['disable-model-invocation'] = disable;
await resourceLoader.writeSkill(path, skill);

Security Boundary

The isFilePathAllowed() guard restricts skill access to:

  • Session current working directory
  • Project root
  • ~/pi-cwd-* temporary directories

Client-Side Integration

Both extension types use the typed fetch helper in lib/agent-client.ts to communicate with their respective routes.

Installing a Plugin

import { post } from '@/lib/agent-client';

async function installPlugin(name: string, version?: string) {
  const payload = { name, ...(version && { version }) };
  const response = await post('/api/plugins', payload);
  
  if (!response.ok) {
    const err = await response.json();
    throw new Error(`Plugin install failed: ${err.message}`);
  }
  
  return response.json(); // Returns refreshed plugin list
}

Listing Skills

import { get } from '@/lib/agent-client';

async function fetchSkills() {
  const { data } = await get('/api/skills');
  // Array of skill descriptors with path, enabled status, metadata
  return data;
}

Toggling Skill Model Invocation

import { patch } from '@/lib/agent-client';

async function toggleSkill(path: string, disable: boolean) {
  await patch('/api/skills', { 
    path, 
    disableModelInvocation: disable 
  });
}

UI Component Separation

The frontend maintains parallel components that map directly to these route structures:

Each component imports only the relevant API methods, enforcing the architectural boundary at the UI layer.

Error Handling Differences

Extension Type HTTP Status Error Source
Plugins 400 NPM failures (missing package, version conflicts)
Skills 422 Front-matter validation failures

Both return JSON with a human-readable message field for display in the respective config panels.

Summary

  • Plugins use a single consolidated route (app/api/plugins/route.ts) backed by DefaultPackageManager for NPM operations
  • Skills use a distributed route hierarchy (/api/skills/*) backed by DefaultResourceLoader for markdown file operations
  • Both systems share the lib/agent-client.ts fetch abstraction but call entirely separate endpoint structures
  • Security boundaries differ: plugins rely on allowFileRoot(), skills use isFilePathAllowed()
  • UI components PluginsConfig.tsx and SkillsConfig.tsx mirror this separation at the presentation layer

Frequently Asked Questions

Why don't plugins and skills share the same API route structure?

Plugins require NPM package management—install, uninstall, version resolution—while skills need markdown file operations with front-matter editing and searchable registries. The Pi SDK separates these concerns into DefaultPackageManager and DefaultResourceLoader, so the API routes follow suit to maintain clean abstraction boundaries.

Can I enable or disable a plugin without uninstalling it?

Yes. The plugin list in ~/.pi/agent/settings.json includes an enabled boolean that the /api/plugins route toggles via PATCH operations, similar to skills. However, unlike skills, this doesn't require a separate sub-route because plugin state is centralized in one configuration file.

What happens when a skill's front-matter is malformed?

The app/api/skills/route.ts handler catches YAML parsing errors and returns HTTP 422 with details about the validation failure. The SkillsConfig.tsx component displays this message inline, allowing users to fix the file manually or remove the corrupted skill.

Are plugin and skill installations scoped to the current project?

Both support global and local installation. Plugins default to global scope in ~/.pi/agent/settings.json but can target project workspaces. Skills automatically scan both ~/.pi/agent/skills and the project's .agents/skills directory, with local skills taking precedence in the UI listing.

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 →