@owloops/claude-powerline 1.25.1 → 1.26.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.
@@ -22,9 +22,12 @@ import {
22
22
  formatLongTimeRemaining,
23
23
  minutesUntilReset,
24
24
  abbreviateFishStyle,
25
+ formatCacheTimerElapsed,
25
26
  } from "../utils/formatters";
26
- import { getBudgetStatus } from "../utils/budget";
27
+ import { resolveBudgetDisplay } from "../utils/budget";
27
28
  import { colorize, truncateAnsi } from "./primitives";
29
+ import { getEffortLevel, getThinkingEnabled } from "../utils/claude";
30
+ import { resolveIconVisibility } from "../utils/icon-visibility";
28
31
 
29
32
  export function resolveTitleToken(
30
33
  template: string,
@@ -111,16 +114,19 @@ export function buildTitleBar(
111
114
  );
112
115
  }
113
116
 
114
- function resolveThresholdColor(
117
+ function resolveThresholdStyle(
115
118
  pct: number,
116
- defaultColor: string,
119
+ defaultFg: string,
120
+ defaultBold: boolean,
117
121
  colors: PowerlineColors,
118
122
  warningAt = 60,
119
123
  criticalAt = 80,
120
- ): string {
121
- if (pct >= criticalAt) return colors.contextCriticalFg;
122
- if (pct >= warningAt) return colors.contextWarningFg;
123
- return defaultColor;
124
+ ): { fg: string; bold: boolean } {
125
+ if (pct >= criticalAt)
126
+ return { fg: colors.contextCriticalFg, bold: colors.contextCriticalBold };
127
+ if (pct >= warningAt)
128
+ return { fg: colors.contextWarningFg, bold: colors.contextWarningBold };
129
+ return { fg: defaultFg, bold: defaultBold };
124
130
  }
125
131
 
126
132
  function buildBarString(
@@ -129,6 +135,7 @@ function buildBarString(
129
135
  sym: SymbolSet,
130
136
  reset: string,
131
137
  fgColor: string,
138
+ bold = false,
132
139
  ): string {
133
140
  barWidth = Math.max(5, barWidth);
134
141
  const filledCount = Math.max(
@@ -138,12 +145,13 @@ function buildBarString(
138
145
  const emptyCount = barWidth - filledCount;
139
146
  const bar =
140
147
  sym.bar_filled.repeat(filledCount) + sym.bar_empty.repeat(emptyCount);
141
- return colorize(bar, fgColor, reset);
148
+ return colorize(bar, fgColor, reset, bold);
142
149
  }
143
150
 
144
151
  export function formatContextParts(
145
152
  data: TuiData,
146
153
  sym: SymbolSet,
154
+ iconVisible = true,
147
155
  ): Record<string, string> {
148
156
  if (!data.contextInfo)
149
157
  return { icon: "", label: "context", bar: "", pct: "", tokens: "" };
@@ -153,7 +161,7 @@ export function formatContextParts(
153
161
  const maxStr = formatTokenCount(data.contextInfo.maxTokens);
154
162
 
155
163
  return {
156
- icon: sym.context_time,
164
+ icon: iconVisible ? sym.context_time : "",
157
165
  label: "context",
158
166
  bar: " ",
159
167
  pct: `${usedPct}%`,
@@ -173,8 +181,13 @@ export function buildContextBar(
173
181
  const usedPct = data.contextInfo.usablePercentage;
174
182
  const defaultFg =
175
183
  partFg?.["context.bar"] ?? partFg?.["context"] ?? colors.contextFg;
176
- const fgColor = resolveThresholdColor(usedPct, defaultFg, colors);
177
- return buildBarString(usedPct, barWidth, sym, reset, fgColor);
184
+ const { fg, bold } = resolveThresholdStyle(
185
+ usedPct,
186
+ defaultFg,
187
+ colors.contextBold,
188
+ colors,
189
+ );
190
+ return buildBarString(usedPct, barWidth, sym, reset, fg, bold);
178
191
  }
179
192
 
180
193
  export function buildBlockBar(
@@ -192,14 +205,15 @@ export function buildBlockBar(
192
205
  const warningThreshold = config.budget?.block?.warningThreshold ?? 80;
193
206
  const defaultFg =
194
207
  partFg?.["block.bar"] ?? partFg?.["block"] ?? colors.blockFg;
195
- const fgColor = resolveThresholdColor(
208
+ const { fg, bold } = resolveThresholdStyle(
196
209
  pct,
197
210
  defaultFg,
211
+ colors.blockBold,
198
212
  colors,
199
213
  50,
200
214
  warningThreshold,
201
215
  );
202
- return buildBarString(pct, barWidth, sym, reset, fgColor);
216
+ return buildBarString(pct, barWidth, sym, reset, fg, bold);
203
217
  }
204
218
 
205
219
  export function buildWeeklyBar(
@@ -216,8 +230,13 @@ export function buildWeeklyBar(
216
230
  const pct = sevenDay.used_percentage;
217
231
  const defaultFg =
218
232
  partFg?.["weekly.bar"] ?? partFg?.["weekly"] ?? colors.weeklyFg;
219
- const fgColor = resolveThresholdColor(pct, defaultFg, colors);
220
- return buildBarString(pct, barWidth, sym, reset, fgColor);
233
+ const { fg, bold } = resolveThresholdStyle(
234
+ pct,
235
+ defaultFg,
236
+ colors.weeklyBold,
237
+ colors,
238
+ );
239
+ return buildBarString(pct, barWidth, sym, reset, fg, bold);
221
240
  }
222
241
 
223
242
  export function buildContextLine(
@@ -244,9 +263,14 @@ export function buildContextLine(
244
263
  const bar =
245
264
  sym.bar_filled.repeat(filledCount) + sym.bar_empty.repeat(emptyCount);
246
265
 
247
- const fgColor = resolveThresholdColor(usedPct, colors.contextFg, colors);
266
+ const { fg, bold } = resolveThresholdStyle(
267
+ usedPct,
268
+ colors.contextFg,
269
+ colors.contextBold,
270
+ colors,
271
+ );
248
272
 
249
- return colorize(`${bar}${suffix}`, fgColor, reset);
273
+ return colorize(`${bar}${suffix}`, fg, reset, bold);
250
274
  }
251
275
 
252
276
  function getDirectoryDisplay(hookData: TuiData["hookData"]): string {
@@ -266,40 +290,70 @@ export function collectMetricSegments(
266
290
  if (data.blockInfo) {
267
291
  segments.push(
268
292
  colorize(
269
- formatBlockSegment(data.blockInfo, sym, config),
293
+ formatBlockSegment(
294
+ data.blockInfo,
295
+ sym,
296
+ config,
297
+ resolveIconVisibility(config, "block"),
298
+ ),
270
299
  colors.blockFg,
271
300
  reset,
301
+ colors.blockBold,
272
302
  ),
273
303
  );
274
304
  }
275
305
  const sevenDay = data.hookData.rate_limits?.seven_day;
276
306
  if (sevenDay) {
277
- segments.push(
278
- colorize(formatWeeklySegment(sevenDay, sym), colors.weeklyFg, reset),
279
- );
280
- }
281
- if (data.usageInfo) {
282
307
  segments.push(
283
308
  colorize(
284
- formatSessionSegment(data.usageInfo, sym, config),
285
- colors.sessionFg,
309
+ formatWeeklySegment(
310
+ sevenDay,
311
+ sym,
312
+ resolveIconVisibility(config, "weekly"),
313
+ ),
314
+ colors.weeklyFg,
286
315
  reset,
316
+ colors.weeklyBold,
287
317
  ),
288
318
  );
289
319
  }
320
+ if (data.usageInfo) {
321
+ const sessionStr = formatSessionSegment(
322
+ data.usageInfo,
323
+ sym,
324
+ config,
325
+ resolveIconVisibility(config, "session"),
326
+ );
327
+ if (sessionStr) {
328
+ segments.push(
329
+ colorize(sessionStr, colors.sessionFg, reset, colors.sessionBold),
330
+ );
331
+ }
332
+ }
290
333
  if (data.todayInfo) {
291
- segments.push(
292
- colorize(
293
- formatTodaySegment(data.todayInfo, sym, config),
294
- colors.todayFg,
295
- reset,
296
- ),
334
+ const todayStr = formatTodaySegment(
335
+ data.todayInfo,
336
+ sym,
337
+ config,
338
+ resolveIconVisibility(config, "today"),
297
339
  );
340
+ if (todayStr) {
341
+ segments.push(
342
+ colorize(todayStr, colors.todayFg, reset, colors.todayBold),
343
+ );
344
+ }
298
345
  }
299
346
 
300
347
  const activityParts = collectActivityParts(data, sym);
301
348
  if (activityParts.length > 0) {
302
- segments.push(colorize(activityParts.join(" · "), colors.metricsFg, reset));
349
+ segments.push(
350
+ colorize(
351
+ activityParts.join(" · "),
352
+ colors.metricsFg,
353
+ reset,
354
+ colors.metricsBold,
355
+ ),
356
+ );
303
357
  }
304
358
 
305
359
  return segments;
@@ -331,14 +385,19 @@ export function collectWorkspaceParts(
331
385
  sym: SymbolSet,
332
386
  reset: string,
333
387
  colors: PowerlineColors,
388
+ config: PowerlineConfig,
334
389
  ): string[] {
335
390
  const parts: string[] = [];
336
391
 
337
- const gitStr = formatGitSegment(data, sym);
338
- if (gitStr) parts.push(colorize(gitStr, colors.gitFg, reset));
392
+ const gitStr = formatGitSegment(
393
+ data,
394
+ sym,
395
+ resolveIconVisibility(config, "git"),
396
+ );
397
+ if (gitStr) parts.push(colorize(gitStr, colors.gitFg, reset, colors.gitBold));
339
398
 
340
399
  const dir = abbreviateFishStyle(getDirectoryDisplay(data.hookData));
341
- parts.push(colorize(dir, colors.modeFg, reset));
400
+ parts.push(colorize(dir, colors.modeFg, reset, colors.modeBold));
342
401
 
343
402
  return parts;
344
403
  }
@@ -352,18 +411,60 @@ export function collectFooterParts(
352
411
  ): string[] {
353
412
  const parts: string[] = [];
354
413
 
355
- if (data.hookData.version) {
414
+ const versionText = formatVersionSegment(
415
+ data,
416
+ sym,
417
+ resolveIconVisibility(config, "version"),
418
+ );
419
+ if (versionText) {
420
+ parts.push(
421
+ colorize(versionText, colors.versionFg, reset, colors.versionBold),
422
+ );
423
+ }
424
+
425
+ const thinkingSegConfig = config.display.lines
426
+ .map((line) => line.segments.thinking)
427
+ .find((t) => t?.enabled);
428
+ const thinkingText = formatThinkingSegment(
429
+ data,
430
+ sym,
431
+ thinkingSegConfig,
432
+ resolveIconVisibility(config, "thinking"),
433
+ );
434
+ if (thinkingText) {
435
+ parts.push(
436
+ colorize(thinkingText, colors.thinkingFg, reset, colors.thinkingBold),
437
+ );
438
+ }
439
+
440
+ const cacheTimerEnabled = config.display.lines.some(
441
+ (line) => line.segments.cacheTimer?.enabled,
442
+ );
443
+ if (cacheTimerEnabled && data.cacheTimerInfo) {
444
+ const cacheTimerText = formatCacheTimerSegment(
445
+ data,
446
+ sym,
447
+ resolveIconVisibility(config, "cacheTimer"),
448
+ );
449
+ if (cacheTimerText) {
450
+ const { fg, bold } = cacheTimerStyle(
451
+ data.cacheTimerInfo.elapsedSeconds,
452
+ colors,
453
+ );
454
+ parts.push(colorize(cacheTimerText, fg, reset, bold));
455
+ }
456
+ }
457
+
458
+ if (data.tmuxSessionId) {
356
459
  parts.push(
357
460
  colorize(
358
- `${sym.version} v${data.hookData.version}`,
359
- colors.versionFg,
461
+ `tmux:${data.tmuxSessionId}`,
462
+ colors.tmuxFg,
360
463
  reset,
464
+ colors.tmuxBold,
361
465
  ),
362
466
  );
363
467
  }
364
- if (data.tmuxSessionId) {
365
- parts.push(colorize(`tmux:${data.tmuxSessionId}`, colors.tmuxFg, reset));
366
- }
367
468
 
368
469
  if (data.metricsInfo) {
369
470
  const metricParts: string[] = [];
@@ -393,7 +494,14 @@ export function collectFooterParts(
393
494
  );
394
495
  }
395
496
  if (metricParts.length > 0) {
396
- parts.push(colorize(metricParts.join(" · "), colors.metricsFg, reset));
497
+ parts.push(
498
+ colorize(
499
+ metricParts.join(" · "),
500
+ colors.metricsFg,
501
+ reset,
502
+ colors.metricsBold,
503
+ ),
504
+ );
397
505
  }
398
506
  }
399
507
 
@@ -406,7 +514,12 @@ export function collectFooterParts(
406
514
  if (envVal) {
407
515
  const prefix = envConfig.prefix ?? envConfig.variable;
408
516
  parts.push(
409
- colorize(prefix ? `${prefix}:${envVal}` : envVal, colors.envFg, reset),
517
+ colorize(
518
+ prefix ? `${prefix}:${envVal}` : envVal,
519
+ colors.envFg,
520
+ reset,
521
+ colors.envBold,
522
+ ),
410
523
  );
411
524
  }
412
525
  }
@@ -418,12 +531,13 @@ export function formatBlockParts(
418
531
  blockInfo: TuiData["blockInfo"] & {},
419
532
  sym: SymbolSet,
420
533
  _config: PowerlineConfig,
534
+ iconVisible = true,
421
535
  ): Record<string, string> {
422
536
  const value = `${Math.round(blockInfo.nativeUtilization)}%`;
423
537
  const time = formatTimeRemaining(blockInfo.timeRemaining);
424
538
 
425
539
  return {
426
- icon: sym.block_cost,
540
+ icon: iconVisible ? sym.block_cost : "",
427
541
  label: "block",
428
542
  value,
429
543
  time,
@@ -436,9 +550,10 @@ export function formatBlockSegment(
436
550
  blockInfo: TuiData["blockInfo"] & {},
437
551
  sym: SymbolSet,
438
552
  config: PowerlineConfig,
553
+ iconVisible = true,
439
554
  ): string {
440
- const parts = formatBlockParts(blockInfo, sym, config);
441
- let text = `${parts.icon} ${parts.value}`;
555
+ const parts = formatBlockParts(blockInfo, sym, config, iconVisible);
556
+ let text = parts.icon ? `${parts.icon} ${parts.value}` : (parts.value ?? "");
442
557
  if (parts.time) text += ` · ${parts.time}`;
443
558
  if (parts.budget) text += parts.budget;
444
559
  return text;
@@ -447,18 +562,26 @@ export function formatBlockSegment(
447
562
  export function formatWeeklyParts(
448
563
  sevenDay: { used_percentage: number; resets_at: number },
449
564
  sym: SymbolSet,
565
+ iconVisible = true,
450
566
  ): Record<string, string> {
451
567
  const pct = `${Math.round(sevenDay.used_percentage)}%`;
452
568
  const time = formatLongTimeRemaining(minutesUntilReset(sevenDay.resets_at));
453
- return { icon: sym.weekly_cost, label: "weekly", pct, time, bar: " " };
569
+ return {
570
+ icon: iconVisible ? sym.weekly_cost : "",
571
+ label: "weekly",
572
+ pct,
573
+ time,
574
+ bar: " ",
575
+ };
454
576
  }
455
577
 
456
578
  export function formatWeeklySegment(
457
579
  sevenDay: { used_percentage: number; resets_at: number },
458
580
  sym: SymbolSet,
581
+ iconVisible = true,
459
582
  ): string {
460
- const parts = formatWeeklyParts(sevenDay, sym);
461
- let text = `${parts.icon} ${parts.pct}`;
583
+ const parts = formatWeeklyParts(sevenDay, sym, iconVisible);
584
+ let text = parts.icon ? `${parts.icon} ${parts.pct}` : (parts.pct ?? "");
462
585
  if (parts.time) text += ` · ${parts.time}`;
463
586
  return text;
464
587
  }
@@ -467,29 +590,30 @@ export function formatSessionParts(
467
590
  usageInfo: TuiData["usageInfo"] & {},
468
591
  sym: SymbolSet,
469
592
  config: PowerlineConfig,
593
+ iconVisible = true,
470
594
  ): Record<string, string> {
595
+ const state = resolveBudgetDisplay(
596
+ usageInfo.session.cost,
597
+ usageInfo.session.tokens,
598
+ config.budget?.session,
599
+ );
600
+
601
+ if (state.suppressAll) {
602
+ return { icon: "", label: "", cost: "", tokens: "", budget: "" };
603
+ }
604
+
471
605
  const sessionTokens = usageInfo.session.tokens;
472
606
  const tokenStr =
473
- sessionTokens !== null && sessionTokens > 0
607
+ state.showBase && sessionTokens !== null && sessionTokens > 0
474
608
  ? formatTokenCount(sessionTokens)
475
609
  : "";
476
610
 
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
611
  return {
488
- icon: sym.session_cost,
489
- label: "session",
490
- cost: formatCost(usageInfo.session.cost),
612
+ icon: iconVisible ? sym.session_cost : "",
613
+ label: state.percentageOnly ? "" : "session",
614
+ cost: state.showBase ? formatCost(usageInfo.session.cost) : "",
491
615
  tokens: tokenStr,
492
- budget,
616
+ budget: state.percentText ? ` ${state.percentText}` : "",
493
617
  };
494
618
  }
495
619
 
@@ -497,11 +621,28 @@ export function formatSessionSegment(
497
621
  usageInfo: TuiData["usageInfo"] & {},
498
622
  sym: SymbolSet,
499
623
  config: PowerlineConfig,
624
+ iconVisible = true,
500
625
  ): 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;
626
+ const state = resolveBudgetDisplay(
627
+ usageInfo.session.cost,
628
+ usageInfo.session.tokens,
629
+ config.budget?.session,
630
+ );
631
+ if (state.suppressAll) return "";
632
+
633
+ const icon = iconVisible ? sym.session_cost : "";
634
+
635
+ if (!state.showBase) {
636
+ return icon ? `${icon} ${state.percentText}` : state.percentText;
637
+ }
638
+
639
+ const costStr = formatCost(usageInfo.session.cost);
640
+ const sessionTokens = usageInfo.session.tokens;
641
+ let text = icon ? `${icon} ${costStr}` : costStr;
642
+ if (sessionTokens !== null && sessionTokens > 0) {
643
+ text += ` · ${formatTokenCount(sessionTokens)}`;
644
+ }
645
+ if (state.percentText) text += ` ${state.percentText}`;
505
646
  return text;
506
647
  }
507
648
 
@@ -509,22 +650,23 @@ export function formatTodayParts(
509
650
  todayInfo: TuiData["todayInfo"] & {},
510
651
  sym: SymbolSet,
511
652
  config: PowerlineConfig,
653
+ iconVisible = true,
512
654
  ): 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;
655
+ const state = resolveBudgetDisplay(
656
+ todayInfo.cost,
657
+ todayInfo.tokens,
658
+ config.budget?.today,
659
+ );
660
+
661
+ if (state.suppressAll) {
662
+ return { icon: "", label: "", cost: "", budget: "" };
521
663
  }
522
664
 
523
665
  return {
524
- icon: sym.today_cost,
525
- cost: formatCost(todayInfo.cost),
526
- label: "today",
527
- budget,
666
+ icon: iconVisible ? sym.today_cost : "",
667
+ cost: state.showBase ? formatCost(todayInfo.cost) : "",
668
+ label: state.percentageOnly ? "" : "today",
669
+ budget: state.percentText ? ` ${state.percentText}` : "",
528
670
  };
529
671
  }
530
672
 
@@ -532,10 +674,24 @@ export function formatTodaySegment(
532
674
  todayInfo: TuiData["todayInfo"] & {},
533
675
  sym: SymbolSet,
534
676
  config: PowerlineConfig,
677
+ iconVisible = true,
535
678
  ): string {
536
- const parts = formatTodayParts(todayInfo, sym, config);
537
- let text = `${parts.icon} ${parts.cost} ${parts.label}`;
538
- if (parts.budget) text += parts.budget;
679
+ const state = resolveBudgetDisplay(
680
+ todayInfo.cost,
681
+ todayInfo.tokens,
682
+ config.budget?.today,
683
+ );
684
+ if (state.suppressAll) return "";
685
+
686
+ const icon = iconVisible ? sym.today_cost : "";
687
+
688
+ if (!state.showBase) {
689
+ return icon ? `${icon} ${state.percentText}` : state.percentText;
690
+ }
691
+
692
+ const costStr = formatCost(todayInfo.cost);
693
+ let text = icon ? `${icon} ${costStr} today` : `${costStr} today`;
694
+ if (state.percentText) text += ` ${state.percentText}`;
539
695
  return text;
540
696
  }
541
697
 
@@ -655,7 +811,11 @@ function formatActivitySegment(data: TuiData, sym: SymbolSet): string {
655
811
  return filled.length > 0 ? filled.join(" · ") : "";
656
812
  }
657
813
 
658
- function formatGitParts(data: TuiData, sym: SymbolSet): Record<string, string> {
814
+ function formatGitParts(
815
+ data: TuiData,
816
+ sym: SymbolSet,
817
+ iconVisible = true,
818
+ ): Record<string, string> {
659
819
  if (!data.gitInfo)
660
820
  return {
661
821
  icon: "",
@@ -691,7 +851,9 @@ function formatGitParts(data: TuiData, sym: SymbolSet): Record<string, string> {
691
851
  counts.push(`?${data.gitInfo.untracked}`);
692
852
  const working = counts.length > 0 ? `(${counts.join(" ")})` : "";
693
853
 
694
- const headParts = [sym.branch, data.gitInfo.branch, statusIcon];
854
+ const headParts: string[] = [];
855
+ if (iconVisible) headParts.push(sym.branch);
856
+ headParts.push(data.gitInfo.branch, statusIcon);
695
857
  if (ahead) headParts.push(ahead);
696
858
  if (behind) headParts.push(behind);
697
859
 
@@ -700,7 +862,7 @@ function formatGitParts(data: TuiData, sym: SymbolSet): Record<string, string> {
700
862
  if (behind) infoParts.push(behind);
701
863
 
702
864
  return {
703
- icon: sym.branch,
865
+ icon: iconVisible ? sym.branch : "",
704
866
  headVal: infoParts.join(" "),
705
867
  branch: data.gitInfo.branch,
706
868
  status: statusIcon,
@@ -711,10 +873,16 @@ function formatGitParts(data: TuiData, sym: SymbolSet): Record<string, string> {
711
873
  };
712
874
  }
713
875
 
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}`;
876
+ function formatGitSegment(
877
+ data: TuiData,
878
+ sym: SymbolSet,
879
+ iconVisible = true,
880
+ ): string {
881
+ const parts = formatGitParts(data, sym, iconVisible);
882
+ if (!parts.branch) return "";
883
+ let text = parts.icon
884
+ ? `${parts.icon} ${parts.branch} ${parts.status}`
885
+ : `${parts.branch} ${parts.status}`;
718
886
  if (parts.ahead) text += ` ${parts.ahead}`;
719
887
  if (parts.behind) text += `${parts.behind}`;
720
888
  if (parts.working) text += ` ${parts.working}`;
@@ -725,8 +893,12 @@ function formatDirParts(
725
893
  data: TuiData,
726
894
  config: PowerlineConfig,
727
895
  sym: SymbolSet,
896
+ iconVisible = true,
728
897
  ): Record<string, string> {
729
- return { icon: sym.dir, value: formatDirValue(data, config) };
898
+ return {
899
+ icon: iconVisible ? sym.dir : "",
900
+ value: formatDirValue(data, config),
901
+ };
730
902
  }
731
903
 
732
904
  function formatDirValue(data: TuiData, config: PowerlineConfig): string {
@@ -747,15 +919,137 @@ function formatDirValue(data: TuiData, config: PowerlineConfig): string {
747
919
  function formatVersionParts(
748
920
  data: TuiData,
749
921
  sym: SymbolSet,
922
+ iconVisible = true,
750
923
  ): Record<string, string> {
751
924
  if (!data.hookData.version) return { icon: "", value: "" };
752
- return { icon: sym.version, value: `v${data.hookData.version}` };
925
+ return {
926
+ icon: iconVisible ? sym.version : "",
927
+ value: `v${data.hookData.version}`,
928
+ };
753
929
  }
754
930
 
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}`;
931
+ function formatVersionSegment(
932
+ data: TuiData,
933
+ sym: SymbolSet,
934
+ iconVisible = true,
935
+ ): string {
936
+ const parts = formatVersionParts(data, sym, iconVisible);
937
+ if (!parts.value) return "";
938
+ return parts.icon ? `${parts.icon} ${parts.value}` : parts.value;
939
+ }
940
+
941
+ function formatAgentParts(
942
+ data: TuiData,
943
+ sym: SymbolSet,
944
+ iconVisible = true,
945
+ ): Record<string, string> {
946
+ const raw = data.hookData.agent?.name;
947
+ if (typeof raw !== "string") return { icon: "", name: "" };
948
+ const name = raw.trim();
949
+ if (!name) return { icon: "", name: "" };
950
+ return {
951
+ icon: iconVisible ? sym.agent : "",
952
+ name,
953
+ };
954
+ }
955
+
956
+ function formatAgentSegment(
957
+ data: TuiData,
958
+ sym: SymbolSet,
959
+ config: PowerlineConfig,
960
+ iconVisible = true,
961
+ ): string {
962
+ const parts = formatAgentParts(data, sym, iconVisible);
963
+ if (!parts.name) return "";
964
+ const agentConfig = config.display.lines
965
+ .map((line) => line.segments.agent)
966
+ .find((a) => a?.enabled);
967
+ const body = agentConfig?.showLabel ? `agent: ${parts.name}` : parts.name;
968
+ return parts.icon ? `${parts.icon} ${body}` : body;
969
+ }
970
+
971
+ function buildThinkingBody(
972
+ data: TuiData,
973
+ thinkingConfig: { showEnabled?: boolean; showEffort?: boolean } | undefined,
974
+ ): string {
975
+ const showEnabled = thinkingConfig?.showEnabled ?? true;
976
+ const showEffort = thinkingConfig?.showEffort ?? true;
977
+ if (!showEnabled && !showEffort) return "";
978
+
979
+ const enabled = showEnabled ? getThinkingEnabled(data.hookData) : null;
980
+ const level = showEffort ? getEffortLevel(data.hookData) : null;
981
+
982
+ const segments: string[] = [];
983
+ if (enabled !== null) segments.push(enabled ? "On" : "Off");
984
+ if (level) segments.push(level);
985
+ return segments.join(" · ");
986
+ }
987
+
988
+ function formatThinkingParts(
989
+ data: TuiData,
990
+ sym: SymbolSet,
991
+ thinkingConfig: { showEnabled?: boolean; showEffort?: boolean } | undefined,
992
+ iconVisible = true,
993
+ ): Record<string, string> {
994
+ const showEnabled = thinkingConfig?.showEnabled ?? true;
995
+ const showEffort = thinkingConfig?.showEffort ?? true;
996
+ const enabled = showEnabled ? getThinkingEnabled(data.hookData) : null;
997
+ const level = showEffort ? getEffortLevel(data.hookData) : null;
998
+
999
+ const enabledText = enabled === null ? "" : enabled ? "On" : "Off";
1000
+ const effortText = level ?? "";
1001
+ const hasAny = enabledText !== "" || effortText !== "";
1002
+ return {
1003
+ icon: hasAny && iconVisible ? sym.thinking : "",
1004
+ enabled: enabledText,
1005
+ effort: effortText,
1006
+ };
1007
+ }
1008
+
1009
+ function formatThinkingSegment(
1010
+ data: TuiData,
1011
+ sym: SymbolSet,
1012
+ thinkingConfig: { showEnabled?: boolean; showEffort?: boolean } | undefined,
1013
+ iconVisible = true,
1014
+ ): string {
1015
+ const body = buildThinkingBody(data, thinkingConfig);
1016
+ if (!body) return "";
1017
+ return iconVisible ? `${sym.thinking} ${body}` : body;
1018
+ }
1019
+
1020
+ function formatCacheTimerParts(
1021
+ data: TuiData,
1022
+ sym: SymbolSet,
1023
+ iconVisible = true,
1024
+ ): Record<string, string> {
1025
+ if (!data.cacheTimerInfo) return { icon: "", value: "" };
1026
+ return {
1027
+ icon: iconVisible ? sym.cache_timer : "",
1028
+ value: formatCacheTimerElapsed(data.cacheTimerInfo.elapsedSeconds),
1029
+ };
1030
+ }
1031
+
1032
+ function formatCacheTimerSegment(
1033
+ data: TuiData,
1034
+ sym: SymbolSet,
1035
+ iconVisible = true,
1036
+ ): string {
1037
+ const parts = formatCacheTimerParts(data, sym, iconVisible);
1038
+ if (!parts.value) return "";
1039
+ return parts.icon ? `${parts.icon} ${parts.value}` : parts.value;
1040
+ }
1041
+
1042
+ function cacheTimerStyle(
1043
+ elapsed: number,
1044
+ colors: PowerlineColors,
1045
+ ): { fg: string; bold: boolean } {
1046
+ if (elapsed >= 300) {
1047
+ return { fg: colors.contextCriticalFg, bold: colors.contextCriticalBold };
1048
+ }
1049
+ if (elapsed >= 180) {
1050
+ return { fg: colors.contextWarningFg, bold: colors.contextWarningBold };
1051
+ }
1052
+ return { fg: colors.cacheTimerFg, bold: colors.cacheTimerBold };
759
1053
  }
760
1054
 
761
1055
  function formatTmuxParts(data: TuiData): Record<string, string> {
@@ -794,11 +1088,12 @@ function addParts(
794
1088
  color: string,
795
1089
  reset: string,
796
1090
  partFg?: Record<string, string>,
1091
+ bold = false,
797
1092
  ): void {
798
1093
  for (const [key, value] of Object.entries(parts)) {
799
1094
  const partKey = `${segment}.${key}`;
800
1095
  const partColor = partFg?.[partKey] ?? partFg?.[segment] ?? color;
801
- result[partKey] = value ? colorize(value, partColor, reset) : "";
1096
+ result[partKey] = value ? colorize(value, partColor, reset, bold) : "";
802
1097
  }
803
1098
  }
804
1099
 
@@ -871,23 +1166,47 @@ export function resolveSegments(
871
1166
  const { sym, config, reset, colors } = ctx;
872
1167
  const pf = colors.partFg;
873
1168
 
874
- const colorizeOrEmpty = (text: string, color: string): string =>
875
- text ? colorize(text, color, reset) : "";
1169
+ const colorizeOrEmpty = (
1170
+ text: string,
1171
+ color: string,
1172
+ bold = false,
1173
+ ): string => (text ? colorize(text, color, reset, bold) : "");
876
1174
 
877
1175
  const result: Record<string, string> = {};
878
1176
 
1177
+ const iconVisible = {
1178
+ model: resolveIconVisibility(config, "model"),
1179
+ context: resolveIconVisibility(config, "context"),
1180
+ block: resolveIconVisibility(config, "block"),
1181
+ session: resolveIconVisibility(config, "session"),
1182
+ today: resolveIconVisibility(config, "today"),
1183
+ weekly: resolveIconVisibility(config, "weekly"),
1184
+ git: resolveIconVisibility(config, "git"),
1185
+ directory: resolveIconVisibility(config, "directory"),
1186
+ version: resolveIconVisibility(config, "version"),
1187
+ agent: resolveIconVisibility(config, "agent"),
1188
+ thinking: resolveIconVisibility(config, "thinking"),
1189
+ cacheTimer: resolveIconVisibility(config, "cacheTimer"),
1190
+ };
1191
+
879
1192
  // Model
880
1193
  const rawModelName = data.hookData.model?.display_name || "Claude";
881
1194
  const modelName = formatModelName(rawModelName).toLowerCase();
882
1195
  const modelColor = pf?.["model"] ?? colors.modelFg;
883
- result.model = colorizeOrEmpty(`${sym.model} ${modelName}`, modelColor);
1196
+ const modelIcon = iconVisible.model ? sym.model : "";
1197
+ result.model = colorizeOrEmpty(
1198
+ modelIcon ? `${modelIcon} ${modelName}` : modelName,
1199
+ modelColor,
1200
+ colors.modelBold,
1201
+ );
884
1202
  addParts(
885
1203
  result,
886
1204
  "model",
887
- { icon: sym.model, value: modelName },
1205
+ { icon: modelIcon, value: modelName },
888
1206
  colors.modelFg,
889
1207
  reset,
890
1208
  pf,
1209
+ colors.modelBold,
891
1210
  );
892
1211
 
893
1212
  // Context (bar is width-dependent, resolved later via lateResolve)
@@ -899,30 +1218,33 @@ export function resolveSegments(
899
1218
  colors,
900
1219
  );
901
1220
  result.context = contextLine ?? "";
902
- const ctxParts = formatContextParts(data, sym);
903
- const ctxColor = data.contextInfo
904
- ? resolveThresholdColor(
1221
+ const ctxParts = formatContextParts(data, sym, iconVisible.context);
1222
+ const ctxStyle = data.contextInfo
1223
+ ? resolveThresholdStyle(
905
1224
  data.contextInfo.usablePercentage,
906
1225
  colors.contextFg,
1226
+ colors.contextBold,
907
1227
  colors,
908
1228
  )
909
- : colors.contextFg;
910
- addParts(result, "context", ctxParts, ctxColor, reset, pf);
1229
+ : { fg: colors.contextFg, bold: colors.contextBold };
1230
+ addParts(result, "context", ctxParts, ctxStyle.fg, reset, pf, ctxStyle.bold);
911
1231
 
912
1232
  // Block
913
1233
  if (data.blockInfo) {
914
1234
  const blockColor = pf?.["block"] ?? colors.blockFg;
915
1235
  result.block = colorizeOrEmpty(
916
- formatBlockSegment(data.blockInfo, sym, config),
1236
+ formatBlockSegment(data.blockInfo, sym, config, iconVisible.block),
917
1237
  blockColor,
1238
+ colors.blockBold,
918
1239
  );
919
1240
  addParts(
920
1241
  result,
921
1242
  "block",
922
- formatBlockParts(data.blockInfo, sym, config),
1243
+ formatBlockParts(data.blockInfo, sym, config, iconVisible.block),
923
1244
  colors.blockFg,
924
1245
  reset,
925
1246
  pf,
1247
+ colors.blockBold,
926
1248
  );
927
1249
  } else {
928
1250
  result.block = "";
@@ -932,16 +1254,18 @@ export function resolveSegments(
932
1254
  if (data.usageInfo) {
933
1255
  const sessionColor = pf?.["session"] ?? colors.sessionFg;
934
1256
  result.session = colorizeOrEmpty(
935
- formatSessionSegment(data.usageInfo, sym, config),
1257
+ formatSessionSegment(data.usageInfo, sym, config, iconVisible.session),
936
1258
  sessionColor,
1259
+ colors.sessionBold,
937
1260
  );
938
1261
  addParts(
939
1262
  result,
940
1263
  "session",
941
- formatSessionParts(data.usageInfo, sym, config),
1264
+ formatSessionParts(data.usageInfo, sym, config, iconVisible.session),
942
1265
  colors.sessionFg,
943
1266
  reset,
944
1267
  pf,
1268
+ colors.sessionBold,
945
1269
  );
946
1270
  } else {
947
1271
  result.session = "";
@@ -951,16 +1275,18 @@ export function resolveSegments(
951
1275
  if (data.todayInfo) {
952
1276
  const todayColor = pf?.["today"] ?? colors.todayFg;
953
1277
  result.today = colorizeOrEmpty(
954
- formatTodaySegment(data.todayInfo, sym, config),
1278
+ formatTodaySegment(data.todayInfo, sym, config, iconVisible.today),
955
1279
  todayColor,
1280
+ colors.todayBold,
956
1281
  );
957
1282
  addParts(
958
1283
  result,
959
1284
  "today",
960
- formatTodayParts(data.todayInfo, sym, config),
1285
+ formatTodayParts(data.todayInfo, sym, config, iconVisible.today),
961
1286
  colors.todayFg,
962
1287
  reset,
963
1288
  pf,
1289
+ colors.todayBold,
964
1290
  );
965
1291
  } else {
966
1292
  result.today = "";
@@ -971,16 +1297,18 @@ export function resolveSegments(
971
1297
  if (sevenDay) {
972
1298
  const weeklyColor = pf?.["weekly"] ?? colors.weeklyFg;
973
1299
  result.weekly = colorizeOrEmpty(
974
- formatWeeklySegment(sevenDay, sym),
1300
+ formatWeeklySegment(sevenDay, sym, iconVisible.weekly),
975
1301
  weeklyColor,
1302
+ colors.weeklyBold,
976
1303
  );
977
1304
  addParts(
978
1305
  result,
979
1306
  "weekly",
980
- formatWeeklyParts(sevenDay, sym),
1307
+ formatWeeklyParts(sevenDay, sym, iconVisible.weekly),
981
1308
  colors.weeklyFg,
982
1309
  reset,
983
1310
  pf,
1311
+ colors.weeklyBold,
984
1312
  );
985
1313
  } else {
986
1314
  result.weekly = "";
@@ -988,46 +1316,78 @@ export function resolveSegments(
988
1316
 
989
1317
  // Git
990
1318
  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);
1319
+ result.git = colorizeOrEmpty(
1320
+ formatGitSegment(data, sym, iconVisible.git),
1321
+ gitColor,
1322
+ colors.gitBold,
1323
+ );
1324
+ addParts(
1325
+ result,
1326
+ "git",
1327
+ formatGitParts(data, sym, iconVisible.git),
1328
+ colors.gitFg,
1329
+ reset,
1330
+ pf,
1331
+ colors.gitBold,
1332
+ );
993
1333
 
994
1334
  // Dir
995
1335
  const dirColor = pf?.["dir"] ?? colors.modeFg;
996
- result.dir = colorizeOrEmpty(formatDirValue(data, config), dirColor);
1336
+ result.dir = colorizeOrEmpty(
1337
+ formatDirValue(data, config),
1338
+ dirColor,
1339
+ colors.modeBold,
1340
+ );
997
1341
  addParts(
998
1342
  result,
999
1343
  "dir",
1000
- formatDirParts(data, config, sym),
1344
+ formatDirParts(data, config, sym, iconVisible.directory),
1001
1345
  colors.modeFg,
1002
1346
  reset,
1003
1347
  pf,
1348
+ colors.modeBold,
1004
1349
  );
1005
1350
 
1006
1351
  // Version
1007
1352
  const versionColor = pf?.["version"] ?? colors.versionFg;
1008
1353
  result.version = colorizeOrEmpty(
1009
- formatVersionSegment(data, sym),
1354
+ formatVersionSegment(data, sym, iconVisible.version),
1010
1355
  versionColor,
1356
+ colors.versionBold,
1011
1357
  );
1012
1358
  addParts(
1013
1359
  result,
1014
1360
  "version",
1015
- formatVersionParts(data, sym),
1361
+ formatVersionParts(data, sym, iconVisible.version),
1016
1362
  colors.versionFg,
1017
1363
  reset,
1018
1364
  pf,
1365
+ colors.versionBold,
1019
1366
  );
1020
1367
 
1021
1368
  // Tmux
1022
1369
  const tmuxColor = pf?.["tmux"] ?? colors.tmuxFg;
1023
- result.tmux = colorizeOrEmpty(formatTmuxSegment(data), tmuxColor);
1024
- addParts(result, "tmux", formatTmuxParts(data), colors.tmuxFg, reset, pf);
1370
+ result.tmux = colorizeOrEmpty(
1371
+ formatTmuxSegment(data),
1372
+ tmuxColor,
1373
+ colors.tmuxBold,
1374
+ );
1375
+ addParts(
1376
+ result,
1377
+ "tmux",
1378
+ formatTmuxParts(data),
1379
+ colors.tmuxFg,
1380
+ reset,
1381
+ pf,
1382
+ colors.tmuxBold,
1383
+ );
1025
1384
 
1026
1385
  // Metrics
1027
1386
  const metricsColor = pf?.["metrics"] ?? colors.metricsFg;
1028
1387
  result.metrics = colorizeOrEmpty(
1029
1388
  formatMetricsSegment(data, sym),
1030
1389
  metricsColor,
1390
+ colors.metricsBold,
1031
1391
  );
1032
1392
  addParts(
1033
1393
  result,
@@ -1036,6 +1396,7 @@ export function resolveSegments(
1036
1396
  colors.metricsFg,
1037
1397
  reset,
1038
1398
  pf,
1399
+ colors.metricsBold,
1039
1400
  );
1040
1401
 
1041
1402
  // Activity
@@ -1043,6 +1404,7 @@ export function resolveSegments(
1043
1404
  result.activity = colorizeOrEmpty(
1044
1405
  formatActivitySegment(data, sym),
1045
1406
  activityColor,
1407
+ colors.metricsBold,
1046
1408
  );
1047
1409
  addParts(
1048
1410
  result,
@@ -1051,12 +1413,81 @@ export function resolveSegments(
1051
1413
  colors.metricsFg,
1052
1414
  reset,
1053
1415
  pf,
1416
+ colors.metricsBold,
1054
1417
  );
1055
1418
 
1056
1419
  // Env
1057
1420
  const envColor = pf?.["env"] ?? colors.envFg;
1058
- result.env = colorizeOrEmpty(formatEnvSegment(config), envColor);
1059
- addParts(result, "env", formatEnvParts(config), colors.envFg, reset, pf);
1421
+ result.env = colorizeOrEmpty(
1422
+ formatEnvSegment(config),
1423
+ envColor,
1424
+ colors.envBold,
1425
+ );
1426
+ addParts(
1427
+ result,
1428
+ "env",
1429
+ formatEnvParts(config),
1430
+ colors.envFg,
1431
+ reset,
1432
+ pf,
1433
+ colors.envBold,
1434
+ );
1435
+
1436
+ // Agent
1437
+ const agentColor = pf?.["agent"] ?? colors.agentFg;
1438
+ result.agent = colorizeOrEmpty(
1439
+ formatAgentSegment(data, sym, config, iconVisible.agent),
1440
+ agentColor,
1441
+ colors.agentBold,
1442
+ );
1443
+ addParts(
1444
+ result,
1445
+ "agent",
1446
+ formatAgentParts(data, sym, iconVisible.agent),
1447
+ colors.agentFg,
1448
+ reset,
1449
+ pf,
1450
+ colors.agentBold,
1451
+ );
1452
+
1453
+ // Thinking (combined enabled + effort)
1454
+ const thinkingSegConfig = config.display.lines
1455
+ .map((line) => line.segments.thinking)
1456
+ .find((t) => t?.enabled);
1457
+ const thinkingColor = pf?.["thinking"] ?? colors.thinkingFg;
1458
+ result.thinking = colorizeOrEmpty(
1459
+ formatThinkingSegment(data, sym, thinkingSegConfig, iconVisible.thinking),
1460
+ thinkingColor,
1461
+ colors.thinkingBold,
1462
+ );
1463
+ addParts(
1464
+ result,
1465
+ "thinking",
1466
+ formatThinkingParts(data, sym, thinkingSegConfig, iconVisible.thinking),
1467
+ colors.thinkingFg,
1468
+ reset,
1469
+ pf,
1470
+ colors.thinkingBold,
1471
+ );
1472
+
1473
+ // CacheTimer
1474
+ const cacheTimerElapsed = data.cacheTimerInfo?.elapsedSeconds ?? 0;
1475
+ const cacheTimerStyleResolved = cacheTimerStyle(cacheTimerElapsed, colors);
1476
+ const cacheTimerColor = pf?.["cacheTimer"] ?? cacheTimerStyleResolved.fg;
1477
+ result.cacheTimer = colorizeOrEmpty(
1478
+ formatCacheTimerSegment(data, sym, iconVisible.cacheTimer),
1479
+ cacheTimerColor,
1480
+ cacheTimerStyleResolved.bold,
1481
+ );
1482
+ addParts(
1483
+ result,
1484
+ "cacheTimer",
1485
+ formatCacheTimerParts(data, sym, iconVisible.cacheTimer),
1486
+ cacheTimerStyleResolved.fg,
1487
+ reset,
1488
+ pf,
1489
+ cacheTimerStyleResolved.bold,
1490
+ );
1060
1491
 
1061
1492
  // Apply segment templates: resolve items and compose default value
1062
1493
  const templates: Record<string, ResolvedTemplate> = {};