Pi Web Plugin and Skill Management APIs: Complete Reference for Extension Management
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 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.
// 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 |
// 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()andisExistingFilePathAllowed()ensurecwdresides within permitted directories - Project trust –
getProjectTrustStatus()from lib/project-trust.ts must return true for project-scoped modifications - Settings persistence –
setPackageDisabled()updates the in-memory settings andflush()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 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 delegates loading to lib/skills-service.ts.
GET /api/skills
Returns all discoverable skills for a working directory with install metadata.
// Load skills for current project
const skills = await fetch(`/api/skills?cwd=${encodeURIComponent(cwd)}`)
.then(r => r.json()); // => SkillsResponse
The loadSkillsWithInstallInfo() function:
- Instantiates
DefaultResourceLoaderwithcwdand globalagentDir - Calls
loader.getSkills()withprojectTrustReloadOptionsbased on trust status - Applies
annotateSkillsWithInstallInfo()from 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.
// 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
filePathagainst allowed roots (including~/.agents/skillsvia explicit whitelist addition) - Parses YAML front-matter with
parseFrontmatter - Inserts or removes
disable-model-invocationwhile preserving formatting - Writes back with
writeFileSync
Common Infrastructure
Both APIs share foundational security and type systems defined in lib/api-types.ts.
File Access Security
lib/file-access.ts centralizes path validation:
getAllowedFileRoots()– Returns permitted directory listisExistingFilePathAllowed()– Validates specific paths
Project Trust System
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:
| 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
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
// 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 | Plugin GET/POST handlers, readPlugins() implementation |
| app/api/skills/route.ts | Skill GET/PATCH handlers, front-matter manipulation |
| lib/skills-service.ts | loadSkillsWithInstallInfo(), resource loader orchestration |
| lib/api-types.ts | TypeScript interfaces for all API responses |
| lib/file-access.ts | Whitelist validation and security boundaries |
| lib/project-trust.ts | Trust determination for project-scoped operations |
| lib/skill-lock.ts | Install metadata annotation for skills |
Summary
- Two API endpoints provide complete plugin and skill management:
/api/pluginsfor packages and/api/skillsfor 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, project trust verification inlib/project-trust.ts, and explicit global skills directory handling - Type safety derives from centralized definitions in
lib/api-types.tswith response shapes matchingPluginsResponseandSkillsResponse
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, 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.
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 →