pi-extmgr 0.1.23 → 0.1.24
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 +25 -12
- package/package.json +1 -1
- package/src/commands/auto-update.ts +1 -1
- package/src/commands/cache.ts +5 -1
- package/src/commands/history.ts +4 -2
- package/src/commands/registry.ts +1 -1
- package/src/packages/discovery.ts +45 -29
- package/src/packages/extensions.ts +171 -63
- package/src/packages/install.ts +116 -22
- package/src/packages/management.ts +12 -37
- package/src/ui/package-config.ts +157 -126
- package/src/ui/remote.ts +77 -52
- package/src/ui/unified.ts +217 -172
- package/src/utils/auto-update.ts +34 -29
- package/src/utils/history.ts +41 -2
- package/src/utils/mode.ts +56 -5
- package/src/utils/package-source.ts +31 -0
- package/src/utils/settings-list.ts +12 -0
- package/src/utils/settings.ts +35 -7
- package/src/utils/status.ts +10 -2
- package/src/utils/timer.ts +32 -8
- package/src/utils/ui-helpers.ts +2 -1
package/src/ui/remote.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
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
|
+
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
20
21
|
|
|
21
22
|
interface PackageInfoCacheEntry {
|
|
22
23
|
timestamp: number;
|
|
@@ -65,8 +66,9 @@ class PackageInfoCache {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
set(name: string, entry: Omit<PackageInfoCacheEntry, "timestamp">): void {
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
if (this.cache.has(name)) {
|
|
70
|
+
this.cache.delete(name);
|
|
71
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
70
72
|
const firstKey = this.cache.keys().next().value;
|
|
71
73
|
if (firstKey) {
|
|
72
74
|
this.cache.delete(firstKey);
|
|
@@ -82,10 +84,6 @@ class PackageInfoCache {
|
|
|
82
84
|
clear(): void {
|
|
83
85
|
this.cache.clear();
|
|
84
86
|
}
|
|
85
|
-
|
|
86
|
-
get size(): number {
|
|
87
|
-
return this.cache.size;
|
|
88
|
-
}
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
// Global LRU cache instance
|
|
@@ -94,6 +92,10 @@ const packageInfoCache = new PackageInfoCache(
|
|
|
94
92
|
CACHE_LIMITS.packageInfoTTL
|
|
95
93
|
);
|
|
96
94
|
|
|
95
|
+
export function clearRemotePackageInfoCache(): void {
|
|
96
|
+
packageInfoCache.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
97
99
|
const REMOTE_MENU_CHOICES = {
|
|
98
100
|
browse: "🔍 Browse pi packages",
|
|
99
101
|
search: "🔎 Search packages",
|
|
@@ -269,50 +271,63 @@ async function selectBrowseAction(
|
|
|
269
271
|
items.push({ value: "nav:refresh", label: "🔄 Refresh search" });
|
|
270
272
|
items.push({ value: "nav:menu", label: "← Back to menu" });
|
|
271
273
|
|
|
272
|
-
return ctx
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
274
|
+
return runCustomUI(ctx, "Remote package browsing", () =>
|
|
275
|
+
ctx.ui.custom<BrowseAction>((tui, theme, _keybindings, done) => {
|
|
276
|
+
const container = new Container();
|
|
277
|
+
const title = new Text("", 1, 0);
|
|
278
|
+
const footer = new Text("", 1, 0);
|
|
279
|
+
const syncThemedContent = (): void => {
|
|
280
|
+
title.setText(theme.fg("accent", theme.bold(titleText)));
|
|
281
|
+
footer.setText(theme.fg("dim", "↑↓ wraps • enter select • esc cancel"));
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
285
|
+
container.addChild(title);
|
|
286
|
+
|
|
287
|
+
const selectList = new SelectList(items, Math.min(items.length, 12), {
|
|
288
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
289
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
290
|
+
description: (t) => theme.fg("muted", t),
|
|
291
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
292
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
selectList.onSelect = (item) => {
|
|
296
|
+
if (item.value === "nav:prev") {
|
|
297
|
+
done({ type: "prev" });
|
|
298
|
+
} else if (item.value === "nav:next") {
|
|
299
|
+
done({ type: "next" });
|
|
300
|
+
} else if (item.value === "nav:refresh") {
|
|
301
|
+
done({ type: "refresh" });
|
|
302
|
+
} else if (item.value === "nav:menu") {
|
|
303
|
+
done({ type: "menu" });
|
|
304
|
+
} else if (item.value.startsWith("pkg:")) {
|
|
305
|
+
done({ type: "package", name: item.value.slice(4) });
|
|
306
|
+
} else {
|
|
307
|
+
done({ type: "cancel" });
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
selectList.onCancel = () => done({ type: "cancel" });
|
|
312
|
+
|
|
313
|
+
syncThemedContent();
|
|
314
|
+
container.addChild(selectList);
|
|
315
|
+
container.addChild(footer);
|
|
316
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
render: (w: number) => container.render(w),
|
|
320
|
+
invalidate: () => {
|
|
321
|
+
container.invalidate();
|
|
322
|
+
syncThemedContent();
|
|
323
|
+
},
|
|
324
|
+
handleInput: (data: string) => {
|
|
325
|
+
selectList.handleInput(data);
|
|
326
|
+
tui.requestRender();
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
})
|
|
330
|
+
);
|
|
316
331
|
}
|
|
317
332
|
|
|
318
333
|
export async function browseRemotePackages(
|
|
@@ -321,6 +336,16 @@ export async function browseRemotePackages(
|
|
|
321
336
|
pi: ExtensionAPI,
|
|
322
337
|
offset = 0
|
|
323
338
|
): Promise<void> {
|
|
339
|
+
if (
|
|
340
|
+
!requireCustomUI(
|
|
341
|
+
ctx,
|
|
342
|
+
"Remote package browsing",
|
|
343
|
+
"Use `/extensions install <source>` to install directly outside the full interactive TUI."
|
|
344
|
+
)
|
|
345
|
+
) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
324
349
|
// Check cache first
|
|
325
350
|
let allPackages: NpmPackage[] = [];
|
|
326
351
|
|
|
@@ -375,8 +400,8 @@ export async function browseRemotePackages(
|
|
|
375
400
|
showLoadMore
|
|
376
401
|
);
|
|
377
402
|
|
|
378
|
-
if (!result) {
|
|
379
|
-
return;
|
|
403
|
+
if (!result || result.type === "cancel") {
|
|
404
|
+
return;
|
|
380
405
|
}
|
|
381
406
|
|
|
382
407
|
// Handle result
|
package/src/ui/unified.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
updatePackageWithOutcome,
|
|
26
26
|
removePackageWithOutcome,
|
|
27
27
|
updatePackagesWithOutcome,
|
|
28
|
+
showInstalledPackagesList,
|
|
28
29
|
} from "../packages/management.js";
|
|
29
30
|
import { showRemote } from "./remote.js";
|
|
30
31
|
import { showHelp } from "./help.js";
|
|
@@ -37,38 +38,39 @@ import {
|
|
|
37
38
|
formatSize,
|
|
38
39
|
} from "./theme.js";
|
|
39
40
|
import { buildFooterState, buildFooterShortcuts, getPendingToggleChangeCount } from "./footer.js";
|
|
40
|
-
import { logExtensionToggle } from "../utils/history.js";
|
|
41
|
+
import { logExtensionDelete, logExtensionToggle } from "../utils/history.js";
|
|
41
42
|
import { getKnownUpdates, promptAutoUpdateWizard } from "../utils/auto-update.js";
|
|
42
43
|
import { updateExtmgrStatus } from "../utils/status.js";
|
|
43
44
|
import { parseChoiceByLabel } from "../utils/command.js";
|
|
44
|
-
import {
|
|
45
|
+
import { notify } from "../utils/notify.js";
|
|
46
|
+
import { getPackageSourceKind, normalizePackageIdentity } from "../utils/package-source.js";
|
|
47
|
+
import { hasCustomUI, runCustomUI } from "../utils/mode.js";
|
|
48
|
+
import { getSettingsListSelectedIndex } from "../utils/settings-list.js";
|
|
45
49
|
import { UI } from "../constants.js";
|
|
46
50
|
import { configurePackageExtensions } from "./package-config.js";
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Safely gets the selected index from a SettingsList component
|
|
56
|
-
* Returns undefined if the component doesn't have the expected interface
|
|
57
|
-
*/
|
|
58
|
-
function getSelectedIndex(settingsList: unknown): number | undefined {
|
|
59
|
-
if (settingsList && typeof settingsList === "object") {
|
|
60
|
-
const selectable = settingsList as SelectableList;
|
|
61
|
-
if (typeof selectable.selectedIndex === "number") {
|
|
62
|
-
return selectable.selectedIndex;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return undefined;
|
|
52
|
+
async function showInteractiveFallback(
|
|
53
|
+
ctx: ExtensionCommandContext,
|
|
54
|
+
pi: ExtensionAPI
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
await showListOnly(ctx);
|
|
57
|
+
await showInstalledPackagesList(ctx, pi);
|
|
66
58
|
}
|
|
67
59
|
|
|
68
60
|
export async function showInteractive(
|
|
69
61
|
ctx: ExtensionCommandContext,
|
|
70
62
|
pi: ExtensionAPI
|
|
71
63
|
): Promise<void> {
|
|
64
|
+
if (!hasCustomUI(ctx)) {
|
|
65
|
+
notify(
|
|
66
|
+
ctx,
|
|
67
|
+
"The unified extensions manager requires the full interactive TUI. Showing read-only local and installed package lists instead.",
|
|
68
|
+
"warning"
|
|
69
|
+
);
|
|
70
|
+
await showInteractiveFallback(ctx, pi);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
72
74
|
// Main loop - keeps showing the menu until user explicitly exits
|
|
73
75
|
while (true) {
|
|
74
76
|
const shouldExit = await showInteractiveOnce(ctx, pi);
|
|
@@ -108,156 +110,192 @@ async function showInteractiveOnce(
|
|
|
108
110
|
const staged = new Map<string, State>();
|
|
109
111
|
const byId = new Map(items.map((item) => [item.id, item]));
|
|
110
112
|
|
|
111
|
-
const result = await
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
container.addChild(settingsList);
|
|
162
|
-
container.addChild(new Spacer(1));
|
|
163
|
-
|
|
164
|
-
// Footer with keyboard shortcuts
|
|
165
|
-
const footerState = buildFooterState(items);
|
|
166
|
-
container.addChild(new Text(theme.fg("dim", buildFooterShortcuts(footerState)), 2, 0));
|
|
167
|
-
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
render(width: number) {
|
|
171
|
-
return container.render(width);
|
|
172
|
-
},
|
|
173
|
-
invalidate() {
|
|
174
|
-
container.invalidate();
|
|
175
|
-
},
|
|
176
|
-
handleInput(data: string) {
|
|
177
|
-
const selIdx = getSelectedIndex(settingsList) ?? 0;
|
|
178
|
-
const selectedId = settingsItems[selIdx]?.id ?? settingsItems[0]?.id;
|
|
179
|
-
const selectedItem = selectedId ? byId.get(selectedId) : undefined;
|
|
180
|
-
|
|
181
|
-
if (matchesKey(data, Key.ctrl("s")) || data === "s" || data === "S") {
|
|
182
|
-
done({ type: "apply" });
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Enter on a package opens its action menu (fewer clicks)
|
|
187
|
-
if ((data === "\r" || data === "\n") && selectedId && selectedItem?.type === "package") {
|
|
188
|
-
done({ type: "action", itemId: selectedId, action: "menu" });
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (data === "a" || data === "A") {
|
|
193
|
-
if (selectedId) {
|
|
194
|
-
done({ type: "action", itemId: selectedId, action: "menu" });
|
|
195
|
-
}
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Quick actions (global)
|
|
200
|
-
if (data === "i") {
|
|
201
|
-
done({ type: "quick", action: "install" });
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (data === "f") {
|
|
205
|
-
done({ type: "quick", action: "search" });
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (data === "U") {
|
|
209
|
-
done({ type: "quick", action: "update-all" });
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (data === "t" || data === "T") {
|
|
213
|
-
done({ type: "quick", action: "auto-update" });
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Fast actions on selected row
|
|
218
|
-
if (selectedId && selectedItem?.type === "package") {
|
|
219
|
-
if (data === "u") {
|
|
220
|
-
done({ type: "action", itemId: selectedId, action: "update" });
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
if (data === "x" || data === "X") {
|
|
224
|
-
done({ type: "action", itemId: selectedId, action: "remove" });
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (data === "v" || data === "V") {
|
|
228
|
-
done({ type: "action", itemId: selectedId, action: "details" });
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (data === "c" || data === "C") {
|
|
232
|
-
done({ type: "action", itemId: selectedId, action: "configure" });
|
|
233
|
-
return;
|
|
113
|
+
const result = await runCustomUI(
|
|
114
|
+
ctx,
|
|
115
|
+
"The unified extensions manager",
|
|
116
|
+
() =>
|
|
117
|
+
ctx.ui.custom<UnifiedAction>((tui, theme, _keybindings, done) => {
|
|
118
|
+
const container = new Container();
|
|
119
|
+
|
|
120
|
+
const titleText = new Text("", 2, 0);
|
|
121
|
+
const subtitleText = new Text("", 2, 0);
|
|
122
|
+
const quickText = new Text("", 2, 0);
|
|
123
|
+
const footerState = buildFooterState(items);
|
|
124
|
+
const footerText = new Text("", 2, 0);
|
|
125
|
+
|
|
126
|
+
// Header
|
|
127
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
128
|
+
container.addChild(titleText);
|
|
129
|
+
container.addChild(subtitleText);
|
|
130
|
+
container.addChild(quickText);
|
|
131
|
+
container.addChild(new Spacer(1));
|
|
132
|
+
|
|
133
|
+
// Build settings items
|
|
134
|
+
const settingsItems = buildSettingsItems(items, staged, theme);
|
|
135
|
+
const syncThemedContent = (): void => {
|
|
136
|
+
titleText.setText(theme.fg("accent", theme.bold("Extensions Manager")));
|
|
137
|
+
subtitleText.setText(
|
|
138
|
+
theme.fg(
|
|
139
|
+
"muted",
|
|
140
|
+
`${items.length} item${items.length === 1 ? "" : "s"} • Space/Enter toggle local • Enter/A actions • c configure pkg extensions • u update pkg • x remove selected`
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
quickText.setText(
|
|
144
|
+
theme.fg(
|
|
145
|
+
"dim",
|
|
146
|
+
"Quick: i Install | f Search | U Update all | t Auto-update | p Palette"
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
footerText.setText(theme.fg("dim", buildFooterShortcuts(footerState)));
|
|
150
|
+
|
|
151
|
+
for (const settingsItem of settingsItems) {
|
|
152
|
+
const item = byId.get(settingsItem.id);
|
|
153
|
+
if (!item) continue;
|
|
154
|
+
|
|
155
|
+
if (item.type === "local") {
|
|
156
|
+
const currentState = staged.get(item.id) ?? item.state!;
|
|
157
|
+
const changed = staged.has(item.id) && currentState !== item.originalState;
|
|
158
|
+
settingsItem.label = formatUnifiedItemLabel(item, currentState, theme, changed);
|
|
159
|
+
} else {
|
|
160
|
+
settingsItem.label = formatUnifiedItemLabel(item, "enabled", theme, false);
|
|
161
|
+
}
|
|
234
162
|
}
|
|
235
|
-
}
|
|
163
|
+
};
|
|
164
|
+
syncThemedContent();
|
|
165
|
+
|
|
166
|
+
const settingsList = new SettingsList(
|
|
167
|
+
settingsItems,
|
|
168
|
+
Math.min(items.length + 2, UI.maxListHeight),
|
|
169
|
+
getSettingsListTheme(),
|
|
170
|
+
(id: string, newValue: string) => {
|
|
171
|
+
const item = byId.get(id);
|
|
172
|
+
if (!item || item.type !== "local") return;
|
|
173
|
+
|
|
174
|
+
const state = newValue as State;
|
|
175
|
+
staged.set(id, state);
|
|
176
|
+
|
|
177
|
+
const settingsItem = settingsItems.find((x) => x.id === id);
|
|
178
|
+
if (settingsItem) {
|
|
179
|
+
const changed = state !== item.originalState;
|
|
180
|
+
settingsItem.label = formatUnifiedItemLabel(item, state, theme, changed);
|
|
181
|
+
}
|
|
182
|
+
tui.requestRender();
|
|
183
|
+
},
|
|
184
|
+
() => done({ type: "cancel" }),
|
|
185
|
+
{ enableSearch: items.length > UI.searchThreshold }
|
|
186
|
+
);
|
|
236
187
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
188
|
+
container.addChild(settingsList);
|
|
189
|
+
container.addChild(new Spacer(1));
|
|
190
|
+
|
|
191
|
+
// Footer with keyboard shortcuts
|
|
192
|
+
container.addChild(footerText);
|
|
193
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
render(width: number) {
|
|
197
|
+
return container.render(width);
|
|
198
|
+
},
|
|
199
|
+
invalidate() {
|
|
200
|
+
container.invalidate();
|
|
201
|
+
syncThemedContent();
|
|
202
|
+
},
|
|
203
|
+
handleInput(data: string) {
|
|
204
|
+
const selIdx = getSettingsListSelectedIndex(settingsList) ?? 0;
|
|
205
|
+
const selectedId = settingsItems[selIdx]?.id ?? settingsItems[0]?.id;
|
|
206
|
+
const selectedItem = selectedId ? byId.get(selectedId) : undefined;
|
|
207
|
+
|
|
208
|
+
if (matchesKey(data, Key.ctrl("s")) || data === "s" || data === "S") {
|
|
209
|
+
done({ type: "apply" });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Enter on a package opens its action menu (fewer clicks)
|
|
214
|
+
if (
|
|
215
|
+
(data === "\r" || data === "\n") &&
|
|
216
|
+
selectedId &&
|
|
217
|
+
selectedItem?.type === "package"
|
|
218
|
+
) {
|
|
219
|
+
done({ type: "action", itemId: selectedId, action: "menu" });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (data === "a" || data === "A") {
|
|
224
|
+
if (selectedId) {
|
|
225
|
+
done({ type: "action", itemId: selectedId, action: "menu" });
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Quick actions (global)
|
|
231
|
+
if (data === "i") {
|
|
232
|
+
done({ type: "quick", action: "install" });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (data === "f") {
|
|
236
|
+
done({ type: "quick", action: "search" });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (data === "U") {
|
|
240
|
+
done({ type: "quick", action: "update-all" });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (data === "t" || data === "T") {
|
|
244
|
+
done({ type: "quick", action: "auto-update" });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Fast actions on selected row
|
|
249
|
+
if (selectedId && selectedItem?.type === "package") {
|
|
250
|
+
if (data === "u") {
|
|
251
|
+
done({ type: "action", itemId: selectedId, action: "update" });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (data === "x" || data === "X") {
|
|
255
|
+
done({ type: "action", itemId: selectedId, action: "remove" });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (data === "v" || data === "V") {
|
|
259
|
+
done({ type: "action", itemId: selectedId, action: "details" });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (data === "c" || data === "C") {
|
|
263
|
+
done({ type: "action", itemId: selectedId, action: "configure" });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (selectedId && selectedItem?.type === "local") {
|
|
269
|
+
if (data === "x" || data === "X") {
|
|
270
|
+
done({ type: "action", itemId: selectedId, action: "remove" });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (data === "r" || data === "R") {
|
|
276
|
+
done({ type: "remote" });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (data === "?" || data === "h" || data === "H") {
|
|
280
|
+
done({ type: "help" });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (data === "m" || data === "M" || data === "p" || data === "P") {
|
|
284
|
+
done({ type: "menu" });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
settingsList.handleInput?.(data);
|
|
288
|
+
tui.requestRender();
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}),
|
|
292
|
+
"Showing read-only local and installed package lists instead."
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if (!result) {
|
|
296
|
+
await showInteractiveFallback(ctx, pi);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
261
299
|
|
|
262
300
|
return await handleUnifiedAction(result, items, staged, byId, ctx, pi);
|
|
263
301
|
}
|
|
@@ -332,7 +370,7 @@ export function buildUnifiedItems(
|
|
|
332
370
|
version: pkg.version,
|
|
333
371
|
description: pkg.description,
|
|
334
372
|
size: pkg.size,
|
|
335
|
-
updateAvailable: knownUpdates.has(pkg.
|
|
373
|
+
updateAvailable: knownUpdates.has(normalizePackageIdentity(pkg.source)),
|
|
336
374
|
});
|
|
337
375
|
}
|
|
338
376
|
|
|
@@ -720,10 +758,12 @@ async function handleUnifiedAction(
|
|
|
720
758
|
ctx.cwd
|
|
721
759
|
);
|
|
722
760
|
if (!removal.ok) {
|
|
761
|
+
logExtensionDelete(pi, item.id, false, removal.error);
|
|
723
762
|
ctx.ui.notify(`Failed to remove extension: ${removal.error}`, "error");
|
|
724
763
|
return false;
|
|
725
764
|
}
|
|
726
765
|
|
|
766
|
+
logExtensionDelete(pi, item.id, true);
|
|
727
767
|
ctx.ui.notify(
|
|
728
768
|
`Removed ${item.displayName}${removal.removedDirectory ? " (directory)" : ""}.`,
|
|
729
769
|
"info"
|
|
@@ -825,12 +865,15 @@ export async function showInstalledPackagesLegacy(
|
|
|
825
865
|
ctx: ExtensionCommandContext,
|
|
826
866
|
pi: ExtensionAPI
|
|
827
867
|
): Promise<void> {
|
|
868
|
+
if (!hasCustomUI(ctx)) {
|
|
869
|
+
await showInstalledPackagesList(ctx, pi);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
|
|
828
873
|
ctx.ui.notify(
|
|
829
874
|
"📦 Use /extensions for the unified view.\nInstalled packages are now shown alongside local extensions.",
|
|
830
875
|
"info"
|
|
831
876
|
);
|
|
832
|
-
// Small delay then open the main manager
|
|
833
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
834
877
|
await showInteractive(ctx, pi);
|
|
835
878
|
}
|
|
836
879
|
|
|
@@ -849,10 +892,12 @@ export async function showListOnly(ctx: ExtensionCommandContext): Promise<void>
|
|
|
849
892
|
|
|
850
893
|
const lines = entries.map(formatExtEntry);
|
|
851
894
|
const output = lines.join("\n");
|
|
895
|
+
const titledOutput = `Local extensions:\n${output}`;
|
|
852
896
|
|
|
853
897
|
if (ctx.hasUI) {
|
|
854
|
-
ctx.ui.notify(
|
|
898
|
+
ctx.ui.notify(titledOutput, "info");
|
|
855
899
|
} else {
|
|
900
|
+
console.log("Local extensions:");
|
|
856
901
|
console.log(output);
|
|
857
902
|
}
|
|
858
903
|
}
|