feat(coding-agent): add PI_SELF_UPDATE_COMMAND and selfUpdateCommand for pi update (#11629)

Refs #7756, #5607.

Problem: when a wrapper or another package manager provides Pi (mise, a
`PNPM_HOME` wrapper such as nub, Nix), `pi update` cannot update it and
prints "pi cannot self-update this installation." Users with several
machines have to remember which tool updates Pi on which machine.

Example:

```sh
export PI_SELF_UPDATE_COMMAND="mise use -g npm:{packageName}@{version}"
pi update
# Updating pi with mise use -g npm:@earendil-works/pi-coding-agent@1.4.0...
# Updated pi from 1.3.0 to 1.4.0
```

or in `~/.pi/agent/settings.json`:

```json
{ "selfUpdateCommand": ["nix", "profile", "upgrade", "pi"] }
```

Behavior:

- `pi update` still checks pi.dev first. The command runs only when a
newer release exists, or with `--force`, and the release note prints
first.
- Placeholders come from that release: `{packageName}`, `{version}`,
`{installSpec}` (`<packageName>@<version>`). A package rename announced
by pi.dev flows into them.
- `PI_SELF_UPDATE_COMMAND` takes precedence and is split on whitespace
(no quoting). The setting is argv, for arguments with spaces, and is
read only from global settings, so a project's `.pi/settings.json`
cannot inject a command.
- The configured command replaces Pi's own update logic for every
install method, including installer-managed installs: it is explicit
user intent.
- Exit code 0 does not prove an update. Pi reports "Updated pi from X to
Y" when its own `package.json` now shows Y (in-place updates) or when
`pi --version` from PATH reports Y (wrappers such as mise or Nix that
install each version into a new directory). Otherwise it warns: ``Ran
<cmd>, but `pi --version` reports X, not Y.``
- "cannot self-update this installation" now mentions the env var and
setting.

Docs: `docs/cli.md` (new "Custom update command" section),
`docs/settings.md`, `docs/environment-variables.md`, `--help` env list,
regenerated `schemas/settings.schema.json`.

Tests: unit tests for argv resolution and placeholders in
`test/config.test.ts`; CLI tests in `test/package-command-paths.test.ts`
for env precedence over the setting and the managed install, global-only
setting gated by the version check, and command failure. `npm run check`
passes.

Manual end-to-end check with real mise 2026.10.23 in a sandboxed HOME,
running this branch as the mise-installed 1.3.0 (`PI_PACKAGE_DIR`
pointing at mise's install):

| Scenario | Result |
|---|---|
| `mise use -g npm:{packageName}@{version}` | mise installs 1.4.0;
`Updated pi from 1.3.0 to 1.4.0` |
| Already 1.4.0 | `pi is already up to date (v1.4.0)`, mise not run |
| In-place `npm install -g --prefix <dir> {installSpec}` via the setting
| `Updated pi from 1.3.0 to 1.4.0` |
| Command `true` (exit 0, installs nothing) | ``Ran true, but `pi
--version` reports 1.3.0, not 1.4.0.`` |

This PR is AI-generated.
This commit is contained in:
Petr Houška 2026-10-30 22:30:39 +01:00 • committed by GitHub
parent 9e4868a605
commit 8bc94b19ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 299 additions and 1 deletions

View file

@ -282,6 +282,16 @@ Add `--force` to reinstall Pi when the selected update includes Pi.
`pi update` cannot update Pi when another package manager provides it, such as Nix. Update Pi with that package manager, for example `nix profile upgrade pi`. Package and model catalog updates still work.
#### Custom update command
If a wrapper or another package manager provides Pi, set `PI_SELF_UPDATE_COMMAND` so `pi update` runs its update command:
```sh
export PI_SELF_UPDATE_COMMAND="mise use -g npm:{packageName}@{version}"
```
`pi update` runs it only when pi.dev has a newer release, replacing `{packageName}`, `{version}`, and `{installSpec}` (`{packageName}@{version}`) with that release. The value is split on spaces. For arguments with spaces, use the `selfUpdateCommand` setting in `~/.pi/agent/settings.json` instead, an argv array such as `["nix", "profile", "upgrade", "pi"]`.
### Aliases and command options
- `pi uninstall <source>` is an alias for `pi remove <source>`.