Pi supports installing packages that provide extensions, skills, prompt templates, and themes.
Pi supports three types of package sources:
- npm:
npm:package-nameornpm:@org/package(optionally@version) - git:
git:host/owner/repoor justhttps://github.com/owner/repo(optionally@ref) - local: path to a directory (e.g.
../my-package)
Install a package globally (user scope):
pi install npm:pi-skills
pi install git:github.com/someuser/my-toolsInstall locally for the current project:
pi install --local npm:@org/project-utilsThis adds the package to your settings.json (global or project) and installs it.
Remove a package:
pi remove npm:pi-skills
pi remove --local npm:@org/project-utilsUpdate all packages (or a specific one):
pi update
pi update npm:pi-skillsPackages with pinned versions (e.g. npm:pkg@1.2.3 or git:repo@v1) are skipped unless the command arguments explicitly change the version.
List installed packages:
pi listWhen a package is installed, Pi looks for resources in the following locations within the package root:
-
Manifest: If
package.jsonhas apisection, it uses the paths defined there."pi": { "extensions": ["dist/extension.js"], "skills": ["skills/"], "prompts": ["prompts/"], "themes": ["themes/"] }
-
Conventions: If no manifest entry exists, Pi looks for standard directories:
extensions/(orindex.ts/index.jsfor single-file extensions)skills/prompts/themes/
For extension package roots, resolution is intentionally deterministic:
- If
package.jsonincludespi.extensions, only those listed entries are considered. - If the
pi.extensionskey is present but empty, Pi loads no extensions from that package. - If
pi.extensionsentries are present but none resolve to existing files, Pi loads no extensions from that package. - In both cases above, Pi does not implicitly fall back to
index.ts,index.js, or directory-level extension loading. - Conventional fallback (
extensions/,index.ts,index.js) applies only when thepi.extensionskey is absent.
You can manually configure packages in settings.json:
{
"packages": [
"npm:pi-skills",
{
"source": "git:github.com/org/repo",
"skills": ["relevant-skill"],
"extensions": []
}
]
}The object form allows filtering which resources are loaded (allowlisting). If a field is omitted, all resources of that type are loaded. If a field is present but empty, no resources of that type are enabled.
Filter values can be strings or arrays. They accept glob-like patterns relative to the package root, with optional prefixes:
pattern→ include matches (if any include patterns exist)!pattern→ exclude matches+pattern→ force include (exact path match)-pattern→ force exclude (exact path match)
Example:
{
"packages": [
{
"source": "git:github.com/org/repo",
"extensions": ["dist/*.js", "!dist/experimental.js"],
"skills": []
}
]
}Pi writes a deterministic package lockfile after successful install/update verification:
- Project scope:
.pi/packages.lock.json - User scope:
~/.pi/agent/packages.lock.json
Lock entries are sorted deterministically and include:
identity(stable package identity, e.g.npm:name,git:host/path,local:/abs/path)sourceandsource_kind- resolved provenance (npm version, git commit/ref/origin, or resolved local path)
digest_sha256(deterministic content digest)trust_state
By default, install/update is fail-closed when trusted provenance or digest does not match:
- pinned npm installs must match pinned version metadata
- pinned git installs must match pinned ref/commit resolution
- trusted digest/provenance mismatches block install/update
For unpinned pi update, provenance/digest rotation is allowed and re-recorded as a trusted update.
Pi appends trust-state transitions as JSONL audit events:
- Project scope:
.pi/package-trust-audit.jsonl - User scope:
~/.pi/agent/package-trust-audit.jsonl
Each event records action (install, update, remove), scope, identity, source,
from_state, to_state, deterministic reason codes, and optional remediation guidance.