@danypops/pi-packed 0.14.0 → 0.16.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 CHANGED
@@ -11,7 +11,7 @@ The extension connects to Packed's authenticated user daemon and starts the pack
11
11
  ## Commands
12
12
 
13
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 appears inline next to the package currently updating -- the list stays visible throughout, nothing swaps to a separate screen. A reload is confirmed separately from the update itself -- decline it to defer: the update already happened, only picking it up in this Pi session is deferred until `/reload`.
14
+ - `u` / `U` -- update the selected package / update every outdated package, one combined confirmation and one reload. `U` shows a spinner inline next to the package currently updating, settling into a real ✓ or ✗ plus a short tail of that update's own captured output once it finishes -- the list stays visible throughout, nothing swaps to a separate screen. A reload is confirmed separately from the update itself -- decline it to defer: the update already happened, only picking it up in this Pi session is deferred until `/reload`. Every confirm in this flow -- approval and reload alike -- renders inline on this same panel (`y`/`n` keys), never 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).
@@ -1,5 +1,5 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
- import type { MenuTheme } from "malevich-tui-components";
2
+ import type { DialogTheme, MenuTheme } from "malevich-tui-components";
3
3
 
4
4
  /** Maps Pi's own Theme onto Malevich's Menu -- the one place this mapping
5
5
  * exists, shared by every ctx.ui.custom overlay menu in this extension. */
@@ -12,3 +12,16 @@ export function menuTheme(theme: Theme): MenuTheme {
12
12
  title: (s) => theme.fg("accent", s),
13
13
  };
14
14
  }
15
+
16
+ /** Maps Pi's own Theme onto Malevich's Dialog -- used for an inline y/n
17
+ * decision (e.g. confirmReload) rendered as part of an already-open
18
+ * ctx.ui.custom overlay's own content, instead of ctx.ui.confirm's separate,
19
+ * arrow-select-only native dialog. */
20
+ export function dialogTheme(theme: Theme): DialogTheme {
21
+ return {
22
+ border: (s) => theme.fg("border", s),
23
+ title: (s) => theme.bold(theme.fg("accent", s)),
24
+ body: (s) => s,
25
+ dim: (s) => theme.fg("muted", s),
26
+ };
27
+ }
@@ -12,24 +12,33 @@
12
12
  * own row percentage, not a fixed row count -- scales with the real
13
13
  * terminal, unlike the anchor:"top-center"+offsetY this replaced, which
14
14
  * was pinned 1 row below the absolute top on any terminal size). Enter
15
- * opens a second, smaller overlay action menu on top of it. U's batch update stays on
16
- * this same package list -- progress renders inline next to the row
17
- * currently being updated, not as a separate screen. Rows render through
18
- * Malevich's Table (real column-aligned Package/Version/status cells,
19
- * per-row selection styling baked into each cell since Table's own
20
- * cellStyle is column-wide, not row-wide) inside this panel's own
21
- * scroll-window slice -- Table deliberately owns no pagination of its
22
- * own, so the visible-window-around-selectedIndex math stays here. Every
23
- * mutation that actually changes something asks confirmReload separately
24
- * from the earlier mutation-approval confirm -- declining defers the
25
- * reload (the mutation itself already happened) and keeps the panel open
26
- * with refreshed rows instead of ending the session. All
27
- * data flows through the packed CLI (thin seam).
15
+ * opens a second, smaller overlay action menu on top of it. U's batch
16
+ * update stays on this same package list -- an indeterminate spinner
17
+ * (Malevich's Spinner, extracted upstream from this panel's original need
18
+ * once no embeddable one existed anywhere) renders inline next to the row
19
+ * currently updating, settling into a real ✓/✗ plus a bounded tail of
20
+ * that row's own actual captured stdout/stderr once it finishes, never a
21
+ * determinate bar (a single subprocess call has no knowable percentage).
22
+ * Rows render through Malevich's Table (real column-aligned
23
+ * Package/Version/status cells, per-row selection styling baked into each
24
+ * cell since Table's own cellStyle is column-wide, not row-wide) inside
25
+ * this panel's own scroll-window slice -- Table deliberately owns no
26
+ * pagination of its own, so the visible-window-around-selectedIndex math
27
+ * stays here. u/x/d and the Enter action menu all run their whole
28
+ * approve+mutate+confirmReload flow inline, without ever closing this
29
+ * overlay first -- every confirm() along the way (mutation approval,
30
+ * confirmReload's separate reload gate) renders as a real Malevich Dialog
31
+ * on this SAME overlay, dispatched by literal y/n keys, instead of
32
+ * ctx.ui.confirm's own separate native dialog (arrow-select only, and a
33
+ * genuinely different overlay stacked on top -- confirmed live as a real
34
+ * user complaint). Declining a reload defers it: the mutation itself
35
+ * already happened, and the panel stays open with refreshed rows instead
36
+ * of ending the session. All data flows through the packed CLI (thin seam).
28
37
  */
