For Pi we currently publish npm-shrinkwrap.json with @earendil-works/pi-coding-agent. That gives the CLI install a vetted dependency graph, but it also makes the package behave poorly as a library. In particular, npm can install duplicate copies of internal Pi packages such as @earendil-works/pi-ai, which breaks module-level singleton state like provider registries. npm 12 is also moving away from shrinkwrap support, so keeping shrinkwrap as the application install mechanism is not an option we can hold on to.
This RFC proposes to remove shrinkwrap and move to a separate mechanism for pi update and the install script on pi.dev.
Goals
The npm packages should stop carrying shrinkwrap files. Installing @earendil-works/pi-coding-agent from npm should let npm resolve, dedupe, and override dependencies normally.
The curl-to-shell installer and pi update should still install a dependency graph that we have reviewed. They should not opportunistically float transitive dependency versions at install time.
The installer mechanism should continue to support platform-specific optional dependencies correctly. It should also keep lifecycle scripts disabled during install.
Non Goals
This does not attempt to make npm library consumers use Pi’s vetted application dependency graph. Library consumers should get normal npm behavior and can use their own lockfile, overrides and package manager policy including min-age.
This also does not introduce bundled dependencies as the primary solution. Bundling is treated as an alternative that was considered and rejected for now.
Why We Are Moving Off Shrinkwrap
Shrinkwrap makes sense for an application but not for reusable libraries. Pi is both a CLI application and a library. Shipping shrinkwrap inside the library package forces application-style dependency locking onto consumers and can cause duplicate internal Pi packages in the dependency tree. It has been brought up as a challenge for both company deployments and for individuals with extensions. See issue #5653.
That duplication is not only inefficient but also causes problems as some Pi APIs rely on shared module identity. If user code imports one copy of @earendil-works/pi-ai while the agent loop imports another, module-level registries are split and custom providers are invisible to the running agent. Today we’re using legacy peer deps within our extension mechanism to ensure that we end up with a shared installation in those scenarios. That behavior must not break as we change.
Removing shrinkwrap from packages restores normal package-manager behavior for library usage.
Installer Lock Artifact
The installer should use a separate generated installer root containing a small package.json and a matching package-lock.json. The root package should be private and depend on the exact Pi CLI version, for example @earendil-works/pi-coding-agent at 0.80.3.
The generated lockfile should be based on the reviewed repository lockfile, but it should not be the monorepo root lockfile directly. The monorepo lockfile contains workspaces, dev dependencies, example packages, and workspace links for internal Pi packages as a standalone installer cannot consume that shape with npm ci.
The generated installer lockfile should turn internal Pi workspace entries into registry tarball entries at the release version, include only the runtime dependency closure, preserve optional platform dependency entries, reject local or workspace references, reject unexpected lifecycle scripts, and validate that dependency versions match the reviewed lock data.
The repository package-lock.json is still what establishes our fundamental trust. It records the dependency versions that were reviewed and checked during development and release. It should be used to derive and validate the installer lockfile.
It is however not suitable as the installer lockfile itself because npm would expect the matching monorepo package.json and workspace layout. A generated installer lockfile gives the installer the shape it needs while preserving the dependency versions we trust.
Semantics and Distribution
The installer should install from the generated lock with npm ci, not npm install. npm install resolves and updates the dependency graph. npm ci consumes the lockfile exactly and fails if the lockfile and package manifest disagree.
The installation command should continue to use --ignore-scripts, --omit=dev, --no-audit, and --no-fund. Lifecycle scripts should remain disabled. Dev dependencies should not be installed. Audit output is not useful at installer time because dependency review happens before release.
Today the shrinkwrap is distributed as part of the pi-coding-agent. When we move towards a separate lock file, we need to provide our own distribution mechanism. The two options are to publish that on GitHub Releases, to commit it into git and/or to make it available via pi.dev. The recommended solution at the moment is to publish it on GitHub releases but to also serve it up cached on pi.dev to avoid running into rate limits on shared IPs.
Minimum Release Age
The npm minimum release age mechanism is not the security property we rely on for Pi application installs. Pi already vets and pins the dependency graph before publishing the installer artifact. Once an installer lockfile exists, the relevant question is whether the artifact was generated from reviewed dependency data, not whether npm considers a package old enough at user install time.
Minimum release age also is a bit of a red herring as it for instance would not help much with bundled dependencies. If dependencies are bundled inside a tarball, npm is not independently resolving and checking each transitive package for min-age. The bundled contents arrive as part of the top-level artifact. In that model the review point must be the bundling process itself, not npm’s release-age resolver.
For the lockfile installer path, using npm ci from the vetted lock is the important control. We can avoid needing a min-age override for transitives because they are not being selected dynamically. Whether the top-level package fetch needs an age override depends on the exact transport. If install artifacts are fetched directly from pi.dev or GitHub release assets, npm’s registry release-age gate is not the meaningful mechanism.
Issues With Bundled Dependencies
A separate @earendil-works/pi-coding-agent-bundled package would make the tarball itself the application artifact. That would avoid dynamic transitive resolution during install and would allow us to fully rely on npm, but it would introduce significant release engineering costs.
Bundled dependencies do not compose well with platform-specific optional packages which we have. Pi’s dependency graph includes platform-specific packages such as clipboard native packages. A single bundle built on one platform may not contain the correct packages for every supported platform. Solving that requires either per-platform packages or a custom all-platform bundling process that manually materializes and validates optional native packages.
Curl Installer Changes
The curl-to-shell installer should stop running a global npm install -g @earendil-works/pi-coding-agent as the primary install path. Instead, it should create a managed Pi application directory, download the generated installer package.json and package-lock.json for the selected version from pi.dev, and run npm ci in a staging directory.
After npm ci succeeds, the installer should verify the installed CLI with a lightweight command such as pi --version, move the staged install into a versioned release directory, and atomically update a current symlink or equivalent pointer. The user-facing pi executable should be a shim that runs the CLI from the current managed install.
The installer should keep the existing Node and npm preflight checks. It should keep lifecycle scripts disabled. It should keep the current interactive behavior around install, reinstall, uninstall, and PATH setup, but the underlying install target should become the managed application directory rather than npm’s global package tree.
Pi Update Changes
pi update should use the same managed-install mechanism as the curl installer. It should query the latest Pi release metadata, download the matching installer manifest and lockfile from pi.dev, run npm ci into a staging release directory, validate the resulting CLI, and then switch the active install to the new release.
This makes pi update independent of how npm would resolve dependencies on that day. It also makes update rollback and cleanup easier because releases live in explicit versioned directories.
Existing self-update paths for npm, pnpm, yarn, and bun installs can remain as the stable path initially. The new managed updater should be gated behind PI_EXPERIMENTAL until the install layout, migration behavior, rollback semantics, and platform coverage have been proven.
Pros
This gives library consumers normal npm behavior while preserving a vetted application dependency graph for Pi’s own installer and updater. It would also follow the recommended way in which npm already wants you to deal with Docker builds and similar.
It avoids duplicate internal Pi packages caused by shrinkwrap. It also avoids relying on npm shrinkwrap support as that support changes.
It preserves npm’s normal optional platform dependency handling because the final install still uses npm ci rather than a prebuilt all-platform bundle.
It gives us a clean place to validate dependency trust before release. The generated installer lock can reject local links, unexpected lifecycle scripts, version drift, missing platform optional packages, and internal package version mismatches.
Cons
This creates a second lock artifact that must be generated, validated, published, and kept in sync with releases. That process is quite manual as it cannot really utilize npm tooling.
The installer and updater become more custom. They need staging directories, atomic activation, shims, cleanup behavior, and migration logic from old npm-global installs.
The application install no longer maps one-to-one to a conventional global npm package install. That is acceptable for the curl installer and pi update, but it needs clear operational behavior and debugging output. It also means that users who install pi in other ways lose all locking capabilities. The install.sh path thus becomes much more important for security.
There is still a bootstrapping question for users who install directly with npm install -g @earendil-works/pi-coding-agent and if / how they migrate to the lock file base approach on update.