@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.
package/src/powerline.ts CHANGED
@@ -18,9 +18,13 @@ import type {
18
18
  SessionIdSegmentConfig,
19
19
  EnvSegmentConfig,
20
20
  WeeklySegmentConfig,
21
+ AgentSegmentConfig,
22
+ ThinkingSegmentConfig,
23
+ CacheTimerSegmentConfig,
21
24
  } from "./segments";
22
25
  import type { BlockInfo } from "./segments/block";
23
26
  import type { TodayInfo } from "./segments/today";
27
+ import type { CacheTimerInfo } from "./segments/cacheTimer";
24
28
  import type { TuiData } from "./tui";
25
29
 
26
30
  import {
@@ -42,6 +46,7 @@ import {
42
46
  } from "./segments";
43
47
  import { BlockProvider } from "./segments/block";
44
48
  import { TodayProvider } from "./segments/today";
49
+ import { CacheTimerProvider } from "./segments/cacheTimer";
45
50
  import {
46
51
  SYMBOLS,
47
52
  TEXT_SYMBOLS,
@@ -58,6 +63,7 @@ interface RenderedSegment {
58
63
  text: string;
59
64
  bgColor: string;
60
65
  fgColor: string;
66
+ bold?: boolean;
61
67
  }
62
68
 
63
69
  export class PowerlineRenderer {
@@ -69,6 +75,7 @@ export class PowerlineRenderer {
69
75
  private _gitService?: GitService;
70
76
  private _tmuxService?: TmuxService;
71
77
  private _metricsProvider?: MetricsProvider;
78
+ private _cacheTimerProvider?: CacheTimerProvider;
72
79
  private _segmentRenderer?: SegmentRenderer;
73
80
 
74
81
  constructor(private readonly config: PowerlineConfig) {
@@ -124,6 +131,13 @@ export class PowerlineRenderer {
124
131
  return this._metricsProvider;
125
132
  }
126
133
 
134
+ private get cacheTimerProvider(): CacheTimerProvider {
135
+ if (!this._cacheTimerProvider) {
136
+ this._cacheTimerProvider = new CacheTimerProvider();
137
+ }
138
+ return this._cacheTimerProvider;
139
+ }
140
+
127
141
  private get segmentRenderer(): SegmentRenderer {
128
142
  if (!this._segmentRenderer) {
129
143
  this._segmentRenderer = new SegmentRenderer(this.config, this.symbols);
@@ -166,6 +180,10 @@ export class PowerlineRenderer {
166
180
  ? await this.metricsProvider.getMetricsInfo(hookData.session_id, hookData)
167
181
  : null;
168
182
 
183
+ const cacheTimerInfo = this.needsSegmentInfo("cacheTimer")
184
+ ? await this.cacheTimerProvider.getCacheTimerInfo(hookData)
185
+ : null;
186
+
169
187
  if (this.config.display.autoWrap) {
170
188
  return this.generateAutoWrapStatusline(
171
189
  hookData,
@@ -174,6 +192,7 @@ export class PowerlineRenderer {
174
192
  todayInfo,
175
193
  contextInfo,
176
194
  metricsInfo,
195
+ cacheTimerInfo,
177
196
  );
178
197
  }
179
198
 
@@ -187,6 +206,7 @@ export class PowerlineRenderer {
187
206
  todayInfo,
188
207
  contextInfo,
189
208
  metricsInfo,
209
+ cacheTimerInfo,
190
210
  ),
191
211
  ),
192
212
  );
@@ -201,6 +221,7 @@ export class PowerlineRenderer {
201
221
  todayInfo: TodayInfo | null,
202
222
  contextInfo: ContextInfo | null,
203
223
  metricsInfo: MetricsInfo | null,
224
+ cacheTimerInfo: CacheTimerInfo | null,
204
225
  ): Promise<string> {
205
226
  const colors = this.getThemeColors();
206
227
  const currentDir = hookData.workspace?.current_dir || hookData.cwd || "/";
@@ -229,6 +250,7 @@ export class PowerlineRenderer {
229
250
  todayInfo,
230
251
  contextInfo,
231
252
  metricsInfo,
253
+ cacheTimerInfo,
232
254
  colors,
233
255
  currentDir,
234
256
  );
@@ -239,6 +261,7 @@ export class PowerlineRenderer {
239
261
  text: segmentData.text,
240
262
  bgColor: segmentData.bgColor,
241
263
  fgColor: segmentData.fgColor,
264
+ bold: segmentData.bold,
242
265
  });
243
266
  }
244
267
  }
@@ -318,6 +341,7 @@ export class PowerlineRenderer {
318
341
  hookData.workspace?.project_dir,
319
342
  ),
320
343
  this.tmuxService.getSessionId(),
344
+ this.cacheTimerProvider.getCacheTimerInfo(hookData),
321
345
  ]);
322
346
  const val = <T>(r: PromiseSettledResult<T>) =>
323
347
  r.status === "fulfilled" ? r.value : null;
@@ -329,6 +353,7 @@ export class PowerlineRenderer {
329
353
  metricsInfo,
330
354
  gitInfo,
331
355
  tmuxSessionId,
356
+ cacheTimerInfo,
332
357
  ] = [
333
358
  val(results[0]!),
334
359
  val(results[1]!),
@@ -337,6 +362,7 @@ export class PowerlineRenderer {
337
362
  val(results[4]!),
338
363
  val(results[5]!),
339
364
  val(results[6]!),
365
+ val(results[7]!),
340
366
  ] as const;
341
367
 
342
368
  const tuiData: TuiData = {
@@ -347,6 +373,7 @@ export class PowerlineRenderer {
347
373
  contextInfo,
348
374
  metricsInfo,
349
375
  gitInfo,
376
+ cacheTimerInfo,
350
377
  tmuxSessionId,
351
378
  colors,
352
379
  };
@@ -398,12 +425,16 @@ export class PowerlineRenderer {
398
425
  line += " ";
399
426
  }
400
427
 
428
+ const bold =
429
+ segment.bold ?? this.getSegmentBoldFlag(segment.type, colors);
430
+
401
431
  line += this.formatSegment(
402
432
  segment.bgColor,
403
433
  segment.fgColor,
404
434
  segment.text,
405
435
  nextSegment?.bgColor,
406
436
  colors,
437
+ bold,
407
438
  );
408
439
  }
409
440
 
@@ -418,6 +449,7 @@ export class PowerlineRenderer {
418
449
  todayInfo: TodayInfo | null,
419
450
  contextInfo: ContextInfo | null,
420
451
  metricsInfo: MetricsInfo | null,
452
+ cacheTimerInfo: CacheTimerInfo | null,
421
453
  ): Promise<string> {
422
454
  const colors = this.getThemeColors();
423
455
  const currentDir = hookData.workspace?.current_dir || hookData.cwd || "/";
@@ -439,6 +471,7 @@ export class PowerlineRenderer {
439
471
  todayInfo,
440
472
  contextInfo,
441
473
  metricsInfo,
474
+ cacheTimerInfo,
442
475
  colors,
443
476
  currentDir,
444
477
  );
@@ -449,6 +482,7 @@ export class PowerlineRenderer {
449
482
  text: segmentData.text,
450
483
  bgColor: segmentData.bgColor,
451
484
  fgColor: segmentData.fgColor,
485
+ bold: segmentData.bold,
452
486
  });
453
487
  }
454
488
  }
@@ -464,6 +498,7 @@ export class PowerlineRenderer {
464
498
  todayInfo: TodayInfo | null,
465
499
  contextInfo: ContextInfo | null,
466
500
  metricsInfo: MetricsInfo | null,
501
+ cacheTimerInfo: CacheTimerInfo | null,
467
502
  colors: PowerlineColors,
468
503
  currentDir: string,
469
504
  ) {
@@ -475,7 +510,7 @@ export class PowerlineRenderer {
475
510
  );
476
511
  }
477
512
  if (segment.type === "model") {
478
- return this.segmentRenderer.renderModel(hookData, colors);
513
+ return this.segmentRenderer.renderModel(hookData, colors, segment.config);
479
514
  }
480
515
 
481
516
  if (segment.type === "git") {
@@ -565,6 +600,31 @@ export class PowerlineRenderer {
565
600
  );
566
601
  }
567
602
 
603
+ if (segment.type === "agent") {
604
+ return this.segmentRenderer.renderAgent(
605
+ hookData,
606
+ colors,
607
+ segment.config as AgentSegmentConfig,
608
+ );
609
+ }
610
+
611
+ if (segment.type === "thinking") {
612
+ return this.segmentRenderer.renderThinking(
613
+ hookData,
614
+ colors,
615
+ segment.config as ThinkingSegmentConfig,
616
+ );
617
+ }
618
+
619
+ if (segment.type === "cacheTimer") {
620
+ if (!cacheTimerInfo) return null;
621
+ return this.segmentRenderer.renderCacheTimer(
622
+ cacheTimerInfo,
623
+ colors,
624
+ segment.config as CacheTimerSegmentConfig,
625
+ );
626
+ }
627
+
568
628
  return null;
569
629
  }
570
630
 
@@ -644,8 +704,7 @@ export class PowerlineRenderer {
644
704
  colors: PowerlineColors,
645
705
  ) {
646
706
  if (!todayInfo) return null;
647
- const todayType = config?.type || "cost";
648
- return this.segmentRenderer.renderToday(todayInfo, colors, todayType);
707
+ return this.segmentRenderer.renderToday(todayInfo, colors, config);
649
708
  }
650
709
 
651
710
  private renderVersionSegment(
@@ -700,6 +759,9 @@ export class PowerlineRenderer {
700
759
  env: symbolSet.env,
701
760
  session_id: symbolSet.session_id,
702
761
  weekly_cost: symbolSet.weekly_cost,
762
+ agent: symbolSet.agent,
763
+ thinking: symbolSet.thinking,
764
+ cache_timer: symbolSet.cache_timer,
703
765
  };
704
766
  }
705
767
 
@@ -753,9 +815,13 @@ export class PowerlineRenderer {
753
815
  fgHex = colors.bg;
754
816
  }
755
817
 
818
+ const bold =
819
+ colorSupport !== "none" && Boolean(custom?.bold ?? fallback.bold);
820
+
756
821
  return {
757
822
  bg: convertHex(colors.bg, true),
758
823
  fg: convertHex(fgHex, false),
824
+ bold,
759
825
  };
760
826
  };
