pi-extmgr 0.1.26 → 0.1.27
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 +3 -2
- package/package.json +3 -3
- package/src/packages/catalog.ts +162 -0
- package/src/packages/discovery.ts +59 -247
- package/src/packages/install.ts +30 -26
- package/src/packages/management.ts +103 -83
- package/src/ui/async-task.ts +158 -0
- package/src/ui/package-config.ts +27 -2
- package/src/ui/remote.ts +69 -17
- package/src/ui/unified.ts +45 -12
- package/src/utils/auto-update.ts +8 -54
- package/src/utils/network.ts +10 -2
- package/src/utils/npm-exec.ts +2 -0
- package/src/utils/package-source.ts +51 -0
- package/src/utils/status.ts +5 -3
- package/src/utils/ui-helpers.ts +1 -1
package/src/ui/remote.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { installPackage, installPackageLocally } from "../packages/install.js";
|
|
|
18
18
|
import { execNpm } from "../utils/npm-exec.js";
|
|
19
19
|
import { notify } from "../utils/notify.js";
|
|
20
20
|
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
21
|
+
import { runTaskWithLoader } from "./async-task.js";
|
|
21
22
|
|
|
22
23
|
interface PackageInfoCacheEntry {
|
|
23
24
|
timestamp: number;
|
|
@@ -109,25 +110,44 @@ const PACKAGE_DETAILS_CHOICES = {
|
|
|
109
110
|
back: "Back to results",
|
|
110
111
|
} as const;
|
|
111
112
|
|
|
113
|
+
function createAbortError(): Error {
|
|
114
|
+
const error = new Error("Operation cancelled");
|
|
115
|
+
error.name = "AbortError";
|
|
116
|
+
return error;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
120
|
+
if (signal?.aborted) {
|
|
121
|
+
throw createAbortError();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
function formatCount(value: number | undefined): string {
|
|
113
126
|
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
114
127
|
return new Intl.NumberFormat().format(value);
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
async function fetchWeeklyDownloads(
|
|
130
|
+
async function fetchWeeklyDownloads(
|
|
131
|
+
packageName: string,
|
|
132
|
+
signal?: AbortSignal
|
|
133
|
+
): Promise<number | undefined> {
|
|
118
134
|
const controller = new AbortController();
|
|
119
135
|
const timer = setTimeout(() => controller.abort(), TIMEOUTS.weeklyDownloads);
|
|
136
|
+
const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
120
137
|
|
|
121
138
|
try {
|
|
122
139
|
const encoded = encodeURIComponent(packageName);
|
|
123
140
|
const res = await fetch(`https://api.npmjs.org/downloads/point/last-week/${encoded}`, {
|
|
124
|
-
signal:
|
|
141
|
+
signal: combinedSignal,
|
|
125
142
|
});
|
|
126
143
|
|
|
127
144
|
if (!res.ok) return undefined;
|
|
128
145
|
const data = (await res.json()) as NpmDownloadsPoint;
|
|
129
146
|
return typeof data.downloads === "number" ? data.downloads : undefined;
|
|
130
|
-
} catch {
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (signal?.aborted && error instanceof Error && error.name === "AbortError") {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
131
151
|
return undefined;
|
|
132
152
|
} finally {
|
|
133
153
|
clearTimeout(timer);
|
|
@@ -137,7 +157,8 @@ async function fetchWeeklyDownloads(packageName: string): Promise<number | undef
|
|
|
137
157
|
async function buildPackageInfoText(
|
|
138
158
|
packageName: string,
|
|
139
159
|
ctx: ExtensionCommandContext,
|
|
140
|
-
pi: ExtensionAPI
|
|
160
|
+
pi: ExtensionAPI,
|
|
161
|
+
signal?: AbortSignal
|
|
141
162
|
): Promise<string> {
|
|
142
163
|
// Check cache first
|
|
143
164
|
const cached = packageInfoCache.get(packageName);
|
|
@@ -148,10 +169,13 @@ async function buildPackageInfoText(
|
|
|
148
169
|
const [infoRes, weeklyDownloads] = await Promise.all([
|
|
149
170
|
execNpm(pi, ["view", packageName, "--json"], ctx, {
|
|
150
171
|
timeout: TIMEOUTS.npmView,
|
|
172
|
+
...(signal ? { signal } : {}),
|
|
151
173
|
}),
|
|
152
|
-
fetchWeeklyDownloads(packageName),
|
|
174
|
+
fetchWeeklyDownloads(packageName, signal),
|
|
153
175
|
]);
|
|
154
176
|
|
|
177
|
+
throwIfAborted(signal);
|
|
178
|
+
|
|
155
179
|
if (infoRes.code !== 0) {
|
|
156
180
|
throw new Error(infoRes.stderr || infoRes.stdout || `npm view failed (exit ${infoRes.code})`);
|
|
157
181
|
}
|
|
@@ -179,7 +203,7 @@ async function buildPackageInfoText(
|
|
|
179
203
|
|
|
180
204
|
const text = lines.join("\n");
|
|
181
205
|
|
|
182
|
-
|
|
206
|
+
throwIfAborted(signal);
|
|
183
207
|
packageInfoCache.set(packageName, { text });
|
|
184
208
|
|
|
185
209
|
return text;
|
|
@@ -346,20 +370,34 @@ export async function browseRemotePackages(
|
|
|
346
370
|
return;
|
|
347
371
|
}
|
|
348
372
|
|
|
349
|
-
|
|
350
|
-
let allPackages: NpmPackage[] = [];
|
|
373
|
+
let allPackages: NpmPackage[] | undefined;
|
|
351
374
|
|
|
352
|
-
if (isCacheValid(query)
|
|
375
|
+
if (isCacheValid(query)) {
|
|
353
376
|
const cache = getSearchCache();
|
|
354
|
-
if (cache)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
377
|
+
if (cache) {
|
|
378
|
+
allPackages = cache.results;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!allPackages) {
|
|
383
|
+
const results = await runTaskWithLoader(
|
|
384
|
+
ctx,
|
|
385
|
+
{
|
|
386
|
+
title: "Remote Packages",
|
|
387
|
+
message: `Searching npm for ${truncate(query, 40)}...`,
|
|
388
|
+
},
|
|
389
|
+
async ({ signal, setMessage }) => {
|
|
390
|
+
setMessage(`Searching npm for ${truncate(query, 40)}...`);
|
|
391
|
+
return searchNpmPackages(query, ctx, { signal });
|
|
392
|
+
}
|
|
393
|
+
);
|
|
358
394
|
|
|
359
|
-
|
|
360
|
-
|
|
395
|
+
if (!results) {
|
|
396
|
+
notify(ctx, "Remote package search was cancelled.", "info");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
361
399
|
|
|
362
|
-
|
|
400
|
+
allPackages = results;
|
|
363
401
|
setSearchCache({
|
|
364
402
|
query,
|
|
365
403
|
results: allPackages,
|
|
@@ -451,7 +489,21 @@ async function showPackageDetails(
|
|
|
451
489
|
return;
|
|
452
490
|
case "viewInfo":
|
|
453
491
|
try {
|
|
454
|
-
const text = await
|
|
492
|
+
const text = await runTaskWithLoader(
|
|
493
|
+
ctx,
|
|
494
|
+
{
|
|
495
|
+
title: packageName,
|
|
496
|
+
message: `Fetching package details for ${packageName}...`,
|
|
497
|
+
},
|
|
498
|
+
({ signal }) => buildPackageInfoText(packageName, ctx, pi, signal)
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
if (!text) {
|
|
502
|
+
notify(ctx, `Loading ${packageName} details was cancelled.`, "info");
|
|
503
|
+
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
455
507
|
ctx.ui.notify(text, "info");
|
|
456
508
|
} catch (error) {
|
|
457
509
|
const message = error instanceof Error ? error.message : String(error);
|
package/src/ui/unified.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "../packages/management.js";
|
|
30
30
|
import { showRemote } from "./remote.js";
|
|
31
31
|
import { showHelp } from "./help.js";
|
|
32
|
+
import { runTaskWithLoader } from "./async-task.js";
|
|
32
33
|
import { formatEntry as formatExtEntry, dynamicTruncate, formatBytes } from "../utils/format.js";
|
|
33
34
|
import {
|
|
34
35
|
getStatusIcon,
|
|
@@ -43,6 +44,7 @@ import { getKnownUpdates, promptAutoUpdateWizard } from "../utils/auto-update.js
|
|
|
43
44
|
import { updateExtmgrStatus } from "../utils/status.js";
|
|
44
45
|
import { parseChoiceByLabel } from "../utils/command.js";
|
|
45
46
|
import { notify } from "../utils/notify.js";
|
|
47
|
+
import { confirmReload } from "../utils/ui-helpers.js";
|
|
46
48
|
import { getPackageSourceKind, normalizePackageIdentity } from "../utils/package-source.js";
|
|
47
49
|
import { hasCustomUI, runCustomUI } from "../utils/mode.js";
|
|
48
50
|
import { getSettingsListSelectedIndex } from "../utils/settings-list.js";
|
|
@@ -82,11 +84,46 @@ async function showInteractiveOnce(
|
|
|
82
84
|
ctx: ExtensionCommandContext,
|
|
83
85
|
pi: ExtensionAPI
|
|
84
86
|
): Promise<boolean> {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
const initialData = await runTaskWithLoader(
|
|
88
|
+
ctx,
|
|
89
|
+
{
|
|
90
|
+
title: "Extensions Manager",
|
|
91
|
+
message: "Loading extensions and packages...",
|
|
92
|
+
},
|
|
93
|
+
async ({ signal, setMessage }) => {
|
|
94
|
+
const localEntriesPromise = discoverExtensions(ctx.cwd);
|
|
95
|
+
const installedPackagesPromise = getInstalledPackages(
|
|
96
|
+
ctx,
|
|
97
|
+
pi,
|
|
98
|
+
(current, total) => {
|
|
99
|
+
if (total <= 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
setMessage(`Loading package metadata... ${current}/${total}`);
|
|
103
|
+
},
|
|
104
|
+
signal
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const [localEntries, installedPackages] = await Promise.all([
|
|
108
|
+
localEntriesPromise,
|
|
109
|
+
installedPackagesPromise,
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
return { localEntries, installedPackages };
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (!initialData) {
|
|
117
|
+
notify(
|
|
118
|
+
ctx,
|
|
119
|
+
"The unified extensions manager requires the full interactive TUI. Showing read-only local and installed package lists instead.",
|
|
120
|
+
"warning"
|
|
121
|
+
);
|
|
122
|
+
await showInteractiveFallback(ctx, pi);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { localEntries, installedPackages } = initialData;
|
|
90
127
|
|
|
91
128
|
// Build unified items list.
|
|
92
129
|
const knownUpdates = getKnownUpdates(ctx);
|
|
@@ -498,7 +535,7 @@ async function applyToggleChangesFromManager(
|
|
|
498
535
|
);
|
|
499
536
|
|
|
500
537
|
if (shouldReload) {
|
|
501
|
-
await
|
|
538
|
+
await ctx.reload();
|
|
502
539
|
return { changed: apply.changed, reloaded: true };
|
|
503
540
|
}
|
|
504
541
|
} else {
|
|
@@ -768,12 +805,8 @@ async function handleUnifiedAction(
|
|
|
768
805
|
"info"
|
|
769
806
|
);
|
|
770
807
|
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
"Extension removed. Reload pi now?"
|
|
774
|
-
);
|
|
775
|
-
if (shouldReload) {
|
|
776
|
-
await (ctx as ExtensionCommandContext & { reload: () => Promise<void> }).reload();
|
|
808
|
+
const reloaded = await confirmReload(ctx, "Extension removed.");
|
|
809
|
+
if (reloaded) {
|
|
777
810
|
return true;
|
|
778
811
|
}
|
|
779
812
|
|
package/src/utils/auto-update.ts
CHANGED
|
@@ -6,8 +6,7 @@ import type {
|
|
|
6
6
|
ExtensionCommandContext,
|
|
7
7
|
ExtensionContext,
|
|
8
8
|
} from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import
|
|
10
|
-
import { getInstalledPackages } from "../packages/discovery.js";
|
|
9
|
+
import { getPackageCatalog } from "../packages/catalog.js";
|
|
11
10
|
import { notify } from "./notify.js";
|
|
12
11
|
import {
|
|
13
12
|
getAutoUpdateConfig,
|
|
@@ -17,21 +16,14 @@ import {
|
|
|
17
16
|
parseDuration,
|
|
18
17
|
type AutoUpdateConfig,
|
|
19
18
|
} from "./settings.js";
|
|
20
|
-
import { parseNpmSource } from "./format.js";
|
|
21
|
-
import { execNpm } from "./npm-exec.js";
|
|
22
19
|
import { normalizePackageIdentity } from "./package-source.js";
|
|
23
20
|
import { logAutoUpdateConfig } from "./history.js";
|
|
24
|
-
import { TIMEOUTS } from "../constants.js";
|
|
25
21
|
|
|
26
22
|
import { startTimer, stopTimer, isTimerRunning } from "./timer.js";
|
|
27
23
|
|
|
28
24
|
// Context provider for safe session handling
|
|
29
25
|
export type ContextProvider = () => (ExtensionCommandContext | ExtensionContext) | undefined;
|
|
30
26
|
|
|
31
|
-
function getUpdateIdentity(pkg: InstalledPackage): string {
|
|
32
|
-
return normalizePackageIdentity(pkg.source);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
27
|
/**
|
|
36
28
|
* Start auto-update background checker
|
|
37
29
|
* Uses a context provider to avoid stale context issues when sessions switch
|
|
@@ -67,7 +59,10 @@ export function startAutoUpdateTimer(
|
|
|
67
59
|
stopAutoUpdateTimer();
|
|
68
60
|
return;
|
|
69
61
|
}
|
|
70
|
-
|
|
62
|
+
|
|
63
|
+
void checkForUpdates(pi, checkCtx, onUpdateAvailable).catch((error) => {
|
|
64
|
+
console.warn("[extmgr] Auto-update check failed:", error);
|
|
65
|
+
});
|
|
71
66
|
},
|
|
72
67
|
{ initialDelayMs }
|
|
73
68
|
);
|
|
@@ -96,19 +91,9 @@ export async function checkForUpdates(
|
|
|
96
91
|
ctx: ExtensionCommandContext | ExtensionContext,
|
|
97
92
|
onUpdateAvailable?: (packages: string[]) => void
|
|
98
93
|
): Promise<string[]> {
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
const updatesAvailable: string[] = [];
|
|
103
|
-
const updatedPackageNames: string[] = [];
|
|
104
|
-
|
|
105
|
-
for (const pkg of npmPackages) {
|
|
106
|
-
const hasUpdate = await checkPackageUpdate(pkg, ctx, pi);
|
|
107
|
-
if (hasUpdate) {
|
|
108
|
-
updatesAvailable.push(getUpdateIdentity(pkg));
|
|
109
|
-
updatedPackageNames.push(pkg.name);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
94
|
+
const updates = await getPackageCatalog(ctx.cwd).checkForAvailableUpdates();
|
|
95
|
+
const updatesAvailable = updates.map((update) => normalizePackageIdentity(update.source));
|
|
96
|
+
const updatedPackageNames = updates.map((update) => update.displayName);
|
|
112
97
|
|
|
113
98
|
const checkedAt = Date.now();
|
|
114
99
|
const config = getAutoUpdateConfig(ctx);
|
|
@@ -126,37 +111,6 @@ export async function checkForUpdates(
|
|
|
126
111
|
return updatedPackageNames;
|
|
127
112
|
}
|
|
128
113
|
|
|
129
|
-
/**
|
|
130
|
-
* Check if a specific package has updates available
|
|
131
|
-
*/
|
|
132
|
-
async function checkPackageUpdate(
|
|
133
|
-
pkg: InstalledPackage,
|
|
134
|
-
ctx: ExtensionCommandContext | ExtensionContext,
|
|
135
|
-
pi: ExtensionAPI
|
|
136
|
-
): Promise<boolean> {
|
|
137
|
-
const parsed = parseNpmSource(pkg.source);
|
|
138
|
-
const pkgName = parsed?.name;
|
|
139
|
-
if (!pkgName) return false;
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const res = await execNpm(pi, ["view", pkgName, "version", "--json"], ctx, {
|
|
143
|
-
timeout: TIMEOUTS.npmView,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (res.code !== 0) return false;
|
|
147
|
-
|
|
148
|
-
const latestVersion = JSON.parse(res.stdout) as string;
|
|
149
|
-
const currentVersion = pkg.version;
|
|
150
|
-
|
|
151
|
-
if (!currentVersion) return false;
|
|
152
|
-
|
|
153
|
-
// Simple version comparison (assumes semver)
|
|
154
|
-
return latestVersion !== currentVersion;
|
|
155
|
-
} catch {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
114
|
/**
|
|
161
115
|
* Get status text for display
|
|
162
116
|
*/
|
package/src/utils/network.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
export async function fetchWithTimeout(
|
|
1
|
+
export async function fetchWithTimeout(
|
|
2
|
+
url: string,
|
|
3
|
+
timeoutMs: number,
|
|
4
|
+
signal?: AbortSignal
|
|
5
|
+
): Promise<Response> {
|
|
2
6
|
const controller = new AbortController();
|
|
3
7
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8
|
+
const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
4
9
|
|
|
5
10
|
try {
|
|
6
|
-
return await fetch(url, { signal:
|
|
11
|
+
return await fetch(url, { signal: combinedSignal });
|
|
7
12
|
} catch (error) {
|
|
8
13
|
if (error instanceof Error && error.name === "AbortError") {
|
|
14
|
+
if (signal?.aborted) {
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
9
17
|
throw new Error(`Request timed out after ${Math.ceil(timeoutMs / 1000)}s`);
|
|
10
18
|
}
|
|
11
19
|
throw error;
|
package/src/utils/npm-exec.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface NpmCommandResolutionOptions {
|
|
|
9
9
|
|
|
10
10
|
interface NpmExecOptions {
|
|
11
11
|
timeout: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function getNpmCliPath(nodeExecPath: string, runtimePlatform: NodeJS.Platform): string {
|
|
@@ -43,5 +44,6 @@ export async function execNpm(
|
|
|
43
44
|
return pi.exec(resolved.command, resolved.args, {
|
|
44
45
|
timeout: options.timeout,
|
|
45
46
|
cwd: ctx.cwd,
|
|
47
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
46
48
|
});
|
|
47
49
|
}
|
|
@@ -100,3 +100,54 @@ export function splitGitRepoAndRef(gitSpec: string): { repo: string; ref?: strin
|
|
|
100
100
|
|
|
101
101
|
return { repo: gitSpec.slice(0, lastAt), ref: tail };
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
function extractGitPackageName(repoSpec: string): string {
|
|
105
|
+
if (repoSpec.startsWith("git@")) {
|
|
106
|
+
const afterColon = repoSpec.split(":").slice(1).join(":");
|
|
107
|
+
if (afterColon) {
|
|
108
|
+
const last = afterColon.split("/").pop() || afterColon;
|
|
109
|
+
return last.replace(/\.git$/i, "") || repoSpec;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const url = new URL(repoSpec);
|
|
115
|
+
const last = url.pathname.split("/").filter(Boolean).pop();
|
|
116
|
+
if (last) {
|
|
117
|
+
return last.replace(/\.git$/i, "") || repoSpec;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Fall back to generic path splitting below.
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const last = repoSpec.split(/[/:]/).filter(Boolean).pop();
|
|
124
|
+
return (last ? last.replace(/\.git$/i, "") : repoSpec) || repoSpec;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function parsePackageNameAndVersion(fullSource: string): {
|
|
128
|
+
name: string;
|
|
129
|
+
version?: string | undefined;
|
|
130
|
+
} {
|
|
131
|
+
const parsedNpm = parseNpmSource(fullSource);
|
|
132
|
+
if (parsedNpm) {
|
|
133
|
+
return parsedNpm;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sourceKind = getPackageSourceKind(fullSource);
|
|
137
|
+
if (sourceKind === "git") {
|
|
138
|
+
const gitSpec = stripGitSourcePrefix(fullSource);
|
|
139
|
+
const { repo } = splitGitRepoAndRef(gitSpec);
|
|
140
|
+
return { name: extractGitPackageName(repo) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (fullSource.includes("node_modules/")) {
|
|
144
|
+
const nmMatch = fullSource.match(/node_modules\/(.+)$/);
|
|
145
|
+
if (nmMatch?.[1]) {
|
|
146
|
+
return { name: nmMatch[1] };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const pathParts = fullSource.split(/[\\/]/);
|
|
151
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
152
|
+
return { name: fileName || fullSource };
|
|
153
|
+
}
|
package/src/utils/status.ts
CHANGED
|
@@ -6,14 +6,16 @@ import type {
|
|
|
6
6
|
ExtensionCommandContext,
|
|
7
7
|
ExtensionContext,
|
|
8
8
|
} from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import {
|
|
9
|
+
import { getPackageCatalog, type PackageCatalog } from "../packages/catalog.js";
|
|
10
10
|
import { getAutoUpdateStatus } from "./auto-update.js";
|
|
11
11
|
import { normalizePackageIdentity } from "./package-source.js";
|
|
12
12
|
import { getAutoUpdateConfigAsync, saveAutoUpdateConfig } from "./settings.js";
|
|
13
13
|
|
|
14
|
+
type CatalogInstalledPackages = Awaited<ReturnType<PackageCatalog["listInstalledPackages"]>>;
|
|
15
|
+
|
|
14
16
|
function filterStaleUpdates(
|
|
15
17
|
knownUpdates: string[],
|
|
16
|
-
installedPackages:
|
|
18
|
+
installedPackages: CatalogInstalledPackages
|
|
17
19
|
): string[] {
|
|
18
20
|
const installedIdentities = new Set(
|
|
19
21
|
installedPackages.map((pkg) =>
|
|
@@ -34,7 +36,7 @@ export async function updateExtmgrStatus(
|
|
|
34
36
|
|
|
35
37
|
try {
|
|
36
38
|
const [packages, autoUpdateConfig] = await Promise.all([
|
|
37
|
-
|
|
39
|
+
getPackageCatalog(ctx.cwd).listInstalledPackages(),
|
|
38
40
|
getAutoUpdateConfigAsync(ctx),
|
|
39
41
|
]);
|
|
40
42
|
const statusParts: string[] = [];
|
package/src/utils/ui-helpers.ts
CHANGED
|
@@ -21,7 +21,7 @@ export async function confirmReload(
|
|
|
21
21
|
const confirmed = await ctx.ui.confirm("Reload Required", `${reason}\nReload pi now?`);
|
|
22
22
|
|
|
23
23
|
if (confirmed) {
|
|
24
|
-
await
|
|
24
|
+
await ctx.reload();
|
|
25
25
|
return true;
|
|
26
26
|
}
|
|
27
27
|
|