pi-extmgr 0.1.27 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  A better way to manage Pi extensions. Browse, install, enable/disable, and remove extensions from one place.
9
+ Built on top of Pi's native package install, update, and config flows, so extmgr stays aligned with current upstream behavior.
9
10
 
10
11
  **🌐 [pi-extmgr landing page](https://ayagmar.github.io/pi-extmgr)**
11
12
 
@@ -15,9 +16,9 @@ A better way to manage Pi extensions. Browse, install, enable/disable, and remov
15
16
  pi install npm:pi-extmgr
16
17
  ```
17
18
 
18
- Then reload Pi.
19
+ If Pi is already running, use `/reload`.
19
20
 
20
- Requires Node.js `>=22.5.0`.
21
+ Requires Node.js `>=22`.
21
22
 
22
23
  ## Features
23
24
 
@@ -155,7 +156,7 @@ Examples:
155
156
 
156
157
  - **Staged local changes**: Toggle local extensions on/off, then press `S` to apply all at once.
157
158
  - **Package extension config**: Select a package and press `c` (or Enter/A → Configure) to enable/disable individual package entrypoints.
158
- - After saving package extension config, restart pi to fully apply changes.
159
+ - After saving package extension config, run /reload to apply changes.
159
160
  - **Two install modes**:
160
161
  - **Managed** (npm): Auto-updates with `pi update`, stored in pi's package cache, supports Pi package manifest/convention loading
161
162
  - **Local** (standalone): Copies to `~/.pi/agent/extensions/{package}/`, so it only accepts runnable standalone layouts (manifest-declared/root entrypoints), requires `tar` on `PATH`, and rejects packages whose runtime `dependencies` are not already bundled with the package contents
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extmgr",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "Enhanced UX for managing local Pi extensions and community packages",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -19,14 +19,14 @@
19
19
  "README.md"
20
20
  ],
21
21
  "scripts": {
22
- "lint": "eslint . --max-warnings=0",
23
- "lint:fix": "eslint . --fix",
24
- "format": "prettier --write .",
25
- "format:check": "prettier --check .",
22
+ "lint": "biome lint . --error-on-warnings",
23
+ "lint:fix": "biome check --write .",
24
+ "format": "biome format --write .",
25
+ "format:check": "biome format .",
26
26
  "typecheck": "tsc --noEmit -p tsconfig.json",
27
27
  "smoke-test": "node --import=tsx ./scripts/smoke-test.mjs",
28
28
  "test": "node --import=tsx --test ./test/*.test.ts",
29
- "check": "pnpm run typecheck && pnpm run smoke-test && pnpm run test && pnpm run lint && pnpm run format:check",
29
+ "check": "tsc --noEmit -p tsconfig.json && node --import=tsx ./scripts/smoke-test.mjs && node --import=tsx --test ./test/*.test.ts && pnpm run lint && pnpm run format:check",
30
30
  "prepublishOnly": "pnpm run check",
31
31
  "prepare": "husky"
32
32
  },
@@ -42,22 +42,19 @@
42
42
  "@mariozechner/pi-tui": "*"
43
43
  },
44
44
  "devDependencies": {
45
- "@mariozechner/pi-coding-agent": "^0.62.0",
46
- "@mariozechner/pi-tui": "^0.62.0",
47
- "@types/node": "^22.13.10",
48
- "@typescript-eslint/eslint-plugin": "^8.42.0",
49
- "@typescript-eslint/parser": "^8.42.0",
50
- "eslint": "^9.38.0",
51
- "eslint-config-prettier": "^10.1.8",
45
+ "@biomejs/biome": "^2.4.9",
46
+ "@mariozechner/pi-coding-agent": "^0.63.1",
47
+ "@mariozechner/pi-tui": "^0.63.1",
48
+ "@types/node": "^22.19.10",
52
49
  "husky": "^9.1.7",
53
- "prettier": "^3.8.1",
54
- "tsx": "^4.19.3",
50
+ "tsx": "^4.21.0",
55
51
  "typescript": "^5.9.3"
56
52
  },
57
53
  "author": "ayagmar",
58
54
  "license": "MIT",
55
+ "packageManager": "pnpm@10.33.0",
59
56
  "engines": {
60
- "node": ">=22.5.0"
57
+ "node": ">=22"
61
58
  },
62
59
  "repository": {
63
60
  "type": "git",
@@ -1,7 +1,7 @@
1
- import type {
2
- ExtensionAPI,
3
- ExtensionCommandContext,
4
- ExtensionContext,
1
+ import {
2
+ type ExtensionAPI,
3
+ type ExtensionCommandContext,
4
+ type ExtensionContext,
5
5
  } from "@mariozechner/pi-coding-agent";
6
6
  import {
7
7
  disableAutoUpdate,
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1
+ import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
2
  import { clearSearchCache } from "../packages/discovery.js";
3
3
  import { clearRemotePackageInfoCache } from "../ui/remote.js";
4
4
  import { clearCache } from "../utils/cache.js";
@@ -1,10 +1,10 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1
+ import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
2
  import {
3
+ type ChangeAction,
3
4
  formatChangeEntry,
5
+ type HistoryFilters,
4
6
  queryGlobalHistory,
5
7
  querySessionChanges,
6
- type ChangeAction,
7
- type HistoryFilters,
8
8
  } from "../utils/history.js";
9
9
  import { notify } from "../utils/notify.js";
10
10
  import { formatListOutput } from "../utils/ui-helpers.js";
@@ -1,5 +1,5 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import { installPackage, type InstallScope } from "../packages/install.js";
1
+ import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
+ import { type InstallScope, installPackage } from "../packages/install.js";
3
3
  import { notify } from "../utils/notify.js";
4
4
 
5
5
  export const INSTALL_USAGE = "Usage: /extensions install <source> [--project|--global]";
@@ -1,7 +1,5 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import type { AutocompleteItem } from "@mariozechner/pi-tui";
3
- import { showInteractive, showInstalledPackagesLegacy, showListOnly } from "../ui/unified.js";
4
- import { showRemote } from "../ui/remote.js";
1
+ import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
+ import { type AutocompleteItem } from "@mariozechner/pi-tui";
5
3
  import {
6
4
  promptRemove,
7
5
  removePackage,
@@ -9,12 +7,14 @@ import {
9
7
  updatePackage,
10
8
  updatePackages,
11
9
  } from "../packages/management.js";
10
+ import { showRemote } from "../ui/remote.js";
11
+ import { showInstalledPackagesLegacy, showInteractive, showListOnly } from "../ui/unified.js";
12
12
  import { notify } from "../utils/notify.js";
13
- import { handleInstallSubcommand, INSTALL_USAGE } from "./install.js";
14
- import { handleHistorySubcommand } from "./history.js";
15
13
  import { handleAutoUpdateSubcommand } from "./auto-update.js";
16
14
  import { clearMetadataCacheCommand } from "./cache.js";
17
- import type { CommandDefinition, CommandId } from "./types.js";
15
+ import { handleHistorySubcommand } from "./history.js";
16
+ import { handleInstallSubcommand, INSTALL_USAGE } from "./install.js";
17
+ import { type CommandDefinition, type CommandId } from "./types.js";
18
18
 
19
19
  const REMOVE_USAGE = "Usage: /extensions remove <npm:package|git:url|path>";
20
20
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1
+ import { type ExtensionAPI, type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
3
  export type CommandId =
4
4
  | "local"
@@ -4,12 +4,13 @@
4
4
  * This module handles discovery and management of local Pi extensions
5
5
  * in both global (~/.pi/agent/extensions) and project (.pi/extensions) scopes.
6
6
  */
7
+
8
+ import { type Dirent } from "node:fs";
7
9
  import { readdir, rename, rm } from "node:fs/promises";
8
- import { basename, dirname, join, relative } from "node:path";
9
10
  import { homedir } from "node:os";
10
- import type { Dirent } from "node:fs";
11
- import type { ExtensionEntry, Scope, State } from "../types/index.js";
11
+ import { basename, dirname, join, relative } from "node:path";
12
12
  import { DISABLED_SUFFIX } from "../constants.js";
13
+ import { type ExtensionEntry, type Scope, type State } from "../types/index.js";
13
14
  import { fileExists, readSummary } from "../utils/fs.js";
14
15
 
15
16
  interface RootConfig {
package/src/index.ts CHANGED
@@ -3,21 +3,12 @@
3
3
  *
4
4
  * Entry point - exports the main extension function
5
5
  */
6
- import type {
7
- ExtensionAPI,
8
- ExtensionCommandContext,
9
- ExtensionContext,
10
- } from "@mariozechner/pi-coding-agent";
11
- import { isPackageSource } from "./utils/format.js";
12
- import { installPackage } from "./packages/install.js";
13
- import { tokenizeArgs } from "./utils/command.js";
14
- import { updateExtmgrStatus } from "./utils/status.js";
15
6
  import {
16
- startAutoUpdateTimer,
17
- stopAutoUpdateTimer,
18
- type ContextProvider,
19
- } from "./utils/auto-update.js";
20
- import { hydrateAutoUpdateConfig, getAutoUpdateConfig } from "./utils/settings.js";
7
+ type ExtensionAPI,
8
+ type ExtensionCommandContext,
9
+ type ExtensionContext,
10
+ } from "@mariozechner/pi-coding-agent";
11
+ import { createAutoUpdateNotificationHandler } from "./commands/auto-update.js";
21
12
  import {
22
13
  getExtensionsAutocompleteItems,
23
14
  resolveCommand,
@@ -25,7 +16,16 @@ import {
25
16
  showNonInteractiveHelp,
26
17
  showUnknownCommandMessage,
27
18
  } from "./commands/registry.js";
28
- import { createAutoUpdateNotificationHandler } from "./commands/auto-update.js";
19
+ import { installPackage } from "./packages/install.js";
20
+ import {
21
+ type ContextProvider,
22
+ startAutoUpdateTimer,
23
+ stopAutoUpdateTimer,
24
+ } from "./utils/auto-update.js";
25
+ import { tokenizeArgs } from "./utils/command.js";
26
+ import { isPackageSource } from "./utils/format.js";
27
+ import { getAutoUpdateConfig, hydrateAutoUpdateConfig } from "./utils/settings.js";
28
+ import { updateExtmgrStatus } from "./utils/status.js";
29
29
 
30
30
  async function executeExtensionsCommand(
31
31
  args: string,
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  DefaultPackageManager,
3
3
  getAgentDir,
4
- SettingsManager,
5
4
  type PackageSource,
6
5
  type ProgressEvent,
6
+ SettingsManager,
7
7
  } from "@mariozechner/pi-coding-agent";
8
- import type { InstalledPackage, Scope } from "../types/index.js";
8
+ import { type InstalledPackage, type Scope } from "../types/index.js";
9
9
  import { normalizePackageIdentity, parsePackageNameAndVersion } from "../utils/package-source.js";
10
10
 
11
11
  type PiScope = "user" | "project";
@@ -57,14 +57,15 @@ function createPackageRecord(
57
57
  };
58
58
  }
59
59
 
60
- function dedupeInstalledPackages(packages: InstalledPackage[]): InstalledPackage[] {
60
+ function dedupeInstalledPackages(packages: InstalledPackage[], cwd: string): InstalledPackage[] {
61
61
  const byIdentity = new Map<string, InstalledPackage>();
62
62
 
63
63
  for (const pkg of packages) {
64
- const identity = normalizePackageIdentity(
65
- pkg.source,
66
- pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : undefined
67
- );
64
+ const baseCwd = pkg.scope === "project" ? cwd : getAgentDir();
65
+ const identity = normalizePackageIdentity(pkg.source, {
66
+ ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
67
+ cwd: baseCwd,
68
+ });
68
69
 
69
70
  if (!byIdentity.has(identity)) {
70
71
  byIdentity.set(identity, pkg);
@@ -97,7 +98,7 @@ function createDefaultPackageCatalog(cwd: string): PackageCatalog {
97
98
 
98
99
  const installed = [...projectPackages, ...globalPackages];
99
100
  return Promise.resolve(
100
- options?.dedupe === false ? installed : dedupeInstalledPackages(installed)
101
+ options?.dedupe === false ? installed : dedupeInstalledPackages(installed, cwd)
101
102
  );
102
103
  },
103
104
 
@@ -3,19 +3,20 @@
3
3
  */
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { join } from "node:path";
6
- import type {
7
- ExtensionAPI,
8
- ExtensionCommandContext,
9
- ExtensionContext,
6
+ import {
7
+ type ExtensionAPI,
8
+ type ExtensionCommandContext,
9
+ type ExtensionContext,
10
+ getAgentDir,
10
11
  } from "@mariozechner/pi-coding-agent";
11
- import type { InstalledPackage, NpmPackage, SearchCache } from "../types/index.js";
12
12
  import { CACHE_TTL, TIMEOUTS } from "../constants.js";
13
- import { readSummary } from "../utils/fs.js";
13
+ import { type InstalledPackage, type NpmPackage, type SearchCache } from "../types/index.js";
14
14
  import { parseNpmSource } from "../utils/format.js";
15
+ import { readSummary } from "../utils/fs.js";
16
+ import { fetchWithTimeout } from "../utils/network.js";
17
+ import { execNpm } from "../utils/npm-exec.js";
15
18
  import { normalizePackageIdentity } from "../utils/package-source.js";
16
19
  import { getPackageCatalog } from "./catalog.js";
17
- import { execNpm } from "../utils/npm-exec.js";
18
- import { fetchWithTimeout } from "../utils/network.js";
19
20
 
20
21
  const NPM_SEARCH_API = "https://registry.npmjs.org/-/v1/search";
21
22
  const NPM_SEARCH_PAGE_SIZE = 250;
@@ -69,13 +70,13 @@ export function isCacheValid(query: string): boolean {
69
70
 
70
71
  // Import persistent cache
71
72
  import {
72
- getCachedSearch,
73
- setCachedSearch,
74
73
  getCachedPackage,
75
- setCachedPackage,
76
- getPackageDescriptions,
77
74
  getCachedPackageSize,
75
+ getCachedSearch,
76
+ getPackageDescriptions,
77
+ setCachedPackage,
78
78
  setCachedPackageSize,
79
+ setCachedSearch,
79
80
  } from "../utils/cache.js";
80
81
 
81
82
  function toNpmPackage(entry: NpmSearchResultObject): NpmPackage | undefined {
@@ -201,11 +202,13 @@ export async function getInstalledPackages(
201
202
  return packages;
202
203
  }
203
204
 
204
- function getInstalledPackageIdentity(pkg: InstalledPackage): string {
205
- return normalizePackageIdentity(
206
- pkg.source,
207
- pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : undefined
208
- );
205
+ function getInstalledPackageIdentity(pkg: InstalledPackage, options?: { cwd?: string }): string {
206
+ const baseCwd = pkg.scope === "project" ? options?.cwd : getAgentDir();
207
+
208
+ return normalizePackageIdentity(pkg.source, {
209
+ ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
210
+ ...(baseCwd ? { cwd: baseCwd } : {}),
211
+ });
209
212
  }
210
213
 
211
214
  export async function isSourceInstalled(
@@ -214,10 +217,10 @@ export async function isSourceInstalled(
214
217
  options?: { scope?: "global" | "project" }
215
218
  ): Promise<boolean> {
216
219
  const installed = await getPackageCatalog(ctx.cwd).listInstalledPackages({ dedupe: false });
217
- const expected = normalizePackageIdentity(source);
220
+ const expected = normalizePackageIdentity(source, { cwd: ctx.cwd });
218
221
 
219
222
  return installed.some((pkg) => {
220
- if (getInstalledPackageIdentity(pkg) !== expected) {
223
+ if (getInstalledPackageIdentity(pkg, { cwd: ctx.cwd }) !== expected) {
221
224
  return false;
222
225
  }
223
226
  return options?.scope ? pkg.scope === options.scope : true;
@@ -1,12 +1,17 @@
1
- import { mkdir, readFile, writeFile, rename, rm, readdir } from "node:fs/promises";
2
- import type { Dirent } from "node:fs";
1
+ import { execFile } from "node:child_process";
2
+ import { type Dirent } from "node:fs";
3
+ import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
3
5
  import { dirname, join, matchesGlob, relative, resolve } from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
- import { homedir } from "node:os";
6
- import { execFile } from "node:child_process";
7
7
  import { promisify } from "node:util";
8
8
  import { getAgentDir } from "@mariozechner/pi-coding-agent";
9
- import type { InstalledPackage, PackageExtensionEntry, Scope, State } from "../types/index.js";
9
+ import {
10
+ type InstalledPackage,
11
+ type PackageExtensionEntry,
12
+ type Scope,
13
+ type State,
14
+ } from "../types/index.js";
10
15
  import { parseNpmSource } from "../utils/format.js";
11
16
  import { fileExists, readSummary } from "../utils/fs.js";
12
17
  import { resolveNpmCommand } from "../utils/npm-exec.js";
@@ -1,30 +1,30 @@
1
1
  /**
2
2
  * Package installation logic
3
3
  */
4
- import { mkdir, rm, writeFile, cp } from "node:fs/promises";
5
- import { join } from "node:path";
4
+ import { cp, mkdir, rm, writeFile } from "node:fs/promises";
6
5
  import { homedir } from "node:os";
7
- import type {
8
- ExtensionAPI,
9
- ExtensionCommandContext,
10
- ProgressEvent,
6
+ import { join } from "node:path";
7
+ import {
8
+ type ExtensionAPI,
9
+ type ExtensionCommandContext,
10
+ type ProgressEvent,
11
11
  } from "@mariozechner/pi-coding-agent";
12
+ import { TIMEOUTS } from "../constants.js";
13
+ import { runTaskWithLoader } from "../ui/async-task.js";
12
14
  import { normalizePackageSource } from "../utils/format.js";
13
15
  import { fileExists } from "../utils/fs.js";
14
- import { clearSearchCache } from "./discovery.js";
15
- import { getPackageCatalog } from "./catalog.js";
16
- import { discoverPackageExtensionEntrypoints, readPackageManifest } from "./extensions.js";
17
16
  import { logPackageInstall } from "../utils/history.js";
18
- import { clearUpdatesAvailable } from "../utils/settings.js";
19
- import { notify, error as notifyError, success } from "../utils/notify.js";
20
- import { confirmAction, confirmReload, showProgress } from "../utils/ui-helpers.js";
21
17
  import { tryOperation } from "../utils/mode.js";
22
- import { runTaskWithLoader } from "../ui/async-task.js";
23
- import { updateExtmgrStatus } from "../utils/status.js";
18
+ import { fetchWithTimeout } from "../utils/network.js";
19
+ import { notify, error as notifyError, success } from "../utils/notify.js";
24
20
  import { execNpm } from "../utils/npm-exec.js";
25
21
  import { normalizePackageIdentity } from "../utils/package-source.js";
26
- import { fetchWithTimeout } from "../utils/network.js";
27
- import { TIMEOUTS } from "../constants.js";
22
+ import { clearUpdatesAvailable } from "../utils/settings.js";
23
+ import { updateExtmgrStatus } from "../utils/status.js";
24
+ import { confirmAction, confirmReload, showProgress } from "../utils/ui-helpers.js";
25
+ import { getPackageCatalog } from "./catalog.js";
26
+ import { clearSearchCache } from "./discovery.js";
27
+ import { discoverPackageExtensionEntrypoints, readPackageManifest } from "./extensions.js";
28
28
 
29
29
  export type InstallScope = "global" | "project";
30
30
 
@@ -171,7 +171,7 @@ export async function installPackage(
171
171
 
172
172
  // Check if it's a GitHub URL to a .ts file - handle as direct download
173
173
  const githubTsMatch = source.match(
174
- /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+\.ts)$/
174
+ /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+\.ts)$/
175
175
  );
176
176
  const githubInfo = safeExtractGithubMatch(githubTsMatch);
177
177
  if (githubInfo) {
@@ -211,6 +211,7 @@ export async function installPackage(
211
211
  title: "Install Package",
212
212
  message: `Installing ${normalized}...`,
213
213
  cancellable: false,
214
+ fallbackWithoutLoader: true,
214
215
  },
215
216
  async ({ setMessage }) => {
216
217
  await getPackageCatalog(ctx.cwd).install(normalized, scope, (event) => {
@@ -231,7 +232,7 @@ export async function installPackage(
231
232
  clearSearchCache();
232
233
  logPackageInstall(pi, normalized, normalized, undefined, scope, true);
233
234
  success(ctx, `Installed ${normalized} (${scope})`);
234
- clearUpdatesAvailable(pi, ctx, [normalizePackageIdentity(normalized)]);
235
+ clearUpdatesAvailable(pi, ctx, [normalizePackageIdentity(normalized, { cwd: ctx.cwd })]);
235
236
 
236
237
  const reloaded = await confirmReload(ctx, "Package installed.");
237
238
  if (!reloaded) {
@@ -1,33 +1,34 @@
1
1
  /**
2
2
  * Package management (update, remove)
3
3
  */
4
- import type {
5
- ExtensionAPI,
6
- ExtensionCommandContext,
7
- ProgressEvent,
8
- } from "@mariozechner/pi-coding-agent";
9
- import type { InstalledPackage } from "../types/index.js";
10
4
  import {
11
- getInstalledPackages,
12
- getInstalledPackagesAllScopes,
13
- clearSearchCache,
14
- } from "./discovery.js";
15
- import { getPackageCatalog } from "./catalog.js";
5
+ type ExtensionAPI,
6
+ type ExtensionCommandContext,
7
+ getAgentDir,
8
+ type ProgressEvent,
9
+ } from "@mariozechner/pi-coding-agent";
10
+ import { UI } from "../constants.js";
11
+ import { type InstalledPackage } from "../types/index.js";
12
+ import { runTaskWithLoader } from "../ui/async-task.js";
16
13
  import { formatInstalledPackageLabel } from "../utils/format.js";
14
+ import { logPackageRemove, logPackageUpdate } from "../utils/history.js";
15
+ import { requireUI } from "../utils/mode.js";
16
+ import { notify, error as notifyError, success } from "../utils/notify.js";
17
17
  import { normalizePackageIdentity } from "../utils/package-source.js";
18
- import { logPackageUpdate, logPackageRemove } from "../utils/history.js";
19
18
  import { clearUpdatesAvailable } from "../utils/settings.js";
20
- import { notify, error as notifyError, success } from "../utils/notify.js";
19
+ import { updateExtmgrStatus } from "../utils/status.js";
21
20
  import {
22
21
  confirmAction,
23
22
  confirmReload,
24
- showProgress,
25
23
  formatListOutput,
24
+ showProgress,
26
25
  } from "../utils/ui-helpers.js";
27
- import { requireUI } from "../utils/mode.js";
28
- import { runTaskWithLoader } from "../ui/async-task.js";
29
- import { updateExtmgrStatus } from "../utils/status.js";
30
- import { UI } from "../constants.js";
26
+ import { getPackageCatalog } from "./catalog.js";
27
+ import {
28
+ clearSearchCache,
29
+ getInstalledPackages,
30
+ getInstalledPackagesAllScopes,
31
+ } from "./discovery.js";
31
32
 
32
33
  export interface PackageMutationOutcome {
33
34
  reloaded: boolean;
@@ -56,7 +57,7 @@ async function updatePackageInternal(
56
57
  ): Promise<PackageMutationOutcome> {
57
58
  showProgress(ctx, "Updating", source);
58
59
 
59
- const updateIdentity = normalizePackageIdentity(source);
60
+ const updateIdentity = normalizePackageIdentity(source, { cwd: ctx.cwd });
60
61
 
61
62
  try {
62
63
  const updates = await getPackageCatalog(ctx.cwd).checkForAvailableUpdates();
@@ -78,6 +79,7 @@ async function updatePackageInternal(
78
79
  title: "Update Package",
79
80
  message: `Updating ${source}...`,
80
81
  cancellable: false,
82
+ fallbackWithoutLoader: true,
81
83
  },
82
84
  async ({ setMessage }) => {
83
85
  await getPackageCatalog(ctx.cwd).update(source, (event) => {
@@ -128,6 +130,7 @@ async function updatePackagesInternal(
128
130
  title: "Update Packages",
129
131
  message: "Updating all packages...",
130
132
  cancellable: false,
133
+ fallbackWithoutLoader: true,
131
134
  },
132
135
  async ({ setMessage }) => {
133
136
  await getPackageCatalog(ctx.cwd).update(undefined, (event) => {
@@ -186,8 +189,31 @@ export async function updatePackagesWithOutcome(
186
189
  return updatePackagesInternal(ctx, pi);
187
190
  }
188
191
 
189
- function packageIdentity(source: string): string {
190
- return normalizePackageIdentity(source);
192
+ function packageIdentity(
193
+ source: string,
194
+ options?: { resolvedPath?: string; cwd?: string }
195
+ ): string {
196
+ return normalizePackageIdentity(source, options);
197
+ }
198
+
199
+ function packageSourceIdentities(source: string, ctx: ExtensionCommandContext): Set<string> {
200
+ return new Set([
201
+ packageIdentity(source, { cwd: ctx.cwd }),
202
+ packageIdentity(source, { cwd: getAgentDir() }),
203
+ ]);
204
+ }
205
+
206
+ function installedPackageMatchesSource(
207
+ pkg: InstalledPackage,
208
+ identities: Set<string>,
209
+ ctx: ExtensionCommandContext
210
+ ): boolean {
211
+ return identities.has(
212
+ packageIdentity(pkg.source, {
213
+ ...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
214
+ cwd: pkg.scope === "project" ? ctx.cwd : getAgentDir(),
215
+ })
216
+ );
191
217
  }
192
218
 
193
219
  async function getInstalledPackagesAllScopesForRemoval(
@@ -244,7 +270,6 @@ function buildRemovalTargets(
244
270
  return addTarget("global");
245
271
  case "project":
246
272
  return addTarget("project");
247
- case "cancel":
248
273
  default:
249
274
  return [];
250
275
  }
@@ -285,6 +310,7 @@ async function executeRemovalTargets(
285
310
  title: "Remove Package",
286
311
  message: `Removing ${target.source}...`,
287
312
  cancellable: false,
313
+ fallbackWithoutLoader: true,
288
314
  },
289
315
  async ({ setMessage }) => {
290
316
  await getPackageCatalog(ctx.cwd).remove(target.source, target.scope, (event) => {
@@ -338,8 +364,8 @@ async function removePackageInternal(
338
364
  pi: ExtensionAPI
339
365
  ): Promise<PackageMutationOutcome> {
340
366
  const installed = await getInstalledPackagesAllScopesForRemoval(ctx);
341
- const identity = packageIdentity(source);
342
- const matching = installed.filter((p) => packageIdentity(p.source) === identity);
367
+ const identities = packageSourceIdentities(source, ctx);
368
+ const matching = installed.filter((pkg) => installedPackageMatchesSource(pkg, identities, ctx));
343
369
 
344
370
  const hasBothScopes =
345
371
  matching.some((pkg) => pkg.scope === "global") &&
@@ -385,13 +411,13 @@ async function removePackageInternal(
385
411
  .filter((result) => result.success)
386
412
  .map((result) => result.target);
387
413
 
388
- const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter(
389
- (p) => packageIdentity(p.source) === identity
414
+ const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter((pkg) =>
415
+ installedPackageMatchesSource(pkg, identities, ctx)
390
416
  );
391
417
  notifyRemovalSummary(source, remaining, failures, ctx);
392
418
 
393
419
  if (failures.length === 0 && remaining.length === 0) {
394
- clearUpdatesAvailable(pi, ctx, [identity]);
420
+ clearUpdatesAvailable(pi, ctx, identities);
395
421
  }
396
422
 
397
423
  const successfulRemovalCount = successfulTargets.length;
@@ -46,25 +46,32 @@ export interface PackageExtensionEntry {
46
46
  state: State;
47
47
  }
48
48
 
49
- export interface UnifiedItem {
50
- type: "local" | "package";
49
+ export interface LocalUnifiedItem {
50
+ type: "local";
51
51
  id: string;
52
52
  displayName: string;
53
53
  summary: string;
54
54
  scope: Scope;
55
- // Local extension fields
56
- state?: State | undefined;
57
- activePath?: string | undefined;
58
- disabledPath?: string | undefined;
59
- originalState?: State | undefined;
60
- // Package fields
61
- source?: string | undefined;
55
+ state: State;
56
+ activePath: string;
57
+ disabledPath: string;
58
+ originalState: State;
59
+ }
60
+
61
+ export interface PackageUnifiedItem {
62
+ type: "package";
63
+ id: string;
64
+ displayName: string;
65
+ scope: Scope;
66
+ source: string;
62
67
  version?: string | undefined;
63
68
  description?: string | undefined;
64
69
  size?: number | undefined; // Package size in bytes
65
70
  updateAvailable?: boolean | undefined;
66
71
  }
67
72
 
73
+ export type UnifiedItem = LocalUnifiedItem | PackageUnifiedItem;
74
+
68
75
  export interface SearchCache {
69
76
  query: string;
70
77
  results: NpmPackage[];