29
38
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
30
39
  import { DynamicBorder, rawKeyHint } from "@earendil-works/pi-coding-agent";
31
40
  import { Container, Input, Spacer, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
32
- import { Envelope, Menu, ProgressBar, Table, type MenuItem, type TableColumn, type TextMeasure } from "malevich-tui-components";
41
+ import { Dialog, Envelope, Menu, Spinner, Table, type MenuItem, type TableColumn, type TextMeasure } from "malevich-tui-components";
33
42
  import { filterRows, mergeRows, nextMode, visibleRows } from "./model.js";
34
43
  import type { Row, ViewMode } from "./model.js";
35
44
  import type { Natives, PackageResources } from "./packed.js";
@@ -37,11 +46,11 @@ import { approvePackageOperation } from "./tools.js";
37
46
  import { showPackedSettings } from "./security-tui.js";
38
47
  import { showResourceConfig, applyResourceToggle } from "./resource-config.js";
39
48
  import { showDiscoverPanel } from "./discover.js";
40
- import { menuTheme } from "./menu-theme.js";
49
+ import { dialogTheme, menuTheme } from "./menu-theme.js";
41
50
  import { confirmReload } from "./reload.js";
42
51
 
43
52
  interface PanelAction {
44
- type: "update" | "remove" | "disable" | "config" | "find" | "refresh" | "settings";
53
+ type: "config" | "find" | "refresh" | "settings";
45
54
  row?: Row;
46
55
  }
47
56
 
@@ -115,7 +124,13 @@ export async function applyPackageChoice(
115
124
 
116
125
  interface UpdateAllResult { changed: number; failedNames: string[]; }
117
126
 
118
- interface UpdateProgressEvent { row: Row; index: number; total: number; phase: "start" | "done"; }
127
+ /** Present only on phase "done" -- the real captured stdout+stderr from
128
+ * ExecInstaller (ok: true) or the thrown error's message (ok: false). This
129
+ * is the actual execution output, not a synthetic status string, so a host
130
+ * can show a genuine success/failure sign instead of guessing from
131
+ * reloadRequired alone. */
132
+ interface UpdateProgressResult { ok: boolean; output: string; }
133
+ interface UpdateProgressEvent { row: Row; index: number; total: number; phase: "start" | "done"; result?: UpdateProgressResult; }
119
134
 
120
135
  /** The core sequential-update loop, with no UI of its own -- reports each
121
136
  * step via onProgress so any host surface (a floating overlay, or the
@@ -134,14 +149,28 @@ async function performUpdateAll(
134
149
  try {
135
150
  const outcome = await natives.update(`npm:${row.name}`, approved);
136
151
  if (outcome.reloadRequired) changed += 1;
152
+ onProgress?.({ row, index: i, total: outdated.length, phase: "done", result: { ok: true, output: outcome.output } });
137
153
  } catch (e) {
138
- failedNames.push(`${row.name}: ${e instanceof Error ? e.message : e}`);
154
+ const message = e instanceof Error ? e.message : String(e);
155
+ failedNames.push(`${row.name}: ${message}`);
156
+ onProgress?.({ row, index: i, total: outdated.length, phase: "done", result: { ok: false, output: message } });
139
157
  }
140
- onProgress?.({ row, index: i, total: outdated.length, phase: "done" });
141
158
  }
142
159
  return { changed, failedNames };
143
160
  }
144
161
 
162
+ const MAX_LOG_TAIL_CHARS = 60;
163
+
164
+ /** The last non-empty line of real captured output, bounded -- never the
165
+ * full stdout/stderr dump inline (a noisy npm install can produce hundreds
166
+ * of lines). undefined when there's nothing worth showing (the common
167
+ * case: `pi update` often produces no output on success at all). */
168
+ function logTail(output: string): string | undefined {
169
+ const line = output.trim().split("\n").filter(Boolean).at(-1);
170
+ if (!line) return undefined;
171
+ return line.length > MAX_LOG_TAIL_CHARS ? `${line.slice(0, MAX_LOG_TAIL_CHARS - 1)}…` : line;
172
+ }
173
+
145
174
  /** Approves once for the whole batch, runs it via whatever runBatch does
146
175
  * (a floating overlay for applyUpdateAll's own public API, or renderPanel's
147
176
  * embedded progress bar), then reports the combined result -- shared so
@@ -182,11 +211,17 @@ async function approveAndRunUpdateAll(
182
211
  return "changed";
183
212
  }
184
213
 
185
- /** Floats its own progress-bar overlay over the still-open panel -- kept
214
+ const MAX_SETTLED_LOG_LINES = 5;
215
+
216
+ /** Floats its own spinner+log overlay over the still-open panel -- kept
186
217
  * for applyUpdateAll's own public API (and anything calling it directly,
187
218
  * outside the packages panel). renderPanel's own U key does not use this;
188
- * it renders the same progress bar inline on its own already-open overlay
189
- * instead of stacking a second one. */
219
+ * it renders the same spinner+log inline on its own already-open overlay
220
+ * instead of stacking a second one. An indeterminate spinner (not a
221
+ * determinate bar) because a single subprocess call has no knowable
222
+ * percentage -- only "still running" or "settled". Each settled row
223
+ * appends one bounded log line (real captured output, not a synthetic
224
+ * status) with a genuine success/failure glyph, up to a small scrollback. */
190
225
  async function runUpdatesWithProgress(
191
226
  outdated: Row[],
192
227
  natives: Natives,
@@ -195,22 +230,35 @@ async function runUpdatesWithProgress(
195
230
  ): Promise<UpdateAllResult> {
196
231
  return ctx.ui.custom<UpdateAllResult>(
197
232
  (tui, theme, _kb, done) => {
198
- const bar = new ProgressBar({ value: 0, max: outdated.length, label: `${outdated[0]?.name ?? ""} (1/${outdated.length})`, style: (s) => theme.fg("accent", s) });
233
+ const spinner = new Spinner();
234
+ const settledLines: string[] = [];
235
+ let currentLabel = `${outdated[0]?.name ?? ""} (1/${outdated.length})`;
199
236
  const border = () => new DynamicBorder((s) => theme.fg("border", s));
200
237
  const container = new Container();
201
238
  container.addChild(new Spacer(1));
202
239
  container.addChild(border());
203
240
  container.addChild({ invalidate() {}, render: (_width: number) => [theme.bold("Updating packages")] });
204
241
  container.addChild(new Spacer(1));
205
- container.addChild(bar);
242
+ container.addChild({ invalidate() {}, render: (width: number) => [truncateToWidth(`${theme.fg("accent", spinner.glyph())} ${currentLabel}`, width, "")] });
243
+ container.addChild(new Spacer(1));
244
+ container.addChild({
245
+ invalidate() {},
246
+ render: (width: number) => settledLines.slice(-MAX_SETTLED_LOG_LINES).map((line) => truncateToWidth(line, width, "")),
247
+ });
206
248
  container.addChild(new Spacer(1));
207
249
  container.addChild(border());
208
250
 
251
+ spinner.start(() => tui.requestRender());
209
252
  performUpdateAll(outdated, natives, approved, (event) => {
210
- bar.setLabel(`${event.row.name} (${event.index + 1}/${event.total})`);
211
- if (event.phase === "done") bar.setValue(event.index + 1);
253
+ if (event.phase === "start") {
254
+ currentLabel = `${event.row.name} (${event.index + 1}/${event.total})`;
255
+ } else {
256
+ const glyph = event.result?.ok ? theme.fg("success", "✓") : theme.fg("error", "✗");
257
+ const tail = event.result ? logTail(event.result.output) : undefined;
258
+ settledLines.push(`${glyph} ${event.row.name}${tail ? theme.fg("dim", ` -- ${tail}`) : ""}`);
259
+ }
212
260
  tui.requestRender();
213
- }).then(done);
261
+ }).finally(() => spinner.stop()).then(done);
214
262
 
215
263
  return { render: (width: number) => container.render(width), invalidate: () => container.invalidate(), handleInput() {} };
216
264
  },
@@ -322,8 +370,10 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
322
370
  }
323
371
 
324
372
  // Panel loop: actions resolve the component, run outside it, then reopen.
325
- // U/updateAll is handled entirely inside renderPanel itself (progress
326
- // rendered on the same already-open overlay) and never reaches here.
373
+ // Every mutation (u/x/d, U, and whatever the Enter action menu picks) is
374
+ // handled entirely inside renderPanel itself -- approval and reload
375
+ // confirms render inline on the same already-open overlay -- and never
376
+ // reaches here; only non-mutation navigation resolves this loop.
327
377
  for (;;) {
328
378
  const action = await renderPanel(ctx, natives, rows);
329
379
  if (!action) return; // closed
@@ -343,24 +393,8 @@ export async function showPackedPanel(ctx: ExtensionCommandContext, natives: Nat
343
393
  continue; // a successful install already reloaded; a no-op returns here
344
394
  }
345
395
 
346
- if (action.type === "config") {
347
- await showResourceConfig(ctx, natives, action.row?.name);
348
- continue; // showResourceConfig already handles its own reload prompt
349
- }
350
-
351
- const row = action.row;
352
- if (!row) continue;
353
-
354
- if (action.type === "disable") {
355
- const outcome = await applyDisableExtensions(row, natives, ctx);
356
- if (outcome === "changed") return;
357
- if (outcome === "deferred") await refreshRows();
358
- continue;
359
- }
360
-
361
- const outcome = await applyPackageChoice(action.type === "update" ? `Update to ${row.latest}` : "Remove", row, natives, ctx);
362
- if (outcome === "changed") return; // ctx.reload() already replaced the session
363
- if (outcome === "deferred") await refreshRows();
396
+ await showResourceConfig(ctx, natives, action.row?.name);
397
+ // showResourceConfig already handles its own reload prompt
364
398
  }
365
399
  }
366
400
 
@@ -372,11 +406,16 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
372
406
  let searchActive = false;
373
407
  let filtered = visibleRows(rows, mode);
374
408
  let selectedIndex = 0;
375
- // Set only while U's batch update is running. The list stays fully
376
- // visible throughout -- this just names which row the shared bar
377
- // (below) is currently sitting next to.
409
+ // Set only while U's batch update is running -- blocks input for the
410
+ // whole batch (the installer owns input until it finishes), same as
411
+ // before. The list stays fully visible throughout. Which specific row
412
+ // currently shows a spinner vs. a settled ✓/✗ is settled's own job
413
+ // (below), not this -- a just-finished row must show its glyph
414
+ // immediately, even for the instant before the next row's "start"
415
+ // event reassigns this to the next name.
378
416
  let updatingRowName: string | undefined;
379
- const updatingBar = new ProgressBar({ value: 0, max: 1, width: 10, style: (s) => theme.fg("accent", s) });
417
+ const spinner = new Spinner();
418
+ const settled = new Map<string, { ok: boolean; tail: string | undefined }>();
380
419
 
381
420
  const maxVisible = 20;
382
421
 
@@ -385,22 +424,78 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
385
424
  selectedIndex = 0;
386
425
  }
