This document defines Pi's offline-first extension discovery registry and the local on-disk index
used by user-facing discovery commands (pi search, pi info) and refresh command (pi update-index).
Key goals:
- No central server: data comes from public backends (npm + GitHub) plus curated seed data shipped with the Pi binary.
- Offline-first: discovery must work on first run with zero network access.
- Fail-open: network failures never break search or install; Pi always falls back to seed/cached data.
The index is a single JSON file containing a list of extension descriptors + metadata, suitable for local (client-side) search.
Default location:
~/.pi/agent/extension-index.json- Override via
PI_EXTENSION_INDEX_PATH
Pi ships a seed index embedded in the binary at compile time. It provides:
- Immediate results on first run (no "please wait")
- A useful offline experience
- A stable fallback if a refresh fails or cache becomes corrupt
The seed index is updated with each Pi release.
Pi writes a cached index to disk after refreshing from remote sources. Pi prefers the cache when it is valid, but will transparently fall back to seed on errors.
Pi merges multiple sources into a single index:
-
npm registry search
- Find packages with keywords like
pi-extension/pi-agent-extension. - Populate: package name, version, description, repository URL, last publish date.
- Find packages with keywords like
-
GitHub search
- Search repositories by topic (e.g.
topic:pi-extension) and/or query terms. - Populate: repo name, description, stars, last updated, repo URL.
- Search repositories by topic (e.g.
-
Curated manifest (Pi-maintained)
- A static list of known-good extensions (high-signal, tested, pinned).
- This is the primary content for the seed index shipped in the binary.
extension-index.json uses a versioned schema so future changes are explicit and migratable.
Example:
{
"schema": "pi.ext.index.v1",
"version": 1,
"generatedAt": "2026-02-06T08:00:00Z",
"lastRefreshedAt": "2026-02-06T08:00:00Z",
"entries": [
{
"id": "npm/checkpoint-pi",
"name": "checkpoint-pi",
"description": "Checkpoint and restore your Pi sessions",
"tags": ["npm", "extension"],
"license": "MIT",
"source": {
"type": "npm",
"package": "checkpoint-pi",
"version": "1.2.3",
"url": "https://www.npmjs.com/package/checkpoint-pi"
},
"installSource": "npm:checkpoint-pi@1.2.3"
}
]
}Field notes:
id: globally unique identifier within the index (stable key).name: primary display identifier (often npm package name or repo name).installSource: optional string compatible with Pi package manager (e.g.npm:pkg@ver,git:https://github.com/org/repo@ref). If absent, the entry is discoverable but not directly install-resolvable by id.
- Auto-refresh when the cache is missing or older than 24 hours (available in store API; command-level wiring can choose eager or lazy refresh behavior).
- Manual refresh via
pi update-index.
Refreshing is best-effort:
- Network errors MUST NOT fail discovery commands.
- If refresh fails, Pi continues using the cached index (if present) or the seed index.
If the cached file cannot be parsed:
- Warn (non-fatal).
- Fall back to seed index.
- Overwrite cache on next successful refresh.
Search is computed locally over cached data:
- Tokenize the user query on whitespace.
- Weighted scoring:
- Name matches: highest weight
- Tag matches: medium
- Description matches: lower
- Tie-breakers:
- Prefer entries with
installSource - Prefer higher-quality signals (future: conformance tier, stars, downloads, recency)
- Prefer entries with
The goal is "good enough" relevance without pulling in a heavy fuzzy-matching dependency.
For ergonomics, Pi should support:
pi install <id-or-name>for entries whereinstallSourceis present and the match is unique.
Resolution rules:
- Exact match (case-insensitive) on
name - Exact match (case-insensitive) on
id - Provider-specific aliases (e.g.
npm/<name>)
If multiple entries match, Pi should refuse to guess and instruct the user to pass an explicit
npm: or git: source string.
src/extension_index.rsimplements the local schema, bundled seed loading, cache staleness checks, search scoring, id/name install source resolution, and remote refresh adapters for npm + GitHub.src/config.rsprovidesConfig::extension_index_path()withPI_EXTENSION_INDEX_PATHoverride.pi install,pi remove, andpi update <source>now resolve shorthand id/name aliases through the local index before delegating to package manager operations.pi update-indexperforms a best-effort remote refresh and writes the merged cache to the local extension-index path.