@bastra-recall/statusline 0.7.0-beta.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/LICENSE +21 -0
- package/README.md +26 -0
- package/bin/claude-powerline +2 -0
- package/dist/browser.d.ts +796 -0
- package/dist/browser.js +3 -0
- package/dist/index.mjs +48 -0
- package/package.json +39 -0
- package/src/browser.ts +208 -0
- package/src/config/defaults.ts +88 -0
- package/src/config/loader.ts +473 -0
- package/src/index.ts +90 -0
- package/src/powerline.ts +1082 -0
- package/src/segments/bastra.ts +123 -0
- package/src/segments/block.ts +31 -0
- package/src/segments/cacheTimer.ts +108 -0
- package/src/segments/context.ts +221 -0
- package/src/segments/git.ts +498 -0
- package/src/segments/index.ts +33 -0
- package/src/segments/metrics.ts +175 -0
- package/src/segments/pricing.ts +454 -0
- package/src/segments/renderer.ts +1002 -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 +61 -0
- package/src/themes/gruvbox.ts +61 -0
- package/src/themes/index.ts +161 -0
- package/src/themes/light.ts +61 -0
- package/src/themes/nord.ts +61 -0
- package/src/themes/rose-pine.ts +61 -0
- package/src/themes/tokyo-night.ts +61 -0
- package/src/tui/grid.ts +712 -0
- package/src/tui/index.ts +4 -0
- package/src/tui/layouts.ts +321 -0
- package/src/tui/primitives.ts +187 -0
- package/src/tui/renderer.ts +206 -0
- package/src/tui/sections.ts +1511 -0
- package/src/tui/types.ts +189 -0
- package/src/utils/budget.ts +116 -0
- package/src/utils/cache.ts +247 -0
- package/src/utils/claude.ts +518 -0
- package/src/utils/color-support.ts +118 -0
- package/src/utils/colors.ts +120 -0
- package/src/utils/constants.ts +184 -0
- package/src/utils/formatters.ts +175 -0
- package/src/utils/icon-visibility.ts +31 -0
- package/src/utils/logger.ts +5 -0
- package/src/utils/terminal-width.ts +117 -0
- package/src/utils/terminal.ts +54 -0
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
import type { ClaudeHookData } from "../utils/claude";
|
|
2
|
+
import { getEffortLevel, getThinkingEnabled } from "../utils/claude";
|
|
3
|
+
import type { PowerlineColors } from "../themes";
|
|
4
|
+
import type { PowerlineConfig } from "../config/loader";
|
|
5
|
+
import type { BlockInfo } from "./block";
|
|
6
|
+
import type { CacheTimerInfo } from "./cacheTimer";
|
|
7
|
+
import type {
|
|
8
|
+
UsageInfo,
|
|
9
|
+
TokenBreakdown,
|
|
10
|
+
GitInfo,
|
|
11
|
+
ContextInfo,
|
|
12
|
+
MetricsInfo,
|
|
13
|
+
} from ".";
|
|
14
|
+
import type { TodayInfo } from "./today";
|
|
15
|
+
import type { BastraInfo } from "./bastra";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
formatModelName,
|
|
19
|
+
abbreviateFishStyle,
|
|
20
|
+
formatCost,
|
|
21
|
+
formatTokens,
|
|
22
|
+
formatTokenCount,
|
|
23
|
+
formatTokenBreakdown,
|
|
24
|
+
formatTimeSince,
|
|
25
|
+
formatDuration,
|
|
26
|
+
formatLongTimeRemaining,
|
|
27
|
+
formatCacheTimerElapsed,
|
|
28
|
+
formatCacheTimerRemaining,
|
|
29
|
+
collapseHome,
|
|
30
|
+
minutesUntilReset,
|
|
31
|
+
} from "../utils/formatters";
|
|
32
|
+
import { resolveBudgetDisplay } from "../utils/budget";
|
|
33
|
+
import type { BudgetItemConfig } from "../config/loader";
|
|
34
|
+
import { shouldShowIcon } from "../utils/icon-visibility";
|
|
35
|
+
|
|
36
|
+
export interface SegmentConfig {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
showIcon?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DirectorySegmentConfig extends SegmentConfig {
|
|
42
|
+
showBasename?: boolean;
|
|
43
|
+
style?: "full" | "fish" | "basename";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GitSegmentConfig extends SegmentConfig {
|
|
47
|
+
showSha?: boolean;
|
|
48
|
+
showAheadBehind?: boolean;
|
|
49
|
+
showWorkingTree?: boolean;
|
|
50
|
+
showOperation?: boolean;
|
|
51
|
+
showTag?: boolean;
|
|
52
|
+
showTimeSinceCommit?: boolean;
|
|
53
|
+
showStashCount?: boolean;
|
|
54
|
+
showUpstream?: boolean;
|
|
55
|
+
showRepoName?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface UsageSegmentConfig extends SegmentConfig {
|
|
59
|
+
type: "cost" | "tokens" | "both" | "breakdown";
|
|
60
|
+
costSource?: "calculated" | "official";
|
|
61
|
+
/** Show the trailing "tokens" unit on token counts. Only affects `type: "tokens"` and `type: "both"` (default: true). Inert in the `tui` display style, which never renders the suffix. */
|
|
62
|
+
showUnits?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface TmuxSegmentConfig extends SegmentConfig {}
|
|
66
|
+
|
|
67
|
+
export type BarDisplayStyle =
|
|
68
|
+
| "text"
|
|
69
|
+
| "ball"
|
|
70
|
+
| "bar"
|
|
71
|
+
| "blocks"
|
|
72
|
+
| "blocks-line"
|
|
73
|
+
| "capped"
|
|
74
|
+
| "dots"
|
|
75
|
+
| "filled"
|
|
76
|
+
| "geometric"
|
|
77
|
+
| "line"
|
|
78
|
+
| "squares";
|
|
79
|
+
|
|
80
|
+
export interface ContextSegmentConfig extends SegmentConfig {
|
|
81
|
+
showPercentageOnly?: boolean;
|
|
82
|
+
displayStyle?: BarDisplayStyle;
|
|
83
|
+
autocompactBuffer?: number;
|
|
84
|
+
percentageMode?: "remaining" | "used";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MetricsSegmentConfig extends SegmentConfig {
|
|
88
|
+
showResponseTime?: boolean;
|
|
89
|
+
showLastResponseTime?: boolean;
|
|
90
|
+
showDuration?: boolean;
|
|
91
|
+
showMessageCount?: boolean;
|
|
92
|
+
showLinesAdded?: boolean;
|
|
93
|
+
showLinesRemoved?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface BlockSegmentConfig extends SegmentConfig {
|
|
97
|
+
type: "cost" | "tokens" | "both" | "time" | "weighted";
|
|
98
|
+
burnType?: "cost" | "tokens" | "both" | "none";
|
|
99
|
+
displayStyle?: BarDisplayStyle;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface TodaySegmentConfig extends SegmentConfig {
|
|
103
|
+
type: "cost" | "tokens" | "both" | "breakdown";
|
|
104
|
+
/** Show the trailing "tokens" unit on token counts. Only affects `type: "tokens"` and `type: "both"` (default: true). Inert in the `tui` display style, which never renders the suffix. */
|
|
105
|
+
showUnits?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface VersionSegmentConfig extends SegmentConfig {}
|
|
109
|
+
|
|
110
|
+
export interface SessionIdSegmentConfig extends SegmentConfig {
|
|
111
|
+
showIdLabel?: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface EnvSegmentConfig extends SegmentConfig {
|
|
115
|
+
variable: string;
|
|
116
|
+
prefix?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface WeeklySegmentConfig extends SegmentConfig {
|
|
120
|
+
displayStyle?: BarDisplayStyle;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface AgentSegmentConfig extends SegmentConfig {
|
|
124
|
+
showLabel?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ThinkingSegmentConfig extends SegmentConfig {
|
|
128
|
+
showEnabled?: boolean;
|
|
129
|
+
showEffort?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface CacheTimerSegmentConfig extends SegmentConfig {
|
|
133
|
+
displayMode?: "elapsed" | "remaining";
|
|
134
|
+
ttlSeconds?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface BastraSegmentConfig extends SegmentConfig {}
|
|
138
|
+
|
|
139
|
+
export type AnySegmentConfig =
|
|
140
|
+
| SegmentConfig
|
|
141
|
+
| DirectorySegmentConfig
|
|
142
|
+
| GitSegmentConfig
|
|
143
|
+
| UsageSegmentConfig
|
|
144
|
+
| TmuxSegmentConfig
|
|
145
|
+
| ContextSegmentConfig
|
|
146
|
+
| MetricsSegmentConfig
|
|
147
|
+
| BlockSegmentConfig
|
|
148
|
+
| TodaySegmentConfig
|
|
149
|
+
| VersionSegmentConfig
|
|
150
|
+
| SessionIdSegmentConfig
|
|
151
|
+
| EnvSegmentConfig
|
|
152
|
+
| WeeklySegmentConfig
|
|
153
|
+
| AgentSegmentConfig
|
|
154
|
+
| ThinkingSegmentConfig
|
|
155
|
+
| CacheTimerSegmentConfig
|
|
156
|
+
| BastraSegmentConfig;
|
|
157
|
+
|
|
158
|
+
export interface PowerlineSymbols {
|
|
159
|
+
right: string;
|
|
160
|
+
left: string;
|
|
161
|
+
branch: string;
|
|
162
|
+
model: string;
|
|
163
|
+
git_clean: string;
|
|
164
|
+
git_dirty: string;
|
|
165
|
+
git_conflicts: string;
|
|
166
|
+
git_ahead: string;
|
|
167
|
+
git_behind: string;
|
|
168
|
+
git_worktree: string;
|
|
169
|
+
git_tag: string;
|
|
170
|
+
git_sha: string;
|
|
171
|
+
git_upstream: string;
|
|
172
|
+
git_stash: string;
|
|
173
|
+
git_time: string;
|
|
174
|
+
session_cost: string;
|
|
175
|
+
block_cost: string;
|
|
176
|
+
today_cost: string;
|
|
177
|
+
context_time: string;
|
|
178
|
+
metrics_response: string;
|
|
179
|
+
metrics_last_response: string;
|
|
180
|
+
metrics_duration: string;
|
|
181
|
+
metrics_messages: string;
|
|
182
|
+
metrics_lines_added: string;
|
|
183
|
+
metrics_lines_removed: string;
|
|
184
|
+
metrics_burn: string;
|
|
185
|
+
version: string;
|
|
186
|
+
bar_filled: string;
|
|
187
|
+
bar_empty: string;
|
|
188
|
+
env: string;
|
|
189
|
+
session_id: string;
|
|
190
|
+
weekly_cost: string;
|
|
191
|
+
agent: string;
|
|
192
|
+
thinking: string;
|
|
193
|
+
cache_timer: string;
|
|
194
|
+
bastra: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface SegmentData {
|
|
198
|
+
text: string;
|
|
199
|
+
bgColor: string;
|
|
200
|
+
fgColor: string;
|
|
201
|
+
bold?: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface BarStyleDef {
|
|
205
|
+
filled: string;
|
|
206
|
+
empty: string;
|
|
207
|
+
cap?: string;
|
|
208
|
+
marker?: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const BAR_STYLES: Record<string, BarStyleDef> = {
|
|
212
|
+
ball: { filled: "─", empty: "─", marker: "●" },
|
|
213
|
+
blocks: { filled: "█", empty: "░" },
|
|
214
|
+
"blocks-line": { filled: "█", empty: "─" },
|
|
215
|
+
capped: { filled: "━", empty: "┄", cap: "╸" },
|
|
216
|
+
dots: { filled: "●", empty: "○" },
|
|
217
|
+
filled: { filled: "■", empty: "□" },
|
|
218
|
+
geometric: { filled: "▰", empty: "▱" },
|
|
219
|
+
line: { filled: "━", empty: "┄" },
|
|
220
|
+
squares: { filled: "◼", empty: "◻" },
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export class SegmentRenderer {
|
|
224
|
+
constructor(
|
|
225
|
+
private readonly config: PowerlineConfig,
|
|
226
|
+
private readonly symbols: PowerlineSymbols,
|
|
227
|
+
) {}
|
|
228
|
+
|
|
229
|
+
private leadingIcon(symbol: string, segConfig?: SegmentConfig): string {
|
|
230
|
+
const show = shouldShowIcon(
|
|
231
|
+
this.config.display?.showIcons,
|
|
232
|
+
segConfig?.showIcon,
|
|
233
|
+
);
|
|
234
|
+
return show ? `${symbol} ` : "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
renderDirectory(
|
|
238
|
+
hookData: ClaudeHookData,
|
|
239
|
+
colors: PowerlineColors,
|
|
240
|
+
config?: DirectorySegmentConfig,
|
|
241
|
+
): SegmentData {
|
|
242
|
+
const worktreeOriginalCwd = hookData.worktree?.original_cwd || undefined;
|
|
243
|
+
const currentDir =
|
|
244
|
+
worktreeOriginalCwd ??
|
|
245
|
+
(hookData.workspace?.current_dir || hookData.cwd || "/");
|
|
246
|
+
const projectDir = worktreeOriginalCwd ?? hookData.workspace?.project_dir;
|
|
247
|
+
|
|
248
|
+
const style = config?.style ?? (config?.showBasename ? "basename" : "full");
|
|
249
|
+
|
|
250
|
+
if (style === "basename") {
|
|
251
|
+
const basename = currentDir.split(/[\\/]/).pop() || "root";
|
|
252
|
+
return {
|
|
253
|
+
text: basename,
|
|
254
|
+
bgColor: colors.modeBg,
|
|
255
|
+
fgColor: colors.modeFg,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const displayDir = collapseHome(currentDir);
|
|
260
|
+
const displayProjectDir = projectDir
|
|
261
|
+
? collapseHome(projectDir)
|
|
262
|
+
: projectDir;
|
|
263
|
+
|
|
264
|
+
let dirName = this.getDisplayDirectoryName(displayDir, displayProjectDir);
|
|
265
|
+
|
|
266
|
+
if (style === "fish") {
|
|
267
|
+
dirName = abbreviateFishStyle(dirName);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
text: dirName,
|
|
272
|
+
bgColor: colors.modeBg,
|
|
273
|
+
fgColor: colors.modeFg,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
renderGit(
|
|
278
|
+
gitInfo: GitInfo,
|
|
279
|
+
colors: PowerlineColors,
|
|
280
|
+
config?: GitSegmentConfig,
|
|
281
|
+
): SegmentData | null {
|
|
282
|
+
if (!gitInfo) return null;
|
|
283
|
+
|
|
284
|
+
const parts: string[] = [];
|
|
285
|
+
|
|
286
|
+
if (config?.showRepoName && gitInfo.repoName) {
|
|
287
|
+
parts.push(gitInfo.repoName);
|
|
288
|
+
if (gitInfo.isWorktree) {
|
|
289
|
+
parts.push(this.symbols.git_worktree);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (config?.showOperation && gitInfo.operation) {
|
|
294
|
+
parts.push(`[${gitInfo.operation}]`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const showBranchIcon = shouldShowIcon(
|
|
298
|
+
this.config.display?.showIcons,
|
|
299
|
+
config?.showIcon,
|
|
300
|
+
);
|
|
301
|
+
parts.push(
|
|
302
|
+
showBranchIcon
|
|
303
|
+
? `${this.symbols.branch} ${gitInfo.branch}`
|
|
304
|
+
: gitInfo.branch,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (config?.showTag && gitInfo.tag) {
|
|
308
|
+
parts.push(`${this.symbols.git_tag} ${gitInfo.tag}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (config?.showSha && gitInfo.sha) {
|
|
312
|
+
parts.push(`${this.symbols.git_sha} ${gitInfo.sha}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (config?.showAheadBehind !== false) {
|
|
316
|
+
if (gitInfo.ahead > 0 && gitInfo.behind > 0) {
|
|
317
|
+
parts.push(
|
|
318
|
+
`${this.symbols.git_ahead}${gitInfo.ahead}${this.symbols.git_behind}${gitInfo.behind}`,
|
|
319
|
+
);
|
|
320
|
+
} else if (gitInfo.ahead > 0) {
|
|
321
|
+
parts.push(`${this.symbols.git_ahead}${gitInfo.ahead}`);
|
|
322
|
+
} else if (gitInfo.behind > 0) {
|
|
323
|
+
parts.push(`${this.symbols.git_behind}${gitInfo.behind}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (config?.showWorkingTree) {
|
|
328
|
+
const counts: string[] = [];
|
|
329
|
+
if (gitInfo.staged && gitInfo.staged > 0)
|
|
330
|
+
counts.push(`+${gitInfo.staged}`);
|
|
331
|
+
if (gitInfo.unstaged && gitInfo.unstaged > 0)
|
|
332
|
+
counts.push(`~${gitInfo.unstaged}`);
|
|
333
|
+
if (gitInfo.untracked && gitInfo.untracked > 0)
|
|
334
|
+
counts.push(`?${gitInfo.untracked}`);
|
|
335
|
+
if (gitInfo.conflicts && gitInfo.conflicts > 0)
|
|
336
|
+
counts.push(`!${gitInfo.conflicts}`);
|
|
337
|
+
if (counts.length > 0) {
|
|
338
|
+
parts.push(`(${counts.join(" ")})`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (config?.showUpstream && gitInfo.upstream) {
|
|
343
|
+
parts.push(`${this.symbols.git_upstream}${gitInfo.upstream}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (
|
|
347
|
+
config?.showStashCount &&
|
|
348
|
+
gitInfo.stashCount &&
|
|
349
|
+
gitInfo.stashCount > 0
|
|
350
|
+
) {
|
|
351
|
+
parts.push(`${this.symbols.git_stash} ${gitInfo.stashCount}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (config?.showTimeSinceCommit && gitInfo.timeSinceCommit !== undefined) {
|
|
355
|
+
const time = formatTimeSince(gitInfo.timeSinceCommit);
|
|
356
|
+
parts.push(`${this.symbols.git_time} ${time}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let gitStatusIcon = this.symbols.git_clean;
|
|
360
|
+
if (gitInfo.status === "conflicts") {
|
|
361
|
+
gitStatusIcon = this.symbols.git_conflicts;
|
|
362
|
+
} else if (gitInfo.status === "dirty") {
|
|
363
|
+
gitStatusIcon = this.symbols.git_dirty;
|
|
364
|
+
}
|
|
365
|
+
parts.push(gitStatusIcon);
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
text: parts.join(" "),
|
|
369
|
+
bgColor: colors.gitBg,
|
|
370
|
+
fgColor: colors.gitFg,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
renderModel(
|
|
375
|
+
hookData: ClaudeHookData,
|
|
376
|
+
colors: PowerlineColors,
|
|
377
|
+
config?: SegmentConfig,
|
|
378
|
+
): SegmentData {
|
|
379
|
+
const rawName = hookData.model?.display_name || "Claude";
|
|
380
|
+
const modelName = formatModelName(rawName);
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
text: `${this.leadingIcon(this.symbols.model, config)}${modelName}`,
|
|
384
|
+
bgColor: colors.modelBg,
|
|
385
|
+
fgColor: colors.modelFg,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
renderSession(
|
|
390
|
+
usageInfo: UsageInfo,
|
|
391
|
+
colors: PowerlineColors,
|
|
392
|
+
config?: UsageSegmentConfig,
|
|
393
|
+
): SegmentData | null {
|
|
394
|
+
const type = config?.type || "cost";
|
|
395
|
+
const costSource = config?.costSource;
|
|
396
|
+
const sessionBudget = this.config.budget?.session;
|
|
397
|
+
|
|
398
|
+
const getCost = () => {
|
|
399
|
+
if (costSource === "calculated") return usageInfo.session.calculatedCost;
|
|
400
|
+
if (costSource === "official") return usageInfo.session.officialCost;
|
|
401
|
+
return usageInfo.session.cost;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const formattedUsage = this.formatUsageWithBudget(
|
|
405
|
+
getCost(),
|
|
406
|
+
usageInfo.session.tokens,
|
|
407
|
+
usageInfo.session.tokenBreakdown,
|
|
408
|
+
type,
|
|
409
|
+
sessionBudget,
|
|
410
|
+
config?.showUnits ?? true,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
if (formattedUsage === null) return null;
|
|
414
|
+
|
|
415
|
+
const text = `${this.leadingIcon(this.symbols.session_cost, config)}${formattedUsage}`;
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
text,
|
|
419
|
+
bgColor: colors.sessionBg,
|
|
420
|
+
fgColor: colors.sessionFg,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
renderSessionId(
|
|
425
|
+
sessionId: string,
|
|
426
|
+
colors: PowerlineColors,
|
|
427
|
+
config?: SessionIdSegmentConfig,
|
|
428
|
+
): SegmentData {
|
|
429
|
+
const showLabel = config?.showIdLabel !== false;
|
|
430
|
+
const text = showLabel
|
|
431
|
+
? `${this.leadingIcon(this.symbols.session_id, config)}${sessionId}`
|
|
432
|
+
: sessionId;
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
text,
|
|
436
|
+
bgColor: colors.sessionBg,
|
|
437
|
+
fgColor: colors.sessionFg,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
renderTmux(
|
|
442
|
+
sessionId: string | null,
|
|
443
|
+
colors: PowerlineColors,
|
|
444
|
+
): SegmentData | null {
|
|
445
|
+
if (!sessionId) {
|
|
446
|
+
return {
|
|
447
|
+
text: `tmux:none`,
|
|
448
|
+
bgColor: colors.tmuxBg,
|
|
449
|
+
fgColor: colors.tmuxFg,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
text: `tmux:${sessionId}`,
|
|
455
|
+
bgColor: colors.tmuxBg,
|
|
456
|
+
fgColor: colors.tmuxFg,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
renderContext(
|
|
461
|
+
contextInfo: ContextInfo | null,
|
|
462
|
+
colors: PowerlineColors,
|
|
463
|
+
config?: ContextSegmentConfig,
|
|
464
|
+
): SegmentData | null {
|
|
465
|
+
const barLength = 10;
|
|
466
|
+
const style = config?.displayStyle ?? "text";
|
|
467
|
+
const defaultMode = style === "text" ? "remaining" : "used";
|
|
468
|
+
const mode = config?.percentageMode ?? defaultMode;
|
|
469
|
+
|
|
470
|
+
const barStyleDef = this.resolveBarStyleDef(style);
|
|
471
|
+
|
|
472
|
+
const emptyPct = mode === "remaining" ? "100%" : "0%";
|
|
473
|
+
if (!contextInfo) {
|
|
474
|
+
if (barStyleDef) {
|
|
475
|
+
const emptyBar = barStyleDef.empty.repeat(barLength);
|
|
476
|
+
return {
|
|
477
|
+
text: `${emptyBar} ${emptyPct}`,
|
|
478
|
+
bgColor: colors.contextBg,
|
|
479
|
+
fgColor: colors.contextFg,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
text: `${this.leadingIcon(this.symbols.context_time, config)}0 (${emptyPct})`,
|
|
484
|
+
bgColor: colors.contextBg,
|
|
485
|
+
fgColor: colors.contextFg,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let bgColor = colors.contextBg;
|
|
490
|
+
let fgColor = colors.contextFg;
|
|
491
|
+
let bold = colors.contextBold;
|
|
492
|
+
|
|
493
|
+
if (contextInfo.contextLeftPercentage <= 20) {
|
|
494
|
+
bgColor = colors.contextCriticalBg;
|
|
495
|
+
fgColor = colors.contextCriticalFg;
|
|
496
|
+
bold = colors.contextCriticalBold;
|
|
497
|
+
} else if (contextInfo.contextLeftPercentage <= 40) {
|
|
498
|
+
bgColor = colors.contextWarningBg;
|
|
499
|
+
fgColor = colors.contextWarningFg;
|
|
500
|
+
bold = colors.contextWarningBold;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const pct =
|
|
504
|
+
mode === "remaining"
|
|
505
|
+
? contextInfo.contextLeftPercentage
|
|
506
|
+
: contextInfo.usablePercentage;
|
|
507
|
+
const filledCount = Math.round(
|
|
508
|
+
(contextInfo.usablePercentage / 100) * barLength,
|
|
509
|
+
);
|
|
510
|
+
const emptyCount = barLength - filledCount;
|
|
511
|
+
|
|
512
|
+
if (barStyleDef) {
|
|
513
|
+
const bar = this.buildBar(
|
|
514
|
+
barStyleDef,
|
|
515
|
+
filledCount,
|
|
516
|
+
emptyCount,
|
|
517
|
+
barLength,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const text = config?.showPercentageOnly
|
|
521
|
+
? `${bar} ${pct}%`
|
|
522
|
+
: `${bar} ${contextInfo.totalTokens.toLocaleString()} (${pct}%)`;
|
|
523
|
+
|
|
524
|
+
return { text, bgColor, fgColor, bold };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const iconPrefix = this.leadingIcon(this.symbols.context_time, config);
|
|
528
|
+
const text = config?.showPercentageOnly
|
|
529
|
+
? `${iconPrefix}${pct}%`
|
|
530
|
+
: `${iconPrefix}${contextInfo.totalTokens.toLocaleString()} (${pct}%)`;
|
|
531
|
+
|
|
532
|
+
return { text, bgColor, fgColor, bold };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private buildBar(
|
|
536
|
+
s: BarStyleDef,
|
|
537
|
+
filledCount: number,
|
|
538
|
+
emptyCount: number,
|
|
539
|
+
barLength: number,
|
|
540
|
+
): string {
|
|
541
|
+
if (s.marker) {
|
|
542
|
+
const pos = Math.min(filledCount, barLength - 1);
|
|
543
|
+
return (
|
|
544
|
+
s.filled.repeat(pos) + s.marker + s.empty.repeat(barLength - pos - 1)
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
if (s.cap) {
|
|
548
|
+
if (filledCount === 0) {
|
|
549
|
+
return s.cap + s.empty.repeat(barLength - 1);
|
|
550
|
+
}
|
|
551
|
+
if (filledCount >= barLength) {
|
|
552
|
+
return s.filled.repeat(barLength);
|
|
553
|
+
}
|
|
554
|
+
return (
|
|
555
|
+
s.filled.repeat(filledCount - 1) + s.cap + s.empty.repeat(emptyCount)
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
return s.filled.repeat(filledCount) + s.empty.repeat(emptyCount);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private resolveBarStyleDef(style: string): BarStyleDef | null {
|
|
562
|
+
return style === "bar"
|
|
563
|
+
? { filled: this.symbols.bar_filled, empty: this.symbols.bar_empty }
|
|
564
|
+
: (BAR_STYLES[style] ?? null);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private formatPercentageWithBar(
|
|
568
|
+
pct: number,
|
|
569
|
+
displayStyle?: BarDisplayStyle,
|
|
570
|
+
timeStr?: string | null,
|
|
571
|
+
): string {
|
|
572
|
+
const style = displayStyle ?? "text";
|
|
573
|
+
const barStyleDef = this.resolveBarStyleDef(style);
|
|
574
|
+
const barLength = 10;
|
|
575
|
+
|
|
576
|
+
if (barStyleDef) {
|
|
577
|
+
const filledCount = Math.round((pct / 100) * barLength);
|
|
578
|
+
const emptyCount = barLength - filledCount;
|
|
579
|
+
const bar = this.buildBar(
|
|
580
|
+
barStyleDef,
|
|
581
|
+
filledCount,
|
|
582
|
+
emptyCount,
|
|
583
|
+
barLength,
|
|
584
|
+
);
|
|
585
|
+
return timeStr ? `${bar} ${pct}% (${timeStr})` : `${bar} ${pct}%`;
|
|
586
|
+
}
|
|
587
|
+
return timeStr ? `${pct}% (${timeStr})` : `${pct}%`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
renderMetrics(
|
|
591
|
+
metricsInfo: MetricsInfo | null,
|
|
592
|
+
colors: PowerlineColors,
|
|
593
|
+
config?: MetricsSegmentConfig,
|
|
594
|
+
): SegmentData | null {
|
|
595
|
+
if (!metricsInfo) {
|
|
596
|
+
return {
|
|
597
|
+
text: `${this.symbols.metrics_response} new`,
|
|
598
|
+
bgColor: colors.metricsBg,
|
|
599
|
+
fgColor: colors.metricsFg,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const parts: string[] = [];
|
|
604
|
+
|
|
605
|
+
if (config?.showLastResponseTime && metricsInfo.lastResponseTime !== null) {
|
|
606
|
+
const lastResponseTime =
|
|
607
|
+
metricsInfo.lastResponseTime < 60
|
|
608
|
+
? `${metricsInfo.lastResponseTime.toFixed(1)}s`
|
|
609
|
+
: `${(metricsInfo.lastResponseTime / 60).toFixed(1)}m`;
|
|
610
|
+
parts.push(`${this.symbols.metrics_last_response} ${lastResponseTime}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (
|
|
614
|
+
config?.showResponseTime !== false &&
|
|
615
|
+
metricsInfo.responseTime !== null
|
|
616
|
+
) {
|
|
617
|
+
const responseTime =
|
|
618
|
+
metricsInfo.responseTime < 60
|
|
619
|
+
? `${metricsInfo.responseTime.toFixed(1)}s`
|
|
620
|
+
: `${(metricsInfo.responseTime / 60).toFixed(1)}m`;
|
|
621
|
+
parts.push(`${this.symbols.metrics_response} ${responseTime}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (
|
|
625
|
+
config?.showDuration !== false &&
|
|
626
|
+
metricsInfo.sessionDuration !== null
|
|
627
|
+
) {
|
|
628
|
+
const duration = formatDuration(metricsInfo.sessionDuration);
|
|
629
|
+
parts.push(`${this.symbols.metrics_duration} ${duration}`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (
|
|
633
|
+
config?.showMessageCount !== false &&
|
|
634
|
+
metricsInfo.messageCount !== null
|
|
635
|
+
) {
|
|
636
|
+
parts.push(
|
|
637
|
+
`${this.symbols.metrics_messages} ${metricsInfo.messageCount}`,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (
|
|
642
|
+
config?.showLinesAdded !== false &&
|
|
643
|
+
metricsInfo.linesAdded !== null &&
|
|
644
|
+
metricsInfo.linesAdded > 0
|
|
645
|
+
) {
|
|
646
|
+
parts.push(
|
|
647
|
+
`${this.symbols.metrics_lines_added} ${metricsInfo.linesAdded}`,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (
|
|
652
|
+
config?.showLinesRemoved !== false &&
|
|
653
|
+
metricsInfo.linesRemoved !== null &&
|
|
654
|
+
metricsInfo.linesRemoved > 0
|
|
655
|
+
) {
|
|
656
|
+
parts.push(
|
|
657
|
+
`${this.symbols.metrics_lines_removed} ${metricsInfo.linesRemoved}`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (parts.length === 0) {
|
|
662
|
+
return {
|
|
663
|
+
text: `${this.symbols.metrics_response} active`,
|
|
664
|
+
bgColor: colors.metricsBg,
|
|
665
|
+
fgColor: colors.metricsFg,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return {
|
|
670
|
+
text: parts.join(" "),
|
|
671
|
+
bgColor: colors.metricsBg,
|
|
672
|
+
fgColor: colors.metricsFg,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
renderBlock(
|
|
677
|
+
blockInfo: BlockInfo,
|
|
678
|
+
colors: PowerlineColors,
|
|
679
|
+
config?: BlockSegmentConfig,
|
|
680
|
+
): SegmentData {
|
|
681
|
+
const pct = Math.round(blockInfo.nativeUtilization);
|
|
682
|
+
const timeStr = formatLongTimeRemaining(blockInfo.timeRemaining);
|
|
683
|
+
const blockBudget = this.config.budget?.block;
|
|
684
|
+
const warningThreshold = blockBudget?.warningThreshold ?? 80;
|
|
685
|
+
|
|
686
|
+
let bgColor = colors.blockBg;
|
|
687
|
+
let fgColor = colors.blockFg;
|
|
688
|
+
let bold = colors.blockBold;
|
|
689
|
+
if (pct >= warningThreshold) {
|
|
690
|
+
bgColor = colors.contextCriticalBg;
|
|
691
|
+
fgColor = colors.contextCriticalFg;
|
|
692
|
+
bold = colors.contextCriticalBold;
|
|
693
|
+
} else if (pct >= 50) {
|
|
694
|
+
bgColor = colors.contextWarningBg;
|
|
695
|
+
fgColor = colors.contextWarningFg;
|
|
696
|
+
bold = colors.contextWarningBold;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
text: `${this.leadingIcon(this.symbols.block_cost, config)}${this.formatPercentageWithBar(pct, config?.displayStyle, timeStr)}`,
|
|
701
|
+
bgColor,
|
|
702
|
+
fgColor,
|
|
703
|
+
bold,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
renderWeekly(
|
|
708
|
+
hookData: ClaudeHookData,
|
|
709
|
+
colors: PowerlineColors,
|
|
710
|
+
config?: WeeklySegmentConfig,
|
|
711
|
+
): SegmentData | null {
|
|
712
|
+
const sevenDay = hookData.rate_limits?.seven_day;
|
|
713
|
+
if (!sevenDay) return null;
|
|
714
|
+
|
|
715
|
+
const pct = Math.round(sevenDay.used_percentage);
|
|
716
|
+
const timeStr = formatLongTimeRemaining(
|
|
717
|
+
minutesUntilReset(sevenDay.resets_at),
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
let bgColor = colors.weeklyBg;
|
|
721
|
+
let fgColor = colors.weeklyFg;
|
|
722
|
+
let bold = colors.weeklyBold;
|
|
723
|
+
if (pct >= 80) {
|
|
724
|
+
bgColor = colors.contextCriticalBg;
|
|
725
|
+
fgColor = colors.contextCriticalFg;
|
|
726
|
+
bold = colors.contextCriticalBold;
|
|
727
|
+
} else if (pct >= 50) {
|
|
728
|
+
bgColor = colors.contextWarningBg;
|
|
729
|
+
fgColor = colors.contextWarningFg;
|
|
730
|
+
bold = colors.contextWarningBold;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
text: `${this.leadingIcon(this.symbols.weekly_cost, config)}${this.formatPercentageWithBar(pct, config?.displayStyle, timeStr)}`,
|
|
735
|
+
bgColor,
|
|
736
|
+
fgColor,
|
|
737
|
+
bold,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
renderToday(
|
|
742
|
+
todayInfo: TodayInfo,
|
|
743
|
+
colors: PowerlineColors,
|
|
744
|
+
configOrType?: TodaySegmentConfig | string,
|
|
745
|
+
): SegmentData | null {
|
|
746
|
+
const config: TodaySegmentConfig | undefined =
|
|
747
|
+
typeof configOrType === "string"
|
|
748
|
+
? ({ enabled: true, type: configOrType } as TodaySegmentConfig)
|
|
749
|
+
: configOrType;
|
|
750
|
+
const type = config?.type ?? "cost";
|
|
751
|
+
const todayBudget = this.config.budget?.today;
|
|
752
|
+
const formattedUsage = this.formatUsageWithBudget(
|
|
753
|
+
todayInfo.cost,
|
|
754
|
+
todayInfo.tokens,
|
|
755
|
+
todayInfo.tokenBreakdown,
|
|
756
|
+
type,
|
|
757
|
+
todayBudget,
|
|
758
|
+
config?.showUnits ?? true,
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
if (formattedUsage === null) return null;
|
|
762
|
+
|
|
763
|
+
const text = `${this.leadingIcon(this.symbols.today_cost, config)}${formattedUsage}`;
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
text,
|
|
767
|
+
bgColor: colors.todayBg,
|
|
768
|
+
fgColor: colors.todayFg,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
private getDisplayDirectoryName(
|
|
773
|
+
currentDir: string,
|
|
774
|
+
projectDir?: string,
|
|
775
|
+
): string {
|
|
776
|
+
if (currentDir.startsWith("~")) {
|
|
777
|
+
return currentDir;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (projectDir && projectDir !== currentDir) {
|
|
781
|
+
if (currentDir.startsWith(projectDir)) {
|
|
782
|
+
const relativePath = currentDir.slice(projectDir.length + 1);
|
|
783
|
+
return relativePath || projectDir.split(/[\\/]/).pop() || "project";
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
return currentDir;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private formatUsageDisplay(
|
|
791
|
+
cost: number | null,
|
|
792
|
+
tokens: number | null,
|
|
793
|
+
tokenBreakdown: TokenBreakdown | null,
|
|
794
|
+
type: string,
|
|
795
|
+
showUnits: boolean,
|
|
796
|
+
): string {
|
|
797
|
+
const tokenStr = showUnits
|
|
798
|
+
? formatTokens(tokens)
|
|
799
|
+
: formatTokenCount(tokens);
|
|
800
|
+
switch (type) {
|
|
801
|
+
case "cost":
|
|
802
|
+
return formatCost(cost);
|
|
803
|
+
case "tokens":
|
|
804
|
+
return tokenStr;
|
|
805
|
+
case "both":
|
|
806
|
+
return `${formatCost(cost)} (${tokenStr})`;
|
|
807
|
+
case "breakdown":
|
|
808
|
+
return formatTokenBreakdown(tokenBreakdown);
|
|
809
|
+
default:
|
|
810
|
+
return formatCost(cost);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private formatUsageWithBudget(
|
|
815
|
+
cost: number | null,
|
|
816
|
+
tokens: number | null,
|
|
817
|
+
tokenBreakdown: TokenBreakdown | null,
|
|
818
|
+
type: string,
|
|
819
|
+
budget: BudgetItemConfig | undefined,
|
|
820
|
+
showUnits: boolean,
|
|
821
|
+
): string | null {
|
|
822
|
+
const state = resolveBudgetDisplay(cost, tokens, budget);
|
|
823
|
+
if (state.suppressAll) return null;
|
|
824
|
+
if (!state.showBase) return state.percentText;
|
|
825
|
+
|
|
826
|
+
const baseDisplay = this.formatUsageDisplay(
|
|
827
|
+
cost,
|
|
828
|
+
tokens,
|
|
829
|
+
tokenBreakdown,
|
|
830
|
+
type,
|
|
831
|
+
showUnits,
|
|
832
|
+
);
|
|
833
|
+
return state.percentText
|
|
834
|
+
? `${baseDisplay} ${state.percentText}`
|
|
835
|
+
: baseDisplay;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
renderVersion(
|
|
839
|
+
hookData: ClaudeHookData,
|
|
840
|
+
colors: PowerlineColors,
|
|
841
|
+
config?: VersionSegmentConfig,
|
|
842
|
+
): SegmentData | null {
|
|
843
|
+
if (!hookData.version) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return {
|
|
848
|
+
text: `${this.leadingIcon(this.symbols.version, config)}v${hookData.version}`,
|
|
849
|
+
bgColor: colors.versionBg,
|
|
850
|
+
fgColor: colors.versionFg,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
renderEnv(
|
|
855
|
+
colors: PowerlineColors,
|
|
856
|
+
config: EnvSegmentConfig,
|
|
857
|
+
): SegmentData | null {
|
|
858
|
+
const value = globalThis.process?.env?.[config.variable];
|
|
859
|
+
if (!value) return null;
|
|
860
|
+
const prefix = config.prefix ?? config.variable;
|
|
861
|
+
const iconPrefix = this.leadingIcon(this.symbols.env, config);
|
|
862
|
+
const text = prefix
|
|
863
|
+
? `${iconPrefix}${prefix}: ${value}`
|
|
864
|
+
: `${iconPrefix}${value}`;
|
|
865
|
+
return { text, bgColor: colors.envBg, fgColor: colors.envFg };
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
renderAgent(
|
|
869
|
+
hookData: ClaudeHookData,
|
|
870
|
+
colors: PowerlineColors,
|
|
871
|
+
config?: AgentSegmentConfig,
|
|
872
|
+
): SegmentData | null {
|
|
873
|
+
const rawName = hookData.agent?.name;
|
|
874
|
+
if (typeof rawName !== "string") return null;
|
|
875
|
+
const name = rawName.trim();
|
|
876
|
+
if (!name) return null;
|
|
877
|
+
|
|
878
|
+
const iconPrefix = this.leadingIcon(this.symbols.agent, config);
|
|
879
|
+
const body = config?.showLabel ? `agent: ${name}` : name;
|
|
880
|
+
|
|
881
|
+
return {
|
|
882
|
+
text: `${iconPrefix}${body}`,
|
|
883
|
+
bgColor: colors.agentBg,
|
|
884
|
+
fgColor: colors.agentFg,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
renderThinking(
|
|
889
|
+
hookData: ClaudeHookData,
|
|
890
|
+
colors: PowerlineColors,
|
|
891
|
+
config?: ThinkingSegmentConfig,
|
|
892
|
+
): SegmentData | null {
|
|
893
|
+
const showEnabled = config?.showEnabled ?? true;
|
|
894
|
+
const showEffort = config?.showEffort ?? true;
|
|
895
|
+
if (!showEnabled && !showEffort) return null;
|
|
896
|
+
|
|
897
|
+
const enabled = showEnabled ? getThinkingEnabled(hookData) : null;
|
|
898
|
+
const level = showEffort ? getEffortLevel(hookData) : null;
|
|
899
|
+
|
|
900
|
+
const parts: string[] = [];
|
|
901
|
+
if (enabled !== null) parts.push(enabled ? "On" : "Off");
|
|
902
|
+
if (level) parts.push(level);
|
|
903
|
+
if (parts.length === 0) return null;
|
|
904
|
+
|
|
905
|
+
const iconPrefix = this.leadingIcon(this.symbols.thinking, config);
|
|
906
|
+
return {
|
|
907
|
+
text: `${iconPrefix}${parts.join(" · ")}`,
|
|
908
|
+
bgColor: colors.thinkingBg,
|
|
909
|
+
fgColor: colors.thinkingFg,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
renderCacheTimer(
|
|
914
|
+
info: CacheTimerInfo,
|
|
915
|
+
colors: PowerlineColors,
|
|
916
|
+
config?: CacheTimerSegmentConfig,
|
|
917
|
+
): SegmentData {
|
|
918
|
+
const e = info.elapsedSeconds;
|
|
919
|
+
const iconPrefix = this.leadingIcon(this.symbols.cache_timer, config);
|
|
920
|
+
|
|
921
|
+
let bgColor = colors.cacheTimerBg;
|
|
922
|
+
let fgColor = colors.cacheTimerFg;
|
|
923
|
+
let bold = colors.cacheTimerBold;
|
|
924
|
+
|
|
925
|
+
if (config?.displayMode === "remaining") {
|
|
926
|
+
const ttl = config.ttlSeconds ?? info.detectedTtlSeconds ?? 3600;
|
|
927
|
+
const remaining = Math.max(0, ttl - e);
|
|
928
|
+
const text = `${iconPrefix}${formatCacheTimerRemaining(remaining)}`;
|
|
929
|
+
if (remaining < 60) {
|
|
930
|
+
bgColor = colors.contextCriticalBg;
|
|
931
|
+
fgColor = colors.contextCriticalFg;
|
|
932
|
+
bold = colors.contextCriticalBold;
|
|
933
|
+
} else if (remaining < 300) {
|
|
934
|
+
bgColor = colors.contextWarningBg;
|
|
935
|
+
fgColor = colors.contextWarningFg;
|
|
936
|
+
bold = colors.contextWarningBold;
|
|
937
|
+
}
|
|
938
|
+
return { text, bgColor, fgColor, bold };
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const text = `${iconPrefix}${formatCacheTimerElapsed(e)}`;
|
|
942
|
+
if (e >= 300) {
|
|
943
|
+
bgColor = colors.contextCriticalBg;
|
|
944
|
+
fgColor = colors.contextCriticalFg;
|
|
945
|
+
bold = colors.contextCriticalBold;
|
|
946
|
+
} else if (e >= 180) {
|
|
947
|
+
bgColor = colors.contextWarningBg;
|
|
948
|
+
fgColor = colors.contextWarningFg;
|
|
949
|
+
bold = colors.contextWarningBold;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return { text, bgColor, fgColor, bold };
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
renderBastra(
|
|
956
|
+
info: BastraInfo | null,
|
|
957
|
+
colors: PowerlineColors,
|
|
958
|
+
config?: BastraSegmentConfig,
|
|
959
|
+
): SegmentData | null {
|
|
960
|
+
if (!info) return null;
|
|
961
|
+
|
|
962
|
+
const icon = this.leadingIcon(this.symbols.bastra, config);
|
|
963
|
+
let text: string;
|
|
964
|
+
|
|
965
|
+
if (info.state === "idle" || info.recallCount === 0) {
|
|
966
|
+
text = `${icon}bastra · ${info.vaultSize} memories`;
|
|
967
|
+
} else if (info.currentStage && info.currentStageStartedAt) {
|
|
968
|
+
// Active recall: live total = accumulated + the running recall's elapsed.
|
|
969
|
+
const live =
|
|
970
|
+
info.currentRecallStartedAt !== null
|
|
971
|
+
? info.totalMs + Math.max(0, Date.now() - info.currentRecallStartedAt)
|
|
972
|
+
: info.totalMs;
|
|
973
|
+
// Prefer the human-readable banter phrase; fall back to the raw stage
|
|
974
|
+
// name (banter off, or older feed without current_message).
|
|
975
|
+
const label = info.currentMessage ?? info.currentStage;
|
|
976
|
+
text = `${icon}bastra · ${info.recallCount} calls · ${info.totalHits} hits · ${live}ms · ${label}`;
|
|
977
|
+
} else {
|
|
978
|
+
// Between/after recalls in this turn — done snapshot. The running-state
|
|
979
|
+
// phrase is too brief (~120ms) for the ≥1s statusline refresh to catch,
|
|
980
|
+
// so the forwarder persists a done phrase here; show it for a short TTL,
|
|
981
|
+
// then fall back to the bare banner.
|
|
982
|
+
const PHRASE_TTL_MS = 10_000;
|
|
983
|
+
const showPhrase =
|
|
984
|
+
info.lastPhrase !== null &&
|
|
985
|
+
info.lastPhraseAt !== null &&
|
|
986
|
+
Date.now() - info.lastPhraseAt < PHRASE_TTL_MS;
|
|
987
|
+
const tail = showPhrase ? ` · ${info.lastPhrase}` : "";
|
|
988
|
+
// hits come from recalls only; ms from any tool call. Show each only
|
|
989
|
+
// when present, so load_memory-only turns read "N calls · Xms" (no hits).
|
|
990
|
+
const hits = info.totalHits > 0 ? ` · ${info.totalHits} hits` : "";
|
|
991
|
+
const ms = info.totalMs > 0 ? ` · ${info.totalMs}ms` : "";
|
|
992
|
+
text = `${icon}✓ bastra · ${info.recallCount} calls${hits}${ms}${tail}`;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return {
|
|
996
|
+
text,
|
|
997
|
+
bgColor: colors.bastraBg,
|
|
998
|
+
fgColor: colors.bastraFg,
|
|
999
|
+
bold: colors.bastraBold,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|