# Pi Web Plugin and Skill Management APIs: Complete Reference for Extension Management

> Explore Pi Web's plugin and skill management APIs for seamless extension control. Manage, install, update, enable, and disable extensions and skills securely via HTTP endpoints.

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

---

**Pi Web's plugin and skill management APIs expose HTTP endpoints under `/api/plugins` and `/api/skills` that enable listing, installing, updating, enabling, disabling, and toggling extensions and individual skills with security controls and trust validation.**

The **pi-coding-agent** SDK provides the underlying engine for these operations, with routes defined in `app/api/` and core logic delegated to `DefaultPackageManager`, `DefaultResourceLoader`, and `SettingsManager`. This guide covers the complete API surface for managing both plugin packages and individual skills in Pi Web.

## Plugin API (`/api/plugins`)

The plugin management endpoint in **[app/api/plugins/route.ts](https://github.com/agegr/pi-web/blob/main/app/api/plugins/route.ts)** handles two primary methods: `GET` for listing and `POST` for state changes.

### GET `/api/plugins`

Returns a complete snapshot of every configured plugin package for the specified working directory.

```typescript
// Query parameters
curl "http://localhost:3000/api/plugins?cwd=/path/to/project"

```

The `readPlugins()` function validates the `cwd` parameter against allowed file roots, loads settings from `~/.pi/agent/settings.json` and project-specific files, and returns a `PluginsResponse` containing per-package metadata, resource totals, and diagnostics.

### POST `/api/plugins`

Executes package lifecycle actions with a JSON body specifying `action`, optional `source` and `scope`, and mandatory `cwd`.

| Action | Description |
|--------|-------------|
| **install** | Adds a new package from npm, git, or local source |
| **remove** | Uninstalls an existing package |
| **update** | Refreshes package to latest compatible version |
| **disable** | Marks package as disabled in settings without removal |
| **enable** | Reactivates a previously disabled package |

```javascript
// Install an npm package globally
const installBody = {
  action: 'install',
  cwd: '/home/user/project',
  source: 'npm:@pi-ai/core-tools@^2.0.0',
  scope: 'global'
};

const response = await fetch('/api/plugins', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(installBody)
});

```

### Security and Trust Verification

Every plugin operation enforces three security layers:

- **Path validation** – `getAllowedFileRoots()` and `isExistingFilePathAllowed()` ensure `cwd` resides within permitted directories
- **Project trust** – `getProjectTrustStatus()` from **[lib/project-trust.ts](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts)** must return true for project-scoped modifications
- **Settings persistence** – `setPackageDisabled()` updates the in-memory settings and `flush()` writes to disk

### Resource Aggregation

`DefaultPackageManager.collectResources()` builds resource maps tracking four resource types per package:

- **extensions** – Code contributions to the IDE
- **skills** – Markdown-based assistant capabilities
- **prompts** – Reusable prompt templates
- **themes** – Visual customization definitions

The `readPackageMetadata()` helper reads [`package.json`](https://github.com/agegr/pi-web/blob/main/package.json) for installed packages, while `getConfiguredVersion()` parses version hints from source strings like `npm:foo@1.2.3`.

## Skill API (`/api/skills`)

Skill management operates on individual `.md` files rather than packages. The endpoint in **[app/api/skills/route.ts](https://github.com/agegr/pi-web/blob/main/app/api/skills/route.ts)** delegates loading to **[lib/skills-service.ts](https://github.com/agegr/pi-web/blob/main/lib/skills-service.ts)**.

### GET `/api/skills`

Returns all discoverable skills for a working directory with install metadata.

```javascript
// Load skills for current project
const skills = await fetch(`/api/skills?cwd=${encodeURIComponent(cwd)}`)
  .then(r => r.json()); // => SkillsResponse

```

The `loadSkillsWithInstallInfo()` function:

1. Instantiates `DefaultResourceLoader` with `cwd` and global `agentDir`
2. Calls `loader.getSkills()` with `projectTrustReloadOptions` based on trust status
3. Applies `annotateSkillsWithInstallInfo()` from **[lib/skill-lock.ts](https://github.com/agegr/pi-web/blob/main/lib/skill-lock.ts)** to attach package source, scope, and version hash

### PATCH `/api/skills`

Toggles the `disable-model-invocation` front-matter flag without modifying skill content.

```javascript
// Disable LLM invocation for a specific skill
const patchBody = {
  filePath: '/home/user/.agents/skills/database/SKILL.md',
  disableModelInvocation: true
};

await fetch('/api/skills', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(patchBody)
});

```

The handler:

- Validates `filePath` against allowed roots (including `~/.agents/skills` via explicit whitelist addition)
- Parses YAML front-matter with `parseFrontmatter`
- Inserts or removes `disable-model-invocation` while preserving formatting
- Writes back with `writeFileSync`

## Common Infrastructure

Both APIs share foundational security and type systems defined in **[lib/api-types.ts](https://github.com/agegr/pi-web/blob/main/lib/api-types.ts)**.

### File Access Security

**[lib/file-access.ts](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)** centralizes path validation:

- `getAllowedFileRoots()` – Returns permitted directory list
- `isExistingFilePathAllowed()` – Validates specific paths

### Project Trust System

**[lib/project-trust.ts](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts)** governs whether project-scoped resources load automatically:

- Trusted projects: Full resource loading with `projectTrustReloadOptions`
- Untrusted projects: Restricted loading or explicit user confirmation required

### Type Definitions

Core response shapes in [`api-types.ts`](https://github.com/agegr/pi-web/blob/main/api-types.ts):

| Type | Purpose |
|------|---------|
| `PluginsResponse` | Complete plugin listing with `PluginPackageInfo[]`, totals, diagnostics |
| `SkillsResponse` | Skill collection with metadata annotations |
| `PluginPackageInfo` | Single package record with resources, version, install status |
| `SkillInfo` | Individual skill with front-matter and provenance |

## Practical Integration Examples

### Complete Plugin Lifecycle

```javascript
import { useCallback, useEffect, useState } from 'react';

function usePluginManager(cwd) {
  const [plugins, setPlugins] = useState(null);

  const refresh = useCallback(async () => {
    const res = await fetch(`/api/plugins?cwd=${encodeURIComponent(cwd)}`);
    const data = await res.json();
    setPlugins(data);
    return data;
  }, [cwd]);

  const install = useCallback(async (source, scope = 'global') => {
    await fetch('/api/plugins', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ action: 'install', cwd, source, scope })
    });
    return refresh();
  }, [cwd, refresh]);

  const toggle = useCallback(async (packageName, enable) => {
    await fetch('/api/plugins', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ 
        action: enable ? 'enable' : 'disable', 
        cwd, 
        source: packageName 
      })
    });
    return refresh();
  }, [cwd, refresh]);

  useEffect(() => { refresh(); }, [refresh]);

  return { plugins, install, toggle, refresh };
}

```

### Skill Invocation Control

```javascript
// React component for skill management
function SkillToggle({ skill, onChange }) {
  const handleToggle = async () => {
    const newValue = !skill.disableModelInvocation;
    await fetch('/api/skills', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        filePath: skill.filePath,
        disableModelInvocation: newValue
      })
    });
    onChange();
  };

  return (
    <button onClick={handleToggle}>
      {skill.disableModelInvocation ? 'Enable' : 'Disable'} LLM Use
    </button>
  );
}

```

## Key Implementation Files

| File | Responsibility |
|------|---------------|
| **[app/api/plugins/route.ts](https://github.com/agegr/pi-web/blob/main/app/api/plugins/route.ts)** | Plugin GET/POST handlers, `readPlugins()` implementation |
| **[app/api/skills/route.ts](https://github.com/agegr/pi-web/blob/main/app/api/skills/route.ts)** | Skill GET/PATCH handlers, front-matter manipulation |
| **[lib/skills-service.ts](https://github.com/agegr/pi-web/blob/main/lib/skills-service.ts)** | `loadSkillsWithInstallInfo()`, resource loader orchestration |
| **[lib/api-types.ts](https://github.com/agegr/pi-web/blob/main/lib/api-types.ts)** | TypeScript interfaces for all API responses |
| **[lib/file-access.ts](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)** | Whitelist validation and security boundaries |
| **[lib/project-trust.ts](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts)** | Trust determination for project-scoped operations |
| **[lib/skill-lock.ts](https://github.com/agegr/pi-web/blob/main/lib/skill-lock.ts)** | Install metadata annotation for skills |

## Summary

- **Two API endpoints** provide complete plugin and skill management: `/api/plugins` for packages and `/api/skills` for individual capabilities
- **Five plugin actions** (install, remove, update, disable, enable) run through POST with scope control for global versus project-specific extension
- **Skill PATCH** enables runtime toggling of LLM invocation without content modification via YAML front-matter manipulation
- **Security layers** include path whitelisting in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts), project trust verification in [`lib/project-trust.ts`](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts), and explicit global skills directory handling
- **Type safety** derives from centralized definitions in [`lib/api-types.ts`](https://github.com/agegr/pi-web/blob/main/lib/api-types.ts) with response shapes matching `PluginsResponse` and `SkillsResponse`

## Frequently Asked Questions

### How does Pi Web validate that plugin operations are allowed?

Pi Web validates operations through `getAllowedFileRoots()` and `isExistingFilePathAllowed()` in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts), which ensure the `cwd` parameter resides within permitted directories. For project-scoped modifications, `getProjectTrustStatus()` additionally verifies the project is trusted before allowing state changes.

### What is the difference between disabling and removing a plugin?

Disabling a plugin via `action: 'disable'` marks it as inactive in settings without uninstalling files—the package remains on disk but contributes zero resources. Removing via `action: 'remove'` completely uninstalls the package and deletes its files. Disabled packages can be re-enabled instantly; removed packages must be re-installed from source.

### Can skills be managed independently of their parent plugins?

Yes. The `/api/skills` endpoint provides granular control over individual skills regardless of package origin. The PATCH method modifies only the `disable-model-invocation` front-matter flag in the specific `.md` file, leaving the skill's package and other skills untouched. This permits fine-grained capability control without affecting extension-wide settings.

### Where does Pi Web store plugin and skill configuration?

Global configuration resides in `~/.pi/agent/settings.json` managed by `SettingsManager`, with project-specific overrides in each project's settings file. Disabled states are persisted as empty resource arrays. The global skills directory at `~/.agents/skills` is explicitly whitelisted for access even though it lives outside the agent directory proper.