@ogulcancelik/pi-minimal-footer 0.1.4 → 0.1.6
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/index.ts +112 -63
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -669,10 +669,54 @@ export default function (pi: ExtensionAPI) {
|
|
|
669
669
|
return `${tokens}`;
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
function
|
|
672
|
+
function fitFooterSegment(width: number, variants: string[]): string {
|
|
673
|
+
const safeWidth = Math.max(1, width);
|
|
674
|
+
|
|
675
|
+
for (const variant of variants) {
|
|
676
|
+
if (visibleWidth(variant) <= safeWidth) return variant;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return truncateToWidth(variants[variants.length - 1] || "", safeWidth);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function wrapFooterSegments(segments: string[], width: number, sep: string): string[] {
|
|
683
|
+
const safeWidth = Math.max(1, width);
|
|
684
|
+
const lines: string[] = [];
|
|
685
|
+
let current = "";
|
|
686
|
+
|
|
687
|
+
for (const segment of segments.filter(Boolean)) {
|
|
688
|
+
const fitted = truncateToWidth(segment, safeWidth);
|
|
689
|
+
|
|
690
|
+
if (!current) {
|
|
691
|
+
current = fitted;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const candidate = current + sep + fitted;
|
|
696
|
+
if (visibleWidth(candidate) <= safeWidth) {
|
|
697
|
+
current = candidate;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
lines.push(truncateToWidth(current, safeWidth));
|
|
702
|
+
current = fitted;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (current) lines.push(truncateToWidth(current, safeWidth));
|
|
706
|
+
return lines;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function renderContextGauge(
|
|
710
|
+
percentage: number,
|
|
711
|
+
theme: any,
|
|
712
|
+
used?: number,
|
|
713
|
+
total?: number,
|
|
714
|
+
options?: { barWidth?: number; includeCounts?: boolean }
|
|
715
|
+
): string {
|
|
716
|
+
const barWidth = Math.max(4, options?.barWidth ?? CTX_GAUGE_WIDTH);
|
|
673
717
|
const clamped = Math.max(0, Math.min(100, percentage));
|
|
674
|
-
const filled = Math.round((clamped / 100) *
|
|
675
|
-
const empty =
|
|
718
|
+
const filled = Math.round((clamped / 100) * barWidth);
|
|
719
|
+
const empty = barWidth - filled;
|
|
676
720
|
|
|
677
721
|
let color: string;
|
|
678
722
|
if (clamped >= 90) color = "error";
|
|
@@ -682,7 +726,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
682
726
|
|
|
683
727
|
const bar = theme.fg(color, BAR_FILLED.repeat(filled)) + theme.fg("dim", BAR_EMPTY.repeat(empty));
|
|
684
728
|
const pct = `${Math.round(clamped)}%`;
|
|
685
|
-
const counts =
|
|
729
|
+
const counts =
|
|
730
|
+
options?.includeCounts === false || used === undefined || !total
|
|
731
|
+
? ""
|
|
732
|
+
: ` ${formatTokenCount(used)}/${formatTokenCount(total)}`;
|
|
686
733
|
|
|
687
734
|
return theme.fg("dim", "ctx ") + bar + " " + theme.fg("dim", pct + counts);
|
|
688
735
|
}
|
|
@@ -700,38 +747,38 @@ export default function (pi: ExtensionAPI) {
|
|
|
700
747
|
return theme.fg(color, BAR_FILLED.repeat(filled)) + theme.fg("dim", BAR_EMPTY.repeat(empty));
|
|
701
748
|
}
|
|
702
749
|
|
|
750
|
+
function renderUsageWindow(
|
|
751
|
+
window: RateWindow,
|
|
752
|
+
theme: any,
|
|
753
|
+
options?: { barWidth?: number; includeReset?: boolean }
|
|
754
|
+
): string {
|
|
755
|
+
const dim = (s: string) => theme.fg("dim", s);
|
|
756
|
+
const bar = renderUsageBar(window.usedPercent, Math.max(4, options?.barWidth ?? 10), theme);
|
|
757
|
+
const pct = dim(`${Math.round(window.usedPercent)}%`);
|
|
758
|
+
const timeStr = options?.includeReset === false || !window.resetsIn ? "" : " " + dim(window.resetsIn);
|
|
759
|
+
return `${dim(window.label)} ${bar} ${pct}${timeStr}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
703
762
|
function renderUsageLine(usage: UsageSnapshot, width: number, theme: any): string[] {
|
|
704
763
|
if (!usage.windows.length) return [];
|
|
705
764
|
|
|
706
765
|
const dim = (s: string) => theme.fg("dim", s);
|
|
707
766
|
const sep = " " + dim(">") + " ";
|
|
708
|
-
const
|
|
709
|
-
const NARROW_THRESHOLD = 60;
|
|
710
|
-
|
|
711
|
-
if (width < NARROW_THRESHOLD) {
|
|
712
|
-
// Narrow: one line per window
|
|
713
|
-
const lines: string[] = [];
|
|
714
|
-
for (const w of usage.windows) {
|
|
715
|
-
const bar = renderUsageBar(w.usedPercent, USAGE_BAR_WIDTH, theme);
|
|
716
|
-
const pct = dim(`${Math.round(w.usedPercent)}%`);
|
|
717
|
-
const timeStr = w.resetsIn ? " " + dim(w.resetsIn) : "";
|
|
718
|
-
lines.push(`${dim(w.label)} ${bar} ${pct}${timeStr}`);
|
|
719
|
-
}
|
|
720
|
-
return lines;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// Wide: single line with > separators, fixed-width bars
|
|
724
|
-
// Format: "Codex > 5h ━━━━━━━━━━ 46% 2h38m > 7d ━━━━━━━━━━ 46% 2d1h"
|
|
725
|
-
const parts: string[] = [theme.fg("accent", usage.provider)];
|
|
767
|
+
const segments: string[] = [theme.fg("accent", usage.provider)];
|
|
726
768
|
|
|
727
769
|
for (const w of usage.windows) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
770
|
+
segments.push(
|
|
771
|
+
fitFooterSegment(width, [
|
|
772
|
+
renderUsageWindow(w, theme, { barWidth: 10, includeReset: true }),
|
|
773
|
+
renderUsageWindow(w, theme, { barWidth: 8, includeReset: true }),
|
|
774
|
+
renderUsageWindow(w, theme, { barWidth: 8, includeReset: false }),
|
|
775
|
+
renderUsageWindow(w, theme, { barWidth: 6, includeReset: false }),
|
|
776
|
+
renderUsageWindow(w, theme, { barWidth: 4, includeReset: false }),
|
|
777
|
+
])
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return wrapFooterSegments(segments, width, sep);
|
|
735
782
|
}
|
|
736
783
|
|
|
737
784
|
function getThinkingLevel(ctx: any): string {
|
|
@@ -754,11 +801,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
754
801
|
const lastAssistant = messages
|
|
755
802
|
.slice()
|
|
756
803
|
.reverse()
|
|
757
|
-
.find((m: any) => m.role === "assistant" && m.stopReason !== "aborted");
|
|
758
|
-
|
|
759
|
-
if (!lastAssistant?.usage) return { percentage: 0, used: 0, total: contextWindow };
|
|
804
|
+
.find((m: any) => m.role === "assistant" && m.stopReason !== "aborted") as any;
|
|
760
805
|
|
|
761
|
-
const usage = lastAssistant
|
|
806
|
+
const usage = lastAssistant?.usage;
|
|
807
|
+
if (!usage) return { percentage: 0, used: 0, total: contextWindow };
|
|
762
808
|
const contextTokens = (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
763
809
|
|
|
764
810
|
return { percentage: (contextTokens / contextWindow) * 100, used: contextTokens, total: contextWindow };
|
|
@@ -884,7 +930,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
884
930
|
|
|
885
931
|
// Model + thinking
|
|
886
932
|
const modelName = ctx.model?.id?.split("/").pop() || "no-model";
|
|
887
|
-
|
|
933
|
+
const plainModelStr = theme.fg("muted", modelName);
|
|
934
|
+
let modelStr = plainModelStr;
|
|
888
935
|
if (ctx.model?.reasoning) {
|
|
889
936
|
const thinkingLevel = getThinkingLevel(ctx);
|
|
890
937
|
if (thinkingLevel !== "off") {
|
|
@@ -892,41 +939,43 @@ export default function (pi: ExtensionAPI) {
|
|
|
892
939
|
}
|
|
893
940
|
}
|
|
894
941
|
|
|
895
|
-
// Context gauge
|
|
896
|
-
const gauge = renderContextGauge(percentage, theme, ctxUsed, ctxTotal);
|
|
897
|
-
|
|
898
942
|
const sep = " " + theme.fg("dim", ">") + " ";
|
|
899
|
-
const NARROW_THRESHOLD = 60;
|
|
900
|
-
|
|
901
943
|
const lines: string[] = [];
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
944
|
+
const pwdStr = theme.fg("accent", pwd);
|
|
945
|
+
const statusBlocks = [
|
|
946
|
+
fitFooterSegment(width, branchStr ? [pwdStr + sep + branchStr, pwdStr] : [pwdStr]),
|
|
947
|
+
fitFooterSegment(width, modelStr === plainModelStr ? [plainModelStr] : [modelStr, plainModelStr]),
|
|
948
|
+
fitFooterSegment(width, [
|
|
949
|
+
renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
|
|
950
|
+
barWidth: CTX_GAUGE_WIDTH,
|
|
951
|
+
includeCounts: true,
|
|
952
|
+
}),
|
|
953
|
+
renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
|
|
954
|
+
barWidth: 10,
|
|
955
|
+
includeCounts: false,
|
|
956
|
+
}),
|
|
957
|
+
renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
|
|
958
|
+
barWidth: 8,
|
|
959
|
+
includeCounts: false,
|
|
960
|
+
}),
|
|
961
|
+
renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
|
|
962
|
+
barWidth: 6,
|
|
963
|
+
includeCounts: false,
|
|
964
|
+
}),
|
|
965
|
+
renderContextGauge(percentage, theme, ctxUsed, ctxTotal, {
|
|
966
|
+
barWidth: 4,
|
|
967
|
+
includeCounts: false,
|
|
968
|
+
}),
|
|
969
|
+
]),
|
|
970
|
+
];
|
|
971
|
+
|
|
972
|
+
lines.push(...wrapFooterSegments(statusBlocks, width, sep));
|
|
921
973
|
|
|
922
974
|
if (latestUsage && latestUsage.windows.length > 0) {
|
|
923
|
-
|
|
924
|
-
for (const line of usageLines) {
|
|
925
|
-
lines.push(truncateToWidth(line, width));
|
|
926
|
-
}
|
|
975
|
+
lines.push(...renderUsageLine(latestUsage, width, theme));
|
|
927
976
|
}
|
|
928
977
|
|
|
929
|
-
return lines;
|
|
978
|
+
return lines.map((line) => truncateToWidth(line, width));
|
|
930
979
|
},
|
|
931
980
|
};
|
|
932
981
|
});
|