761
827
 
@@ -773,37 +839,63 @@ export class PowerlineRenderer {
773
839
  const version = getSegmentColors("version");
774
840
  const env = getSegmentColors("env");
775
841
  const weekly = getSegmentColors("weekly");
842
+ const agent = getSegmentColors("agent");
843
+ const thinking = getSegmentColors("thinking");
844
+ const cacheTimer = getSegmentColors("cacheTimer");
776
845
 
777
846
  return {
778
847
  reset: colorSupport === "none" ? "" : RESET_CODE,
779
848
  modeBg: directory.bg,
780
849
  modeFg: directory.fg,
850
+ modeBold: directory.bold,
781
851
  gitBg: git.bg,
782
852
  gitFg: git.fg,
853
+ gitBold: git.bold,
783
854
  modelBg: model.bg,
784
855
  modelFg: model.fg,
856
+ modelBold: model.bold,
785
857
  sessionBg: session.bg,
786
858
  sessionFg: session.fg,
859
+ sessionBold: session.bold,
787
860
  blockBg: block.bg,
788
861
  blockFg: block.fg,
862
+ blockBold: block.bold,
789
863
  todayBg: today.bg,
790
864
  todayFg: today.fg,
865
+ todayBold: today.bold,
791
866
  tmuxBg: tmux.bg,
792
867
  tmuxFg: tmux.fg,
868
+ tmuxBold: tmux.bold,
793
869
  contextBg: context.bg,
794
870
  contextFg: context.fg,
871
+ contextBold: context.bold,
795
872
  contextWarningBg: contextWarning.bg,
796
873
  contextWarningFg: contextWarning.fg,
874
+ contextWarningBold: contextWarning.bold,
797
875
  contextCriticalBg: contextCritical.bg,
798
876
  contextCriticalFg: contextCritical.fg,
877
+ contextCriticalBold: contextCritical.bold,
799
878
  metricsBg: metrics.bg,
800
879
  metricsFg: metrics.fg,
880
+ metricsBold: metrics.bold,
801
881
  versionBg: version.bg,
802
882
  versionFg: version.fg,
883
+ versionBold: version.bold,
803
884
  envBg: env.bg,
804
885
  envFg: env.fg,
886
+ envBold: env.bold,
805
887
  weeklyBg: weekly.bg,
806
888
  weeklyFg: weekly.fg,
889
+ weeklyBold: weekly.bold,
890
+ agentBg: agent.bg,
891
+ agentFg: agent.fg,
892
+ agentBold: agent.bold,
893
+ thinkingBg: thinking.bg,
894
+ thinkingFg: thinking.fg,
895
+ thinkingBold: thinking.bold,
896
+ cacheTimerBg: cacheTimer.bg,
897
+ cacheTimerFg: cacheTimer.fg,
898
+ cacheTimerBold: cacheTimer.bold,
807
899
  partFg: theme === "custom" ? this.resolvePartColors(convertHex) : {},
808
900
  };
809
901
  }
