@danypops/pi-packed 0.9.0 → 0.11.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 +3 -3
- package/extension/src/discover.ts +3 -2
- package/extension/src/resource-config.ts +3 -2
- package/extension/src/tui.ts +166 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ The extension connects to Packed's authenticated user daemon and starts the pack
|
|
|
10
10
|
|
|
11
11
|
## Commands
|
|
12
12
|
|
|
13
|
-
- `/packed` --
|
|
14
|
-
- `u` / `U` -- update the selected package / update every outdated package, one combined confirmation and one reload.
|
|
13
|
+
- `/packed` -- a floating overlay panel: every installed Pi package, with update availability. Mnemonics follow lazy.nvim's own convention (uppercase acts on every row, lowercase on the one under the cursor):
|
|
14
|
+
- `u` / `U` -- update the selected package / update every outdated package, one combined confirmation and one reload. `U`'s progress bar renders inline on this same panel, not a separate popup.
|
|
15
15
|
- `x` -- remove the selected package.
|
|
16
16
|
- `d` -- disable (or re-enable) the selected package's own extensions.
|
|
17
17
|
- `c` -- jump to resource config for the selected package (skills, prompts, themes).
|
|
18
18
|
- `f` -- find: search the npm registry for new Pi packages and install one.
|
|
19
|
-
- `s` -- settings. `Enter` opens a floating action menu with all of the above. `r` refreshes, `/` filters, `Tab` cycles view modes.
|
|
19
|
+
- `s` -- settings. `Enter` opens a smaller floating action menu with all of the above. `r` refreshes, `/` filters, `Tab` cycles view modes.
|
|
20
20
|
- `/packed config` -- enable or disable individual extensions, skills, prompt templates, and themes declared by installed packages, per package, at global or project scope.
|
|
21
21
|
- `/packed setup plan [path] [--prune]` / `/packed setup apply [path] [--prune]` -- preview or apply a reproducible-environment manifest (`pi-setup.json`) against installed packages and profiles.
|
|
22
22
|
- `/profile [name]` -- switch to a named Pi profile (provider, model, tools, instructions, theme), reading `~/.pi/agent/profiles.json` and a trusted project's own `.pi/profiles.json`. `pi --profile <name>` activates one at startup; `Ctrl+Shift+U` cycles through them.
|
|
@@ -149,15 +149,16 @@ function renderDiscoverPanel(ctx: ExtensionCommandContext, natives: Natives): Pr
|
|
|
149
149
|
},
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
+
const border = () => new DynamicBorder((s) => theme.fg("border", s));
|
|
152
153
|
const container = new Container();
|
|
153
154
|
container.addChild(new Spacer(1));
|
|
154
|
-
container.addChild(
|
|
155
|
+
container.addChild(border());
|
|
155
156
|
container.addChild(new Spacer(1));
|
|
156
157
|
container.addChild(header);
|
|
157
158
|
container.addChild(new Spacer(1));
|
|
158
159
|
container.addChild(list);
|
|
159
160
|
container.addChild(new Spacer(1));
|
|
160
|
-
container.addChild(
|
|
161
|
+
container.addChild(border());
|
|
161
162
|
|
|
162
163
|
return {
|
|
163
164
|
render: (width: number) => container.render(width),
|
|
@@ -190,15 +190,16 @@ function renderConfig(ctx: ExtensionCommandContext, data: { global: PackageResou
|
|
|
190
190
|
},
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
+
const border = () => new DynamicBorder((s) => theme.fg("border", s));
|
|
193
194
|
const container = new Container();
|
|
194
195
|
container.addChild(new Spacer(1));
|
|
195
|
-
container.addChild(
|
|
196
|
+
container.addChild(border());
|
|
196
197
|
container.addChild(new Spacer(1));
|
|
197
198
|
container.addChild(header);
|
|
198
199
|
container.addChild(new Spacer(1));
|
|
199
200
|
container.addChild(list);
|
|
200
201
|
container.addChild(new Spacer(1));
|
|
201
|
-
container.addChild(
|
|
202
|
+
container.addChild(border());
|
|
202
203
|
|
|
203
204
|
return {
|
|
204
205
|
render: (width: number) => container.render(width),
|
package/extension/src/tui.ts
CHANGED
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
* only (x remove -- there is no safe "remove every installed package"
|
|
7
7
|
* bulk analog, so no X is bound). Adds d disable/enable this package's
|
|
8
8
|
* extensions, c jump to full resource config, f find/install new
|
|
9
|
-
* packages, s settings.
|
|
10
|
-
* overlay:true)
|
|
11
|
-
*
|
|
9
|
+
* packages, s settings. The panel itself is a floating overlay
|
|
10
|
+
* (`ctx.ui.custom` with overlay:true), and Enter opens a second, smaller
|
|
11
|
+
* overlay action menu on top of it. U's batch update renders its progress
|
|
12
|
+
* bar inline on this same overlay instead of opening a separate one --
|
|
13
|
+
* the list flips to a progress view, then back (or closes, if a reload
|
|
14
|
+
* already replaced the session). All data flows through the packed CLI
|
|
12
15
|
* (thin seam).
|
|
13
16
|
*/
|
|
14
17
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
15
18
|
import { DynamicBorder, rawKeyHint } from "@earendil-works/pi-coding-agent";
|
|
16
19
|
import { Container, Input, Spacer, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
17
|
-
import { Menu, type MenuItem } from "malevich-tui-components";
|
|
20
|
+
import { Menu, ProgressBar, type MenuItem } from "malevich-tui-components";
|
|
18
21
|
import { filterRows, mergeRows, nextMode, visibleRows } from "./model.js";
|
|
19
22
|
import type { Row, ViewMode } from "./model.js";
|
|
20
23
|
import type { Natives, PackageResources } from "./packed.js";
|
|
@@ -25,7 +28,7 @@ import { showDiscoverPanel } from "./discover.js";
|
|
|
25
28
|
import { menuTheme } from "./menu-theme.js";
|
|
26
29
|
|
|
27
30
|
interface PanelAction {
|
|
28
|
-
type: "update" | "
|
|
31
|
+
type: "update" | "remove" | "disable" | "config" | "find" | "refresh" | "settings";
|
|
29
32
|
row?: Row;
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -86,11 +89,45 @@ export async function applyPackageChoice(
|
|
|
86
89
|
return "cancelled";
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
interface UpdateAllResult { changed: number; failedNames: string[]; }
|
|
93
|
+
|
|
94
|
+
interface UpdateProgressEvent { row: Row; index: number; total: number; phase: "start" | "done"; }
|
|
95
|
+
|
|
96
|
+
/** The core sequential-update loop, with no UI of its own -- reports each
|
|
97
|
+
* step via onProgress so any host surface (a floating overlay, or the
|
|
98
|
+
* packages panel's own body) can render it however fits. */
|
|
99
|
+
async function performUpdateAll(
|
|
100
|
+
outdated: Row[],
|
|
101
|
+
natives: Natives,
|
|
102
|
+
approved: boolean | undefined,
|
|
103
|
+
onProgress?: (event: UpdateProgressEvent) => void,
|
|
104
|
+
): Promise<UpdateAllResult> {
|
|
105
|
+
let changed = 0;
|
|
106
|
+
const failedNames: string[] = [];
|
|
107
|
+
for (let i = 0; i < outdated.length; i++) {
|
|
108
|
+
const row = outdated[i]!;
|
|
109
|
+
onProgress?.({ row, index: i, total: outdated.length, phase: "start" });
|
|
110
|
+
try {
|
|
111
|
+
const outcome = await natives.update(`npm:${row.name}`, approved);
|
|
112
|
+
if (outcome.reloadRequired) changed += 1;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
failedNames.push(`${row.name}: ${e instanceof Error ? e.message : e}`);
|
|
115
|
+
}
|
|
116
|
+
onProgress?.({ row, index: i, total: outdated.length, phase: "done" });
|
|
117
|
+
}
|
|
118
|
+
return { changed, failedNames };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Approves once for the whole batch, runs it via whatever runBatch does
|
|
122
|
+
* (a floating overlay for applyUpdateAll's own public API, or renderPanel's
|
|
123
|
+
* embedded progress bar), then reports the combined result -- shared so
|
|
124
|
+
* both surfaces stay behaviorally identical. */
|
|
125
|
+
async function approveAndRunUpdateAll(
|
|
126
|
+
outdated: Row[],
|
|
127
|
+
natives: Natives,
|
|
128
|
+
ctx: ExtensionCommandContext,
|
|
129
|
+
runBatch: (outdated: Row[], approved: boolean | undefined) => Promise<UpdateAllResult>,
|
|
130
|
+
): Promise<PackageChoiceOutcome> {
|
|
94
131
|
if (outdated.length === 0) {
|
|
95
132
|
ctx.ui.notify("Nothing to update.", "info");
|
|
96
133
|
return "unchanged";
|
|
@@ -105,27 +142,63 @@ export async function applyUpdateAll(rows: Row[], natives: Natives, ctx: Extensi
|
|
|
105
142
|
ctx.ui.notify(approval.message ?? "update denied", "warning");
|
|
106
143
|
return "cancelled";
|
|
107
144
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let failed = 0;
|
|
111
|
-
for (const row of outdated) {
|
|
112
|
-
try {
|
|
113
|
-
const outcome = await natives.update(`npm:${row.name}`, approval.approved);
|
|
114
|
-
if (outcome.reloadRequired) changed += 1;
|
|
115
|
-
} catch (e) {
|
|
116
|
-
failed += 1;
|
|
117
|
-
ctx.ui.notify(`${row.name} update failed: ${e instanceof Error ? e.message : e}`, "error");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
145
|
+
const { changed, failedNames } = await runBatch(outdated, approval.approved);
|
|
146
|
+
for (const failure of failedNames) ctx.ui.notify(`update failed: ${failure}`, "error");
|
|
120
147
|
if (changed === 0) {
|
|
121
|
-
ctx.ui.notify(
|
|
122
|
-
return
|
|
148
|
+
ctx.ui.notify(failedNames.length > 0 ? `No packages updated; ${failedNames.length} failed.` : "All packages already up to date.", failedNames.length > 0 ? "warning" : "info");
|
|
149
|
+
return failedNames.length > 0 ? "cancelled" : "unchanged";
|
|
123
150
|
}
|
|
124
|
-
ctx.ui.notify(`Updated ${changed} package(s)${
|
|
151
|
+
ctx.ui.notify(`Updated ${changed} package(s)${failedNames.length > 0 ? `, ${failedNames.length} failed` : ""}; reloading Pi resources.`, "info");
|
|
125
152
|
await ctx.reload();
|
|
126
153
|
return "changed";
|
|
127
154
|
}
|
|
128
155
|
|
|
156
|
+
/** Floats its own progress-bar overlay over the still-open panel -- kept
|
|
157
|
+
* for applyUpdateAll's own public API (and anything calling it directly,
|
|
158
|
+
* outside the packages panel). renderPanel's own U key does not use this;
|
|
159
|
+
* it renders the same progress bar inline on its own already-open overlay
|
|
160
|
+
* instead of stacking a second one. */
|
|
161
|
+
async function runUpdatesWithProgress(
|
|
162
|
+
outdated: Row[],
|
|
163
|
+
natives: Natives,
|
|
164
|
+
approved: boolean | undefined,
|
|
165
|
+
ctx: ExtensionCommandContext,
|
|
166
|
+
): Promise<UpdateAllResult> {
|
|
167
|
+
return ctx.ui.custom<UpdateAllResult>(
|
|
168
|
+
(tui, theme, _kb, done) => {
|
|
169
|
+
const bar = new ProgressBar({ value: 0, max: outdated.length, label: `${outdated[0]?.name ?? ""} (1/${outdated.length})`, style: (s) => theme.fg("accent", s) });
|
|
170
|
+
const border = () => new DynamicBorder((s) => theme.fg("border", s));
|
|
171
|
+
const container = new Container();
|
|
172
|
+
container.addChild(new Spacer(1));
|
|
173
|
+
container.addChild(border());
|
|
174
|
+
container.addChild({ invalidate() {}, render: (_width: number) => [theme.bold("Updating packages")] });
|
|
175
|
+
container.addChild(new Spacer(1));
|
|
176
|
+
container.addChild(bar);
|
|
177
|
+
container.addChild(new Spacer(1));
|
|
178
|
+
container.addChild(border());
|
|
179
|
+
|
|
180
|
+
performUpdateAll(outdated, natives, approved, (event) => {
|
|
181
|
+
bar.setLabel(`${event.row.name} (${event.index + 1}/${event.total})`);
|
|
182
|
+
if (event.phase === "done") bar.setValue(event.index + 1);
|
|
183
|
+
tui.requestRender();
|
|
184
|
+
}).then(done);
|
|
185
|
+
|
|
186
|
+
return { render: (width: number) => container.render(width), invalidate: () => container.invalidate(), handleInput() {} };
|
|
187
|
+
},
|
|
188
|
+
{ overlay: true, overlayOptions: { width: 50, anchor: "center" } },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** U -- update every outdated row with one combined approval and one
|
|
193
|
+
* reload, instead of applyPackageChoice's own per-call reload (which
|
|
194
|
+
* would end the session after the first successful update). Public API:
|
|
195
|
+
* opens its own progress overlay. renderPanel's own U key instead renders
|
|
196
|
+
* progress inline via approveAndRunUpdateAll + performUpdateAll directly. */
|
|
197
|
+
export async function applyUpdateAll(rows: Row[], natives: Natives, ctx: ExtensionCommandContext): Promise<PackageChoiceOutcome> {
|
|
198
|
+
const outdated = rows.filter((row) => row.hasUpdate);
|
|
199
|
+
return approveAndRunUpdateAll(outdated, natives, ctx, (batch, approved) => runUpdatesWithProgress(batch, natives, approved, ctx));
|
|
200
|
+
}
|
|
201
|
+
|
|
129
202
|
/** d -- toggles every declared extension of this package on or off in one
|
|
130
203
|
* step (disables if any are enabled, otherwise re-enables all). Reuses
|
|
131
204
|
* applyResourceToggle per item for its own tested approval/mutation path
|
|
@@ -207,8 +280,10 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
|
|
|
207
280
|
}
|
|
208
281
|
|
|
209
282
|
// Panel loop: actions resolve the component, run outside it, then reopen.
|
|
283
|
+
// U/updateAll is handled entirely inside renderPanel itself (progress
|
|
284
|
+
// rendered on the same already-open overlay) and never reaches here.
|
|
210
285
|
for (;;) {
|
|
211
|
-
const action = await renderPanel(ctx, rows);
|
|
286
|
+
const action = await renderPanel(ctx, natives, rows);
|
|
212
287
|
if (!action) return; // closed
|
|
213
288
|
|
|
214
289
|
if (action.type === "refresh") {
|
|
@@ -227,12 +302,6 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
|
|
|
227
302
|
continue; // a successful install already reloaded; a no-op returns here
|
|
228
303
|
}
|
|
229
304
|
|
|
230
|
-
if (action.type === "updateAll") {
|
|
231
|
-
const outcome = await applyUpdateAll(rows, natives, ctx);
|
|
232
|
-
if (outcome === "changed") return; // ctx.reload() already replaced the session
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
305
|
if (action.type === "config") {
|
|
237
306
|
await showResourceConfig(ctx, natives, action.row?.name);
|
|
238
307
|
continue; // showResourceConfig already handles its own reload prompt
|
|
@@ -252,13 +321,17 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
|
|
|
252
321
|
}
|
|
253
322
|
}
|
|
254
323
|
|
|
255
|
-
function renderPanel(ctx: ExtensionCommandContext,
|
|
324
|
+
function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows: Row[]): Promise<PanelAction | undefined> {
|
|
256
325
|
return ctx.ui.custom<PanelAction | undefined>((tui, theme, _kb, done) => {
|
|
326
|
+
let rows = initialRows;
|
|
257
327
|
let mode: ViewMode = "all";
|
|
258
328
|
const searchInput = new Input();
|
|
259
329
|
let searchActive = false;
|
|
260
330
|
let filtered = visibleRows(rows, mode);
|
|
261
331
|
let selectedIndex = 0;
|
|
332
|
+
// Set only while U's batch update is running -- the installer, rendered
|
|
333
|
+
// inline on this same still-open overlay instead of a second one.
|
|
334
|
+
let installing: ProgressBar | undefined;
|
|
262
335
|
|
|
263
336
|
const maxVisible = 20;
|
|
264
337
|
|
|
@@ -267,6 +340,34 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
267
340
|
selectedIndex = 0;
|
|
268
341
|
}
|
|
269
342
|
|
|
343
|
+
/** U -- runs the whole approve+update+notify+reload flow without ever
|
|
344
|
+
* closing this panel: progress renders on the same overlay via
|
|
345
|
+
* `installing`, and rows refresh in place afterward unless a reload
|
|
346
|
+
* already ended the session. */
|
|
347
|
+
async function runUpdateAllInline(): Promise<void> {
|
|
348
|
+
const outdated = rows.filter((row) => row.hasUpdate);
|
|
349
|
+
const outcome = await approveAndRunUpdateAll(outdated, natives, ctx, (batch, approved) => {
|
|
350
|
+
const bar = new ProgressBar({ value: 0, max: batch.length, label: `${batch[0]?.name ?? ""} (1/${batch.length})`, style: (s) => theme.fg("accent", s) });
|
|
351
|
+
installing = bar;
|
|
352
|
+
tui.requestRender();
|
|
353
|
+
return performUpdateAll(batch, natives, approved, (event) => {
|
|
354
|
+
bar.setLabel(`${event.row.name} (${event.index + 1}/${event.total})`);
|
|
355
|
+
if (event.phase === "done") bar.setValue(event.index + 1);
|
|
356
|
+
tui.requestRender();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
installing = undefined;
|
|
360
|
+
if (outcome === "changed") {
|
|
361
|
+
done(undefined); // ctx.reload() already replaced the session
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const reloaded = await loadRows(natives);
|
|
365
|
+
if (reloaded.error) ctx.ui.notify(`refresh failed: ${reloaded.error}`, "error");
|
|
366
|
+
else rows = reloaded.rows;
|
|
367
|
+
applyFilter();
|
|
368
|
+
tui.requestRender();
|
|
369
|
+
}
|
|
370
|
+
|
|
270
371
|
const header = {
|
|
271
372
|
invalidate() {},
|
|
272
373
|
render(width: number): string[] {
|
|
@@ -329,20 +430,39 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
329
430
|
},
|
|
330
431
|
};
|
|
331
432
|
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
433
|
+
const border = () => new DynamicBorder((s) => theme.fg("border", s));
|
|
434
|
+
|
|
435
|
+
function buildListContainer(): Container {
|
|
436
|
+
const container = new Container();
|
|
437
|
+
container.addChild(new Spacer(1));
|
|
438
|
+
container.addChild(border());
|
|
439
|
+
container.addChild(new Spacer(1));
|
|
440
|
+
container.addChild(header);
|
|
441
|
+
container.addChild(new Spacer(1));
|
|
442
|
+
container.addChild(list);
|
|
443
|
+
container.addChild(new Spacer(1));
|
|
444
|
+
container.addChild(border());
|
|
445
|
+
return container;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function buildInstallingContainer(bar: ProgressBar): Container {
|
|
449
|
+
const container = new Container();
|
|
450
|
+
container.addChild(new Spacer(1));
|
|
451
|
+
container.addChild(border());
|
|
452
|
+
container.addChild({ invalidate() {}, render: (_width: number) => [theme.bold("Updating packages")] });
|
|
453
|
+
container.addChild(new Spacer(1));
|
|
454
|
+
container.addChild(bar);
|
|
455
|
+
container.addChild(new Spacer(1));
|
|
456
|
+
container.addChild(border());
|
|
457
|
+
return container;
|
|
458
|
+
}
|
|
341
459
|
|
|
342
460
|
return {
|
|
343
|
-
render: (width: number) =>
|
|
344
|
-
invalidate: () =>
|
|
461
|
+
render: (width: number) => (installing ? buildInstallingContainer(installing) : buildListContainer()).render(width),
|
|
462
|
+
invalidate: () => (installing ? buildInstallingContainer(installing) : buildListContainer()).invalidate(),
|
|
345
463
|
handleInput(data: string) {
|
|
464
|
+
if (installing) return; // the installer owns the panel until it finishes
|
|
465
|
+
|
|
346
466
|
if (searchActive) {
|
|
347
467
|
if (data === "\x1b") {
|
|
348
468
|
searchActive = false;
|
|
@@ -382,7 +502,7 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
382
502
|
done({ type: "find" });
|
|
383
503
|
return;
|
|
384
504
|
case "U":
|
|
385
|
-
|
|
505
|
+
void runUpdateAllInline();
|
|
386
506
|
return;
|
|
387
507
|
case "u": {
|
|
388
508
|
const row = filtered[selectedIndex];
|
|
@@ -425,5 +545,5 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
425
545
|
tui.requestRender();
|
|
426
546
|
},
|
|
427
547
|
};
|
|
428
|
-
});
|
|
548
|
+
}, { overlay: true, overlayOptions: { width: "70%", maxHeight: "70%", anchor: "center" } });
|
|
429
549
|
}
|