387
426
 
427
+ // A y/n decision rendered as this SAME overlay's own content (via a real
428
+ // Malevich Dialog, dispatched by literal key press) instead of
429
+ // ctx.ui.confirm's separate native dialog -- confirmed live as a real
430
+ // user complaint: ctx.ui.confirm opens as its own distinct overlay on
431
+ // top of this one ("a new window"), arrow-select only, no y/n keys.
432
+ let pendingDialog: Dialog | undefined;
433
+
434
+ function confirmInline(title: string, message: string): Promise<boolean> {
435
+ return new Promise((resolve) => {
436
+ const settle = (value: boolean) => {
437
+ pendingDialog = undefined;
438
+ tui.requestRender();
439
+ resolve(value);
440
+ };
441
+ pendingDialog = new Dialog({
442
+ title,
443
+ body: message,
444
+ actions: [
445
+ { label: "Yes", key: "y", action: () => settle(true) },
446
+ { label: "No", key: "n", action: () => settle(false) },
447
+ ],
448
+ theme: dialogTheme(theme),
449
+ });
450
+ tui.requestRender();
451
+ });
452
+ }
453
+
454
+ // Every confirm() call inside a mutation flow driven from this open
455
+ // overlay (approvePackageOperation's own approval, confirmReload's
456
+ // separate reload gate) routes through confirmInline instead of the
457
+ // real ctx.ui.confirm -- notify/reload/etc. stay the genuine ctx.
458
+ const inlineCtx: ExtensionCommandContext = { ...ctx, hasUI: true, ui: { ...ctx.ui, confirm: confirmInline } };
459
+
460
+ /** u/x/d and the Enter action menu all funnel through here: approval and
461
+ * reload confirms render inline (via inlineCtx/confirmInline) on this
462
+ * same overlay, never closing it first the way done()-dispatch used to. */
463
+ async function runRowActionInline(action: { type: "update" | "remove" | "disable"; row: Row }): Promise<void> {
464
+ updatingRowName = action.row.name;
465
+ tui.requestRender();
466
+ const outcome = action.type === "disable"
467
+ ? await applyDisableExtensions(action.row, natives, inlineCtx)
468
+ : await applyPackageChoice(action.type === "update" ? `Update to ${action.row.latest}` : "Remove", action.row, natives, inlineCtx);
469
+ updatingRowName = undefined;
470
+ if (outcome === "changed") {
471
+ done(undefined); // ctx.reload() already replaced the session
472
+ return;
473
+ }
474
+ const reloaded = await loadRows(natives);
475
+ if (reloaded.error) ctx.ui.notify(`refresh failed: ${reloaded.error}`, "error");
476
+ else rows = reloaded.rows;
477
+ applyFilter();
478
+ tui.requestRender();
479
+ }
480
+
388
481
  /** U -- runs the whole approve+update+notify+reload flow without ever
389
- * closing this panel or replacing the list: each step's progress
390
- * appears inline next to the row currently being updated, via
391
- * updatingRowName/updatingBar, which list's own render checks. Rows
392
- * refresh in place afterward unless a reload already ended the
393
- * session. */
482
+ * closing this panel or replacing the list: each row's own settled
483
+ * outcome (spinner while in flight, then a real ✓/✗ plus a bounded tail
484
+ * of its actual captured output) appears inline next to that row, via
485
+ * updatingRowName/spinner/settled, which list's own render checks. Rows
486
+ * refresh in place afterward unless a reload already ended the session. */
394
487
  async function runUpdateAllInline(): Promise<void> {
395
488
  const outdated = rows.filter((row) => row.hasUpdate);
396
- const outcome = await approveAndRunUpdateAll(outdated, natives, ctx, (batch, approved) => {
397
- updatingBar.setValue(0);
398
- updatingBar.setMax(batch.length);
489
+ settled.clear();
490
+ const outcome = await approveAndRunUpdateAll(outdated, natives, inlineCtx, (batch, approved) => {
491
+ spinner.start(() => tui.requestRender());
399
492
  return performUpdateAll(batch, natives, approved, (event) => {
400
493
  updatingRowName = event.row.name;
401
- if (event.phase === "done") updatingBar.setValue(event.index + 1);
494
+ if (event.phase === "done" && event.result) {
495
+ settled.set(event.row.name, { ok: event.result.ok, tail: logTail(event.result.output) });
496
+ }
402
497
  tui.requestRender();
403
- });
498
+ }).finally(() => spinner.stop());
404
499
  });
405
500
  updatingRowName = undefined;
406
501
  if (outcome === "changed") {
@@ -410,6 +505,7 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
410
505
  const reloaded = await loadRows(natives);
411
506
  if (reloaded.error) ctx.ui.notify(`refresh failed: ${reloaded.error}`, "error");
412
507
  else rows = reloaded.rows;
508
+ settled.clear(); // fresh state for a subsequent batch
413
509
  applyFilter();
414
510
  tui.requestRender();
415
511
  }
@@ -485,10 +581,16 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
485
581
  const selected = i === selectedIndex;
486
582
  const cursor = selected ? theme.fg("accent", "❯ ") : " ";
487
583
  const name = selected ? theme.bold(row.name) : row.name;
488
- const isUpdating = updatingRowName === row.name;
584
+ // A settled row shows its real outcome even for the instant
585
+ // before the next row's "start" event moves updatingRowName
586
+ // off it -- settled always wins over "still spinning".
587
+ const rowSettled = settled.get(row.name);
588
+ const isUpdating = !rowSettled && updatingRowName === row.name;
489
589
  const status = isUpdating
490
- ? theme.fg("accent", `${updatingBar.format(10)} updating…`)
491
- : row.hasUpdate ? theme.fg("warning", `↑${row.latest}`) : "";
590
+ ? theme.fg("accent", `${spinner.glyph()} updating…`)
591
+ : rowSettled
592
+ ? theme.fg(rowSettled.ok ? "success" : "error", `${rowSettled.ok ? "✓" : "✗"}${rowSettled.tail ? ` ${rowSettled.tail}` : ""}`)
593
+ : row.hasUpdate ? theme.fg("warning", `↑${row.latest}`) : "";
492
594
  return { name: `${cursor}${name}`, version: theme.fg("dim", row.version), status };
493
595
  }),
