feat(coding-agent): make bare update self-only

This commit is contained in:
Armin Ronacher
2026-06-18 18:09:03 +02:00
parent 2b46f38863
commit aae62dfa87
7 changed files with 58 additions and 20 deletions

View File

@@ -8,6 +8,7 @@
### Changed
- Changed bare `pi update` to update only pi, added `pi update --all` for updating pi and extensions together, and clarified extension update prompts.
- Updated extension docs, examples, runtime help, trust prompts, and config labels to use the configured project config directory instead of hardcoded `.pi` paths.
### Fixed

View File

@@ -419,7 +419,8 @@ pi install ssh://git@github.com/user/repo@v1 # tag or commit
pi remove npm:@foo/pi-tools
pi uninstall npm:@foo/pi-tools # alias for remove
pi list
pi update # update pi and packages (skips pinned packages)
pi update # update pi only
pi update --all # update pi and packages
pi update --extensions # update packages only
pi update --self # update pi only
pi update --self --force # reinstall pi even if current
@@ -427,7 +428,7 @@ pi update npm:@foo/pi-tools # update one package
pi config # enable/disable extensions, skills, prompts, themes
```
Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update --extensions` and `pi update --all`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
Create a package by adding a `pi` key to `package.json`:
@@ -518,7 +519,8 @@ pi [options] [@files...] [messages...]
pi install <source> [-l] # Install package, -l for project-local
pi remove <source> [-l] # Remove package
pi uninstall <source> [-l] # Alias for remove
pi update [source|self|pi] # Update pi and packages (skips pinned packages)
pi update [source|self|pi] # Update pi only, or one package source
pi update --all # Update pi and packages
pi update --extensions # Update packages only
pi update --self # Update pi only
pi update --self --force # Reinstall pi even if current

View File

@@ -28,7 +28,8 @@ pi install ./relative/path/to/package
pi remove npm:@foo/bar
pi list # show installed packages from settings
pi update # update pi, update packages, and reconcile pinned git refs
pi update # update pi only
pi update --all # update pi, update packages, and reconcile pinned git refs
pi update --extensions # update packages and reconcile pinned git refs only
pi update --self # update pi only
pi update --self --force # reinstall pi even if current
@@ -36,7 +37,7 @@ pi update npm:@foo/bar # update one package
pi update --extension npm:@foo/bar
```
These commands manage pi packages, not the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
These commands manage pi packages and `pi update` can update the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
By default, `install` and `remove` write to user settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. Project settings can be shared with your team, and pi installs any missing packages automatically on startup after the project is trusted.
@@ -58,7 +59,7 @@ npm:@scope/pkg@1.2.3
npm:pkg
```
- Versioned specs are pinned and skipped by package updates (`pi update`, `pi update --extensions`).
- Versioned specs are pinned and skipped by package updates (`pi update --extensions`, `pi update --all`).
- User installs go under `~/.pi/agent/npm/`.
- Project installs go under `.pi/npm/`.
- Set `npmCommand` in `settings.json` to pin npm package lookup and install operations to a specific wrapper command such as `mise` or `asdf`.
@@ -85,7 +86,7 @@ ssh://git@github.com/user/repo@v1
- HTTPS and SSH URLs are both supported.
- SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
- For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
- Refs are pinned tags or commits. `pi update` and `pi update --extensions` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
- Refs are pinned tags or commits. `pi update --extensions` and `pi update --all` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
- Use `pi install git:host/user/repo@new-ref` to update settings and move an existing package to a new pinned ref.
- Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
- When reconciliation changes the checkout, pi resets and cleans the clone, then runs `npm install` if `package.json` exists.

View File

