@femtomc/mu-agent 26.2.111 → 26.2.113

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.
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/extensions/ui.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAsqCpF,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,QA6N3C;AAED,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/extensions/ui.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAw4CpF,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,QA6N3C;AAED,eAAe,WAAW,CAAC"}
@@ -13,6 +13,13 @@ const UI_PICKER_PANEL_MAX_WIDTH = 118;
13
13
  const UI_PICKER_PANEL_WIDTH_RATIO = 0.9;
14
14
  const UI_PICKER_PANEL_TOP_MARGIN = 1;
15
15
  const UI_PICKER_PANEL_BOTTOM_MARGIN = 1;
16
+ const UI_PICKER_PANEL_INNER_PADDING_X = 2;
17
+ const UI_PICKER_PANEL_INNER_PADDING_Y = 1;
18
+ const UI_PICKER_TWO_PANE_MIN_WIDTH = 92;
19
+ const UI_PICKER_TWO_PANE_LEFT_MIN = 24;
20
+ const UI_PICKER_TWO_PANE_RIGHT_MIN = 32;
21
+ const UI_PICKER_TWO_PANE_SEPARATOR_WIDTH = 3;
22
+ const UI_PICKER_INTERACTION_HINT = "↑/↓ move · tab switch pane · enter select/submit · esc cancel · click rows";
16
23
  const UI_ENABLE_MOUSE_TRACKING = "\x1b[?1000h\x1b[?1006h";
17
24
  const UI_DISABLE_MOUSE_TRACKING = "\x1b[?1000l\x1b[?1006l";
