pi-sage 0.2.8 → 0.2.10

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.
@@ -1,4 +1,5 @@
1
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import { DynamicBorder, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
2
3
  import { Type } from "@sinclair/typebox";
3
4
  import {
4
5
  evaluateSoftBudget,
@@ -377,8 +378,10 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
377
378
  ctx.ui.notify(`Saved Sage settings to ${target}`, "info");
378
379
  };
379
380
 
381
+ let rootSelectionIndex = 0;
382
+
380
383
  while (true) {
381
- const action = await selectScrollable(ctx, "Sage settings", [
384
+ const rootOptions = [
382
385
  `Enabled: ${onOff(draft.enabled)}`,
383
386
  `Autonomous mode: ${onOff(draft.autonomousEnabled)}`,
384
387
  `Explicit requests bypass soft limits: ${onOff(draft.explicitRequestAlwaysAllowed)}`,
@@ -400,13 +403,18 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
400
403
  `Cost cap/session: ${draft.maxEstimatedCostPerSession ?? "(none)"}`,
401
404
  `Save scope: ${scope}`,
402
405
  "Test Sage call"
403
- ]);
406
+ ];
407
+
408
+ const action = await selectScrollable(ctx, "Sage settings", rootOptions, 10, rootSelectionIndex);
404
409
 
405
410
  if (!action) {
406
411
  ctx.ui.notify("Sage settings closed (changes are saved immediately)", "info");
407
412
  return;
408
413
  }
409
414
 
415
+ const selectedIndex = rootOptions.indexOf(action);
416
+ if (selectedIndex >= 0) rootSelectionIndex = selectedIndex;
417
+
410
418
  if (action === "Test Sage call") {
411
419
  await runSettingsTestCall(ctx, draft);
412
420
  continue;
@@ -720,82 +728,58 @@ async function selectScrollable(
720
728
  ctx: HasUIContext,
721
729
  title: string,
722
730
  options: string[],
723
- maxVisible = 10
731
+ maxVisible = 10,
732
+ initialSelectedIndex = 0
724
733
  ): Promise<string | undefined> {
725
734
  if (options.length === 0) return undefined;
726
735
 
727
- return await ctx.ui.custom<string | undefined>((tui, theme, _keybindings, done) => {
728
- let selectedIndex = 0;
736
+ const items: SelectItem[] = options.map((option, index) => ({
737
+ value: String(index),
738
+ label: option
739
+ }));
740
+
741
+ const selectedValue = await ctx.ui.custom<string | undefined>((tui, theme, _keybindings, done) => {
742
+ const container = new Container();
743
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
744
+ container.addChild(new Text(theme.fg("accent", title), 1, 0));
745
+ container.addChild(new Text("", 0, 0));
746
+
747
+ const selectList = new SelectList(items, Math.max(3, Math.min(maxVisible, items.length)), {
748
+ selectedPrefix: (text) => theme.fg("accent", text),
749
+ selectedText: (text) => theme.fg("accent", text),
750
+ description: (text) => theme.fg("muted", text),
751
+ scrollInfo: (text) => theme.fg("dim", text),
752
+ noMatch: (text) => theme.fg("warning", text)
753
+ });
729
754
 
730
- const move = (delta: number): void => {
731
- const next = selectedIndex + delta;
732
- selectedIndex = Math.max(0, Math.min(options.length - 1, next));
733
- tui.requestRender();
734
- };
755
+ selectList.setSelectedIndex(initialSelectedIndex);
735
756
 
736
- const render = (width: number): string[] => {
737
- const lines: string[] = [];
738
- lines.push(theme.fg("accent", title));
739
- lines.push("");
757
+ selectList.onSelect = (item) => done(item.value);
758
+ selectList.onCancel = () => done(undefined);
759
+ container.addChild(selectList);
740
760
 
741
- const visible = Math.max(3, Math.min(maxVisible, options.length));
742
- const start = Math.max(0, Math.min(selectedIndex - Math.floor(visible / 2), options.length - visible));
743
- const end = Math.min(options.length, start + visible);
761
+ container.addChild(new Text("", 0, 0));
762
+ container.addChild(new Text(theme.fg("dim", "↑↓ navigate enter select • esc back"), 1, 0));
763
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
744
764
 
745
- for (let i = start; i < end; i += 1) {
746
- const option = options[i] ?? "";
747
- const prefix = i === selectedIndex ? "→ " : " ";
748
- const raw = `${prefix}${option}`;
749
- lines.push(i === selectedIndex ? theme.fg("accent", raw) : raw);
765
+ return {
766
+ render: (width: number) => container.render(width),
767
+ invalidate: () => container.invalidate(),
768
+ handleInput: (data: string) => {
769
+ selectList.handleInput(data);
770
+ tui.requestRender();
750
771
  }
751
-
752
- lines.push("");
753
- lines.push(theme.fg("muted", `(${selectedIndex + 1}/${options.length}) ↑↓ navigate • enter select • esc back`));
754
-
755
- return lines.map((line) => (line.length > width ? `${line.slice(0, Math.max(0, width - 1))}…` : line));
756
772
  };
773
+ });
757
774
 
758
- const handleInput = (data: string): void => {
759
- const isUp = data.includes("\u001b[A") || data.includes("\u001bOA");
760
- const isDown = data.includes("\u001b[B") || data.includes("\u001bOB");
761
- const isEnter = data.includes("\r") || data.includes("\n") || data.includes("\u001bOM");
762
- const isEscLike = data.startsWith("\u001b");
775
+ if (selectedValue === undefined) return undefined;
763
776
 
764
- if (isUp) {
765
- move(-1);
766
- return;
767
- }
768
- if (isDown) {
769
- move(1);
770
- return;
771
- }
772
- if (data === "k" || data === "K") {
773
- move(-1);
774
- return;
775
- }
776
- if (data === "j" || data === "J") {
777
- move(1);
778
- return;
779
- }
780
- if (isEnter) {
781
- done(options[selectedIndex]);
782
- return;
783
- }
784
- if (data.includes("\u0003")) {
785
- done(undefined);
786
- return;
787
- }
788
- if (isEscLike) {
789
- done(undefined);
790
- }
791
- };
777
+ const selectedIndex = Number(selectedValue);
778
+ if (!Number.isInteger(selectedIndex) || selectedIndex < 0 || selectedIndex >= options.length) {
779
+ return undefined;
780
+ }
792
781
 
793
- return {
794
- render,
795
- invalidate: () => {},
796
- handleInput
797
- };
798
- });
782
+ return options[selectedIndex];
799
783
  }
800
784
 
801
785
  function onOff(value: boolean): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sage",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Interactive-only advisory Sage extension for Pi",