@owloops/claude-powerline 1.24.3 → 1.25.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 +5 -43
- package/dist/browser.d.ts +676 -0
- package/dist/browser.js +3 -0
- package/dist/index.mjs +12 -12
- package/package.json +9 -1
- package/plugin/templates/config-full.json +1 -1
- package/plugin/templates/config-tui-compact.json +3 -3
- package/plugin/templates/config-tui-full.json +4 -4
- package/plugin/templates/config-tui-standard.json +4 -4
- package/src/browser.ts +203 -0
- package/src/config/defaults.ts +79 -0
- package/src/config/loader.ts +462 -0
- package/src/index.ts +90 -0
- package/src/powerline.ts +904 -0
- package/src/segments/block.ts +31 -0
- package/src/segments/context.ts +221 -0
- package/src/segments/git.ts +492 -0
- package/src/segments/index.ts +25 -0
- package/src/segments/metrics.ts +175 -0
- package/src/segments/pricing.ts +454 -0
- package/src/segments/renderer.ts +796 -0
- package/src/segments/session.ts +207 -0
- package/src/segments/tmux.ts +35 -0
- package/src/segments/today.ts +191 -0
- package/src/themes/dark.ts +52 -0
- package/src/themes/gruvbox.ts +52 -0
- package/src/themes/index.ts +131 -0
- package/src/themes/light.ts +52 -0
- package/src/themes/nord.ts +52 -0
- package/src/themes/rose-pine.ts +52 -0
- package/src/themes/tokyo-night.ts +52 -0
- package/src/tui/grid.ts +712 -0
- package/src/tui/index.ts +4 -0
- package/src/tui/layouts.ts +285 -0
- package/src/tui/primitives.ts +175 -0
- package/src/tui/renderer.ts +206 -0
- package/src/tui/sections.ts +1080 -0
- package/src/tui/types.ts +181 -0
- package/src/utils/budget.ts +47 -0
- package/src/utils/cache.ts +247 -0
- package/src/utils/claude.ts +489 -0
- package/src/utils/color-support.ts +118 -0
- package/src/utils/colors.ts +120 -0
- package/src/utils/constants.ts +176 -0
- package/src/utils/formatters.ts +160 -0
- package/src/utils/logger.ts +5 -0
- package/src/utils/terminal-width.ts +117 -0
- package/src/utils/terminal.ts +11 -0
|
@@ -0,0 +1,1080 @@
|
|
|
1
|
+
import type { PowerlineConfig } from "../config/loader";
|
|
2
|
+
import type { PowerlineColors } from "../themes";
|
|
3
|
+
import type {
|
|
4
|
+
TuiData,
|
|
5
|
+
SymbolSet,
|
|
6
|
+
BoxChars,
|
|
7
|
+
RenderCtx,
|
|
8
|
+
SegmentTemplate,
|
|
9
|
+
JustifyValue,
|
|
10
|
+
TuiTitleConfig,
|
|
11
|
+
} from "./types";
|
|
12
|
+
import { visibleLength } from "../utils/terminal";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
formatCost,
|
|
16
|
+
formatTokenCount,
|
|
17
|
+
collapseHome,
|
|
18
|
+
formatDuration,
|
|
19
|
+
formatModelName,
|
|
20
|
+
formatResponseTime,
|
|
21
|
+
formatTimeRemaining,
|
|
22
|
+
formatLongTimeRemaining,
|
|
23
|
+
minutesUntilReset,
|
|
24
|
+
abbreviateFishStyle,
|
|
25
|
+
} from "../utils/formatters";
|
|
26
|
+
import { getBudgetStatus } from "../utils/budget";
|
|
27
|
+
import { colorize, truncateAnsi } from "./primitives";
|
|
28
|
+
|
|
29
|
+
export function resolveTitleToken(
|
|
30
|
+
template: string,
|
|
31
|
+
data: TuiData,
|
|
32
|
+
resolvedData?: Record<string, string>,
|
|
33
|
+
): string {
|
|
34
|
+
const rawName = data.hookData.model?.display_name || "Claude";
|
|
35
|
+
const modelName = formatModelName(rawName).toLowerCase();
|
|
36
|
+
|
|
37
|
+
return template.replace(/\{([^}]+)\}/g, (_match, token: string) => {
|
|
38
|
+
if (resolvedData) {
|
|
39
|
+
const value = resolvedData[token];
|
|
40
|
+
if (value !== undefined) return value;
|
|
41
|
+
}
|
|
42
|
+
if (token === "model") return modelName;
|
|
43
|
+
return "";
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildTitleBar(
|
|
48
|
+
data: TuiData,
|
|
49
|
+
box: BoxChars,
|
|
50
|
+
innerWidth: number,
|
|
51
|
+
titleConfig?: TuiTitleConfig,
|
|
52
|
+
resolvedData?: Record<string, string>,
|
|
53
|
+
): string {
|
|
54
|
+
const leftTemplate = titleConfig?.left ?? "{model}";
|
|
55
|
+
const rightTemplate = titleConfig?.right;
|
|
56
|
+
const leftResolved = resolveTitleToken(leftTemplate, data, resolvedData);
|
|
57
|
+
const leftText = leftResolved ? ` ${leftResolved} ` : "";
|
|
58
|
+
const leftLen = visibleLength(leftText);
|
|
59
|
+
|
|
60
|
+
if (!rightTemplate) {
|
|
61
|
+
const simpleFill = innerWidth - leftLen;
|
|
62
|
+
return (
|
|
63
|
+
box.topLeft +
|
|
64
|
+
leftText +
|
|
65
|
+
box.horizontal.repeat(Math.max(0, simpleFill)) +
|
|
66
|
+
box.topRight
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rightResolved = resolveTitleToken(rightTemplate, data, resolvedData);
|
|
71
|
+
const rightText = rightResolved ? ` ${rightResolved} ` : "";
|
|
72
|
+
const rightLen = visibleLength(rightText);
|
|
73
|
+
|
|
74
|
+
// Truncate if combined text exceeds innerWidth
|
|
75
|
+
let finalLeft = leftText;
|
|
76
|
+
let finalLeftLen = leftLen;
|
|
77
|
+
let finalRight = rightText;
|
|
78
|
+
let finalRightLen = rightLen;
|
|
79
|
+
|
|
80
|
+
if (finalLeftLen + finalRightLen > innerWidth) {
|
|
81
|
+
const maxLeft = Math.max(0, innerWidth - finalRightLen);
|
|
82
|
+
if (finalLeftLen > maxLeft) {
|
|
83
|
+
finalLeft = truncateAnsi(finalLeft, maxLeft);
|
|
84
|
+
finalLeftLen = visibleLength(finalLeft);
|
|
85
|
+
}
|
|
86
|
+
if (finalLeftLen + finalRightLen > innerWidth) {
|
|
87
|
+
const maxRight = Math.max(0, innerWidth - finalLeftLen);
|
|
88
|
+
finalRight = truncateAnsi(finalRight, maxRight);
|
|
89
|
+
finalRightLen = visibleLength(finalRight);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fillCount = innerWidth - finalLeftLen - finalRightLen;
|
|
94
|
+
|
|
95
|
+
if (fillCount < 2) {
|
|
96
|
+
const simpleFill = innerWidth - finalLeftLen;
|
|
97
|
+
return (
|
|
98
|
+
box.topLeft +
|
|
99
|
+
finalLeft +
|
|
100
|
+
box.horizontal.repeat(Math.max(0, simpleFill)) +
|
|
101
|
+
box.topRight
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
box.topLeft +
|
|
107
|
+
finalLeft +
|
|
108
|
+
box.horizontal.repeat(fillCount) +
|
|
109
|
+
finalRight +
|
|
110
|
+
box.topRight
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolveThresholdColor(
|
|
115
|
+
pct: number,
|
|
116
|
+
defaultColor: string,
|
|
117
|
+
colors: PowerlineColors,
|
|
118
|
+
warningAt = 60,
|
|
119
|
+
criticalAt = 80,
|
|
120
|
+
): string {
|
|
121
|
+
if (pct >= criticalAt) return colors.contextCriticalFg;
|
|
122
|
+
if (pct >= warningAt) return colors.contextWarningFg;
|
|
123
|
+
return defaultColor;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildBarString(
|
|
127
|
+
pct: number,
|
|
128
|
+
barWidth: number,
|
|
129
|
+
sym: SymbolSet,
|
|
130
|
+
reset: string,
|
|
131
|
+
fgColor: string,
|
|
132
|
+
): string {
|
|
133
|
+
barWidth = Math.max(5, barWidth);
|
|
134
|
+
const filledCount = Math.max(
|
|
135
|
+
0,
|
|
136
|
+
Math.min(barWidth, Math.round((pct / 100) * barWidth)),
|
|
137
|
+
);
|
|
138
|
+
const emptyCount = barWidth - filledCount;
|
|
139
|
+
const bar =
|
|
140
|
+
sym.bar_filled.repeat(filledCount) + sym.bar_empty.repeat(emptyCount);
|
|
141
|
+
return colorize(bar, fgColor, reset);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function formatContextParts(
|
|
145
|
+
data: TuiData,
|
|
146
|
+
sym: SymbolSet,
|
|
147
|
+
): Record<string, string> {
|
|
148
|
+
if (!data.contextInfo)
|
|
149
|
+
return { icon: "", label: "context", bar: "", pct: "", tokens: "" };
|
|
150
|
+
|
|
151
|
+
const usedPct = data.contextInfo.usablePercentage;
|
|
152
|
+
const tokenStr = formatTokenCount(data.contextInfo.totalTokens);
|
|
153
|
+
const maxStr = formatTokenCount(data.contextInfo.maxTokens);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
icon: sym.context_time,
|
|
157
|
+
label: "context",
|
|
158
|
+
bar: " ",
|
|
159
|
+
pct: `${usedPct}%`,
|
|
160
|
+
tokens: `${tokenStr}/${maxStr}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function buildContextBar(
|
|
165
|
+
data: TuiData,
|
|
166
|
+
barWidth: number,
|
|
167
|
+
sym: SymbolSet,
|
|
168
|
+
reset: string,
|
|
169
|
+
colors: PowerlineColors,
|
|
170
|
+
partFg?: Record<string, string>,
|
|
171
|
+
): string {
|
|
172
|
+
if (!data.contextInfo) return "";
|
|
173
|
+
const usedPct = data.contextInfo.usablePercentage;
|
|
174
|
+
const defaultFg =
|
|
175
|
+
partFg?.["context.bar"] ?? partFg?.["context"] ?? colors.contextFg;
|
|
176
|
+
const fgColor = resolveThresholdColor(usedPct, defaultFg, colors);
|
|
177
|
+
return buildBarString(usedPct, barWidth, sym, reset, fgColor);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function buildBlockBar(
|
|
181
|
+
data: TuiData,
|
|
182
|
+
barWidth: number,
|
|
183
|
+
sym: SymbolSet,
|
|
184
|
+
reset: string,
|
|
185
|
+
colors: PowerlineColors,
|
|
186
|
+
config: PowerlineConfig,
|
|
187
|
+
partFg?: Record<string, string>,
|
|
188
|
+
): string {
|
|
189
|
+
if (!data.blockInfo) return "";
|
|
190
|
+
|
|
191
|
+
const pct = data.blockInfo.nativeUtilization;
|
|
192
|
+
const warningThreshold = config.budget?.block?.warningThreshold ?? 80;
|
|
193
|
+
const defaultFg =
|
|
194
|
+
partFg?.["block.bar"] ?? partFg?.["block"] ?? colors.blockFg;
|
|
195
|
+
const fgColor = resolveThresholdColor(
|
|
196
|
+
pct,
|
|
197
|
+
defaultFg,
|
|
198
|
+
colors,
|
|
199
|
+
50,
|
|
200
|
+
warningThreshold,
|
|
201
|
+
);
|
|
202
|
+
return buildBarString(pct, barWidth, sym, reset, fgColor);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function buildWeeklyBar(
|
|
206
|
+
data: TuiData,
|
|
207
|
+
barWidth: number,
|
|
208
|
+
sym: SymbolSet,
|
|
209
|
+
reset: string,
|
|
210
|
+
colors: PowerlineColors,
|
|
211
|
+
partFg?: Record<string, string>,
|
|
212
|
+
): string {
|
|
213
|
+
const sevenDay = data.hookData.rate_limits?.seven_day;
|
|
214
|
+
if (!sevenDay) return "";
|
|
215
|
+
|
|
216
|
+
const pct = sevenDay.used_percentage;
|
|
217
|
+
const defaultFg =
|
|
218
|
+
partFg?.["weekly.bar"] ?? partFg?.["weekly"] ?? colors.weeklyFg;
|
|
219
|
+
const fgColor = resolveThresholdColor(pct, defaultFg, colors);
|
|
220
|
+
return buildBarString(pct, barWidth, sym, reset, fgColor);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function buildContextLine(
|
|
224
|
+
data: TuiData,
|
|
225
|
+
contentWidth: number,
|
|
226
|
+
sym: SymbolSet,
|
|
227
|
+
reset: string,
|
|
228
|
+
colors: PowerlineColors,
|
|
229
|
+
): string | null {
|
|
230
|
+
if (!data.contextInfo) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const usedPct = data.contextInfo.usablePercentage;
|
|
235
|
+
const tokenStr = formatTokenCount(data.contextInfo.totalTokens);
|
|
236
|
+
const maxStr = formatTokenCount(data.contextInfo.maxTokens);
|
|
237
|
+
const suffix = ` ${usedPct}% ${tokenStr}/${maxStr}`;
|
|
238
|
+
const barLen = Math.max(5, contentWidth - suffix.length);
|
|
239
|
+
const filledCount = Math.max(
|
|
240
|
+
0,
|
|
241
|
+
Math.min(barLen, Math.round((usedPct / 100) * barLen)),
|
|
242
|
+
);
|
|
243
|
+
const emptyCount = barLen - filledCount;
|
|
244
|
+
const bar =
|
|
245
|
+
sym.bar_filled.repeat(filledCount) + sym.bar_empty.repeat(emptyCount);
|
|
246
|
+
|
|
247
|
+
const fgColor = resolveThresholdColor(usedPct, colors.contextFg, colors);
|
|
248
|
+
|
|
249
|
+
return colorize(`${bar}${suffix}`, fgColor, reset);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getDirectoryDisplay(hookData: TuiData["hookData"]): string {
|
|
253
|
+
const currentDir = hookData.workspace?.current_dir || hookData.cwd || "/";
|
|
254
|
+
return collapseHome(currentDir);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function collectMetricSegments(
|
|
258
|
+
data: TuiData,
|
|
259
|
+
sym: SymbolSet,
|
|
260
|
+
config: PowerlineConfig,
|
|
261
|
+
reset: string,
|
|
262
|
+
colors: PowerlineColors,
|
|
263
|
+
): string[] {
|
|
264
|
+
const segments: string[] = [];
|
|
265
|
+
|
|
266
|
+
if (data.blockInfo) {
|
|
267
|
+
segments.push(
|
|
268
|
+
colorize(
|
|
269
|
+
formatBlockSegment(data.blockInfo, sym, config),
|
|
270
|
+
colors.blockFg,
|
|
271
|
+
reset,
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const sevenDay = data.hookData.rate_limits?.seven_day;
|
|
276
|
+
if (sevenDay) {
|
|
277
|
+
segments.push(
|
|
278
|
+
colorize(formatWeeklySegment(sevenDay, sym), colors.weeklyFg, reset),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (data.usageInfo) {
|
|
282
|
+
segments.push(
|
|
283
|
+
colorize(
|
|
284
|
+
formatSessionSegment(data.usageInfo, sym, config),
|
|
285
|
+
colors.sessionFg,
|
|
286
|
+
reset,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (data.todayInfo) {
|
|
291
|
+
segments.push(
|
|
292
|
+
colorize(
|
|
293
|
+
formatTodaySegment(data.todayInfo, sym, config),
|
|
294
|
+
colors.todayFg,
|
|
295
|
+
reset,
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const activityParts = collectActivityParts(data, sym);
|
|
301
|
+
if (activityParts.length > 0) {
|
|
302
|
+
segments.push(colorize(activityParts.join(" · "), colors.metricsFg, reset));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return segments;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function collectActivityParts(data: TuiData, sym: SymbolSet): string[] {
|
|
309
|
+
const parts: string[] = [];
|
|
310
|
+
if (data.metricsInfo) {
|
|
311
|
+
if (
|
|
312
|
+
data.metricsInfo.sessionDuration !== null &&
|
|
313
|
+
data.metricsInfo.sessionDuration > 0
|
|
314
|
+
) {
|
|
315
|
+
parts.push(
|
|
316
|
+
`${sym.metrics_duration} ${formatDuration(data.metricsInfo.sessionDuration)}`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
if (
|
|
320
|
+
data.metricsInfo.messageCount !== null &&
|
|
321
|
+
data.metricsInfo.messageCount > 0
|
|
322
|
+
) {
|
|
323
|
+
parts.push(`${sym.metrics_messages} ${data.metricsInfo.messageCount}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return parts;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function collectWorkspaceParts(
|
|
330
|
+
data: TuiData,
|
|
331
|
+
sym: SymbolSet,
|
|
332
|
+
reset: string,
|
|
333
|
+
colors: PowerlineColors,
|
|
334
|
+
): string[] {
|
|
335
|
+
const parts: string[] = [];
|
|
336
|
+
|
|
337
|
+
const gitStr = formatGitSegment(data, sym);
|
|
338
|
+
if (gitStr) parts.push(colorize(gitStr, colors.gitFg, reset));
|
|
339
|
+
|
|
340
|
+
const dir = abbreviateFishStyle(getDirectoryDisplay(data.hookData));
|
|
341
|
+
parts.push(colorize(dir, colors.modeFg, reset));
|
|
342
|
+
|
|
343
|
+
return parts;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function collectFooterParts(
|
|
347
|
+
data: TuiData,
|
|
348
|
+
sym: SymbolSet,
|
|
349
|
+
config: PowerlineConfig,
|
|
350
|
+
reset: string,
|
|
351
|
+
colors: PowerlineColors,
|
|
352
|
+
): string[] {
|
|
353
|
+
const parts: string[] = [];
|
|
354
|
+
|
|
355
|
+
if (data.hookData.version) {
|
|
356
|
+
parts.push(
|
|
357
|
+
colorize(
|
|
358
|
+
`${sym.version} v${data.hookData.version}`,
|
|
359
|
+
colors.versionFg,
|
|
360
|
+
reset,
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (data.tmuxSessionId) {
|
|
365
|
+
parts.push(colorize(`tmux:${data.tmuxSessionId}`, colors.tmuxFg, reset));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (data.metricsInfo) {
|
|
369
|
+
const metricParts: string[] = [];
|
|
370
|
+
if (
|
|
371
|
+
data.metricsInfo.responseTime !== null &&
|
|
372
|
+
!isNaN(data.metricsInfo.responseTime) &&
|
|
373
|
+
data.metricsInfo.responseTime > 0
|
|
374
|
+
) {
|
|
375
|
+
metricParts.push(
|
|
376
|
+
`${sym.metrics_response} ${formatResponseTime(data.metricsInfo.responseTime)}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (
|
|
380
|
+
data.metricsInfo.linesAdded !== null &&
|
|
381
|
+
data.metricsInfo.linesAdded > 0
|
|
382
|
+
) {
|
|
383
|
+
metricParts.push(
|
|
384
|
+
`${sym.metrics_lines_added}${data.metricsInfo.linesAdded}`,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
if (
|
|
388
|
+
data.metricsInfo.linesRemoved !== null &&
|
|
389
|
+
data.metricsInfo.linesRemoved > 0
|
|
390
|
+
) {
|
|
391
|
+
metricParts.push(
|
|
392
|
+
`${sym.metrics_lines_removed}${data.metricsInfo.linesRemoved}`,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (metricParts.length > 0) {
|
|
396
|
+
parts.push(colorize(metricParts.join(" · "), colors.metricsFg, reset));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const envConfig = config.display.lines
|
|
401
|
+
.map((line) => line.segments.env)
|
|
402
|
+
.find((env) => env?.enabled);
|
|
403
|
+
|
|
404
|
+
if (envConfig && envConfig.variable) {
|
|
405
|
+
const envVal = globalThis.process?.env?.[envConfig.variable];
|
|
406
|
+
if (envVal) {
|
|
407
|
+
const prefix = envConfig.prefix ?? envConfig.variable;
|
|
408
|
+
parts.push(
|
|
409
|
+
colorize(prefix ? `${prefix}:${envVal}` : envVal, colors.envFg, reset),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return parts;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function formatBlockParts(
|
|
418
|
+
blockInfo: TuiData["blockInfo"] & {},
|
|
419
|
+
sym: SymbolSet,
|
|
420
|
+
_config: PowerlineConfig,
|
|
421
|
+
): Record<string, string> {
|
|
422
|
+
const value = `${Math.round(blockInfo.nativeUtilization)}%`;
|
|
423
|
+
const time = formatTimeRemaining(blockInfo.timeRemaining);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
icon: sym.block_cost,
|
|
427
|
+
label: "block",
|
|
428
|
+
value,
|
|
429
|
+
time,
|
|
430
|
+
budget: "",
|
|
431
|
+
bar: " ",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function formatBlockSegment(
|
|
436
|
+
blockInfo: TuiData["blockInfo"] & {},
|
|
437
|
+
sym: SymbolSet,
|
|
438
|
+
config: PowerlineConfig,
|
|
439
|
+
): string {
|
|
440
|
+
const parts = formatBlockParts(blockInfo, sym, config);
|
|
441
|
+
let text = `${parts.icon} ${parts.value}`;
|
|
442
|
+
if (parts.time) text += ` · ${parts.time}`;
|
|
443
|
+
if (parts.budget) text += parts.budget;
|
|
444
|
+
return text;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function formatWeeklyParts(
|
|
448
|
+
sevenDay: { used_percentage: number; resets_at: number },
|
|
449
|
+
sym: SymbolSet,
|
|
450
|
+
): Record<string, string> {
|
|
451
|
+
const pct = `${Math.round(sevenDay.used_percentage)}%`;
|
|
452
|
+
const time = formatLongTimeRemaining(minutesUntilReset(sevenDay.resets_at));
|
|
453
|
+
return { icon: sym.weekly_cost, label: "weekly", pct, time, bar: " " };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function formatWeeklySegment(
|
|
457
|
+
sevenDay: { used_percentage: number; resets_at: number },
|
|
458
|
+
sym: SymbolSet,
|
|
459
|
+
): string {
|
|
460
|
+
const parts = formatWeeklyParts(sevenDay, sym);
|
|
461
|
+
let text = `${parts.icon} ${parts.pct}`;
|
|
462
|
+
if (parts.time) text += ` · ${parts.time}`;
|
|
463
|
+
return text;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function formatSessionParts(
|
|
467
|
+
usageInfo: TuiData["usageInfo"] & {},
|
|
468
|
+
sym: SymbolSet,
|
|
469
|
+
config: PowerlineConfig,
|
|
470
|
+
): Record<string, string> {
|
|
471
|
+
const sessionTokens = usageInfo.session.tokens;
|
|
472
|
+
const tokenStr =
|
|
473
|
+
sessionTokens !== null && sessionTokens > 0
|
|
474
|
+
? formatTokenCount(sessionTokens)
|
|
475
|
+
: "";
|
|
476
|
+
|
|
477
|
+
let budget = "";
|
|
478
|
+
const sessionBudget = config.budget?.session;
|
|
479
|
+
if (sessionBudget?.amount && usageInfo.session.cost !== null) {
|
|
480
|
+
budget = getBudgetStatus(
|
|
481
|
+
usageInfo.session.cost,
|
|
482
|
+
sessionBudget.amount,
|
|
483
|
+
sessionBudget.warningThreshold || 80,
|
|
484
|
+
).displayText;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
icon: sym.session_cost,
|
|
489
|
+
label: "session",
|
|
490
|
+
cost: formatCost(usageInfo.session.cost),
|
|
491
|
+
tokens: tokenStr,
|
|
492
|
+
budget,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function formatSessionSegment(
|
|
497
|
+
usageInfo: TuiData["usageInfo"] & {},
|
|
498
|
+
sym: SymbolSet,
|
|
499
|
+
config: PowerlineConfig,
|
|
500
|
+
): string {
|
|
501
|
+
const parts = formatSessionParts(usageInfo, sym, config);
|
|
502
|
+
let text = `${parts.icon} ${parts.cost}`;
|
|
503
|
+
if (parts.tokens) text += ` · ${parts.tokens}`;
|
|
504
|
+
if (parts.budget) text += parts.budget;
|
|
505
|
+
return text;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export function formatTodayParts(
|
|
509
|
+
todayInfo: TuiData["todayInfo"] & {},
|
|
510
|
+
sym: SymbolSet,
|
|
511
|
+
config: PowerlineConfig,
|
|
512
|
+
): Record<string, string> {
|
|
513
|
+
let budget = "";
|
|
514
|
+
const todayBudget = config.budget?.today;
|
|
515
|
+
if (todayBudget?.amount && todayInfo.cost !== null) {
|
|
516
|
+
budget = getBudgetStatus(
|
|
517
|
+
todayInfo.cost,
|
|
518
|
+
todayBudget.amount,
|
|
519
|
+
todayBudget.warningThreshold || 80,
|
|
520
|
+
).displayText;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
icon: sym.today_cost,
|
|
525
|
+
cost: formatCost(todayInfo.cost),
|
|
526
|
+
label: "today",
|
|
527
|
+
budget,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function formatTodaySegment(
|
|
532
|
+
todayInfo: TuiData["todayInfo"] & {},
|
|
533
|
+
sym: SymbolSet,
|
|
534
|
+
config: PowerlineConfig,
|
|
535
|
+
): string {
|
|
536
|
+
const parts = formatTodayParts(todayInfo, sym, config);
|
|
537
|
+
let text = `${parts.icon} ${parts.cost} ${parts.label}`;
|
|
538
|
+
if (parts.budget) text += parts.budget;
|
|
539
|
+
return text;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function formatMetricsParts(
|
|
543
|
+
data: TuiData,
|
|
544
|
+
sym: SymbolSet,
|
|
545
|
+
): Record<string, string> {
|
|
546
|
+
const empty = {
|
|
547
|
+
response: "",
|
|
548
|
+
responseIcon: "",
|
|
549
|
+
responseVal: "",
|
|
550
|
+
lastResponse: "",
|
|
551
|
+
lastResponseIcon: "",
|
|
552
|
+
lastResponseVal: "",
|
|
553
|
+
added: "",
|
|
554
|
+
addedIcon: "",
|
|
555
|
+
addedVal: "",
|
|
556
|
+
removed: "",
|
|
557
|
+
removedIcon: "",
|
|
558
|
+
removedVal: "",
|
|
559
|
+
};
|
|
560
|
+
if (!data.metricsInfo) return empty;
|
|
561
|
+
|
|
562
|
+
const hasResponse =
|
|
563
|
+
data.metricsInfo.responseTime !== null &&
|
|
564
|
+
!isNaN(data.metricsInfo.responseTime) &&
|
|
565
|
+
data.metricsInfo.responseTime > 0;
|
|
566
|
+
const responseValStr = hasResponse
|
|
567
|
+
? formatResponseTime(data.metricsInfo.responseTime!)
|
|
568
|
+
: "";
|
|
569
|
+
|
|
570
|
+
const hasLast =
|
|
571
|
+
data.metricsInfo.lastResponseTime !== null &&
|
|
572
|
+
!isNaN(data.metricsInfo.lastResponseTime) &&
|
|
573
|
+
data.metricsInfo.lastResponseTime > 0;
|
|
574
|
+
const lastValStr = hasLast
|
|
575
|
+
? formatResponseTime(data.metricsInfo.lastResponseTime!)
|
|
576
|
+
: "";
|
|
577
|
+
|
|
578
|
+
const hasAdded =
|
|
579
|
+
data.metricsInfo.linesAdded !== null && data.metricsInfo.linesAdded > 0;
|
|
580
|
+
const addedValStr = hasAdded ? `${data.metricsInfo.linesAdded}` : "";
|
|
581
|
+
|
|
582
|
+
const hasRemoved =
|
|
583
|
+
data.metricsInfo.linesRemoved !== null && data.metricsInfo.linesRemoved > 0;
|
|
584
|
+
const removedValStr = hasRemoved ? `${data.metricsInfo.linesRemoved}` : "";
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
response: hasResponse ? `${sym.metrics_response} ${responseValStr}` : "",
|
|
588
|
+
responseIcon: hasResponse ? sym.metrics_response : "",
|
|
589
|
+
responseVal: responseValStr,
|
|
590
|
+
lastResponse: hasLast
|
|
591
|
+
? `${sym.metrics_last_response} ${lastValStr}`
|
|
592
|
+
: `${sym.metrics_last_response} --`,
|
|
593
|
+
lastResponseIcon: sym.metrics_last_response,
|
|
594
|
+
lastResponseVal: hasLast ? lastValStr : "--",
|
|
595
|
+
added: hasAdded ? `${sym.metrics_lines_added}${addedValStr}` : "",
|
|
596
|
+
addedIcon: hasAdded ? sym.metrics_lines_added : "",
|
|
597
|
+
addedVal: addedValStr,
|
|
598
|
+
removed: hasRemoved ? `${sym.metrics_lines_removed}${removedValStr}` : "",
|
|
599
|
+
removedIcon: hasRemoved ? sym.metrics_lines_removed : "",
|
|
600
|
+
removedVal: removedValStr,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function formatMetricsSegment(data: TuiData, sym: SymbolSet): string {
|
|
605
|
+
const parts = formatMetricsParts(data, sym);
|
|
606
|
+
const filled = [
|
|
607
|
+
parts.response,
|
|
608
|
+
parts.lastResponse,
|
|
609
|
+
parts.added,
|
|
610
|
+
parts.removed,
|
|
611
|
+
].filter(Boolean);
|
|
612
|
+
return filled.length > 0 ? filled.join(" · ") : "";
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function formatActivityParts(
|
|
616
|
+
data: TuiData,
|
|
617
|
+
sym: SymbolSet,
|
|
618
|
+
): Record<string, string> {
|
|
619
|
+
const empty = {
|
|
620
|
+
icon: "",
|
|
621
|
+
duration: "",
|
|
622
|
+
durationIcon: "",
|
|
623
|
+
durationVal: "",
|
|
624
|
+
messages: "",
|
|
625
|
+
messagesIcon: "",
|
|
626
|
+
messagesVal: "",
|
|
627
|
+
};
|
|
628
|
+
if (!data.metricsInfo) return empty;
|
|
629
|
+
|
|
630
|
+
const hasDuration =
|
|
631
|
+
data.metricsInfo.sessionDuration !== null &&
|
|
632
|
+
data.metricsInfo.sessionDuration > 0;
|
|
633
|
+
const durationValStr = hasDuration
|
|
634
|
+
? formatDuration(data.metricsInfo.sessionDuration!)
|
|
635
|
+
: "";
|
|
636
|
+
|
|
637
|
+
const hasMessages =
|
|
638
|
+
data.metricsInfo.messageCount !== null && data.metricsInfo.messageCount > 0;
|
|
639
|
+
const messagesValStr = hasMessages ? `${data.metricsInfo.messageCount}` : "";
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
icon: sym.activity,
|
|
643
|
+
duration: hasDuration ? `${sym.metrics_duration} ${durationValStr}` : "",
|
|
644
|
+
durationIcon: hasDuration ? sym.metrics_duration : "",
|
|
645
|
+
durationVal: durationValStr,
|
|
646
|
+
messages: hasMessages ? `${sym.metrics_messages} ${messagesValStr}` : "",
|
|
647
|
+
messagesIcon: hasMessages ? sym.metrics_messages : "",
|
|
648
|
+
messagesVal: messagesValStr,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function formatActivitySegment(data: TuiData, sym: SymbolSet): string {
|
|
653
|
+
const parts = formatActivityParts(data, sym);
|
|
654
|
+
const filled = [parts.duration, parts.messages].filter(Boolean);
|
|
655
|
+
return filled.length > 0 ? filled.join(" · ") : "";
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function formatGitParts(data: TuiData, sym: SymbolSet): Record<string, string> {
|
|
659
|
+
if (!data.gitInfo)
|
|
660
|
+
return {
|
|
661
|
+
icon: "",
|
|
662
|
+
info: "",
|
|
663
|
+
branch: "",
|
|
664
|
+
status: "",
|
|
665
|
+
ahead: "",
|
|
666
|
+
behind: "",
|
|
667
|
+
working: "",
|
|
668
|
+
head: "",
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
let statusIcon: string;
|
|
672
|
+
if (data.gitInfo.status === "conflicts") {
|
|
673
|
+
statusIcon = sym.git_conflicts;
|
|
674
|
+
} else if (data.gitInfo.status === "dirty") {
|
|
675
|
+
statusIcon = sym.git_dirty;
|
|
676
|
+
} else {
|
|
677
|
+
statusIcon = sym.git_clean;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const ahead =
|
|
681
|
+
data.gitInfo.ahead > 0 ? `${sym.git_ahead}${data.gitInfo.ahead}` : "";
|
|
682
|
+
const behind =
|
|
683
|
+
data.gitInfo.behind > 0 ? `${sym.git_behind}${data.gitInfo.behind}` : "";
|
|
684
|
+
|
|
685
|
+
const counts: string[] = [];
|
|
686
|
+
if (data.gitInfo.staged && data.gitInfo.staged > 0)
|
|
687
|
+
counts.push(`+${data.gitInfo.staged}`);
|
|
688
|
+
if (data.gitInfo.unstaged && data.gitInfo.unstaged > 0)
|
|
689
|
+
counts.push(`~${data.gitInfo.unstaged}`);
|
|
690
|
+
if (data.gitInfo.untracked && data.gitInfo.untracked > 0)
|
|
691
|
+
counts.push(`?${data.gitInfo.untracked}`);
|
|
692
|
+
const working = counts.length > 0 ? `(${counts.join(" ")})` : "";
|
|
693
|
+
|
|
694
|
+
const headParts = [sym.branch, data.gitInfo.branch, statusIcon];
|
|
695
|
+
if (ahead) headParts.push(ahead);
|
|
696
|
+
if (behind) headParts.push(behind);
|
|
697
|
+
|
|
698
|
+
const infoParts = [data.gitInfo.branch, statusIcon];
|
|
699
|
+
if (ahead) infoParts.push(ahead);
|
|
700
|
+
if (behind) infoParts.push(behind);
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
icon: sym.branch,
|
|
704
|
+
info: infoParts.join(" "),
|
|
705
|
+
branch: data.gitInfo.branch,
|
|
706
|
+
status: statusIcon,
|
|
707
|
+
ahead,
|
|
708
|
+
behind,
|
|
709
|
+
working,
|
|
710
|
+
head: headParts.join(" "),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function formatGitSegment(data: TuiData, sym: SymbolSet): string {
|
|
715
|
+
const parts = formatGitParts(data, sym);
|
|
716
|
+
if (!parts.icon) return "";
|
|
717
|
+
let text = `${parts.icon} ${parts.branch} ${parts.status}`;
|
|
718
|
+
if (parts.ahead) text += ` ${parts.ahead}`;
|
|
719
|
+
if (parts.behind) text += `${parts.behind}`;
|
|
720
|
+
if (parts.working) text += ` ${parts.working}`;
|
|
721
|
+
return text;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function formatDirParts(
|
|
725
|
+
data: TuiData,
|
|
726
|
+
config: PowerlineConfig,
|
|
727
|
+
sym: SymbolSet,
|
|
728
|
+
): Record<string, string> {
|
|
729
|
+
return { icon: sym.dir, value: formatDirValue(data, config) };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function formatDirValue(data: TuiData, config: PowerlineConfig): string {
|
|
733
|
+
const raw = getDirectoryDisplay(data.hookData);
|
|
734
|
+
const dirConfig = config.display.lines
|
|
735
|
+
.map((line) => line.segments.directory)
|
|
736
|
+
.find((d) => d?.enabled);
|
|
737
|
+
const style =
|
|
738
|
+
dirConfig?.style ?? (dirConfig?.showBasename ? "basename" : "fish");
|
|
739
|
+
if (style === "basename") {
|
|
740
|
+
const sep = raw.includes("/") ? "/" : "\\";
|
|
741
|
+
return raw.split(sep).pop() || raw;
|
|
742
|
+
}
|
|
743
|
+
if (style === "full") return raw;
|
|
744
|
+
return abbreviateFishStyle(raw);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function formatVersionParts(
|
|
748
|
+
data: TuiData,
|
|
749
|
+
sym: SymbolSet,
|
|
750
|
+
): Record<string, string> {
|
|
751
|
+
if (!data.hookData.version) return { icon: "", value: "" };
|
|
752
|
+
return { icon: sym.version, value: `v${data.hookData.version}` };
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function formatVersionSegment(data: TuiData, sym: SymbolSet): string {
|
|
756
|
+
const parts = formatVersionParts(data, sym);
|
|
757
|
+
if (!parts.icon) return "";
|
|
758
|
+
return `${parts.icon} ${parts.value}`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function formatTmuxParts(data: TuiData): Record<string, string> {
|
|
762
|
+
if (!data.tmuxSessionId) return { label: "", value: "" };
|
|
763
|
+
return { label: "tmux", value: data.tmuxSessionId };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function formatTmuxSegment(data: TuiData): string {
|
|
767
|
+
const parts = formatTmuxParts(data);
|
|
768
|
+
if (!parts.label) return "";
|
|
769
|
+
return `${parts.label}:${parts.value}`;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function formatEnvParts(config: PowerlineConfig): Record<string, string> {
|
|
773
|
+
const envConfig = config.display.lines
|
|
774
|
+
.map((line) => line.segments.env)
|
|
775
|
+
.find((env) => env?.enabled);
|
|
776
|
+
|
|
777
|
+
if (!envConfig || !envConfig.variable) return { prefix: "", value: "" };
|
|
778
|
+
const envVal = globalThis.process?.env?.[envConfig.variable];
|
|
779
|
+
if (!envVal) return { prefix: "", value: "" };
|
|
780
|
+
const prefix = envConfig.prefix ?? envConfig.variable;
|
|
781
|
+
return { prefix: prefix || "", value: envVal };
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function formatEnvSegment(config: PowerlineConfig): string {
|
|
785
|
+
const parts = formatEnvParts(config);
|
|
786
|
+
if (!parts.value) return "";
|
|
787
|
+
return parts.prefix ? `${parts.prefix}:${parts.value}` : parts.value;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function addParts(
|
|
791
|
+
result: Record<string, string>,
|
|
792
|
+
segment: string,
|
|
793
|
+
parts: Record<string, string>,
|
|
794
|
+
color: string,
|
|
795
|
+
reset: string,
|
|
796
|
+
partFg?: Record<string, string>,
|
|
797
|
+
): void {
|
|
798
|
+
for (const [key, value] of Object.entries(parts)) {
|
|
799
|
+
const partKey = `${segment}.${key}`;
|
|
800
|
+
const partColor = partFg?.[partKey] ?? partFg?.[segment] ?? color;
|
|
801
|
+
result[partKey] = value ? colorize(value, partColor, reset) : "";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// --- Template Composition ---
|
|
806
|
+
|
|
807
|
+
export interface ResolvedTemplate {
|
|
808
|
+
items: string[];
|
|
809
|
+
gap: number;
|
|
810
|
+
justify: JustifyValue;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function resolveTemplateItems(
|
|
814
|
+
template: SegmentTemplate,
|
|
815
|
+
segmentRef: string,
|
|
816
|
+
resolvedData: Record<string, string>,
|
|
817
|
+
): string[] {
|
|
818
|
+
const dotIdx = segmentRef.indexOf(".");
|
|
819
|
+
const baseSegment = dotIdx !== -1 ? segmentRef.slice(0, dotIdx) : segmentRef;
|
|
820
|
+
|
|
821
|
+
return template.items
|
|
822
|
+
.map((item) => {
|
|
823
|
+
const match = item.match(/^\{(.+)\}$/);
|
|
824
|
+
if (!match) return item ? colorize(item, "", "") : "";
|
|
825
|
+
const partName = match[1]!;
|
|
826
|
+
const key = `${baseSegment}.${partName}`;
|
|
827
|
+
return resolvedData[key] ?? "";
|
|
828
|
+
})
|
|
829
|
+
.filter(Boolean);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export function composeTemplate(
|
|
833
|
+
items: string[],
|
|
834
|
+
gap: number,
|
|
835
|
+
justify: JustifyValue,
|
|
836
|
+
cellWidth?: number,
|
|
837
|
+
): string {
|
|
838
|
+
if (items.length === 0) return "";
|
|
839
|
+
|
|
840
|
+
if (justify === "between" && cellWidth !== undefined && items.length > 1) {
|
|
841
|
+
const totalContent = items.reduce(
|
|
842
|
+
(sum, item) => sum + visibleLength(item),
|
|
843
|
+
0,
|
|
844
|
+
);
|
|
845
|
+
const totalGap = Math.max(
|
|
846
|
+
gap * (items.length - 1),
|
|
847
|
+
cellWidth - totalContent,
|
|
848
|
+
);
|
|
849
|
+
const baseGap = Math.floor(totalGap / (items.length - 1));
|
|
850
|
+
const extraSpaces = totalGap % (items.length - 1);
|
|
851
|
+
|
|
852
|
+
let result = items[0]!;
|
|
853
|
+
for (let i = 1; i < items.length; i++) {
|
|
854
|
+
result += " ".repeat(baseGap + (i <= extraSpaces ? 1 : 0)) + items[i];
|
|
855
|
+
}
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return items.join(" ".repeat(gap));
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export interface ResolvedSegments {
|
|
863
|
+
data: Record<string, string>;
|
|
864
|
+
templates: Record<string, ResolvedTemplate>;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
export function resolveSegments(
|
|
868
|
+
data: TuiData,
|
|
869
|
+
ctx: RenderCtx,
|
|
870
|
+
): ResolvedSegments {
|
|
871
|
+
const { sym, config, reset, colors } = ctx;
|
|
872
|
+
const pf = colors.partFg;
|
|
873
|
+
|
|
874
|
+
const colorizeOrEmpty = (text: string, color: string): string =>
|
|
875
|
+
text ? colorize(text, color, reset) : "";
|
|
876
|
+
|
|
877
|
+
const result: Record<string, string> = {};
|
|
878
|
+
|
|
879
|
+
// Model
|
|
880
|
+
const rawModelName = data.hookData.model?.display_name || "Claude";
|
|
881
|
+
const modelName = formatModelName(rawModelName).toLowerCase();
|
|
882
|
+
const modelColor = pf?.["model"] ?? colors.modelFg;
|
|
883
|
+
result.model = colorizeOrEmpty(`${sym.model} ${modelName}`, modelColor);
|
|
884
|
+
addParts(
|
|
885
|
+
result,
|
|
886
|
+
"model",
|
|
887
|
+
{ icon: sym.model, value: modelName },
|
|
888
|
+
colors.modelFg,
|
|
889
|
+
reset,
|
|
890
|
+
pf,
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
// Context (bar is width-dependent, resolved later via lateResolve)
|
|
894
|
+
const contextLine = buildContextLine(
|
|
895
|
+
data,
|
|
896
|
+
ctx.contentWidth,
|
|
897
|
+
sym,
|
|
898
|
+
reset,
|
|
899
|
+
colors,
|
|
900
|
+
);
|
|
901
|
+
result.context = contextLine ?? "";
|
|
902
|
+
const ctxParts = formatContextParts(data, sym);
|
|
903
|
+
const ctxColor = data.contextInfo
|
|
904
|
+
? resolveThresholdColor(
|
|
905
|
+
data.contextInfo.usablePercentage,
|
|
906
|
+
colors.contextFg,
|
|
907
|
+
colors,
|
|
908
|
+
)
|
|
909
|
+
: colors.contextFg;
|
|
910
|
+
addParts(result, "context", ctxParts, ctxColor, reset, pf);
|
|
911
|
+
|
|
912
|
+
// Block
|
|
913
|
+
if (data.blockInfo) {
|
|
914
|
+
const blockColor = pf?.["block"] ?? colors.blockFg;
|
|
915
|
+
result.block = colorizeOrEmpty(
|
|
916
|
+
formatBlockSegment(data.blockInfo, sym, config),
|
|
917
|
+
blockColor,
|
|
918
|
+
);
|
|
919
|
+
addParts(
|
|
920
|
+
result,
|
|
921
|
+
"block",
|
|
922
|
+
formatBlockParts(data.blockInfo, sym, config),
|
|
923
|
+
colors.blockFg,
|
|
924
|
+
reset,
|
|
925
|
+
pf,
|
|
926
|
+
);
|
|
927
|
+
} else {
|
|
928
|
+
result.block = "";
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Session
|
|
932
|
+
if (data.usageInfo) {
|
|
933
|
+
const sessionColor = pf?.["session"] ?? colors.sessionFg;
|
|
934
|
+
result.session = colorizeOrEmpty(
|
|
935
|
+
formatSessionSegment(data.usageInfo, sym, config),
|
|
936
|
+
sessionColor,
|
|
937
|
+
);
|
|
938
|
+
addParts(
|
|
939
|
+
result,
|
|
940
|
+
"session",
|
|
941
|
+
formatSessionParts(data.usageInfo, sym, config),
|
|
942
|
+
colors.sessionFg,
|
|
943
|
+
reset,
|
|
944
|
+
pf,
|
|
945
|
+
);
|
|
946
|
+
} else {
|
|
947
|
+
result.session = "";
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Today
|
|
951
|
+
if (data.todayInfo) {
|
|
952
|
+
const todayColor = pf?.["today"] ?? colors.todayFg;
|
|
953
|
+
result.today = colorizeOrEmpty(
|
|
954
|
+
formatTodaySegment(data.todayInfo, sym, config),
|
|
955
|
+
todayColor,
|
|
956
|
+
);
|
|
957
|
+
addParts(
|
|
958
|
+
result,
|
|
959
|
+
"today",
|
|
960
|
+
formatTodayParts(data.todayInfo, sym, config),
|
|
961
|
+
colors.todayFg,
|
|
962
|
+
reset,
|
|
963
|
+
pf,
|
|
964
|
+
);
|
|
965
|
+
} else {
|
|
966
|
+
result.today = "";
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Weekly
|
|
970
|
+
const sevenDay = data.hookData.rate_limits?.seven_day;
|
|
971
|
+
if (sevenDay) {
|
|
972
|
+
const weeklyColor = pf?.["weekly"] ?? colors.weeklyFg;
|
|
973
|
+
result.weekly = colorizeOrEmpty(
|
|
974
|
+
formatWeeklySegment(sevenDay, sym),
|
|
975
|
+
weeklyColor,
|
|
976
|
+
);
|
|
977
|
+
addParts(
|
|
978
|
+
result,
|
|
979
|
+
"weekly",
|
|
980
|
+
formatWeeklyParts(sevenDay, sym),
|
|
981
|
+
colors.weeklyFg,
|
|
982
|
+
reset,
|
|
983
|
+
pf,
|
|
984
|
+
);
|
|
985
|
+
} else {
|
|
986
|
+
result.weekly = "";
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Git
|
|
990
|
+
const gitColor = pf?.["git"] ?? colors.gitFg;
|
|
991
|
+
result.git = colorizeOrEmpty(formatGitSegment(data, sym), gitColor);
|
|
992
|
+
addParts(result, "git", formatGitParts(data, sym), colors.gitFg, reset, pf);
|
|
993
|
+
|
|
994
|
+
// Dir
|
|
995
|
+
const dirColor = pf?.["dir"] ?? colors.modeFg;
|
|
996
|
+
result.dir = colorizeOrEmpty(formatDirValue(data, config), dirColor);
|
|
997
|
+
addParts(
|
|
998
|
+
result,
|
|
999
|
+
"dir",
|
|
1000
|
+
formatDirParts(data, config, sym),
|
|
1001
|
+
colors.modeFg,
|
|
1002
|
+
reset,
|
|
1003
|
+
pf,
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
// Version
|
|
1007
|
+
const versionColor = pf?.["version"] ?? colors.versionFg;
|
|
1008
|
+
result.version = colorizeOrEmpty(
|
|
1009
|
+
formatVersionSegment(data, sym),
|
|
1010
|
+
versionColor,
|
|
1011
|
+
);
|
|
1012
|
+
addParts(
|
|
1013
|
+
result,
|
|
1014
|
+
"version",
|
|
1015
|
+
formatVersionParts(data, sym),
|
|
1016
|
+
colors.versionFg,
|
|
1017
|
+
reset,
|
|
1018
|
+
pf,
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
// Tmux
|
|
1022
|
+
const tmuxColor = pf?.["tmux"] ?? colors.tmuxFg;
|
|
1023
|
+
result.tmux = colorizeOrEmpty(formatTmuxSegment(data), tmuxColor);
|
|
1024
|
+
addParts(result, "tmux", formatTmuxParts(data), colors.tmuxFg, reset, pf);
|
|
1025
|
+
|
|
1026
|
+
// Metrics
|
|
1027
|
+
const metricsColor = pf?.["metrics"] ?? colors.metricsFg;
|
|
1028
|
+
result.metrics = colorizeOrEmpty(
|
|
1029
|
+
formatMetricsSegment(data, sym),
|
|
1030
|
+
metricsColor,
|
|
1031
|
+
);
|
|
1032
|
+
addParts(
|
|
1033
|
+
result,
|
|
1034
|
+
"metrics",
|
|
1035
|
+
formatMetricsParts(data, sym),
|
|
1036
|
+
colors.metricsFg,
|
|
1037
|
+
reset,
|
|
1038
|
+
pf,
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
// Activity
|
|
1042
|
+
const activityColor = pf?.["activity"] ?? colors.metricsFg;
|
|
1043
|
+
result.activity = colorizeOrEmpty(
|
|
1044
|
+
formatActivitySegment(data, sym),
|
|
1045
|
+
activityColor,
|
|
1046
|
+
);
|
|
1047
|
+
addParts(
|
|
1048
|
+
result,
|
|
1049
|
+
"activity",
|
|
1050
|
+
formatActivityParts(data, sym),
|
|
1051
|
+
colors.metricsFg,
|
|
1052
|
+
reset,
|
|
1053
|
+
pf,
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
// Env
|
|
1057
|
+
const envColor = pf?.["env"] ?? colors.envFg;
|
|
1058
|
+
result.env = colorizeOrEmpty(formatEnvSegment(config), envColor);
|
|
1059
|
+
addParts(result, "env", formatEnvParts(config), colors.envFg, reset, pf);
|
|
1060
|
+
|
|
1061
|
+
// Apply segment templates: resolve items and compose default value
|
|
1062
|
+
const templates: Record<string, ResolvedTemplate> = {};
|
|
1063
|
+
const segmentConfigs = config.display.tui?.segments;
|
|
1064
|
+
if (segmentConfigs) {
|
|
1065
|
+
for (const [segRef, tmpl] of Object.entries(segmentConfigs)) {
|
|
1066
|
+
const items = resolveTemplateItems(tmpl, segRef, result);
|
|
1067
|
+
const gap = tmpl.gap ?? 1;
|
|
1068
|
+
const justify = tmpl.justify ?? "start";
|
|
1069
|
+
templates[segRef] = { items, gap, justify };
|
|
1070
|
+
// Compose default (without cell width for "between")
|
|
1071
|
+
result[segRef] = composeTemplate(
|
|
1072
|
+
items,
|
|
1073
|
+
gap,
|
|
1074
|
+
justify === "between" ? "start" : justify,
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return { data: result, templates };
|
|
1080
|
+
}
|