From aae62dfa87c7935f124c4349fb34216eee84502a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 18 Jun 2026 18:09:03 +0200 Subject: [PATCH] feat(coding-agent): make bare update self-only --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/README.md | 8 +-- packages/coding-agent/docs/packages.md | 9 ++-- packages/coding-agent/docs/usage.md | 5 +- packages/coding-agent/src/cli/args.ts | 2 +- .../src/modes/interactive/interactive-mode.ts | 2 +- .../coding-agent/src/package-manager-cli.ts | 51 +++++++++++++++---- 7 files changed, 58 insertions(+), 20 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a2fd43e8..e29e19d2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -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 diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 41a7bbc2..ce7dba2a 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -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 [-l] # Install package, -l for project-local pi remove [-l] # Remove package pi uninstall [-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 diff --git a/packages/coding-agent/docs/packages.md b/packages/coding-agent/docs/packages.md index 7009b773..73f2dccb 100644 --- a/packages/coding-agent/docs/packages.md +++ b/packages/coding-agent/docs/packages.md @@ -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//` (global) or `.pi/git//` (project). - When reconciliation changes the checkout, pi resets and cleans the clone, then runs `npm install` if `package.json` exists. diff --git a/packages/coding-agent/docs/usage.md b/packages/coding-agent/docs/usage.md index 4f8f5954..5ff0cf73 100644 --- a/packages/coding-agent/docs/usage.md +++ b/packages/coding-agent/docs/usage.md @@ -145,7 +145,8 @@ pi [options] [@files...] [messages...] pi install [-l] # Install package, -l for project-local pi remove [-l] # Remove package pi uninstall [-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 # 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. diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 839c60e8..5d3c4af8 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -229,7 +229,7 @@ ${chalk.bold("Commands:")} ${APP_NAME} install [-l] Install extension source and add to settings ${APP_NAME} remove [-l] Remove extension source from settings ${APP_NAME} uninstall [-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} --help Show help for install/remove/uninstall/update/list diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 0cde5ee4..00054eb2 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -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"); diff --git a/packages/coding-agent/src/package-manager-cli.ts b/packages/coding-agent/src/package-manager-cli.ts index e00de1b4..d47df930 100644 --- a/packages/coding-agent/src/package-manager-cli.ts +++ b/packages/coding-agent/src/package-manager-cli.ts @@ -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 [-l] [--approve|--no-approve]`; case "update": - return `${APP_NAME} update [source|self|pi] [--self] [--extensions] [--extension ] [--approve|--no-approve] [--force]`; + return `${APP_NAME} update [source|self|pi] [--self|--extensions|--all] [--extension ] [--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 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 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);