@@ -855,20 +947,71 @@ export class PowerlineRenderer {
855
947
  return colors.envBg;
856
948
  case "weekly":
857
949
  return colors.weeklyBg;
950
+ case "agent":
951
+ return colors.agentBg;
952
+ case "thinking":
953
+ return colors.thinkingBg;
954
+ case "cacheTimer":
955
+ return colors.cacheTimerBg;
858
956
  default:
859
957
  return colors.modeBg;
860
958
  }
861
959
  }
862
960
 
961
+ private getSegmentBoldFlag(
962
+ segmentType: string,
963
+ colors: PowerlineColors,
964
+ ): boolean {
965
+ switch (segmentType) {
966
+ case "directory":
967
+ return colors.modeBold;
968
+ case "git":
969
+ return colors.gitBold;
970
+ case "model":
971
+ return colors.modelBold;
972
+ case "session":
973
+ case "sessionId":
974
+ return colors.sessionBold;
975
+ case "block":
976
+ return colors.blockBold;
977
+ case "today":
978
+ return colors.todayBold;
979
+ case "tmux":
980
+ return colors.tmuxBold;
981
+ case "context":
982
+ return colors.contextBold;
983
+ case "metrics":
984
+ return colors.metricsBold;
985
+ case "version":
986
+ return colors.versionBold;
987
+ case "env":
988
+ return colors.envBold;
989
+ case "weekly":
990
+ return colors.weeklyBold;
991
+ case "agent":
992
+ return colors.agentBold;
993
+ case "thinking":
994
+ return colors.thinkingBold;
995
+ case "cacheTimer":
996
+ return colors.cacheTimerBold;
997
+ default:
998
+ return colors.modeBold;
999
+ }
1000
+ }
1001
+
863
1002
  private formatSegment(
864
1003
  bgColor: string,
865
1004
  fgColor: string,
866
1005
  text: string,
867
1006
  nextBgColor: string | undefined,
868
1007
  colors: PowerlineColors,
1008
+ bold: boolean,
869
1009
  ): string {
870
1010
  const isCapsuleStyle = this.config.display.style === "capsule";
871
1011
  const padding = " ".repeat(this.config.display.padding ?? 1);
1012
+ const useBold = bold && colors.reset !== "";
1013
+ const boldOn = useBold ? "\x1b[1m" : "";
1014
+ const boldOff = useBold ? "\x1b[22m" : "";
872
1015
 
873
1016
  if (isCapsuleStyle) {
874
1017
  const colorMode = this.config.display.colorCompatibility || "auto";
@@ -879,14 +1022,14 @@ export class PowerlineRenderer {
879
1022
 
880
1023
  const leftCap = `${capFgColor}${this.symbols.left}${colors.reset}`;
881
1024
 
882
- const content = `${bgColor}${fgColor}${padding}${text}${padding}${colors.reset}`;
1025
+ const content = `${bgColor}${fgColor}${boldOn}${padding}${text}${padding}${boldOff}${colors.reset}`;
883
1026
 
884
1027
  const rightCap = `${capFgColor}${this.symbols.right}${colors.reset}`;
885
1028
 
886
1029
  return `${leftCap}${content}${rightCap}`;
887
1030
  }
888
1031
 
889
- let output = `${bgColor}${fgColor}${padding}${text}${padding}`;
1032
+ let output = `${bgColor}${fgColor}${boldOn}${padding}${text}${padding}${boldOff}`;
890
1033
 
891
1034
  const colorMode = this.config.display.colorCompatibility || "auto";
892
1035
  const colorSupport = colorMode === "auto" ? getColorSupport() : colorMode;
@@ -0,0 +1,72 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { debug } from "../utils/logger";
3
+ import type { ClaudeHookData } from "../utils/claude";
4
+
5
+ export interface CacheTimerInfo {
6
+ elapsedSeconds: number;
7
+ }
8
+
9
+ interface TranscriptEntry {
10
+ type?: string;
11
+ timestamp?: string;
12
+ message?: { role?: string; type?: string };
13
+ }
14
+
15
+ export class CacheTimerProvider {
16
+ async getCacheTimerInfo(
17
+ hookData: ClaudeHookData,
18
+ ): Promise<CacheTimerInfo | null> {
19
+ const path = hookData?.transcript_path;
20
+ if (!path) {
21
+ debug("CacheTimer: no transcript_path in hookData");
22
+ return null;
23
+ }
24
+
25
+ const anchor =
26
+ (await this.lastUserTimestamp(path)) ?? (await this.fileMtime(path));
27
+ if (anchor === null) return null;
28
+
29
+ const elapsedSeconds = Math.max(
30
+ 0,
31
+ Math.floor((Date.now() - anchor) / 1000),
32
+ );
33
+ return { elapsedSeconds };
34
+ }
35
+
36
+ private async lastUserTimestamp(path: string): Promise<number | null> {
37
+ try {
38
+ const content = await readFile(path, "utf-8");
39
+ const lines = content.split("\n");
40
+ for (let i = lines.length - 1; i >= 0; i--) {
41
+ const line = lines[i]?.trim();
42
+ if (!line) continue;
43
+ try {
44
+ const entry = JSON.parse(line) as TranscriptEntry;
45
+ const messageType =
46
+ entry.type || entry.message?.role || entry.message?.type;
47
+ if (messageType !== "user") continue;
48
+ if (!entry.timestamp) continue;
49
+ const t = Date.parse(entry.timestamp);
50
+ if (Number.isNaN(t)) continue;
51
+ return t;
52
+ } catch {
53
+ continue;
54
+ }
55
+ }
56
+ return null;
57
+ } catch (error) {
58
+ debug(`CacheTimer: readFile failed for ${path}: ${String(error)}`);
59
+ return null;
60
+ }
61
+ }
62
+
63
+ private async fileMtime(path: string): Promise<number | null> {
64
+ try {
65
+ const { mtime } = await stat(path);
66
+ return mtime.getTime();
67
+ } catch (error) {
68
+ debug(`CacheTimer: stat failed for ${path}: ${String(error)}`);
69
+ return null;
70
+ }
71
+ }
72
+ }
@@ -7,6 +7,8 @@ export { ContextProvider } from "./context";
7
7
  export type { ContextInfo } from "./context";
8
8
  export { MetricsProvider } from "./metrics";
9
9
  export type { MetricsInfo } from "./metrics";
10
+ export { CacheTimerProvider } from "./cacheTimer";
11
+ export type { CacheTimerInfo } from "./cacheTimer";
10
12
  export { SegmentRenderer } from "./renderer";
11
13
  export type {
12
14
  PowerlineSymbols,
@@ -22,4 +24,7 @@ export type {
22
24
  SessionIdSegmentConfig,
23
25
  EnvSegmentConfig,
24
26
  WeeklySegmentConfig,
27
+ AgentSegmentConfig,
28
+ ThinkingSegmentConfig,
29
+ CacheTimerSegmentConfig,
25
30
  } from "./renderer";