494
596
  );
@@ -525,11 +627,17 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
525
627
  return {
526
628
  render(width: number): string[] {
527
629
  envelope.setTitle(panelTitle());
630
+ envelope.setContent(pendingDialog ?? body);
528
631
  return envelope.render(width);
529
632
  },
530
633
  invalidate: () => envelope.invalidate(),
531
634
  handleInput(data: string) {
532
- if (updatingRowName) return; // the installer owns input until it finishes
635
+ if (pendingDialog) {
636
+ pendingDialog.handleInput(data);
637
+ tui.requestRender();
638
+ return;
639
+ }
640
+ if (updatingRowName) return; // the mutation/installer owns input until it finishes
533
641
 
534
642
  if (searchActive) {
535
643
  if (data === "\x1b") {
@@ -574,17 +682,17 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
574
682
  return;
575
683
  case "u": {
576
684
  const row = filtered[selectedIndex];
577
- if (row?.hasUpdate) done({ type: "update", row });
685
+ if (row?.hasUpdate) void runRowActionInline({ type: "update", row });
578
686
  return;
579
687
  }
580
688
  case "x": {
581
689
  const row = filtered[selectedIndex];
582
- if (row) done({ type: "remove", row });
690
+ if (row) void runRowActionInline({ type: "remove", row });
583
691
  return;
584
692
  }
585
693
  case "d": {
586
694
  const row = filtered[selectedIndex];
587
- if (row) done({ type: "disable", row });
695
+ if (row) void runRowActionInline({ type: "disable", row });
588
696
  return;
589
697
  }
590
698
  case "c": {
@@ -597,9 +705,7 @@ function renderPanel(ctx: ExtensionCommandContext, natives: Natives, initialRows
597
705
  if (!row) return;
598
706
  void (async () => {
599
707
  const choice = await showActionMenu(ctx, row);
600
- if (choice === "update") done({ type: "update", row });
601
- else if (choice === "remove") done({ type: "remove", row });
602
- else if (choice === "disable") done({ type: "disable", row });
708
+ if (choice === "update" || choice === "remove" || choice === "disable") void runRowActionInline({ type: choice, row });
603
709
  else if (choice === "config") done({ type: "config", row });
604
710
  })();
605
711
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-packed",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Pi package tools, commands, profiles, and TUI for the Packed daemon",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
@@ -15,7 +15,7 @@
15
15
  "@danypops/packed": "^0.2.0",
16
16
  "@danypops/vehicle-client": "^0.1.1",
17
17
  "@danypops/vehicle-client-pi": "^0.1.5",
18
- "malevich-tui-components": "^0.8.0"
18
+ "malevich-tui-components": "^0.11.0"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "@earendil-works/pi-coding-agent": "*",