@danypops/pi-packed 0.10.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/tui.ts +150 -72
- 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.
|
package/extension/src/tui.ts
CHANGED
|
@@ -6,9 +6,12 @@
|
|
|
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";
|
|
@@ -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
|
|
|
@@ -88,11 +91,73 @@ export async function applyPackageChoice(
|
|
|
88
91
|
|
|
89
92
|
interface UpdateAllResult { changed: number; failedNames: string[]; }
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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> {
|
|
131
|
+
if (outdated.length === 0) {
|
|
132
|
+
ctx.ui.notify("Nothing to update.", "info");
|
|
133
|
+
return "unchanged";
|
|
134
|
+
}
|
|
135
|
+
const approval = await approvePackageOperation(
|
|
136
|
+
"update",
|
|
137
|
+
`pi update --extension ${outdated.map((row) => `npm:${row.name}`).join(" ")}`,
|
|
138
|
+
natives,
|
|
139
|
+
ctx,
|
|
140
|
+
);
|
|
141
|
+
if (!approval.allowed) {
|
|
142
|
+
ctx.ui.notify(approval.message ?? "update denied", "warning");
|
|
143
|
+
return "cancelled";
|
|
144
|
+
}
|
|
145
|
+
const { changed, failedNames } = await runBatch(outdated, approval.approved);
|
|
146
|
+
for (const failure of failedNames) ctx.ui.notify(`update failed: ${failure}`, "error");
|
|
147
|
+
if (changed === 0) {
|
|
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";
|
|
150
|
+
}
|
|
151
|
+
ctx.ui.notify(`Updated ${changed} package(s)${failedNames.length > 0 ? `, ${failedNames.length} failed` : ""}; reloading Pi resources.`, "info");
|
|
152
|
+
await ctx.reload();
|
|
153
|
+
return "changed";
|
|
154
|
+
}
|
|
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. */
|
|
96
161
|
async function runUpdatesWithProgress(
|
|
97
162
|
outdated: Row[],
|
|
98
163
|
natives: Natives,
|
|
@@ -112,24 +177,11 @@ async function runUpdatesWithProgress(
|
|
|
112
177
|
container.addChild(new Spacer(1));
|
|
113
178
|
container.addChild(border());
|
|
114
179
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
bar.setLabel(`${row.name} (${i + 1}/${outdated.length})`);
|
|
121
|
-
tui.requestRender();
|
|
122
|
-
try {
|
|
123
|
-
const outcome = await natives.update(`npm:${row.name}`, approved);
|
|
124
|
-
if (outcome.reloadRequired) changed += 1;
|
|
125
|
-
} catch (e) {
|
|
126
|
-
failedNames.push(`${row.name}: ${e instanceof Error ? e.message : e}`);
|
|
127
|
-
}
|
|
128
|
-
bar.setValue(i + 1);
|
|
129
|
-
tui.requestRender();
|
|
130
|
-
}
|
|
131
|
-
done({ changed, failedNames });
|
|
132
|
-
})();
|
|
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);
|
|
133
185
|
|
|
134
186
|
return { render: (width: number) => container.render(width), invalidate: () => container.invalidate(), handleInput() {} };
|
|
135
187
|
},
|
|
@@ -139,32 +191,12 @@ async function runUpdatesWithProgress(
|
|
|
139
191
|
|
|
140
192
|
/** U -- update every outdated row with one combined approval and one
|
|
141
193
|
* reload, instead of applyPackageChoice's own per-call reload (which
|
|
142
|
-
* would end the session after the first successful update).
|
|
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. */
|
|
143
197
|
export async function applyUpdateAll(rows: Row[], natives: Natives, ctx: ExtensionCommandContext): Promise<PackageChoiceOutcome> {
|
|
144
198
|
const outdated = rows.filter((row) => row.hasUpdate);
|
|
145
|
-
|
|
146
|
-
ctx.ui.notify("Nothing to update.", "info");
|
|
147
|
-
return "unchanged";
|
|
148
|
-
}
|
|
149
|
-
const approval = await approvePackageOperation(
|
|
150
|
-
"update",
|
|
151
|
-
`pi update --extension ${outdated.map((row) => `npm:${row.name}`).join(" ")}`,
|
|
152
|
-
natives,
|
|
153
|
-
ctx,
|
|
154
|
-
);
|
|
155
|
-
if (!approval.allowed) {
|
|
156
|
-
ctx.ui.notify(approval.message ?? "update denied", "warning");
|
|
157
|
-
return "cancelled";
|
|
158
|
-
}
|
|
159
|
-
const { changed, failedNames } = await runUpdatesWithProgress(outdated, natives, approval.approved, ctx);
|
|
160
|
-
for (const failure of failedNames) ctx.ui.notify(`update failed: ${failure}`, "error");
|
|
161
|
-
if (changed === 0) {
|
|
162
|
-
ctx.ui.notify(failedNames.length > 0 ? `No packages updated; ${failedNames.length} failed.` : "All packages already up to date.", failedNames.length > 0 ? "warning" : "info");
|
|
163
|
-
return failedNames.length > 0 ? "cancelled" : "unchanged";
|
|
164
|
-
}
|
|
165
|
-
ctx.ui.notify(`Updated ${changed} package(s)${failedNames.length > 0 ? `, ${failedNames.length} failed` : ""}; reloading Pi resources.`, "info");
|
|
166
|
-
await ctx.reload();
|
|
167
|
-
return "changed";
|
|
199
|
+
return approveAndRunUpdateAll(outdated, natives, ctx, (batch, approved) => runUpdatesWithProgress(batch, natives, approved, ctx));
|
|
168
200
|
}
|
|
169
201
|
|
|
170
202
|
/** d -- toggles every declared extension of this package on or off in one
|
|
@@ -248,8 +280,10 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
|
|
|
248
280
|
}
|
|
249
281
|
|
|
250
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.
|
|
251
285
|
for (;;) {
|
|
252
|
-
const action = await renderPanel(ctx, rows);
|
|
286
|
+
const action = await renderPanel(ctx, natives, rows);
|
|
253
287
|
if (!action) return; // closed
|
|
254
288
|
|
|
255
289
|
if (action.type === "refresh") {
|
|
@@ -268,12 +302,6 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
|
|
|
268
302
|
continue; // a successful install already reloaded; a no-op returns here
|
|
269
303
|
}
|
|
270
304
|
|
|
271
|
-
if (action.type === "updateAll") {
|
|
272
|
-
const outcome = await applyUpdateAll(rows, natives, ctx);
|
|
273
|
-
if (outcome === "changed") return; // ctx.reload() already replaced the session
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
305
|
if (action.type === "config") {
|
|
278
306
|
await showResourceConfig(ctx, natives, action.row?.name);
|
|
279
307
|
continue; // showResourceConfig already handles its own reload prompt
|
|
@@ -293,13 +321,17 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
|
|
|
293
321
|
}
|
|
294
322
|
}
|
|
295
323
|
|
|
296
|
-
function renderPanel(ctx: ExtensionCommandContext,
|
|
324
|
+
function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows: Row[]): Promise<PanelAction | undefined> {
|
|
297
325
|
return ctx.ui.custom<PanelAction | undefined>((tui, theme, _kb, done) => {
|
|
326
|
+
let rows = initialRows;
|
|
298
327
|
let mode: ViewMode = "all";
|
|
299
328
|
const searchInput = new Input();
|
|
300
329
|
let searchActive = false;
|
|
301
330
|
let filtered = visibleRows(rows, mode);
|
|
302
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;
|
|
303
335
|
|
|
304
336
|
const maxVisible = 20;
|
|
305
337
|
|
|
@@ -308,6 +340,34 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
308
340
|
selectedIndex = 0;
|
|
309
341
|
}
|
|
310
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
|
+
|
|
311
371
|
const header = {
|
|
312
372
|
invalidate() {},
|
|
313
373
|
render(width: number): string[] {
|
|
@@ -371,20 +431,38 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
371
431
|
};
|
|
372
432
|
|
|
373
433
|
const border = () => new DynamicBorder((s) => theme.fg("border", s));
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
+
}
|
|
383
459
|
|
|
384
460
|
return {
|
|
385
|
-
render: (width: number) =>
|
|
386
|
-
invalidate: () =>
|
|
461
|
+
render: (width: number) => (installing ? buildInstallingContainer(installing) : buildListContainer()).render(width),
|
|
462
|
+
invalidate: () => (installing ? buildInstallingContainer(installing) : buildListContainer()).invalidate(),
|
|
387
463
|
handleInput(data: string) {
|
|
464
|
+
if (installing) return; // the installer owns the panel until it finishes
|
|
465
|
+
|
|
388
466
|
if (searchActive) {
|
|
389
467
|
if (data === "\x1b") {
|
|
390
468
|
searchActive = false;
|
|
@@ -424,7 +502,7 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
424
502
|
done({ type: "find" });
|
|
425
503
|
return;
|
|
426
504
|
case "U":
|
|
427
|
-
|
|
505
|
+
void runUpdateAllInline();
|
|
428
506
|
return;
|
|
429
507
|
case "u": {
|
|
430
508
|
const row = filtered[selectedIndex];
|
|
@@ -467,5 +545,5 @@ function renderPanel(ctx: ExtensionCommandContext, rows: Row[]): Promise<PanelAc
|
|
|
467
545
|
tui.requestRender();
|
|
468
546
|
},
|
|
469
547
|
};
|
|
470
|
-
});
|
|
548
|
+
}, { overlay: true, overlayOptions: { width: "70%", maxHeight: "70%", anchor: "center" } });
|
|
471
549
|
}
|