Pi Packages & Model Catalog
pi install/update/list over npm: & git:
“min-release-age=2, exact pins, --ignore-scripts; never edit models.generated.ts”
Deep Dive
min-release-age=2, exact pin, --ignore-scripts; never hand-edit models.generated.ts
Learning Objectives
After this chapter you will be able to:
- Explain pi package manager's npm: and git: dual-source architecture
- Describe the security implications of --ignore-scripts and --omit=dev
- Explain the design rationale for concurrency throttling and offline mode
- Understand why the model catalog must be script-generated, never hand-edited
Key Source Files
| File | Lines | Responsibility |
|---|---|---|
packages/coding-agent/src/core/package-manager.ts | 2650 | Core: install, update, remove, semver resolution |
packages/coding-agent/src/package-manager-cli.ts | 887 | CLI interaction: pi install/update/list/config |
packages/ai/scripts/generate-models.ts | 2712 | Model catalog generation script |
Design Motivation: Why Does pi Need Its Own Package Manager?
Problem: pi's extensions (→ Chapter 13) and Skills (→ Chapter 12) need a distribution mechanism. Users may want to:
- Install community extensions from npm (
pi install npm:@scope/pi-ext-deploy) - Install unpublished extensions from git (
pi install git:user/repo@main) - Manage extension versions (update, rollback, lock)
If users manually clone + place in directory: No version management, no dependency resolution, no security validation.
Analogy: Like VS Code's extension marketplace — users don't need to know which directory extensions live in or how to update. One pi install command handles it.
npm: Source: semver + Safe Install
pi install npm:@scope/pkg@^1.2:
① npm view @scope/pkg versions → ['1.2.0', '1.2.5', '1.3.1', '2.0.0']
② maxSatisfying(versions, "^1.2") → '1.3.1'
③ npm install @scope/pkg@1.3.1
--prefix ~/.pi/agent/npm/@scope/pkg
--omit=dev ← don't install devDependencies
--ignore-scripts ← don't run postinstall scripts
④ Write to ~/.pi/agent/lock.json (exact pin to 1.3.1)
The security trio:
--omit=dev: devDependencies may include build toolchains — extensions don't need them at runtime--ignore-scripts: postinstall scripts can execute arbitrary code — a common supply-chain attack vectormin-release-age=2: Only install versions published more than 2 days ago — gives the community a window to discover malicious packages
Analogy: Like an App Store review waiting period — newly published apps are observed for a few days before being recommended.
git: Source: clone + .piignore
pi install git:user/repo@main:
① parseGitUrl("git:user/repo@main")
→ { host: "github.com", user: "user", repo: "repo", ref: "main" }
② git clone --depth 1 --branch main
→ ~/.pi/agent/git/user/repo/
③ Apply .piignore (similar to .gitignore syntax)
Filter unwanted files with minimatch/ignore
④ Write to lock.json (pin to commit hash)
Why pin git source to commit hash?
@mainis a moving target — next update might grab incompatible changes- Pin to hash = reproducible —
pi updateis explicit, nothing changes silently
Concurrency Throttling & Offline Mode
// Concurrency control for batch updates
const UPDATE_CHECK_CONCURRENCY = 5 // npm concurrent queries
const GIT_UPDATE_CONCURRENCY = 3 // git concurrent clones
// Offline mode
if (process.env.PI_OFFLINE === "1") {
// Skip network entirely — use local cache only
// Use case: on a plane, behind firewall, CI cache hit
}
// Self-update
// pi update --self --force → check pi.dev/api/latest-version
// PI_SKIP_VERSION_CHECK=1 → skip (CI doesn't want to check every time)
Why throttle? Users may have 20 extensions installed. Firing 20 simultaneous npm view requests could trigger registry rate limits. 5 concurrent is the "fast but not violent" sweet spot.
Model Catalog Generation
// scripts/generate-models.ts (2712 lines)
// Run: node scripts/generate-models.ts
// Output: packages/ai/src/models.generated.ts + providers/data/
async function generate() {
// ① Fetch model lists from each vendor's API
const anthropic = await fetchAnthropicModels()
const openai = await fetchOpenAIModels()
const google = await fetchGoogleModels()
// ... 40+ vendors
// ② Normalize to unified Model format
const models = normalize(anthropic, openai, google, ...)
// ③ Generate TypeScript file
writeFileSync("models.generated.ts", format(models))
}
Iron law: NEVER hand-edit models.generated.ts
// models.generated.ts file header
// ⚠️ AUTO-GENERATED — DO NOT EDIT
// Run: node scripts/generate-models.ts
Why?
- Hand edits get overwritten on next generation — wasted effort
- Hand-introduced inconsistencies can't be detected by CI
- To change something, modify generate-models.ts script, then regenerate
Analogy: Like protobuf-generated code — you edit the .proto file, not the generated .pb.go.
Engineering Insights
-
package-manager.ts 2650 lines (~83KB): One of pi's largest single files. Complexity from: npm semver parsing, diverse git URL formats (SSH/HTTPS/short), lock file read/write, error recovery (half-installed state cleanup).
-
lock.json uses exact pins: No ranges, only exact versions/hashes.
pi updateis the only upgrade path — avoids "worked yesterday, broken today." -
generate-models.ts 2712 lines: Each vendor's API returns different formats, different pagination, different rate limits. 80% of 2712 lines adapts to vendor quirks.
Cross-Chapter Links
| Direction | Chapter | Relationship |
|---|---|---|
| Prerequisite | Ch10 CLI Modes | pi install/update subcommands short-circuit to package manager |
| Prerequisite | Ch13 Extensions | Extensions installed via package manager |
| Follow-up | Ch06 Models & Providers | models.generated.ts is Models' data source |
| Follow-up | Ch14 Model Runtime | ModelRuntime reads the generated model catalog |
| Follow-up | Ch21 Evals & Supply Chain | Supply chain security and min-release-age |
Check Your Understanding
- Without --ignore-scripts, what could a malicious npm package's postinstall script do?
- Why pin git source to commit hash rather than branch name? What impact on reproducibility?
- How does min-release-age=2 balance "getting new versions promptly" vs "supply chain security"?
Takeaways
- npm: + git: dual source — semver resolution + git clone, unified lock.json exact pin
- Security trio: --omit=dev, --ignore-scripts, min-release-age=2
- Concurrency throttle + PI_OFFLINE — fast but not violent, works offline
- Model catalog script-generated, never hand-edited — modify the script, regenerate
This chapter is based on the main-branch source of earendil-works/pi.