@bastra-recall/statusline 1.0.0 → 1.0.1

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.
@@ -0,0 +1,215 @@
1
+ import type { PowerlineConfig } from "../config/loader";
2
+ import type { PowerlineColors } from "../themes";
3
+ import type { TuiData, SymbolSet, BoxChars, TuiTitleConfig } from "./types";
4
+ import { visibleLength } from "../utils/terminal";
5
+ import { formatTokenCount, formatModelName } from "../utils/formatters";
6
+ import { colorize, truncateAnsi } from "./primitives";
7
+ import { resolveThresholdStyle } from "./sections-shared";
8
+
9
+ export function resolveTitleToken(
10
+ template: string,
11
+ data: TuiData,
12
+ resolvedData?: Record<string, string>,
13
+ ): string {
14
+ const rawName = data.hookData.model?.display_name || "Claude";
15
+ const modelName = formatModelName(rawName).toLowerCase();
16
+
17
+ return template.replace(/\{([^}]+)\}/g, (_match, token: string) => {
18
+ if (resolvedData) {
19
+ const value = resolvedData[token];
20
+ if (value !== undefined) return value;
21
+ }
22
+ if (token === "model") return modelName;
23
+ return "";
24
+ });
25
+ }
26
+
27
+ export function buildTitleBar(
28
+ data: TuiData,
29
+ box: BoxChars,
30
+ innerWidth: number,
31
+ titleConfig?: TuiTitleConfig,
32
+ resolvedData?: Record<string, string>,
33
+ ): string {
34
+ const leftTemplate = titleConfig?.left ?? "{model}";
35
+ const rightTemplate = titleConfig?.right;
36
+ const leftResolved = resolveTitleToken(leftTemplate, data, resolvedData);
37
+ const leftText = leftResolved ? ` ${leftResolved} ` : "";
38
+ const leftLen = visibleLength(leftText);
39
+
40
+ if (!rightTemplate) {
41
+ const simpleFill = innerWidth - leftLen;
42
+ return (
43
+ box.topLeft +
44
+ leftText +
45
+ box.horizontal.repeat(Math.max(0, simpleFill)) +
46
+ box.topRight
47
+ );
48
+ }
49
+
50
+ const rightResolved = resolveTitleToken(rightTemplate, data, resolvedData);
51
+ const rightText = rightResolved ? ` ${rightResolved} ` : "";
52
+ const rightLen = visibleLength(rightText);
53
+
54
+ // Truncate if combined text exceeds innerWidth
55
+ let finalLeft = leftText;
56
+ let finalLeftLen = leftLen;
57
+ let finalRight = rightText;
58
+ let finalRightLen = rightLen;
59
+
60
+ if (finalLeftLen + finalRightLen > innerWidth) {
61
+ const maxLeft = Math.max(0, innerWidth - finalRightLen);
62
+ if (finalLeftLen > maxLeft) {
63
+ finalLeft = truncateAnsi(finalLeft, maxLeft);
64
+ finalLeftLen = visibleLength(finalLeft);
65
+ }
66
+ if (finalLeftLen + finalRightLen > innerWidth) {
67
+ const maxRight = Math.max(0, innerWidth - finalLeftLen);
68
+ finalRight = truncateAnsi(finalRight, maxRight);
69
+ finalRightLen = visibleLength(finalRight);
70
+ }
71
+ }
72
+
73
+ const fillCount = innerWidth - finalLeftLen - finalRightLen;
74
+
75
+ if (fillCount < 2) {
76
+ const simpleFill = innerWidth - finalLeftLen;
77
+ return (
78
+ box.topLeft +
79
+ finalLeft +
80
+ box.horizontal.repeat(Math.max(0, simpleFill)) +
81
+ box.topRight
82
+ );
83
+ }
84
+
85
+ return (
86
+ box.topLeft +
87
+ finalLeft +
88
+ box.horizontal.repeat(fillCount) +
89
+ finalRight +
90
+ box.topRight
91
+ );
92
+ }
93
+
94
+ function buildBarString(
95
+ pct: number,
96
+ barWidth: number,
97
+ sym: SymbolSet,
98
+ reset: string,
99
+ fgColor: string,
100
+ bold = false,
101
+ ): string {
102
+ barWidth = Math.max(5, barWidth);
103
+ const filledCount = Math.max(
104
+ 0,
105
+ Math.min(barWidth, Math.round((pct / 100) * barWidth)),
106
+ );
107
+ const emptyCount = barWidth - filledCount;
108
+ const bar =
109
+ sym.bar_filled.repeat(filledCount) + sym.bar_empty.repeat(emptyCount);
110
+ return colorize(bar, fgColor, reset, bold);
111
+ }
112
+
113
+ export function buildContextBar(
114
+ data: TuiData,
115
+ barWidth: number,
116
+ sym: SymbolSet,
117
+ reset: string,
118
+ colors: PowerlineColors,
119
+ partFg?: Record<string, string>,
120
+ ): string {
121
+ if (!data.contextInfo) return "";
122
+ const usedPct = data.contextInfo.usablePercentage;
123
+ const defaultFg =
124
+ partFg?.["context.bar"] ?? partFg?.["context"] ?? colors.contextFg;
125
+ const { fg, bold } = resolveThresholdStyle(
126
+ usedPct,
127
+ defaultFg,
128
+ colors.contextBold,
129
+ colors,
130
+ );
131
+ return buildBarString(usedPct, barWidth, sym, reset, fg, bold);
132
+ }
133
+
134
+ export function buildBlockBar(
135
+ data: TuiData,
136
+ barWidth: number,
137
+ sym: SymbolSet,
138
+ reset: string,
139
+ colors: PowerlineColors,
140
+ config: PowerlineConfig,
141
+ partFg?: Record<string, string>,
142
+ ): string {
143
+ if (!data.blockInfo) return "";
144
+
145
+ const pct = data.blockInfo.nativeUtilization;
146
+ const warningThreshold = config.budget?.block?.warningThreshold ?? 80;
147
+ const defaultFg =
148
+ partFg?.["block.bar"] ?? partFg?.["block"] ?? colors.blockFg;
149
+ const { fg, bold } = resolveThresholdStyle(
150
+ pct,
151
+ defaultFg,
152
+ colors.blockBold,
153
+ colors,
154
+ 50,
155
+ warningThreshold,
156
+ );
157
+ return buildBarString(pct, barWidth, sym, reset, fg, bold);
158
+ }
159
+
160
+ export function buildWeeklyBar(
161
+ data: TuiData,
162
+ barWidth: number,
163
+ sym: SymbolSet,
164
+ reset: string,
165
+ colors: PowerlineColors,
166
+ partFg?: Record<string, string>,
167
+ ): string {
168
+ const sevenDay = data.hookData.rate_limits?.seven_day;
169
+ if (!sevenDay) return "";
170
+
171
+ const pct = sevenDay.used_percentage;
172
+ const defaultFg =
173
+ partFg?.["weekly.bar"] ?? partFg?.["weekly"] ?? colors.weeklyFg;
174
+ const { fg, bold } = resolveThresholdStyle(
175
+ pct,
176
+ defaultFg,
177
+ colors.weeklyBold,
178
+ colors,
179
+ );
180
+ return buildBarString(pct, barWidth, sym, reset, fg, bold);
181
+ }
182
+
183
+ export function buildContextLine(
184
+ data: TuiData,
185
+ contentWidth: number,
186
+ sym: SymbolSet,
187
+ reset: string,
188
+ colors: PowerlineColors,
189
+ ): string | null {
190
+ if (!data.contextInfo) {
191
+ return null;
192
+ }
193
+
194
+ const usedPct = data.contextInfo.usablePercentage;
195
+ const tokenStr = formatTokenCount(data.contextInfo.totalTokens);
196
+ const maxStr = formatTokenCount(data.contextInfo.maxTokens);
197
+ const suffix = ` ${usedPct}% ${tokenStr}/${maxStr}`;
198
+ const barLen = Math.max(5, contentWidth - suffix.length);
199
+ const filledCount = Math.max(
200
+ 0,
201
+ Math.min(barLen, Math.round((usedPct / 100) * barLen)),
202
+ );
203
+ const emptyCount = barLen - filledCount;
204
+ const bar =
205
+ sym.bar_filled.repeat(filledCount) + sym.bar_empty.repeat(emptyCount);
206
+
207
+ const { fg, bold } = resolveThresholdStyle(
208
+ usedPct,
209
+ colors.contextFg,
210
+ colors.contextBold,
211
+ colors,
212
+ );
213
+
214
+ return colorize(`${bar}${suffix}`, fg, reset, bold);
215
+ }