pi-extmgr 0.1.28 → 0.2.0
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 +18 -8
- package/package.json +10 -2
- package/src/commands/auto-update.ts +1 -1
- package/src/commands/history.ts +2 -31
- package/src/constants.ts +0 -8
- package/src/extensions/discovery.ts +121 -39
- package/src/packages/discovery.ts +34 -0
- package/src/packages/extensions.ts +55 -98
- package/src/packages/install.ts +85 -56
- package/src/packages/management.ts +25 -38
- package/src/types/index.ts +4 -2
- package/src/ui/footer.ts +49 -29
- package/src/ui/help.ts +15 -11
- package/src/ui/remote.ts +704 -112
- package/src/ui/unified.ts +922 -311
- package/src/utils/auto-update.ts +34 -29
- package/src/utils/cache.ts +205 -34
- package/src/utils/duration.ts +132 -0
- package/src/utils/format.ts +0 -30
- package/src/utils/fs.ts +8 -4
- package/src/utils/history.ts +43 -7
- package/src/utils/mode.ts +1 -1
- package/src/utils/notify.ts +0 -14
- package/src/utils/package-source.ts +2 -5
- package/src/utils/path-identity.ts +7 -0
- package/src/utils/relative-path-selection.ts +100 -0
- package/src/utils/settings.ts +4 -63
- package/src/utils/retry.ts +0 -49
package/src/packages/install.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@mariozechner/pi-coding-agent";
|
|
12
12
|
import { TIMEOUTS } from "../constants.js";
|
|
13
13
|
import { runTaskWithLoader } from "../ui/async-task.js";
|
|
14
|
+
import { parseChoiceByLabel } from "../utils/command.js";
|
|
14
15
|
import { normalizePackageSource } from "../utils/format.js";
|
|
15
16
|
import { fileExists } from "../utils/fs.js";
|
|
16
17
|
import { logPackageInstall } from "../utils/history.js";
|
|
@@ -32,6 +33,17 @@ export interface InstallOptions {
|
|
|
32
33
|
scope?: InstallScope;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
export interface InstallOutcome {
|
|
37
|
+
installed: boolean;
|
|
38
|
+
reloaded: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const INSTALL_SCOPE_CHOICES = {
|
|
42
|
+
global: "Global (~/.pi/agent/settings.json)",
|
|
43
|
+
project: "Project (.pi/settings.json)",
|
|
44
|
+
cancel: "Cancel",
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
35
47
|
function getProgressMessage(event: ProgressEvent, fallback: string): string {
|
|
36
48
|
return event.message?.trim() || fallback;
|
|
37
49
|
}
|
|
@@ -44,14 +56,12 @@ async function resolveInstallScope(
|
|
|
44
56
|
|
|
45
57
|
if (!ctx.hasUI) return "global";
|
|
46
58
|
|
|
47
|
-
const choice =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]);
|
|
59
|
+
const choice = parseChoiceByLabel(
|
|
60
|
+
INSTALL_SCOPE_CHOICES,
|
|
61
|
+
await ctx.ui.select("Install scope", Object.values(INSTALL_SCOPE_CHOICES))
|
|
62
|
+
);
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
return choice.startsWith("Project") ? "project" : "global";
|
|
64
|
+
return choice === "cancel" ? undefined : choice;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
function getExtensionInstallDir(ctx: ExtensionCommandContext, scope: InstallScope): string {
|
|
@@ -61,28 +71,6 @@ function getExtensionInstallDir(ctx: ExtensionCommandContext, scope: InstallScop
|
|
|
61
71
|
return join(homedir(), ".pi", "agent", "extensions");
|
|
62
72
|
}
|
|
63
73
|
|
|
64
|
-
interface GithubUrlInfo {
|
|
65
|
-
owner: string;
|
|
66
|
-
repo: string;
|
|
67
|
-
branch: string;
|
|
68
|
-
filePath: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Safely extracts regex match groups with validation
|
|
73
|
-
*/
|
|
74
|
-
function safeExtractGithubMatch(match: RegExpMatchArray | null): GithubUrlInfo | undefined {
|
|
75
|
-
if (!match) return undefined;
|
|
76
|
-
|
|
77
|
-
const [, owner, repo, branch, filePath] = match;
|
|
78
|
-
|
|
79
|
-
if (!owner || !repo || !branch || !filePath) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { owner, repo, branch, filePath };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
74
|
async function ensureTarAvailable(
|
|
87
75
|
pi: ExtensionAPI,
|
|
88
76
|
ctx: ExtensionCommandContext
|
|
@@ -157,36 +145,35 @@ async function cleanupStandaloneTempArtifacts(tempDir: string, extractDir?: stri
|
|
|
157
145
|
);
|
|
158
146
|
}
|
|
159
147
|
|
|
160
|
-
|
|
148
|
+
async function installPackageInternal(
|
|
161
149
|
source: string,
|
|
162
150
|
ctx: ExtensionCommandContext,
|
|
163
151
|
pi: ExtensionAPI,
|
|
164
152
|
options?: InstallOptions
|
|
165
|
-
): Promise<
|
|
153
|
+
): Promise<InstallOutcome> {
|
|
166
154
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
167
155
|
if (!scope) {
|
|
168
156
|
notify(ctx, "Installation cancelled.", "info");
|
|
169
|
-
return;
|
|
157
|
+
return { installed: false, reloaded: false };
|
|
170
158
|
}
|
|
171
159
|
|
|
172
160
|
// Check if it's a GitHub URL to a .ts file - handle as direct download
|
|
173
161
|
const githubTsMatch = source.match(
|
|
174
162
|
/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+\.ts)$/
|
|
175
163
|
);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
if (githubTsMatch) {
|
|
165
|
+
const [, owner, repo, branch, filePath] = githubTsMatch;
|
|
166
|
+
if (owner && repo && branch && filePath) {
|
|
167
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
|
|
168
|
+
const fileName = filePath.split("/").pop() || `${owner}-${repo}.ts`;
|
|
169
|
+
return await installFromUrl(rawUrl, fileName, ctx, pi, { scope });
|
|
170
|
+
}
|
|
183
171
|
}
|
|
184
172
|
|
|
185
173
|
// Check if it's already a raw URL to a .ts file
|
|
186
174
|
if (source.match(/^https:\/\/raw\.githubusercontent\.com\/.*\.ts$/)) {
|
|
187
175
|
const fileName = source.split("/").pop() || "extension.ts";
|
|
188
|
-
await installFromUrl(source, fileName, ctx, pi, { scope });
|
|
189
|
-
return;
|
|
176
|
+
return await installFromUrl(source, fileName, ctx, pi, { scope });
|
|
190
177
|
}
|
|
191
178
|
|
|
192
179
|
const normalized = normalizePackageSource(source);
|
|
@@ -199,7 +186,7 @@ export async function installPackage(
|
|
|
199
186
|
);
|
|
200
187
|
if (!confirmed) {
|
|
201
188
|
notify(ctx, "Installation cancelled.", "info");
|
|
202
|
-
return;
|
|
189
|
+
return { installed: false, reloaded: false };
|
|
203
190
|
}
|
|
204
191
|
|
|
205
192
|
showProgress(ctx, "Installing", normalized);
|
|
@@ -226,7 +213,7 @@ export async function installPackage(
|
|
|
226
213
|
logPackageInstall(pi, normalized, normalized, undefined, scope, false, errorMsg);
|
|
227
214
|
notifyError(ctx, errorMsg);
|
|
228
215
|
void updateExtmgrStatus(ctx, pi);
|
|
229
|
-
return;
|
|
216
|
+
return { installed: false, reloaded: false };
|
|
230
217
|
}
|
|
231
218
|
|
|
232
219
|
clearSearchCache();
|
|
@@ -238,6 +225,26 @@ export async function installPackage(
|
|
|
238
225
|
if (!reloaded) {
|
|
239
226
|
void updateExtmgrStatus(ctx, pi);
|
|
240
227
|
}
|
|
228
|
+
|
|
229
|
+
return { installed: true, reloaded };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function installPackage(
|
|
233
|
+
source: string,
|
|
234
|
+
ctx: ExtensionCommandContext,
|
|
235
|
+
pi: ExtensionAPI,
|
|
236
|
+
options?: InstallOptions
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
await installPackageInternal(source, ctx, pi, options);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function installPackageWithOutcome(
|
|
242
|
+
source: string,
|
|
243
|
+
ctx: ExtensionCommandContext,
|
|
244
|
+
pi: ExtensionAPI,
|
|
245
|
+
options?: InstallOptions
|
|
246
|
+
): Promise<InstallOutcome> {
|
|
247
|
+
return installPackageInternal(source, ctx, pi, options);
|
|
241
248
|
}
|
|
242
249
|
|
|
243
250
|
export async function installFromUrl(
|
|
@@ -246,11 +253,11 @@ export async function installFromUrl(
|
|
|
246
253
|
ctx: ExtensionCommandContext,
|
|
247
254
|
pi: ExtensionAPI,
|
|
248
255
|
options?: InstallOptions
|
|
249
|
-
): Promise<
|
|
256
|
+
): Promise<InstallOutcome> {
|
|
250
257
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
251
258
|
if (!scope) {
|
|
252
259
|
notify(ctx, "Installation cancelled.", "info");
|
|
253
|
-
return;
|
|
260
|
+
return { installed: false, reloaded: false };
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
const extensionDir = getExtensionInstallDir(ctx, scope);
|
|
@@ -263,7 +270,7 @@ export async function installFromUrl(
|
|
|
263
270
|
);
|
|
264
271
|
if (!confirmed) {
|
|
265
272
|
notify(ctx, "Installation cancelled.", "info");
|
|
266
|
-
return;
|
|
273
|
+
return { installed: false, reloaded: false };
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
const result = await tryOperation(
|
|
@@ -289,7 +296,7 @@ export async function installFromUrl(
|
|
|
289
296
|
if (!result) {
|
|
290
297
|
logPackageInstall(pi, url, fileName, undefined, scope, false, "Installation failed");
|
|
291
298
|
void updateExtmgrStatus(ctx, pi);
|
|
292
|
-
return;
|
|
299
|
+
return { installed: false, reloaded: false };
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
const { fileName: name, destPath } = result;
|
|
@@ -300,6 +307,8 @@ export async function installFromUrl(
|
|
|
300
307
|
if (!reloaded) {
|
|
301
308
|
void updateExtmgrStatus(ctx, pi);
|
|
302
309
|
}
|
|
310
|
+
|
|
311
|
+
return { installed: true, reloaded };
|
|
303
312
|
}
|
|
304
313
|
|
|
305
314
|
/**
|
|
@@ -324,16 +333,16 @@ function parsePackageInfo(viewOutput: string): { version: string; tarballUrl: st
|
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
|
|
327
|
-
|
|
336
|
+
async function installPackageLocallyInternal(
|
|
328
337
|
packageName: string,
|
|
329
338
|
ctx: ExtensionCommandContext,
|
|
330
339
|
pi: ExtensionAPI,
|
|
331
340
|
options?: InstallOptions
|
|
332
|
-
): Promise<
|
|
341
|
+
): Promise<InstallOutcome> {
|
|
333
342
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
334
343
|
if (!scope) {
|
|
335
344
|
notify(ctx, "Installation cancelled.", "info");
|
|
336
|
-
return;
|
|
345
|
+
return { installed: false, reloaded: false };
|
|
337
346
|
}
|
|
338
347
|
|
|
339
348
|
const extensionDir = getExtensionInstallDir(ctx, scope);
|
|
@@ -346,7 +355,7 @@ export async function installPackageLocally(
|
|
|
346
355
|
);
|
|
347
356
|
if (!confirmed) {
|
|
348
357
|
notify(ctx, "Installation cancelled.", "info");
|
|
349
|
-
return;
|
|
358
|
+
return { installed: false, reloaded: false };
|
|
350
359
|
}
|
|
351
360
|
|
|
352
361
|
const result = await tryOperation(
|
|
@@ -384,7 +393,7 @@ export async function installPackageLocally(
|
|
|
384
393
|
"Failed to fetch package info"
|
|
385
394
|
);
|
|
386
395
|
void updateExtmgrStatus(ctx, pi);
|
|
387
|
-
return;
|
|
396
|
+
return { installed: false, reloaded: false };
|
|
388
397
|
}
|
|
389
398
|
const { version, tarballUrl } = result;
|
|
390
399
|
|
|
@@ -401,7 +410,7 @@ export async function installPackageLocally(
|
|
|
401
410
|
tarAvailability.error
|
|
402
411
|
);
|
|
403
412
|
void updateExtmgrStatus(ctx, pi);
|
|
404
|
-
return;
|
|
413
|
+
return { installed: false, reloaded: false };
|
|
405
414
|
}
|
|
406
415
|
|
|
407
416
|
// Download and extract
|
|
@@ -439,7 +448,7 @@ export async function installPackageLocally(
|
|
|
439
448
|
"Download failed"
|
|
440
449
|
);
|
|
441
450
|
void updateExtmgrStatus(ctx, pi);
|
|
442
|
-
return;
|
|
451
|
+
return { installed: false, reloaded: false };
|
|
443
452
|
}
|
|
444
453
|
const { tarballPath } = extractResult;
|
|
445
454
|
|
|
@@ -496,7 +505,7 @@ export async function installPackageLocally(
|
|
|
496
505
|
"Extraction failed"
|
|
497
506
|
);
|
|
498
507
|
void updateExtmgrStatus(ctx, pi);
|
|
499
|
-
return;
|
|
508
|
+
return { installed: false, reloaded: false };
|
|
500
509
|
}
|
|
501
510
|
|
|
502
511
|
// Copy to extensions dir
|
|
@@ -527,7 +536,7 @@ export async function installPackageLocally(
|
|
|
527
536
|
"Failed to copy extension"
|
|
528
537
|
);
|
|
529
538
|
void updateExtmgrStatus(ctx, pi);
|
|
530
|
-
return;
|
|
539
|
+
return { installed: false, reloaded: false };
|
|
531
540
|
}
|
|
532
541
|
|
|
533
542
|
clearSearchCache();
|
|
@@ -538,4 +547,24 @@ export async function installPackageLocally(
|
|
|
538
547
|
if (!reloaded) {
|
|
539
548
|
void updateExtmgrStatus(ctx, pi);
|
|
540
549
|
}
|
|
550
|
+
|
|
551
|
+
return { installed: true, reloaded };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export async function installPackageLocally(
|
|
555
|
+
packageName: string,
|
|
556
|
+
ctx: ExtensionCommandContext,
|
|
557
|
+
pi: ExtensionAPI,
|
|
558
|
+
options?: InstallOptions
|
|
559
|
+
): Promise<void> {
|
|
560
|
+
await installPackageLocallyInternal(packageName, ctx, pi, options);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export async function installPackageLocallyWithOutcome(
|
|
564
|
+
packageName: string,
|
|
565
|
+
ctx: ExtensionCommandContext,
|
|
566
|
+
pi: ExtensionAPI,
|
|
567
|
+
options?: InstallOptions
|
|
568
|
+
): Promise<InstallOutcome> {
|
|
569
|
+
return installPackageLocallyInternal(packageName, ctx, pi, options);
|
|
541
570
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { UI } from "../constants.js";
|
|
11
11
|
import { type InstalledPackage } from "../types/index.js";
|
|
12
12
|
import { runTaskWithLoader } from "../ui/async-task.js";
|
|
13
|
+
import { parseChoiceByLabel } from "../utils/command.js";
|
|
13
14
|
import { formatInstalledPackageLabel } from "../utils/format.js";
|
|
14
15
|
import { logPackageRemove, logPackageUpdate } from "../utils/history.js";
|
|
15
16
|
import { requireUI } from "../utils/mode.js";
|
|
@@ -34,17 +35,13 @@ export interface PackageMutationOutcome {
|
|
|
34
35
|
reloaded: boolean;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
const NO_PACKAGE_MUTATION_OUTCOME: PackageMutationOutcome = {
|
|
38
|
-
reloaded: false,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
38
|
const BULK_UPDATE_LABEL = "all packages";
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
39
|
+
const REMOVAL_SCOPE_CHOICES = {
|
|
40
|
+
both: "Both global + project",
|
|
41
|
+
global: "Global only",
|
|
42
|
+
project: "Project only",
|
|
43
|
+
cancel: "Cancel",
|
|
44
|
+
} as const;
|
|
48
45
|
|
|
49
46
|
function getProgressMessage(event: ProgressEvent, fallback: string): string {
|
|
50
47
|
return event.message?.trim() || fallback;
|
|
@@ -70,7 +67,7 @@ async function updatePackageInternal(
|
|
|
70
67
|
logPackageUpdate(pi, source, source, undefined, true);
|
|
71
68
|
clearUpdatesAvailable(pi, ctx, [updateIdentity]);
|
|
72
69
|
void updateExtmgrStatus(ctx, pi);
|
|
73
|
-
return
|
|
70
|
+
return { reloaded: false };
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
await runTaskWithLoader(
|
|
@@ -94,7 +91,7 @@ async function updatePackageInternal(
|
|
|
94
91
|
logPackageUpdate(pi, source, source, undefined, false, errorMsg);
|
|
95
92
|
notifyError(ctx, errorMsg);
|
|
96
93
|
void updateExtmgrStatus(ctx, pi);
|
|
97
|
-
return
|
|
94
|
+
return { reloaded: false };
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
logPackageUpdate(pi, source, source, undefined, true);
|
|
@@ -105,7 +102,7 @@ async function updatePackageInternal(
|
|
|
105
102
|
if (!reloaded) {
|
|
106
103
|
void updateExtmgrStatus(ctx, pi);
|
|
107
104
|
}
|
|
108
|
-
return
|
|
105
|
+
return { reloaded };
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
async function updatePackagesInternal(
|
|
@@ -121,7 +118,7 @@ async function updatePackagesInternal(
|
|
|
121
118
|
logPackageUpdate(pi, BULK_UPDATE_LABEL, BULK_UPDATE_LABEL, undefined, true);
|
|
122
119
|
clearUpdatesAvailable(pi, ctx);
|
|
123
120
|
void updateExtmgrStatus(ctx, pi);
|
|
124
|
-
return
|
|
121
|
+
return { reloaded: false };
|
|
125
122
|
}
|
|
126
123
|
|
|
127
124
|
await runTaskWithLoader(
|
|
@@ -145,7 +142,7 @@ async function updatePackagesInternal(
|
|
|
145
142
|
logPackageUpdate(pi, BULK_UPDATE_LABEL, BULK_UPDATE_LABEL, undefined, false, errorMsg);
|
|
146
143
|
notifyError(ctx, errorMsg);
|
|
147
144
|
void updateExtmgrStatus(ctx, pi);
|
|
148
|
-
return
|
|
145
|
+
return { reloaded: false };
|
|
149
146
|
}
|
|
150
147
|
|
|
151
148
|
logPackageUpdate(pi, BULK_UPDATE_LABEL, BULK_UPDATE_LABEL, undefined, true);
|
|
@@ -156,7 +153,7 @@ async function updatePackagesInternal(
|
|
|
156
153
|
if (!reloaded) {
|
|
157
154
|
void updateExtmgrStatus(ctx, pi);
|
|
158
155
|
}
|
|
159
|
-
return
|
|
156
|
+
return { reloaded };
|
|
160
157
|
}
|
|
161
158
|
|
|
162
159
|
export async function updatePackage(
|
|
@@ -230,25 +227,15 @@ interface RemovalTarget {
|
|
|
230
227
|
name: string;
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
function scopeChoiceFromLabel(choice: string | undefined): RemovalScopeChoice {
|
|
234
|
-
if (!choice || choice === "Cancel") return "cancel";
|
|
235
|
-
if (choice.includes("Both")) return "both";
|
|
236
|
-
if (choice.includes("Global")) return "global";
|
|
237
|
-
if (choice.includes("Project")) return "project";
|
|
238
|
-
return "cancel";
|
|
239
|
-
}
|
|
240
|
-
|
|
241
230
|
async function selectRemovalScope(ctx: ExtensionCommandContext): Promise<RemovalScopeChoice> {
|
|
242
231
|
if (!ctx.hasUI) return "global";
|
|
243
232
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
"
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return scopeChoiceFromLabel(choice);
|
|
233
|
+
return (
|
|
234
|
+
parseChoiceByLabel(
|
|
235
|
+
REMOVAL_SCOPE_CHOICES,
|
|
236
|
+
await ctx.ui.select("Remove scope", Object.values(REMOVAL_SCOPE_CHOICES))
|
|
237
|
+
) ?? "cancel"
|
|
238
|
+
);
|
|
252
239
|
}
|
|
253
240
|
|
|
254
241
|
function buildRemovalTargets(
|
|
@@ -374,18 +361,18 @@ async function removePackageInternal(
|
|
|
374
361
|
|
|
375
362
|
if (scopeChoice === "cancel") {
|
|
376
363
|
notify(ctx, "Removal cancelled.", "info");
|
|
377
|
-
return
|
|
364
|
+
return { reloaded: false };
|
|
378
365
|
}
|
|
379
366
|
|
|
380
367
|
if (matching.length === 0) {
|
|
381
368
|
notify(ctx, `${source} is not installed.`, "info");
|
|
382
|
-
return
|
|
369
|
+
return { reloaded: false };
|
|
383
370
|
}
|
|
384
371
|
|
|
385
372
|
const targets = buildRemovalTargets(matching, ctx.hasUI, scopeChoice);
|
|
386
373
|
if (targets.length === 0) {
|
|
387
374
|
notify(ctx, "Nothing to remove.", "info");
|
|
388
|
-
return
|
|
375
|
+
return { reloaded: false };
|
|
389
376
|
}
|
|
390
377
|
|
|
391
378
|
const confirmed = await confirmAction(
|
|
@@ -396,7 +383,7 @@ async function removePackageInternal(
|
|
|
396
383
|
);
|
|
397
384
|
if (!confirmed) {
|
|
398
385
|
notify(ctx, "Removal cancelled.", "info");
|
|
399
|
-
return
|
|
386
|
+
return { reloaded: false };
|
|
400
387
|
}
|
|
401
388
|
|
|
402
389
|
const results = await executeRemovalTargets(targets, ctx, pi);
|
|
@@ -424,7 +411,7 @@ async function removePackageInternal(
|
|
|
424
411
|
|
|
425
412
|
if (successfulRemovalCount === 0) {
|
|
426
413
|
void updateExtmgrStatus(ctx, pi);
|
|
427
|
-
return
|
|
414
|
+
return { reloaded: false };
|
|
428
415
|
}
|
|
429
416
|
|
|
430
417
|
const reloaded = await confirmReload(ctx, "Removal complete.");
|
|
@@ -432,7 +419,7 @@ async function removePackageInternal(
|
|
|
432
419
|
void updateExtmgrStatus(ctx, pi);
|
|
433
420
|
}
|
|
434
421
|
|
|
435
|
-
return
|
|
422
|
+
return { reloaded };
|
|
436
423
|
}
|
|
437
424
|
|
|
438
425
|
export async function removePackage(
|
package/src/types/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface NpmPackage {
|
|
|
19
19
|
name: string;
|
|
20
20
|
version?: string | undefined;
|
|
21
21
|
description?: string | undefined;
|
|
22
|
+
author?: string | undefined;
|
|
22
23
|
keywords?: string[] | undefined;
|
|
23
24
|
date?: string | undefined;
|
|
24
25
|
size?: number | undefined; // Package size in bytes
|
|
@@ -64,6 +65,7 @@ export interface PackageUnifiedItem {
|
|
|
64
65
|
displayName: string;
|
|
65
66
|
scope: Scope;
|
|
66
67
|
source: string;
|
|
68
|
+
resolvedPath?: string | undefined;
|
|
67
69
|
version?: string | undefined;
|
|
68
70
|
description?: string | undefined;
|
|
69
71
|
size?: number | undefined; // Package size in bytes
|
|
@@ -97,7 +99,7 @@ export type BrowseAction =
|
|
|
97
99
|
| { type: "prev" }
|
|
98
100
|
| { type: "next" }
|
|
99
101
|
| { type: "refresh" }
|
|
102
|
+
| { type: "search"; query: string }
|
|
103
|
+
| { type: "install" }
|
|
100
104
|
| { type: "menu" }
|
|
101
|
-
| { type: "main" }
|
|
102
|
-
| { type: "help" }
|
|
103
105
|
| { type: "cancel" };
|
package/src/ui/footer.ts
CHANGED
|
@@ -4,18 +4,24 @@
|
|
|
4
4
|
import { type State, type UnifiedItem } from "../types/index.js";
|
|
5
5
|
|
|
6
6
|
export interface FooterState {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
selectedType?: UnifiedItem["type"];
|
|
8
|
+
pendingChanges: number;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
export function buildFooterState(
|
|
12
|
+
staged: Map<string, State>,
|
|
13
|
+
byId: Map<string, UnifiedItem>,
|
|
14
|
+
selectedItem?: UnifiedItem
|
|
15
|
+
): FooterState {
|
|
16
|
+
const state: FooterState = {
|
|
17
|
+
pendingChanges: getPendingToggleChangeCount(staged, byId),
|
|
18
18
|
};
|
|
19
|
+
|
|
20
|
+
if (selectedItem) {
|
|
21
|
+
state.selectedType = selectedItem.type;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return state;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export function getPendingToggleChangeCount(
|
|
@@ -37,27 +43,41 @@ export function getPendingToggleChangeCount(
|
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
40
|
-
* Build keyboard shortcuts text for the footer.
|
|
46
|
+
* Build contextual keyboard shortcuts text for the footer.
|
|
41
47
|
*/
|
|
42
48
|
export function buildFooterShortcuts(state: FooterState): string {
|
|
43
49
|
const parts: string[] = [];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
|
|
51
|
+
if (state.selectedType === "local") {
|
|
52
|
+
parts.push("Space toggle");
|
|
53
|
+
parts.push("Enter/A actions");
|
|
54
|
+
parts.push("V details");
|
|
55
|
+
parts.push("X remove");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (state.selectedType === "package") {
|
|
59
|
+
parts.push("Enter/A actions");
|
|
60
|
+
parts.push("V details");
|
|
61
|
+
parts.push("c configure");
|
|
62
|
+
parts.push("u update");
|
|
63
|
+
parts.push("X remove");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (state.pendingChanges > 0) {
|
|
67
|
+
parts.push(`S save (${state.pendingChanges})`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
parts.push("/ search");
|
|
71
|
+
parts.push("Tab filters");
|
|
72
|
+
parts.push("1-5 filters");
|
|
73
|
+
parts.push("i install");
|
|
74
|
+
parts.push("f remote search");
|
|
75
|
+
parts.push("U update all");
|
|
76
|
+
parts.push("t auto-update");
|
|
77
|
+
parts.push("P palette");
|
|
78
|
+
parts.push("R browse");
|
|
79
|
+
parts.push("? help");
|
|
80
|
+
parts.push("Esc clear/cancel");
|
|
81
|
+
|
|
82
|
+
return parts.join(" · ");
|
|
63
83
|
}
|
package/src/ui/help.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Help display
|
|
3
3
|
*/
|
|
4
4
|
import { type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import { notify } from "../utils/notify.js";
|
|
5
6
|
|
|
6
7
|
export function showHelp(ctx: ExtensionCommandContext): void {
|
|
7
8
|
const lines = [
|
|
@@ -9,25 +10,33 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
9
10
|
"",
|
|
10
11
|
"Unified View:",
|
|
11
12
|
" Local extensions and npm/git packages are displayed together",
|
|
13
|
+
" The list is grouped into Local extensions and Installed packages sections",
|
|
14
|
+
" Rows stay compact; details for the selected item appear below the list",
|
|
12
15
|
" Local extensions show ● enabled / ○ disabled with G/P scope",
|
|
13
|
-
" Packages show
|
|
16
|
+
" Packages show a source-type icon with name@version, scope, and size when known",
|
|
14
17
|
"",
|
|
15
18
|
"Navigation:",
|
|
16
19
|
" ↑↓ Navigate list",
|
|
17
|
-
"
|
|
20
|
+
" PageUp/Down Jump through longer lists",
|
|
21
|
+
" Home/End Jump to top or bottom",
|
|
22
|
+
" Space Toggle selected local extension enabled/disabled",
|
|
18
23
|
" S Save changes to local extensions",
|
|
19
|
-
" Enter/A Open actions for selected
|
|
24
|
+
" Enter/A Open actions for the selected item",
|
|
25
|
+
" / or Ctrl+F Search visible items",
|
|
26
|
+
" Tab/Shift+Tab Cycle filters",
|
|
27
|
+
" 1-5 Quick filters: All / Local / Packages / Updates / Disabled",
|
|
20
28
|
" c Configure selected package extensions (reload after save)",
|
|
21
29
|
" u Update selected package",
|
|
30
|
+
" V View full details for the selected item",
|
|
22
31
|
" X Remove selected item (package or local extension)",
|
|
23
32
|
" i Quick install by source",
|
|
24
|
-
" f
|
|
33
|
+
" f Remote package search",
|
|
25
34
|
" U Update all packages",
|
|
26
35
|
" t Auto-update wizard",
|
|
27
36
|
" P/M Quick actions palette",
|
|
28
37
|
" R Browse remote packages",
|
|
29
38
|
" ?/H Show this help",
|
|
30
|
-
" Esc
|
|
39
|
+
" Esc Clear search or cancel",
|
|
31
40
|
"",
|
|
32
41
|
"Extension Sources:",
|
|
33
42
|
" - ~/.pi/agent/extensions/ (global - G)",
|
|
@@ -49,10 +58,5 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
49
58
|
" /extensions auto-update Show or change update schedule",
|
|
50
59
|
];
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
if (ctx.hasUI) {
|
|
54
|
-
ctx.ui.notify(output, "info");
|
|
55
|
-
} else {
|
|
56
|
-
console.log(output);
|
|
57
|
-
}
|
|
61
|
+
notify(ctx, lines.join("\n"), "info");
|
|
58
62
|
}
|