ππ Agent
Chapter 15 / 21

Pi Packages & Model Catalog

pi install/update/list over npm: & git:

min-release-age=2, exact pins, --ignore-scripts; never edit models.generated.ts
No simulation scenario available for this chapter

Deep Dive

~7 min read·6804 chars

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

FileLinesResponsibility
packages/coding-agent/src/core/package-manager.ts2650Core: install, update, remove, semver resolution
packages/coding-agent/src/package-manager-cli.ts887CLI interaction: pi install/update/list/config
packages/ai/scripts/generate-models.ts2712Model 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 vector
  • min-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?

  • @main is a moving target — next update might grab incompatible changes
  • Pin to hash = reproducible — pi update is 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

  1. 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).

  2. lock.json uses exact pins: No ranges, only exact versions/hashes. pi update is the only upgrade path — avoids "worked yesterday, broken today."

  3. 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

DirectionChapterRelationship
PrerequisiteCh10 CLI Modespi install/update subcommands short-circuit to package manager
PrerequisiteCh13 ExtensionsExtensions installed via package manager
Follow-upCh06 Models & Providersmodels.generated.ts is Models' data source
Follow-upCh14 Model RuntimeModelRuntime reads the generated model catalog
Follow-upCh21 Evals & Supply ChainSupply chain security and min-release-age

Check Your Understanding

  1. Without --ignore-scripts, what could a malicious npm package's postinstall script do?
  2. Why pin git source to commit hash rather than branch name? What impact on reproducibility?
  3. 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.