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

> Discover how Pi-web manages plugins and skills using distinct API routes. Learn about specialized endpoints and backend logic for NPM packages and markdown skill files.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-08-18

---

**Pi-web separates plugins and skills into distinct API route hierarchies—[`app/api/plugins/route.ts`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/plugin-manager.ts) in the Pi SDK, specifically the `DefaultPackageManager` class. This abstraction handles:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/app/api/skills/install/route.ts) | Fetch and place new skill files (`npx skills add …`) |
| Search | [`app/api/skills/search/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/skills/search/route.ts) | Query public skill registry |
| Check | [`app/api/skills/check/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/skills/check/route.ts) | Validate skill file integrity |
| Update | [`app/api/skills/update/route.ts`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/lib/agent-client.ts) to communicate with their respective routes.

### Installing a Plugin

```typescript
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

```typescript
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

```typescript
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:

- **[`components/PluginsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/components/PluginsConfig.tsx)** – Renders installed NPM packages with version numbers and enable toggles
- **[`components/SkillsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/components/SkillsConfig.tsx)** – Lists skill files with search functionality and model-invocation switches

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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/PluginsConfig.tsx) and [`SkillsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/app/api/skills/route.ts) handler catches YAML parsing errors and returns HTTP 422 with details about the validation failure. The [`SkillsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/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.