18
25
  const UI_INTERACT_OVERLAY_OPTIONS = {
@@ -522,6 +529,17 @@ function isPrimaryMouseRelease(event) {
522
529
  const primaryButton = event.buttonCode & 0b11;
523
530
  return primaryButton === 0;
524
531
  }
532
+ function fitStyledLine(text, width) {
533
+ const trimmed = truncateToWidth(text, width, "…", true);
534
+ const missing = width - visibleWidth(trimmed);
535
+ if (missing <= 0) {
536
+ return trimmed;
537
+ }
538
+ return `${trimmed}${" ".repeat(missing)}`;
539
+ }
540
+ function pluralize(count, singular, plural = `${singular}s`) {
541
+ return count === 1 ? singular : plural;
542
+ }
525
543
  function pickerComponentLines(component) {
526
544
  switch (component.kind) {
527
545
  case "text":
@@ -572,8 +590,7 @@ class UiActionPickerComponent {
572
590
  #panelRowEnd = 1;
573
591
  #panelColStart = 1;
574
592
  #panelColEnd = 1;
575
- #docHitRows = new Map();
576
- #actionHitRows = new Map();
593
+ #mouseTargets = [];
577
594
  constructor(opts) {
578
595
  this.#tui = opts.tui;
579
596
  this.#entries = opts.entries;
@@ -642,6 +659,118 @@ class UiActionPickerComponent {
642
659
  action,
643
660
  });
644
661
  }
662
+ #kindChip(action) {
663
+ switch (action.kind) {
664
+ case "primary":
665
+ return this.#theme.fg("success", "[primary]");
666
+ case "danger":
667
+ return this.#theme.fg("error", "[danger]");
668
+ case "link":
669
+ return this.#theme.fg("accent", "[link]");
670
+ case "secondary":
671
+ default:
672
+ return this.#theme.fg("muted", "[secondary]");
673
+ }
674
+ }
675
+ #buildDocsLines() {
676
+ const lines = [];
677
+ lines.push({
678
+ text: this.#theme.fg(this.#mode === "doc" ? "accent" : "dim", `Documents (${this.#entries.length})`),
679
+ });
680
+ for (let idx = 0; idx < this.#entries.length; idx += 1) {
681
+ const entry = this.#entries[idx];
682
+ const active = idx === this.#docIndex;
683
+ const marker = active ? (this.#mode === "doc" ? "▶" : "▸") : " ";
684
+ const actionCount = entry.actions.length;
685
+ const actionLabel = actionCount > 0
686
+ ? `${actionCount} ${pluralize(actionCount, "action")}`
687
+ : "status";
688
+ const title = this.#theme.fg(active ? "text" : "muted", `${marker} ${entry.doc.title}`);
689
+ const meta = this.#theme.fg("dim", `${entry.doc.ui_id} · ${actionLabel}`);
690
+ lines.push({
691
+ text: `${title} ${meta}`,
692
+ selected: active,
693
+ docIndex: idx,
694
+ });
695
+ }
696
+ return lines;
697
+ }
698
+ #buildDetailLines() {
699
+ const lines = [];
700
+ const selectedDoc = this.#currentEntry().doc;
701
+ const selectedActions = this.#currentActions();
702
+ lines.push({ text: this.#theme.fg("accent", selectedDoc.title) });
703
+ lines.push({
704
+ text: this.#theme.fg("dim", `${selectedDoc.ui_id} · revision ${selectedDoc.revision.version}`),
705
+ });
706
+ if (selectedDoc.summary) {
707
+ lines.push({ text: this.#theme.fg("dim", `Summary: ${selectedDoc.summary}`) });
708
+ }
709
+ lines.push({ text: "" });
710
+ lines.push({
711
+ text: this.#theme.fg("dim", `Components (${selectedDoc.components.length})`),
712
+ });
713
+ const visibleComponents = selectedDoc.components.slice(0, UI_PICKER_COMPONENTS_MAX);
714
+ for (const component of visibleComponents) {
715
+ const componentLines = pickerComponentLines(component);
716
+ for (let idx = 0; idx < componentLines.length; idx += 1) {
717
+ const line = componentLines[idx];
718
+ const prefix = idx === 0 ? " " : " ";
719
+ lines.push({ text: this.#theme.fg("text", `${prefix}${line}`) });
720
+ }
721
+ }
722
+ if (selectedDoc.components.length > visibleComponents.length) {
723
+ lines.push({
724
+ text: this.#theme.fg("muted", ` ... (+${selectedDoc.components.length - visibleComponents.length} more components)`),
725
+ });
726
+ }
727
+ lines.push({ text: "" });
728
+ lines.push({
729
+ text: this.#theme.fg(this.#mode === "action" ? "accent" : "dim", `Actions (${selectedActions.length})`),
730
+ });
731
+ for (let idx = 0; idx < selectedActions.length; idx += 1) {
732
+ const action = selectedActions[idx];
733
+ const active = idx === this.#actionIndex;
734
+ const marker = active ? (this.#mode === "action" ? "▶" : "▸") : " ";
735
+ const label = this.#theme.fg(active ? "text" : "muted", `${marker} ${action.label}`);
736
+ const chip = this.#kindChip(action);
737
+ const idLabel = this.#theme.fg("dim", `#${action.id}`);
738
+ lines.push({
739
+ text: `${label} ${chip} ${idLabel}`,
740
+ selected: active,
741
+ actionIndex: idx,
742
+ });
743
+ }
744
+ const action = this.#currentAction();
745
+ if (action?.description) {
746
+ lines.push({ text: "" });
747
+ lines.push({ text: this.#theme.fg("dim", `Ask: ${action.description}`) });
748
+ }
749
+ if (action?.component_id) {
750
+ lines.push({
751
+ text: this.#theme.fg("dim", `Targets component: ${action.component_id}`),
752
+ });
753
+ }
754
+ const commandText = action ? actionCommandText(action) : null;
755
+ if (commandText) {
756
+ lines.push({ text: "" });
757
+ lines.push({ text: this.#theme.fg("dim", `Prompt template: ${commandText}`) });
758
+ }
759
+ return lines;
760
+ }
761
+ #singleColumnLines() {
762
+ const lines = [];
763
+ const docs = this.#buildDocsLines();
764
+ const detail = this.#buildDetailLines();
765
+ for (const line of docs) {
766
+ lines.push(line);
767
+ }
768
+ lines.push({ text: "" });
769
+ for (const line of detail) {
770
+ lines.push(line);
771
+ }
772
+ return lines;
773
+ }
645
774
  #handleMouseEvent(event) {
646
775
  if (!isPrimaryMouseRelease(event)) {
647
776
  return;
@@ -652,19 +781,23 @@ class UiActionPickerComponent {
652
781
  event.col > this.#panelColEnd) {
653
782
  return;
654
783
  }
655
- const docIndex = this.#docHitRows.get(event.row);
656
- if (docIndex !== undefined) {
657
- this.#docIndex = boundedIndex(docIndex, this.#entries.length);
784
+ const target = this.#mouseTargets.find((candidate) => {
785
+ return (candidate.row === event.row &&
786
+ event.col >= candidate.colStart &&
787
+ event.col <= candidate.colEnd);
788
+ });
789
+ if (!target) {
790
+ return;
791
+ }
792
+ if (target.kind === "doc") {
793
+ this.#docIndex = boundedIndex(target.index, this.#entries.length);
658
794
  this.#actionIndex = boundedIndex(this.#actionIndex, this.#currentActions().length);
659
795
  this.#mode = "doc";
660
796
  return;
661
797
  }
662
- const actionIndex = this.#actionHitRows.get(event.row);
663
- if (actionIndex !== undefined) {
664
- this.#mode = "action";
665
- this.#actionIndex = boundedIndex(actionIndex, this.#currentActions().length);
666
- this.#submit();
667
- }
798
+ this.#mode = "action";
799
+ this.#actionIndex = boundedIndex(target.index, this.#currentActions().length);
800
+ this.#submit();
668
801
  }
669
802
  handleInput(data) {
670
803
  const mouse = parseSgrMouseEvent(data);
@@ -727,72 +860,94 @@ class UiActionPickerComponent {
727
860
  const panelTargetWidth = Math.max(UI_PICKER_PANEL_MIN_WIDTH, Math.min(UI_PICKER_PANEL_MAX_WIDTH, Math.floor(width * UI_PICKER_PANEL_WIDTH_RATIO)));
728
861
  const panelWidth = Math.max(4, Math.min(width, panelTargetWidth));
729
862
  const innerWidth = Math.max(1, panelWidth - 2);
730
- const maxContentWidth = Math.max(8, innerWidth);
731
- const content = [];
732
- const docHitRowsByContentIndex = new Map();
733
- const actionHitRowsByContentIndex = new Map();
734
- const pushContent = (line) => {
735
- content.push(truncateToWidth(line, innerWidth, "…", true));
736
- };
737
- pushContent(this.#theme.fg("accent", short("Programmable UI", maxContentWidth)));
738
- pushContent(this.#theme.fg("dim", short("↑/↓ move · tab switch · enter select/submit · esc cancel · click action to submit", maxContentWidth)));
739
- pushContent("");
740
- pushContent(this.#theme.fg(this.#mode === "doc" ? "accent" : "dim", short(`Documents (${this.#entries.length})`, maxContentWidth)));
741
- for (let idx = 0; idx < this.#entries.length; idx += 1) {
742
- const entry = this.#entries[idx];
743
- const active = idx === this.#docIndex;
744
- const marker = active ? (this.#mode === "doc" ? "▶" : "▸") : " ";
745
- const label = `${marker} ${entry.doc.ui_id} · ${entry.doc.title}`;
746
- docHitRowsByContentIndex.set(content.length, idx);
747
- pushContent(this.#theme.fg(active ? "accent" : "muted", short(label, maxContentWidth)));
748
- }
863
+ const horizontalPadding = Math.min(UI_PICKER_PANEL_INNER_PADDING_X, Math.max(0, Math.floor((innerWidth - 1) / 2)));
864
+ const contentWidth = Math.max(1, innerWidth - horizontalPadding * 2);
749
865
  const selectedDoc = this.#currentEntry().doc;
750
- if (selectedDoc.summary) {
751
- pushContent("");
752
- pushContent(this.#theme.fg("dim", short(`Summary: ${selectedDoc.summary}`, maxContentWidth)));
753
- }
754
- pushContent("");
755
- pushContent(this.#theme.fg("dim", short(`Components (${selectedDoc.components.length})`, maxContentWidth)));
756
- const visibleComponents = selectedDoc.components.slice(0, UI_PICKER_COMPONENTS_MAX);
757
- for (const component of visibleComponents) {
758
- const componentLines = pickerComponentLines(component);
759
- for (let idx = 0; idx < componentLines.length; idx += 1) {
760
- const line = componentLines[idx];
761
- const prefix = idx === 0 ? " " : " ";
762
- pushContent(this.#theme.fg("text", short(`${prefix}${line}`, maxContentWidth)));
866
+ const selectedActions = this.#currentActions();
867
+ const renderLines = [];
868
+ const innerPadSegment = horizontalPadding > 0 ? this.#theme.bg("customMessageBg", " ".repeat(horizontalPadding)) : "";
869
+ const contentRowText = (line, bg) => {
870
+ const core = this.#theme.bg(bg, fitStyledLine(line, contentWidth));
871
+ return `${innerPadSegment}${core}${innerPadSegment}`;
872
+ };
873
+ const pushFullLine = (line) => {
874
+ renderLines.push({
875
+ text: contentRowText(line, "customMessageBg"),
876
+ });
877
+ };
878
+ const modeLabel = this.#mode === "action" ? "action focus" : "document focus";
879
+ pushFullLine(`${this.#theme.fg("accent", "mu_ui")}${this.#theme.fg("dim", ` · ${this.#entries.length} ${pluralize(this.#entries.length, "doc")} · ${modeLabel}`)}`);
880
+ pushFullLine(this.#theme.fg("dim", short(UI_PICKER_INTERACTION_HINT, Math.max(8, contentWidth))));
881
+ pushFullLine(this.#theme.fg("borderMuted", "─".repeat(contentWidth)));
882
+ const minTwoPaneWidth = UI_PICKER_TWO_PANE_LEFT_MIN + UI_PICKER_TWO_PANE_RIGHT_MIN + UI_PICKER_TWO_PANE_SEPARATOR_WIDTH;
883
+ const useTwoPane = contentWidth >= UI_PICKER_TWO_PANE_MIN_WIDTH && contentWidth >= minTwoPaneWidth;
884
+ if (useTwoPane) {
885
+ let leftWidth = Math.max(UI_PICKER_TWO_PANE_LEFT_MIN, Math.floor(contentWidth * 0.34));
886
+ let rightWidth = contentWidth - leftWidth - UI_PICKER_TWO_PANE_SEPARATOR_WIDTH;
887
+ if (rightWidth < UI_PICKER_TWO_PANE_RIGHT_MIN) {
888
+ leftWidth = Math.max(UI_PICKER_TWO_PANE_LEFT_MIN, contentWidth - UI_PICKER_TWO_PANE_RIGHT_MIN - UI_PICKER_TWO_PANE_SEPARATOR_WIDTH);
889
+ rightWidth = contentWidth - leftWidth - UI_PICKER_TWO_PANE_SEPARATOR_WIDTH;
890
+ }
891
+ const leftLines = this.#buildDocsLines();
892
+ const rightLines = this.#buildDetailLines();
893
+ const rowCount = Math.max(leftLines.length, rightLines.length);
894
+ for (let idx = 0; idx < rowCount; idx += 1) {
895
+ const left = leftLines[idx];
896
+ const right = rightLines[idx];
897
+ const leftCell = this.#theme.bg(left?.selected ? "selectedBg" : "customMessageBg", fitStyledLine(left?.text ?? "", leftWidth));
898
+ const separator = this.#theme.bg("customMessageBg", this.#theme.fg("borderMuted", " │ "));
899
+ const rightCell = this.#theme.bg(right?.selected ? "selectedBg" : "customMessageBg", fitStyledLine(right?.text ?? "", rightWidth));
900
+ const row = {
901
+ text: `${innerPadSegment}${leftCell}${separator}${rightCell}${innerPadSegment}`,
902
+ };
903
+ if (left?.docIndex !== undefined) {
904
+ row.docTarget = {
905
+ index: left.docIndex,
906
+ colStart: 1,
907
+ colEnd: leftWidth,
908
+ };
909
+ }
910
+ if (right?.actionIndex !== undefined) {
911
+ row.actionTarget = {
912
+ index: right.actionIndex,
913
+ colStart: leftWidth + UI_PICKER_TWO_PANE_SEPARATOR_WIDTH + 1,
914
+ colEnd: leftWidth + UI_PICKER_TWO_PANE_SEPARATOR_WIDTH + rightWidth,
915
+ };
916
+ }
917
+ renderLines.push(row);
763
918
  }
764
919
  }
765
- if (selectedDoc.components.length > visibleComponents.length) {
766
- pushContent(this.#theme.fg("muted", short(` ... (+${selectedDoc.components.length - visibleComponents.length} more components)`, maxContentWidth)));
767
- }
768
- const actions = this.#currentActions();
769
- pushContent("");
770
- pushContent(this.#theme.fg(this.#mode === "action" ? "accent" : "dim", short(`Actions (${actions.length})`, maxContentWidth)));
771
- for (let idx = 0; idx < actions.length; idx += 1) {
772
- const action = actions[idx];
773
- const active = idx === this.#actionIndex;
774
- const marker = active ? (this.#mode === "action" ? "▶" : "▸") : " ";
775
- const label = `${marker} ${action.id} · ${action.label}`;
776
- actionHitRowsByContentIndex.set(content.length, idx);
777
- pushContent(this.#theme.fg(active ? "accent" : "text", short(label, maxContentWidth)));
778
- }
779
- const action = this.#currentAction();
780
- if (action?.description) {
781
- pushContent("");
782
- pushContent(this.#theme.fg("dim", short(`Ask: ${action.description}`, maxContentWidth)));
783
- }
784
- if (action?.component_id) {
785
- pushContent(this.#theme.fg("dim", short(`Targets component: ${action.component_id}`, maxContentWidth)));
786
- }
787
- const commandText = action ? actionCommandText(action) : null;
788
- if (commandText) {
789
- pushContent("");
790
- pushContent(this.#theme.fg("dim", short(`Prompt template: ${commandText}`, maxContentWidth)));
920
+ else {
921
+ const singleColumn = this.#singleColumnLines();
922
+ for (const line of singleColumn) {
923
+ const row = {
924
+ text: contentRowText(line.text, line.selected ? "selectedBg" : "customMessageBg"),
925
+ };
926
+ if (line.docIndex !== undefined) {
927
+ row.docTarget = {
928
+ index: line.docIndex,
929
+ colStart: 1,
930
+ colEnd: contentWidth,
931
+ };
932
+ }
933
+ if (line.actionIndex !== undefined) {
934
+ row.actionTarget = {
935
+ index: line.actionIndex,
936
+ colStart: 1,
937
+ colEnd: contentWidth,
938
+ };
939
+ }
940
+ renderLines.push(row);
941
+ }
791
942
  }
943
+ pushFullLine(this.#theme.fg("borderMuted", "─".repeat(contentWidth)));
944
+ pushFullLine(this.#theme.fg("dim", short(`selected ${selectedDoc.ui_id} · revision ${selectedDoc.revision.version} · ${selectedActions.length} ${pluralize(selectedActions.length, "action")}`, Math.max(8, contentWidth))));
792
945
  const topMarginRows = Math.max(0, UI_PICKER_PANEL_TOP_MARGIN);
793
946
  const bottomMarginRows = Math.max(0, UI_PICKER_PANEL_BOTTOM_MARGIN);
947
+ const verticalPadding = Math.max(0, UI_PICKER_PANEL_INNER_PADDING_Y);
794
948
  const leftPadWidth = Math.max(0, Math.floor((width - panelWidth) / 2));
795
949
  const leftPad = " ".repeat(leftPadWidth);
950
+ const panelColStart = leftPadWidth + 1;
796
951
  const frame = [];
797
952
  for (let row = 0; row < topMarginRows; row += 1) {
798
953
  frame.push("");
@@ -802,31 +957,46 @@ class UiActionPickerComponent {
802
957
  const leftRule = "─".repeat(Math.max(0, Math.floor((innerWidth - titleWidth) / 2)));
803
958
  const rightRule = "─".repeat(Math.max(0, innerWidth - titleWidth - leftRule.length));
804
959
  frame.push(`${leftPad}${this.#theme.fg("borderAccent", `╭${leftRule}`)}${this.#theme.fg("accent", title)}${this.#theme.fg("borderAccent", `${rightRule}╮`)}`);
805
- this.#docHitRows.clear();
806
- this.#actionHitRows.clear();
960
+ this.#mouseTargets = [];
961
+ const blankInnerRow = `${leftPad}${this.#theme.fg("border", "│")}${this.#theme.bg("customMessageBg", " ".repeat(innerWidth))}${this.#theme.fg("border", "│")}`;
962
+ for (let row = 0; row < verticalPadding; row += 1) {
963
+ frame.push(blankInnerRow);
964
+ }
807
965
  const contentStartRow = frame.length + 1;
808
- for (let idx = 0; idx < content.length; idx += 1) {
809
- const line = content[idx] ?? "";
810
- const visible = visibleWidth(line);
811
- const fill = " ".repeat(Math.max(0, innerWidth - visible));
812
- frame.push(`${leftPad}${this.#theme.fg("border", "│")}${this.#theme.bg("customMessageBg", `${line}${fill}`)}${this.#theme.fg("border", "│")}`);
966
+ for (let idx = 0; idx < renderLines.length; idx += 1) {
967
+ const line = renderLines[idx];
968
+ frame.push(`${leftPad}${this.#theme.fg("border", "│")}${line.text}${this.#theme.fg("border", "│")}`);
813
969
  const row = contentStartRow + idx;
814
- const docIndex = docHitRowsByContentIndex.get(idx);
815
- if (docIndex !== undefined) {
816
- this.#docHitRows.set(row, docIndex);
970
+ if (line.docTarget) {
971
+ this.#mouseTargets.push({
972
+ kind: "doc",
973
+ index: line.docTarget.index,
974
+ row,
975
+ colStart: panelColStart + horizontalPadding + line.docTarget.colStart,
976
+ colEnd: panelColStart + horizontalPadding + line.docTarget.colEnd,
977
+ });
817
978
  }
818
- const actionIndex = actionHitRowsByContentIndex.get(idx);
819
- if (actionIndex !== undefined) {
820
- this.#actionHitRows.set(row, actionIndex);
979
+ if (line.actionTarget) {
980
+ this.#mouseTargets.push({
981
+ kind: "action",
982
+ index: line.actionTarget.index,
983
+ row,
984
+ colStart: panelColStart + horizontalPadding + line.actionTarget.colStart,
985
+ colEnd: panelColStart + horizontalPadding + line.actionTarget.colEnd,
986
+ });
821
987
  }
822
988
  }
989
+ for (let row = 0; row < verticalPadding; row += 1) {
990
+ frame.push(blankInnerRow);
991
+ }
823
992
  frame.push(`${leftPad}${this.#theme.fg("borderAccent", `╰${"─".repeat(innerWidth)}╯`)}`);
824
993
  for (let row = 0; row < bottomMarginRows; row += 1) {
825
994
  frame.push("");
826
995
  }
827
996
  this.#panelRowStart = topMarginRows + 1;
828
- this.#panelRowEnd = this.#panelRowStart + content.length + 1;
829
- this.#panelColStart = leftPadWidth + 1;
997
+ const panelRows = 1 + verticalPadding + renderLines.length + verticalPadding + 1;
998
+ this.#panelRowEnd = this.#panelRowStart + panelRows - 1;
999
+ this.#panelColStart = panelColStart;
830
1000
  this.#panelColEnd = leftPadWidth + panelWidth;
831
1001
  return frame;
832
1002
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-agent",
3
- "version": "26.2.111",
3
+ "version": "26.2.113",
4
4
  "description": "Shared operator runtime for mu assistant sessions and serve extensions.",
5
5
  "keywords": [
6
6
  "mu",
@@ -25,7 +25,7 @@
25
25
  "themes/**"
26
26
  ],
27
27
  "dependencies": {
28
- "@femtomc/mu-core": "26.2.111",
28
+ "@femtomc/mu-core": "26.2.113",
29
29
  "@mariozechner/pi-agent-core": "^0.54.2",
30
30
  "@mariozechner/pi-ai": "^0.54.2",
31
31
  "@mariozechner/pi-coding-agent": "^0.54.2",