pi-extmgr 0.1.28 → 0.2.1

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.
@@ -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
- function packageMutationOutcome(
44
- overrides: Partial<PackageMutationOutcome>
45
- ): PackageMutationOutcome {
46
- return { ...NO_PACKAGE_MUTATION_OUTCOME, ...overrides };
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 NO_PACKAGE_MUTATION_OUTCOME;
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 NO_PACKAGE_MUTATION_OUTCOME;
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 packageMutationOutcome({ reloaded });
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 NO_PACKAGE_MUTATION_OUTCOME;
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 NO_PACKAGE_MUTATION_OUTCOME;
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 packageMutationOutcome({ reloaded });
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
- const choice = await ctx.ui.select("Remove scope", [
245
- "Both global + project",
246
- "Global only",
247
- "Project only",
248
- "Cancel",
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 NO_PACKAGE_MUTATION_OUTCOME;
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 NO_PACKAGE_MUTATION_OUTCOME;
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 NO_PACKAGE_MUTATION_OUTCOME;
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 NO_PACKAGE_MUTATION_OUTCOME;
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 NO_PACKAGE_MUTATION_OUTCOME;
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 packageMutationOutcome({ reloaded });
422
+ return { reloaded };
436
423
  }
437
424
 
438
425
  export async function removePackage(
@@ -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
- hasLocals: boolean;
8
- hasPackages: boolean;
7
+ selectedType?: UnifiedItem["type"];
8
+ pendingChanges: number;
9
9
  }
10
10
 
11
- /**
12
- * Build footer state from visible items.
13
- */
14
- export function buildFooterState(items: UnifiedItem[]): FooterState {
15
- return {
16
- hasLocals: items.some((i) => i.type === "local"),
17
- hasPackages: items.some((i) => i.type === "package"),
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
- parts.push("↑↓ Navigate");
45
-
46
- if (state.hasLocals) parts.push("Space/Enter Toggle");
47
- if (state.hasLocals) parts.push("S Save");
48
- if (state.hasPackages) parts.push("Enter/A Actions");
49
- if (state.hasPackages) parts.push("c Configure");
50
- if (state.hasPackages) parts.push("u Update");
51
- if (state.hasPackages || state.hasLocals) parts.push("X Remove");
52
-
53
- parts.push("i Install");
54
- parts.push("f Search");
55
- parts.push("U Update all");
56
- parts.push("t Auto-update");
57
- parts.push("P Palette");
58
- parts.push("R Browse");
59
- parts.push("? Help");
60
- parts.push("Esc Cancel");
61
-
62
- return parts.join(" | ");
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 📦 with name@version and G/P scope",
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
- " Space/Enter Toggle local extension enabled/disabled",
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 package",
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 Quick search",
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 Cancel",
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
- const output = lines.join("\n");
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
  }