@@ -145,7 +145,8 @@ pi [options] [@files...] [messages...]
pi install <source> [-l] # Install package, -l for project-local
pi remove <source> [-l] # Remove package
pi uninstall <source> [-l] # Alias for remove
pi update [source|self|pi] # Update pi and packages; reconcile pinned git refs
pi update [source|self|pi] # Update pi only, or one package source
pi update --all # Update pi and packages; reconcile pinned git refs
pi update --extensions # Update packages only; reconcile pinned git refs
pi update --self # Update pi only
pi update --extension <src> # Update one package
@@ -153,7 +154,7 @@ pi list # List installed packages
pi config # Enable/disable package resources
```
These commands manage pi packages, not the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall). `pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
These commands manage pi packages and `pi update` can update the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall). `pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
See [Pi Packages](packages.md) for package sources and security notes.

View File

@@ -229,7 +229,7 @@ ${chalk.bold("Commands:")}
${APP_NAME} install <source> [-l] Install extension source and add to settings
${APP_NAME} remove <source> [-l] Remove extension source from settings
${APP_NAME} uninstall <source> [-l] Alias for remove
${APP_NAME} update [source|self|pi] Update pi and installed extensions
${APP_NAME} update [source|self|pi] Update pi (use --all for pi and extensions)
${APP_NAME} list List installed extensions from settings
${APP_NAME} config Open TUI to enable/disable package resources
${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list

View File

@@ -3740,7 +3740,7 @@ export class InteractiveMode {
}
showPackageUpdateNotification(packages: string[]): void {
const action = theme.fg("accent", `${APP_NAME} update`);
const action = theme.fg("accent", `${APP_NAME} update --extensions`);
const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");

View File

@@ -52,6 +52,7 @@ interface PackageCommandOptions {
command: PackageCommand;
source?: string;
updateTarget?: UpdateTarget;
showExtensionsSkippedNote: boolean;
local: boolean;
force: boolean;
projectTrustOverride?: boolean;
@@ -79,7 +80,7 @@ function getPackageCommandUsage(command: PackageCommand): string {
case "remove":
return `${APP_NAME} remove <source> [-l] [--approve|--no-approve]`;
case "update":
return `${APP_NAME} update [source|self|pi] [--self] [--extensions] [--extension <source>] [--approve|--no-approve] [--force]`;
return `${APP_NAME} update [source|self|pi] [--self|--extensions|--all] [--extension <source>] [--approve|--no-approve] [--force]`;
case "list":
return `${APP_NAME} list [--approve|--no-approve]`;
}
@@ -133,15 +134,17 @@ Examples:
Update pi and installed packages.
Options:
--self Update pi only
--self Update pi only (default when no target is given)
--extensions Update installed packages only
--all Update pi and installed packages
--extension <source> Update one package only
-a, --approve Trust project-local files for this command
-na, --no-approve Ignore project-local files for this command
--force Reinstall pi even if the current version is latest
Short forms:
${APP_NAME} update Update pi and all extensions
${APP_NAME} update Update pi only
${APP_NAME} update --all Update pi and all extensions
${APP_NAME} update <source> Update one package
${APP_NAME} update pi Update pi only (self works as alias to pi)
`);
@@ -184,6 +187,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
let source: string | undefined;
let selfFlag = false;
let extensionsFlag = false;
let allFlag = false;
let extensionFlagSource: string | undefined;
for (let index = 0; index < rest.length; index++) {
@@ -220,6 +224,15 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
continue;
}
if (arg === "--all") {
if (command === "update") {
allFlag = true;
} else {
invalidOption = invalidOption ?? arg;
}
continue;
}
if (arg === "--approve" || arg === "-a") {
projectTrustOverride = true;
continue;
@@ -271,10 +284,20 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
}
let updateTarget: UpdateTarget | undefined;
let showExtensionsSkippedNote = false;
if (command === "update") {
if (allFlag && (selfFlag || extensionsFlag || extensionFlagSource)) {
conflictingOptions =
conflictingOptions ?? "--all cannot be combined with --self, --extensions, or --extension";
}
if (allFlag && source) {
conflictingOptions = conflictingOptions ?? "--all cannot be combined with a positional source";
}
if (extensionFlagSource) {
if (selfFlag || extensionsFlag) {
conflictingOptions = conflictingOptions ?? "--extension cannot be combined with --self or --extensions";
if (selfFlag || extensionsFlag || allFlag) {
conflictingOptions =
conflictingOptions ?? "--extension cannot be combined with --self, --extensions, or --all";
}
if (source) {
conflictingOptions = conflictingOptions ?? "--extension cannot be combined with a positional source";
@@ -285,12 +308,15 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
if (sourceIsSelf) {
updateTarget = extensionsFlag ? { type: "all" } : { type: "self" };
} else {
if (extensionsFlag || selfFlag) {
if (extensionsFlag || selfFlag || allFlag) {
conflictingOptions =
conflictingOptions ?? "positional update targets cannot be combined with --self or --extensions";
conflictingOptions ??
"positional update targets cannot be combined with --self, --extensions, or --all";
}
updateTarget = { type: "extensions", source };
}
} else if (allFlag) {
updateTarget = { type: "all" };
} else if (selfFlag && extensionsFlag) {
updateTarget = { type: "all" };
} else if (selfFlag) {
@@ -298,7 +324,8 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
} else if (extensionsFlag) {
updateTarget = { type: "extensions" };
} else {
updateTarget = { type: "all" };
updateTarget = { type: "self" };
showExtensionsSkippedNote = true;
}
}
@@ -306,6 +333,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
command,
source,
updateTarget,
showExtensionsSkippedNote,
local,
force,
projectTrustOverride,
@@ -660,7 +688,12 @@ export async function handlePackageCommand(
}
case "update": {
const target = options.updateTarget ?? { type: "all" };
const target = options.updateTarget ?? { type: "self" };
if (options.showExtensionsSkippedNote) {
console.log(
chalk.dim(`Extensions are skipped. Run ${APP_NAME} update --extensions to update extensions.`),
);
}
if (updateTargetIncludesExtensions(target)) {
const updateSource = target.type === "extensions" ? target.source : undefined;
await packageManager.update(updateSource);