@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.
- package/dist/.build-revision +1 -2
- package/dist/browser.d.ts +100 -102
- package/dist/browser.js +3 -3
- package/dist/index.mjs +14 -14
- package/package.json +3 -3
- package/src/config/defaults.ts +5 -1
- package/src/powerline-layout.ts +174 -0
- package/src/powerline-segments.ts +374 -0
- package/src/powerline-theme.ts +323 -0
- package/src/powerline.ts +53 -835
- package/src/segments/git.ts +3 -3
- package/src/segments/pricing.ts +78 -1
- package/src/segments/renderer-bars.ts +83 -0
- package/src/segments/renderer-format.ts +89 -0
- package/src/segments/renderer-types.ts +171 -0
- package/src/segments/renderer.ts +80 -328
- package/src/segments/session.ts +29 -5
- package/src/segments/today.ts +11 -13
- package/src/tui/sections-bars.ts +215 -0
- package/src/tui/sections-parts.ts +604 -0
- package/src/tui/sections-shared.ts +23 -0
- package/src/tui/sections.ts +67 -819
- package/src/utils/budget.ts +1 -1
- package/src/utils/claude.ts +55 -16
- package/src/utils/formatters.ts +12 -5
- package/src/utils/terminal.ts +5 -5
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import type { PowerlineConfig } from "../config/loader";
|
|
2
|
+
import type { PowerlineColors } from "../themes";
|
|
3
|
+
import type { TuiData, SymbolSet } from "./types";
|
|
4
|
+
import {
|
|
5
|
+
formatCost,
|
|
6
|
+
formatTokenCount,
|
|
7
|
+
formatDuration,
|
|
8
|
+
formatResponseTime,
|
|
9
|
+
formatTimeRemaining,
|
|
10
|
+
formatLongTimeRemaining,
|
|
11
|
+
minutesUntilReset,
|
|
12
|
+
abbreviateFishStyle,
|
|
13
|
+
formatCacheTimerElapsed,
|
|
14
|
+
} from "../utils/formatters";
|
|
15
|
+
import { resolveBudgetDisplay } from "../utils/budget";
|
|
16
|
+
import { getEffortLevel, getThinkingEnabled } from "../utils/claude";
|
|
17
|
+
import { getDirectoryDisplay } from "./sections-shared";
|
|
18
|
+
|
|
19
|
+
export function formatContextParts(
|
|
20
|
+
data: TuiData,
|
|
21
|
+
sym: SymbolSet,
|
|
22
|
+
iconVisible = true,
|
|
23
|
+
): Record<string, string> {
|
|
24
|
+
if (!data.contextInfo)
|
|
25
|
+
return { icon: "", label: "context", bar: "", pct: "", tokens: "" };
|
|
26
|
+
|
|
27
|
+
const usedPct = data.contextInfo.usablePercentage;
|
|
28
|
+
const tokenStr = formatTokenCount(data.contextInfo.totalTokens);
|
|
29
|
+
const maxStr = formatTokenCount(data.contextInfo.maxTokens);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
icon: iconVisible ? sym.context_time : "",
|
|
33
|
+
label: "context",
|
|
34
|
+
bar: " ",
|
|
35
|
+
pct: `${usedPct}%`,
|
|
36
|
+
tokens: `${tokenStr}/${maxStr}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatBlockParts(
|
|
41
|
+
blockInfo: TuiData["blockInfo"] & {},
|
|
42
|
+
sym: SymbolSet,
|
|
43
|
+
_config: PowerlineConfig,
|
|
44
|
+
iconVisible = true,
|
|
45
|
+
): Record<string, string> {
|
|
46
|
+
const value = `${Math.round(blockInfo.nativeUtilization)}%`;
|
|
47
|
+
const time = formatTimeRemaining(blockInfo.timeRemaining);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
icon: iconVisible ? sym.block_cost : "",
|
|
51
|
+
label: "block",
|
|
52
|
+
value,
|
|
53
|
+
time,
|
|
54
|
+
budget: "",
|
|
55
|
+
bar: " ",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatBlockSegment(
|
|
60
|
+
blockInfo: TuiData["blockInfo"] & {},
|
|
61
|
+
sym: SymbolSet,
|
|
62
|
+
config: PowerlineConfig,
|
|
63
|
+
iconVisible = true,
|
|
64
|
+
): string {
|
|
65
|
+
const parts = formatBlockParts(blockInfo, sym, config, iconVisible);
|
|
66
|
+
let text = parts.icon ? `${parts.icon} ${parts.value}` : (parts.value ?? "");
|
|
67
|
+
if (parts.time) text += ` · ${parts.time}`;
|
|
68
|
+
if (parts.budget) text += parts.budget;
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function formatWeeklyParts(
|
|
73
|
+
sevenDay: { used_percentage: number; resets_at: number },
|
|
74
|
+
sym: SymbolSet,
|
|
75
|
+
iconVisible = true,
|
|
76
|
+
): Record<string, string> {
|
|
77
|
+
const pct = `${Math.round(sevenDay.used_percentage)}%`;
|
|
78
|
+
const time = formatLongTimeRemaining(minutesUntilReset(sevenDay.resets_at));
|
|
79
|
+
return {
|
|
80
|
+
icon: iconVisible ? sym.weekly_cost : "",
|
|
81
|
+
label: "weekly",
|
|
82
|
+
pct,
|
|
83
|
+
time,
|
|
84
|
+
bar: " ",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function formatWeeklySegment(
|
|
89
|
+
sevenDay: { used_percentage: number; resets_at: number },
|
|
90
|
+
sym: SymbolSet,
|
|
91
|
+
iconVisible = true,
|
|
92
|
+
): string {
|
|
93
|
+
const parts = formatWeeklyParts(sevenDay, sym, iconVisible);
|
|
94
|
+
let text = parts.icon ? `${parts.icon} ${parts.pct}` : (parts.pct ?? "");
|
|
95
|
+
if (parts.time) text += ` · ${parts.time}`;
|
|
96
|
+
return text;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function formatSessionParts(
|
|
100
|
+
usageInfo: TuiData["usageInfo"] & {},
|
|
101
|
+
sym: SymbolSet,
|
|
102
|
+
config: PowerlineConfig,
|
|
103
|
+
iconVisible = true,
|
|
104
|
+
): Record<string, string> {
|
|
105
|
+
const state = resolveBudgetDisplay(
|
|
106
|
+
usageInfo.session.cost,
|
|
107
|
+
usageInfo.session.tokens,
|
|
108
|
+
config.budget?.session,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (state.suppressAll) {
|
|
112
|
+
return { icon: "", label: "", cost: "", tokens: "", budget: "" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const sessionTokens = usageInfo.session.tokens;
|
|
116
|
+
const tokenStr =
|
|
117
|
+
state.showBase && sessionTokens !== null && sessionTokens > 0
|
|
118
|
+
? formatTokenCount(sessionTokens)
|
|
119
|
+
: "";
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
icon: iconVisible ? sym.session_cost : "",
|
|
123
|
+
label: state.percentageOnly ? "" : "session",
|
|
124
|
+
cost: state.showBase ? formatCost(usageInfo.session.cost) : "",
|
|
125
|
+
tokens: tokenStr,
|
|
126
|
+
budget: state.percentText ? ` ${state.percentText}` : "",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function formatSessionSegment(
|
|
131
|
+
usageInfo: TuiData["usageInfo"] & {},
|
|
132
|
+
sym: SymbolSet,
|
|
133
|
+
config: PowerlineConfig,
|
|
134
|
+
iconVisible = true,
|
|
135
|
+
): string {
|
|
136
|
+
const state = resolveBudgetDisplay(
|
|
137
|
+
usageInfo.session.cost,
|
|
138
|
+
usageInfo.session.tokens,
|
|
139
|
+
config.budget?.session,
|
|
140
|
+
);
|
|
141
|
+
if (state.suppressAll) return "";
|
|
142
|
+
|
|
143
|
+
const icon = iconVisible ? sym.session_cost : "";
|
|
144
|
+
|
|
145
|
+
if (!state.showBase) {
|
|
146
|
+
return icon ? `${icon} ${state.percentText}` : state.percentText;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const costStr = formatCost(usageInfo.session.cost);
|
|
150
|
+
const sessionTokens = usageInfo.session.tokens;
|
|
151
|
+
let text = icon ? `${icon} ${costStr}` : costStr;
|
|
152
|
+
if (sessionTokens !== null && sessionTokens > 0) {
|
|
153
|
+
text += ` · ${formatTokenCount(sessionTokens)}`;
|
|
154
|
+
}
|
|
155
|
+
if (state.percentText) text += ` ${state.percentText}`;
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function formatTodayParts(
|
|
160
|
+
todayInfo: TuiData["todayInfo"] & {},
|
|
161
|
+
sym: SymbolSet,
|
|
162
|
+
config: PowerlineConfig,
|
|
163
|
+
iconVisible = true,
|
|
164
|
+
): Record<string, string> {
|
|
165
|
+
const state = resolveBudgetDisplay(
|
|
166
|
+
todayInfo.cost,
|
|
167
|
+
todayInfo.tokens,
|
|
168
|
+
config.budget?.today,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (state.suppressAll) {
|
|
172
|
+
return { icon: "", label: "", cost: "", budget: "" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
icon: iconVisible ? sym.today_cost : "",
|
|
177
|
+
cost: state.showBase ? formatCost(todayInfo.cost) : "",
|
|
178
|
+
label: state.percentageOnly ? "" : "today",
|
|
179
|
+
budget: state.percentText ? ` ${state.percentText}` : "",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function formatTodaySegment(
|
|
184
|
+
todayInfo: TuiData["todayInfo"] & {},
|
|
185
|
+
sym: SymbolSet,
|
|
186
|
+
config: PowerlineConfig,
|
|
187
|
+
iconVisible = true,
|
|
188
|
+
): string {
|
|
189
|
+
const state = resolveBudgetDisplay(
|
|
190
|
+
todayInfo.cost,
|
|
191
|
+
todayInfo.tokens,
|
|
192
|
+
config.budget?.today,
|
|
193
|
+
);
|
|
194
|
+
if (state.suppressAll) return "";
|
|
195
|
+
|
|
196
|
+
const icon = iconVisible ? sym.today_cost : "";
|
|
197
|
+
|
|
198
|
+
if (!state.showBase) {
|
|
199
|
+
return icon ? `${icon} ${state.percentText}` : state.percentText;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const costStr = formatCost(todayInfo.cost);
|
|
203
|
+
let text = icon ? `${icon} ${costStr} today` : `${costStr} today`;
|
|
204
|
+
if (state.percentText) text += ` ${state.percentText}`;
|
|
205
|
+
return text;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function formatMetricsParts(
|
|
209
|
+
data: TuiData,
|
|
210
|
+
sym: SymbolSet,
|
|
211
|
+
): Record<string, string> {
|
|
212
|
+
const empty = {
|
|
213
|
+
response: "",
|
|
214
|
+
responseIcon: "",
|
|
215
|
+
responseVal: "",
|
|
216
|
+
lastResponse: "",
|
|
217
|
+
lastResponseIcon: "",
|
|
218
|
+
lastResponseVal: "",
|
|
219
|
+
added: "",
|
|
220
|
+
addedIcon: "",
|
|
221
|
+
addedVal: "",
|
|
222
|
+
removed: "",
|
|
223
|
+
removedIcon: "",
|
|
224
|
+
removedVal: "",
|
|
225
|
+
};
|
|
226
|
+
if (!data.metricsInfo) return empty;
|
|
227
|
+
|
|
228
|
+
const hasResponse =
|
|
229
|
+
data.metricsInfo.responseTime !== null &&
|
|
230
|
+
!isNaN(data.metricsInfo.responseTime) &&
|
|
231
|
+
data.metricsInfo.responseTime > 0;
|
|
232
|
+
const responseValStr = hasResponse
|
|
233
|
+
? formatResponseTime(data.metricsInfo.responseTime!)
|
|
234
|
+
: "";
|
|
235
|
+
|
|
236
|
+
const hasLast =
|
|
237
|
+
data.metricsInfo.lastResponseTime !== null &&
|
|
238
|
+
!isNaN(data.metricsInfo.lastResponseTime) &&
|
|
239
|
+
data.metricsInfo.lastResponseTime > 0;
|
|
240
|
+
const lastValStr = hasLast
|
|
241
|
+
? formatResponseTime(data.metricsInfo.lastResponseTime!)
|
|
242
|
+
: "";
|
|
243
|
+
|
|
244
|
+
const hasAdded =
|
|
245
|
+
data.metricsInfo.linesAdded !== null && data.metricsInfo.linesAdded > 0;
|
|
246
|
+
const addedValStr = hasAdded ? `${data.metricsInfo.linesAdded}` : "";
|
|
247
|
+
|
|
248
|
+
const hasRemoved =
|
|
249
|
+
data.metricsInfo.linesRemoved !== null && data.metricsInfo.linesRemoved > 0;
|
|
250
|
+
const removedValStr = hasRemoved ? `${data.metricsInfo.linesRemoved}` : "";
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
response: hasResponse ? `${sym.metrics_response} ${responseValStr}` : "",
|
|
254
|
+
responseIcon: hasResponse ? sym.metrics_response : "",
|
|
255
|
+
responseVal: responseValStr,
|
|
256
|
+
lastResponse: hasLast
|
|
257
|
+
? `${sym.metrics_last_response} ${lastValStr}`
|
|
258
|
+
: `${sym.metrics_last_response} --`,
|
|
259
|
+
lastResponseIcon: sym.metrics_last_response,
|
|
260
|
+
lastResponseVal: hasLast ? lastValStr : "--",
|
|
261
|
+
added: hasAdded ? `${sym.metrics_lines_added}${addedValStr}` : "",
|
|
262
|
+
addedIcon: hasAdded ? sym.metrics_lines_added : "",
|
|
263
|
+
addedVal: addedValStr,
|
|
264
|
+
removed: hasRemoved ? `${sym.metrics_lines_removed}${removedValStr}` : "",
|
|
265
|
+
removedIcon: hasRemoved ? sym.metrics_lines_removed : "",
|
|
266
|
+
removedVal: removedValStr,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function formatMetricsSegment(data: TuiData, sym: SymbolSet): string {
|
|
271
|
+
const parts = formatMetricsParts(data, sym);
|
|
272
|
+
const filled = [
|
|
273
|
+
parts.response,
|
|
274
|
+
parts.lastResponse,
|
|
275
|
+
parts.added,
|
|
276
|
+
parts.removed,
|
|
277
|
+
].filter(Boolean);
|
|
278
|
+
return filled.length > 0 ? filled.join(" · ") : "";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function formatActivityParts(
|
|
282
|
+
data: TuiData,
|
|
283
|
+
sym: SymbolSet,
|
|
284
|
+
): Record<string, string> {
|
|
285
|
+
const empty = {
|
|
286
|
+
icon: "",
|
|
287
|
+
duration: "",
|
|
288
|
+
durationIcon: "",
|
|
289
|
+
durationVal: "",
|
|
290
|
+
messages: "",
|
|
291
|
+
messagesIcon: "",
|
|
292
|
+
messagesVal: "",
|
|
293
|
+
};
|
|
294
|
+
if (!data.metricsInfo) return empty;
|
|
295
|
+
|
|
296
|
+
const hasDuration =
|
|
297
|
+
data.metricsInfo.sessionDuration !== null &&
|
|
298
|
+
data.metricsInfo.sessionDuration > 0;
|
|
299
|
+
const durationValStr = hasDuration
|
|
300
|
+
? formatDuration(data.metricsInfo.sessionDuration!)
|
|
301
|
+
: "";
|
|
302
|
+
|
|
303
|
+
const hasMessages =
|
|
304
|
+
data.metricsInfo.messageCount !== null && data.metricsInfo.messageCount > 0;
|
|
305
|
+
const messagesValStr = hasMessages ? `${data.metricsInfo.messageCount}` : "";
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
icon: sym.activity,
|
|
309
|
+
duration: hasDuration ? `${sym.metrics_duration} ${durationValStr}` : "",
|
|
310
|
+
durationIcon: hasDuration ? sym.metrics_duration : "",
|
|
311
|
+
durationVal: durationValStr,
|
|
312
|
+
messages: hasMessages ? `${sym.metrics_messages} ${messagesValStr}` : "",
|
|
313
|
+
messagesIcon: hasMessages ? sym.metrics_messages : "",
|
|
314
|
+
messagesVal: messagesValStr,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function formatActivitySegment(data: TuiData, sym: SymbolSet): string {
|
|
319
|
+
const parts = formatActivityParts(data, sym);
|
|
320
|
+
const filled = [parts.duration, parts.messages].filter(Boolean);
|
|
321
|
+
return filled.length > 0 ? filled.join(" · ") : "";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function formatGitParts(
|
|
325
|
+
data: TuiData,
|
|
326
|
+
sym: SymbolSet,
|
|
327
|
+
iconVisible = true,
|
|
328
|
+
): Record<string, string> {
|
|
329
|
+
if (!data.gitInfo)
|
|
330
|
+
return {
|
|
331
|
+
icon: "",
|
|
332
|
+
headVal: "",
|
|
333
|
+
branch: "",
|
|
334
|
+
status: "",
|
|
335
|
+
ahead: "",
|
|
336
|
+
behind: "",
|
|
337
|
+
working: "",
|
|
338
|
+
head: "",
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
let statusIcon: string;
|
|
342
|
+
if (data.gitInfo.status === "conflicts") {
|
|
343
|
+
statusIcon = sym.git_conflicts;
|
|
344
|
+
} else if (data.gitInfo.status === "dirty") {
|
|
345
|
+
statusIcon = sym.git_dirty;
|
|
346
|
+
} else if (data.gitInfo.status === "unknown") {
|
|
347
|
+
statusIcon = "?";
|
|
348
|
+
} else {
|
|
349
|
+
statusIcon = sym.git_clean;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const ahead =
|
|
353
|
+
data.gitInfo.ahead > 0 ? `${sym.git_ahead}${data.gitInfo.ahead}` : "";
|
|
354
|
+
const behind =
|
|
355
|
+
data.gitInfo.behind > 0 ? `${sym.git_behind}${data.gitInfo.behind}` : "";
|
|
356
|
+
|
|
357
|
+
const counts: string[] = [];
|
|
358
|
+
if (data.gitInfo.staged && data.gitInfo.staged > 0)
|
|
359
|
+
counts.push(`+${data.gitInfo.staged}`);
|
|
360
|
+
if (data.gitInfo.unstaged && data.gitInfo.unstaged > 0)
|
|
361
|
+
counts.push(`~${data.gitInfo.unstaged}`);
|
|
362
|
+
if (data.gitInfo.untracked && data.gitInfo.untracked > 0)
|
|
363
|
+
counts.push(`?${data.gitInfo.untracked}`);
|
|
364
|
+
const working = counts.length > 0 ? `(${counts.join(" ")})` : "";
|
|
365
|
+
|
|
366
|
+
const headParts: string[] = [];
|
|
367
|
+
if (iconVisible) headParts.push(sym.branch);
|
|
368
|
+
headParts.push(data.gitInfo.branch, statusIcon);
|
|
369
|
+
if (ahead) headParts.push(ahead);
|
|
370
|
+
if (behind) headParts.push(behind);
|
|
371
|
+
|
|
372
|
+
const infoParts = [data.gitInfo.branch, statusIcon];
|
|
373
|
+
if (ahead) infoParts.push(ahead);
|
|
374
|
+
if (behind) infoParts.push(behind);
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
icon: iconVisible ? sym.branch : "",
|
|
378
|
+
headVal: infoParts.join(" "),
|
|
379
|
+
branch: data.gitInfo.branch,
|
|
380
|
+
status: statusIcon,
|
|
381
|
+
ahead,
|
|
382
|
+
behind,
|
|
383
|
+
working,
|
|
384
|
+
head: headParts.join(" "),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function formatGitSegment(
|
|
389
|
+
data: TuiData,
|
|
390
|
+
sym: SymbolSet,
|
|
391
|
+
iconVisible = true,
|
|
392
|
+
): string {
|
|
393
|
+
const parts = formatGitParts(data, sym, iconVisible);
|
|
394
|
+
if (!parts.branch) return "";
|
|
395
|
+
let text = parts.icon
|
|
396
|
+
? `${parts.icon} ${parts.branch} ${parts.status}`
|
|
397
|
+
: `${parts.branch} ${parts.status}`;
|
|
398
|
+
if (parts.ahead) text += ` ${parts.ahead}`;
|
|
399
|
+
if (parts.behind) text += `${parts.behind}`;
|
|
400
|
+
if (parts.working) text += ` ${parts.working}`;
|
|
401
|
+
return text;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function formatDirParts(
|
|
405
|
+
data: TuiData,
|
|
406
|
+
config: PowerlineConfig,
|
|
407
|
+
sym: SymbolSet,
|
|
408
|
+
iconVisible = true,
|
|
409
|
+
): Record<string, string> {
|
|
410
|
+
return {
|
|
411
|
+
icon: iconVisible ? sym.dir : "",
|
|
412
|
+
value: formatDirValue(data, config),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function formatDirValue(data: TuiData, config: PowerlineConfig): string {
|
|
417
|
+
const raw = getDirectoryDisplay(data.hookData);
|
|
418
|
+
const dirConfig = config.display.lines
|
|
419
|
+
.map((line) => line.segments.directory)
|
|
420
|
+
.find((d) => d?.enabled);
|
|
421
|
+
const style =
|
|
422
|
+
dirConfig?.style ?? (dirConfig?.showBasename ? "basename" : "fish");
|
|
423
|
+
if (style === "basename") {
|
|
424
|
+
const sep = raw.includes("/") ? "/" : "\\";
|
|
425
|
+
return raw.split(sep).pop() || raw;
|
|
426
|
+
}
|
|
427
|
+
if (style === "full") return raw;
|
|
428
|
+
return abbreviateFishStyle(raw);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function formatVersionParts(
|
|
432
|
+
data: TuiData,
|
|
433
|
+
sym: SymbolSet,
|
|
434
|
+
iconVisible = true,
|
|
435
|
+
): Record<string, string> {
|
|
436
|
+
if (!data.hookData.version) return { icon: "", value: "" };
|
|
437
|
+
return {
|
|
438
|
+
icon: iconVisible ? sym.version : "",
|
|
439
|
+
value: `v${data.hookData.version}`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function formatVersionSegment(
|
|
444
|
+
data: TuiData,
|
|
445
|
+
sym: SymbolSet,
|
|
446
|
+
iconVisible = true,
|
|
447
|
+
): string {
|
|
448
|
+
const parts = formatVersionParts(data, sym, iconVisible);
|
|
449
|
+
if (!parts.value) return "";
|
|
450
|
+
return parts.icon ? `${parts.icon} ${parts.value}` : parts.value;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function formatAgentParts(
|
|
454
|
+
data: TuiData,
|
|
455
|
+
sym: SymbolSet,
|
|
456
|
+
iconVisible = true,
|
|
457
|
+
): Record<string, string> {
|
|
458
|
+
const raw = data.hookData.agent?.name;
|
|
459
|
+
if (typeof raw !== "string") return { icon: "", name: "" };
|
|
460
|
+
const name = raw.trim();
|
|
461
|
+
if (!name) return { icon: "", name: "" };
|
|
462
|
+
return {
|
|
463
|
+
icon: iconVisible ? sym.agent : "",
|
|
464
|
+
name,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function formatAgentSegment(
|
|
469
|
+
data: TuiData,
|
|
470
|
+
sym: SymbolSet,
|
|
471
|
+
config: PowerlineConfig,
|
|
472
|
+
iconVisible = true,
|
|
473
|
+
): string {
|
|
474
|
+
const parts = formatAgentParts(data, sym, iconVisible);
|
|
475
|
+
if (!parts.name) return "";
|
|
476
|
+
const agentConfig = config.display.lines
|
|
477
|
+
.map((line) => line.segments.agent)
|
|
478
|
+
.find((a) => a?.enabled);
|
|
479
|
+
const body = agentConfig?.showLabel ? `agent: ${parts.name}` : parts.name;
|
|
480
|
+
return parts.icon ? `${parts.icon} ${body}` : body;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function buildThinkingBody(
|
|
484
|
+
data: TuiData,
|
|
485
|
+
thinkingConfig: { showEnabled?: boolean; showEffort?: boolean } | undefined,
|
|
486
|
+
): string {
|
|
487
|
+
const showEnabled = thinkingConfig?.showEnabled ?? true;
|
|
488
|
+
const showEffort = thinkingConfig?.showEffort ?? true;
|
|
489
|
+
if (!showEnabled && !showEffort) return "";
|
|
490
|
+
|
|
491
|
+
const enabled = showEnabled ? getThinkingEnabled(data.hookData) : null;
|
|
492
|
+
const level = showEffort ? getEffortLevel(data.hookData) : null;
|
|
493
|
+
|
|
494
|
+
const segments: string[] = [];
|
|
495
|
+
if (enabled !== null) segments.push(enabled ? "On" : "Off");
|
|
496
|
+
if (level) segments.push(level);
|
|
497
|
+
return segments.join(" · ");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function formatThinkingParts(
|
|
501
|
+
data: TuiData,
|
|
502
|
+
sym: SymbolSet,
|
|
503
|
+
thinkingConfig: { showEnabled?: boolean; showEffort?: boolean } | undefined,
|
|
504
|
+
iconVisible = true,
|
|
505
|
+
): Record<string, string> {
|
|
506
|
+
const showEnabled = thinkingConfig?.showEnabled ?? true;
|
|
507
|
+
const showEffort = thinkingConfig?.showEffort ?? true;
|
|
508
|
+
const enabled = showEnabled ? getThinkingEnabled(data.hookData) : null;
|
|
509
|
+
const level = showEffort ? getEffortLevel(data.hookData) : null;
|
|
510
|
+
|
|
511
|
+
const enabledText = enabled === null ? "" : enabled ? "On" : "Off";
|
|
512
|
+
const effortText = level ?? "";
|
|
513
|
+
const hasAny = enabledText !== "" || effortText !== "";
|
|
514
|
+
return {
|
|
515
|
+
icon: hasAny && iconVisible ? sym.thinking : "",
|
|
516
|
+
enabled: enabledText,
|
|
517
|
+
effort: effortText,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function formatThinkingSegment(
|
|
522
|
+
data: TuiData,
|
|
523
|
+
sym: SymbolSet,
|
|
524
|
+
thinkingConfig: { showEnabled?: boolean; showEffort?: boolean } | undefined,
|
|
525
|
+
iconVisible = true,
|
|
526
|
+
): string {
|
|
527
|
+
const body = buildThinkingBody(data, thinkingConfig);
|
|
528
|
+
if (!body) return "";
|
|
529
|
+
return iconVisible ? `${sym.thinking} ${body}` : body;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export function formatCacheTimerParts(
|
|
533
|
+
data: TuiData,
|
|
534
|
+
sym: SymbolSet,
|
|
535
|
+
iconVisible = true,
|
|
536
|
+
): Record<string, string> {
|
|
537
|
+
if (!data.cacheTimerInfo) return { icon: "", value: "" };
|
|
538
|
+
return {
|
|
539
|
+
icon: iconVisible ? sym.cache_timer : "",
|
|
540
|
+
value: formatCacheTimerElapsed(data.cacheTimerInfo.elapsedSeconds),
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export function formatCacheTimerSegment(
|
|
545
|
+
data: TuiData,
|
|
546
|
+
sym: SymbolSet,
|
|
547
|
+
iconVisible = true,
|
|
548
|
+
): string {
|
|
549
|
+
const parts = formatCacheTimerParts(data, sym, iconVisible);
|
|
550
|
+
if (!parts.value) return "";
|
|
551
|
+
return parts.icon ? `${parts.icon} ${parts.value}` : parts.value;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** The TTL the timer colors against: configured, else detected, else the 300s default
|
|
555
|
+
* of cacheTimerStyle — the same order the classic renderer uses. */
|
|
556
|
+
export function cacheTimerTtl(config: PowerlineConfig, data: TuiData): number | undefined {
|
|
557
|
+
const configured = config.display.lines
|
|
558
|
+
.map((line) => line.segments.cacheTimer)
|
|
559
|
+
.find((t) => t?.enabled)?.ttlSeconds;
|
|
560
|
+
return configured ?? data.cacheTimerInfo?.detectedTtlSeconds;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function cacheTimerStyle(
|
|
564
|
+
elapsed: number,
|
|
565
|
+
colors: PowerlineColors,
|
|
566
|
+
ttlSeconds = 300,
|
|
567
|
+
): { fg: string; bold: boolean } {
|
|
568
|
+
if (elapsed >= ttlSeconds) {
|
|
569
|
+
return { fg: colors.contextCriticalFg, bold: colors.contextCriticalBold };
|
|
570
|
+
}
|
|
571
|
+
if (elapsed >= ttlSeconds * 0.6) {
|
|
572
|
+
return { fg: colors.contextWarningFg, bold: colors.contextWarningBold };
|
|
573
|
+
}
|
|
574
|
+
return { fg: colors.cacheTimerFg, bold: colors.cacheTimerBold };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function formatTmuxParts(data: TuiData): Record<string, string> {
|
|
578
|
+
if (!data.tmuxSessionId) return { label: "", value: "" };
|
|
579
|
+
return { label: "tmux", value: data.tmuxSessionId };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function formatTmuxSegment(data: TuiData): string {
|
|
583
|
+
const parts = formatTmuxParts(data);
|
|
584
|
+
if (!parts.label) return "";
|
|
585
|
+
return `${parts.label}:${parts.value}`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export function formatEnvParts(config: PowerlineConfig): Record<string, string> {
|
|
589
|
+
const envConfig = config.display.lines
|
|
590
|
+
.map((line) => line.segments.env)
|
|
591
|
+
.find((env) => env?.enabled);
|
|
592
|
+
|
|
593
|
+
if (!envConfig || !envConfig.variable) return { prefix: "", value: "" };
|
|
594
|
+
const envVal = globalThis.process?.env?.[envConfig.variable];
|
|
595
|
+
if (!envVal) return { prefix: "", value: "" };
|
|
596
|
+
const prefix = envConfig.prefix ?? envConfig.variable;
|
|
597
|
+
return { prefix: prefix || "", value: envVal };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function formatEnvSegment(config: PowerlineConfig): string {
|
|
601
|
+
const parts = formatEnvParts(config);
|
|
602
|
+
if (!parts.value) return "";
|
|
603
|
+
return parts.prefix ? `${parts.prefix}:${parts.value}` : parts.value;
|
|
604
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PowerlineColors } from "../themes";
|
|
2
|
+
import type { TuiData } from "./types";
|
|
3
|
+
import { collapseHome } from "../utils/formatters";
|
|
4
|
+
|
|
5
|
+
export function resolveThresholdStyle(
|
|
6
|
+
pct: number,
|
|
7
|
+
defaultFg: string,
|
|
8
|
+
defaultBold: boolean,
|
|
9
|
+
colors: PowerlineColors,
|
|
10
|
+
warningAt = 60,
|
|
11
|
+
criticalAt = 80,
|
|
12
|
+
): { fg: string; bold: boolean } {
|
|
13
|
+
if (pct >= criticalAt)
|
|
14
|
+
return { fg: colors.contextCriticalFg, bold: colors.contextCriticalBold };
|
|
15
|
+
if (pct >= warningAt)
|
|
16
|
+
return { fg: colors.contextWarningFg, bold: colors.contextWarningBold };
|
|
17
|
+
return { fg: defaultFg, bold: defaultBold };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getDirectoryDisplay(hookData: TuiData["hookData"]): string {
|
|
21
|
+
const currentDir = hookData.workspace?.current_dir || hookData.cwd || "/";
|
|
22
|
+
return collapseHome(currentDir);
|
|
23
|
+
}
|