@hank-warren/pi-statusline 0.2.4 → 0.4.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/CHANGELOG.md +20 -0
- package/README.md +57 -4
- package/celebration-preview.ts +125 -0
- package/celebration-styles.ts +158 -0
- package/index.ts +237 -72
- package/package.json +6 -1
- package/settings-menu.ts +342 -0
- package/settings.ts +252 -0
- package/themes.ts +129 -0
- package/worktrees.ts +63 -37
package/index.ts
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
1
2
|
import { basename } from "node:path";
|
|
2
|
-
import
|
|
3
|
-
|
|
3
|
+
import {
|
|
4
|
+
type ExtensionAPI,
|
|
5
|
+
type ExtensionContext,
|
|
6
|
+
getSelectListTheme,
|
|
7
|
+
getSettingsListTheme,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { SettingsList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
4
10
|
import {
|
|
5
11
|
CacheCelebrationController,
|
|
6
12
|
type CacheCelebrationSnapshot,
|
|
7
13
|
triggerCacheCelebrationForMessage,
|
|
8
14
|
} from "./cache-celebration.ts";
|
|
15
|
+
import { CelebrationPreview, trackSelectedLabel } from "./celebration-preview.ts";
|
|
16
|
+
import { DEFAULT_CELEBRATION_STYLE, renderCacheBadge } from "./celebration-styles.ts";
|
|
9
17
|
import { FullRedrawScheduler } from "./redraw.ts";
|
|
18
|
+
import {
|
|
19
|
+
applySettingChange,
|
|
20
|
+
buildSettingItems,
|
|
21
|
+
CACHE_CELEBRATION_LABEL,
|
|
22
|
+
createAliasSubmenu,
|
|
23
|
+
createWorktreeRootSubmenu,
|
|
24
|
+
} from "./settings-menu.ts";
|
|
25
|
+
import { defaultSettings, repoAlias, SettingsStore, type StatuslineSettings } from "./settings.ts";
|
|
26
|
+
import { resolvePalette, type StatuslinePalette, STATUSLINE_THEMES } from "./themes.ts";
|
|
10
27
|
import { type UsageSnapshot, usageBand, UsageTracker } from "./usage.ts";
|
|
11
28
|
import {
|
|
12
29
|
type GitRepositoryStatus,
|
|
13
30
|
readGitStatus,
|
|
14
|
-
repoAlias,
|
|
15
31
|
type SessionWorktree,
|
|
16
32
|
SessionWorktreeTracker,
|
|
17
33
|
} from "./worktrees.ts";
|
|
@@ -28,19 +44,8 @@ export interface StatuslineData {
|
|
|
28
44
|
usage?: UsageSnapshot;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
|
-
const BLUE = "\x1b[38;2;0;153;255m";
|
|
32
|
-
const ORANGE = "\x1b[38;2;255;176;85m";
|
|
33
|
-
const GREEN = "\x1b[38;2;0;175;80m";
|
|
34
|
-
const CYAN = "\x1b[38;2;86;182;194m";
|
|
35
|
-
const RED = "\x1b[38;2;255;85;85m";
|
|
36
|
-
const YELLOW = "\x1b[38;2;230;200;0m";
|
|
37
|
-
const WHITE = "\x1b[38;2;220;220;220m";
|
|
38
|
-
const MAGENTA = "\x1b[38;2;190;120;255m";
|
|
39
|
-
const NEON_CYAN = "\x1b[38;2;0;255;255m";
|
|
40
|
-
const NEON_MAGENTA = "\x1b[38;2;255;0;255m";
|
|
41
|
-
const DIM = "\x1b[2m";
|
|
42
|
-
const BOLD = "\x1b[1m";
|
|
43
47
|
const RESET = "\x1b[0m";
|
|
48
|
+
const DEFAULT_PALETTE = STATUSLINE_THEMES.default;
|
|
44
49
|
|
|
45
50
|
function styled(style: string, text: string): string {
|
|
46
51
|
return `${style}${text}${RESET}`;
|
|
@@ -48,32 +53,37 @@ function styled(style: string, text: string): string {
|
|
|
48
53
|
|
|
49
54
|
const CLAUDE_ICON = "\uec82";
|
|
50
55
|
const OPENAI_ICON = "\uec81";
|
|
51
|
-
const USAGE_BAND_COLORS = { green: GREEN, yellow: YELLOW, orange: ORANGE, red: RED } as const;
|
|
52
56
|
|
|
53
|
-
function
|
|
54
|
-
return
|
|
57
|
+
function bandColor(remaining: number, palette: StatuslinePalette): string {
|
|
58
|
+
return { green: palette.ok, yellow: palette.warn, orange: palette.caution, red: palette.danger }[
|
|
59
|
+
usageBand(remaining)
|
|
60
|
+
];
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
export function renderUsageSegment(
|
|
63
|
+
export function renderUsageSegment(
|
|
64
|
+
usage: UsageSnapshot,
|
|
65
|
+
palette: StatuslinePalette = DEFAULT_PALETTE,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
const percent = (remaining: number) => styled(bandColor(remaining, palette), `${remaining}`);
|
|
58
68
|
const parts: string[] = [];
|
|
59
69
|
if (usage.claude) {
|
|
60
|
-
const dot = styled(
|
|
61
|
-
let claude = `${
|
|
62
|
-
if (usage.claude.scopedWeekly !== undefined) claude += `${dot}${
|
|
63
|
-
parts.push(`${styled(
|
|
70
|
+
const dot = styled(palette.dim, "\u00b7");
|
|
71
|
+
let claude = `${percent(usage.claude.fiveHour)}${dot}${percent(usage.claude.sevenDay)}`;
|
|
72
|
+
if (usage.claude.scopedWeekly !== undefined) claude += `${dot}${percent(usage.claude.scopedWeekly)}`;
|
|
73
|
+
parts.push(`${styled(palette.text, CLAUDE_ICON)} ${claude}`);
|
|
64
74
|
}
|
|
65
75
|
if (usage.codex) {
|
|
66
|
-
parts.push(`${styled(
|
|
76
|
+
parts.push(`${styled(palette.text, OPENAI_ICON)} ${percent(usage.codex.weekly)}`);
|
|
67
77
|
}
|
|
68
78
|
return parts.length > 0 ? parts.join(" ") : undefined;
|
|
69
79
|
}
|
|
70
80
|
|
|
71
|
-
function contextColor(contextTokens: number, contextWindow: number): string {
|
|
81
|
+
function contextColor(contextTokens: number, contextWindow: number, palette: StatuslinePalette): string {
|
|
72
82
|
const percent = contextWindow > 0 ? Math.floor((contextTokens * 100) / contextWindow) : 0;
|
|
73
|
-
if (percent >= 90) return
|
|
74
|
-
if (percent >= 70) return
|
|
75
|
-
if (percent >= 50) return
|
|
76
|
-
return
|
|
83
|
+
if (percent >= 90) return palette.danger;
|
|
84
|
+
if (percent >= 70) return palette.warn;
|
|
85
|
+
if (percent >= 50) return palette.caution;
|
|
86
|
+
return palette.ok;
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
export function formatTokenCount(tokens: number): string {
|
|
@@ -86,70 +96,116 @@ export function formatTokenCount(tokens: number): string {
|
|
|
86
96
|
return `${safeTokens}`;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
|
-
function renderRepository(
|
|
99
|
+
function renderRepository(
|
|
100
|
+
name: string,
|
|
101
|
+
status: GitRepositoryStatus,
|
|
102
|
+
palette: StatuslinePalette,
|
|
103
|
+
nameColor = palette.path,
|
|
104
|
+
): string {
|
|
90
105
|
let part = styled(nameColor, name);
|
|
91
|
-
part += styled(
|
|
92
|
-
part += styled(
|
|
93
|
-
if (status.dirty) part += styled(
|
|
94
|
-
if (status.behind > 0) part += ` ${styled(
|
|
106
|
+
part += styled(palette.dim, ":");
|
|
107
|
+
part += styled(palette.branch, status.branch);
|
|
108
|
+
if (status.dirty) part += styled(palette.warn, "*");
|
|
109
|
+
if (status.behind > 0) part += ` ${styled(palette.caution, `⇣${status.behind}`)}`;
|
|
95
110
|
return part;
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
export function renderCacheCelebrationLine(
|
|
99
114
|
summary: string,
|
|
100
115
|
celebration: CacheCelebrationSnapshot,
|
|
116
|
+
palette: StatuslinePalette = DEFAULT_PALETTE,
|
|
117
|
+
style: string = DEFAULT_CELEBRATION_STYLE,
|
|
101
118
|
): string {
|
|
102
|
-
const badge =
|
|
103
|
-
|
|
104
|
-
const animatedBadge = `${BOLD}${color}${badge}${RESET}`;
|
|
105
|
-
return `${summary}${styled(DIM, " | ")}${animatedBadge}`;
|
|
119
|
+
const badge = renderCacheBadge(celebration.percent, celebration.frame, style, palette);
|
|
120
|
+
return `${summary}${styled(palette.dim, " | ")}${badge}`;
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
function renderWorktreeLine(
|
|
109
|
-
|
|
123
|
+
function renderWorktreeLine(
|
|
124
|
+
worktrees: SessionWorktree[],
|
|
125
|
+
settings: StatuslineSettings,
|
|
126
|
+
palette: StatuslinePalette,
|
|
127
|
+
): string {
|
|
128
|
+
const separator = styled(palette.dim, " | ");
|
|
110
129
|
const parts = worktrees.map((worktree) => {
|
|
111
|
-
let part = renderRepository(repoAlias(worktree.repo), worktree);
|
|
130
|
+
let part = renderRepository(repoAlias(worktree.repo, settings.repoAliases), worktree, palette);
|
|
112
131
|
if (worktree.pr !== undefined) {
|
|
113
132
|
const state = worktree.prState?.toUpperCase();
|
|
114
|
-
const color =
|
|
133
|
+
const color =
|
|
134
|
+
state === "OPEN"
|
|
135
|
+
? palette.ok
|
|
136
|
+
: state === "MERGED"
|
|
137
|
+
? palette.accent
|
|
138
|
+
: state === "CLOSED"
|
|
139
|
+
? palette.danger
|
|
140
|
+
: palette.dim;
|
|
115
141
|
part += ` ${styled(color, `#${worktree.pr}`)}`;
|
|
116
142
|
}
|
|
117
143
|
return part;
|
|
118
144
|
});
|
|
119
|
-
return `${styled(
|
|
145
|
+
return `${styled(palette.dim, "⑂")} ${parts.join(separator)}`;
|
|
120
146
|
}
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (width <= 0) return Array.from({ length: lineCount }, () => "");
|
|
148
|
+
/** Home-independent defaults; only `worktreeRoot` varies by host and rendering never reads it. */
|
|
149
|
+
const RENDER_DEFAULTS = defaultSettings("");
|
|
125
150
|
|
|
126
|
-
|
|
151
|
+
export function renderStatusline(
|
|
152
|
+
data: StatuslineData,
|
|
153
|
+
width: number,
|
|
154
|
+
settings: StatuslineSettings = RENDER_DEFAULTS,
|
|
155
|
+
): string[] {
|
|
156
|
+
const palette = resolvePalette(settings.theme);
|
|
157
|
+
const separator = styled(palette.dim, " | ");
|
|
127
158
|
const used =
|
|
128
159
|
data.contextTokens === null
|
|
129
|
-
? styled(
|
|
130
|
-
: styled(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
? styled(palette.dim, "?")
|
|
161
|
+
: styled(
|
|
162
|
+
contextColor(data.contextTokens, data.contextWindow, palette),
|
|
163
|
+
formatTokenCount(data.contextTokens),
|
|
164
|
+
);
|
|
165
|
+
const usageSegment = settings.showUsage && data.usage ? renderUsageSegment(data.usage, palette) : undefined;
|
|
166
|
+
const segments = [
|
|
167
|
+
settings.showModel ? styled(palette.model, data.model) : undefined,
|
|
168
|
+
settings.showDirectory
|
|
169
|
+
? data.cwdGit
|
|
170
|
+
? renderRepository(data.cwd, data.cwdGit, palette)
|
|
171
|
+
: styled(palette.branch, data.cwd)
|
|
172
|
+
: undefined,
|
|
173
|
+
settings.showContext
|
|
174
|
+
? `${used}${styled(palette.dim, "/")}${styled(palette.text, formatTokenCount(data.contextWindow))}`
|
|
175
|
+
: undefined,
|
|
176
|
+
usageSegment,
|
|
177
|
+
].filter((segment): segment is string => segment !== undefined);
|
|
178
|
+
|
|
179
|
+
const showWorktreeLine = settings.showWorktrees && data.worktrees.length > 0;
|
|
180
|
+
const lineCount = (segments.length > 0 ? 1 : 0) + (showWorktreeLine ? 1 : 0) + (settings.showSessionId ? 1 : 0);
|
|
181
|
+
// A footer that renders nothing would collapse the block; keep one stable row.
|
|
182
|
+
if (lineCount === 0) return [""];
|
|
183
|
+
if (width <= 0) return Array.from({ length: lineCount }, () => "");
|
|
184
|
+
|
|
185
|
+
const lines: string[] = [];
|
|
186
|
+
if (segments.length > 0) {
|
|
187
|
+
const summary = segments.join(separator);
|
|
188
|
+
// A badge-only first line is separator soup, so the celebration needs a summary.
|
|
189
|
+
const celebration = settings.showCacheCelebration ? data.cacheCelebration : undefined;
|
|
190
|
+
lines.push(
|
|
191
|
+
truncateToWidth(
|
|
192
|
+
celebration
|
|
193
|
+
? renderCacheCelebrationLine(summary, celebration, palette, settings.cacheCelebrationStyle)
|
|
194
|
+
: summary,
|
|
195
|
+
width,
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (showWorktreeLine) {
|
|
200
|
+
lines.push(truncateToWidth(renderWorktreeLine(data.worktrees, settings, palette), width, "…"));
|
|
201
|
+
}
|
|
202
|
+
if (settings.showSessionId) lines.push(truncateToWidth(styled(palette.dim, data.sessionId), width));
|
|
148
203
|
return lines;
|
|
149
204
|
}
|
|
150
205
|
|
|
151
206
|
export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
152
207
|
let requestRender: (() => void) | undefined;
|
|
208
|
+
const celebrationPreview = new CelebrationPreview(() => requestRender?.());
|
|
153
209
|
const fullRedraw = new FullRedrawScheduler();
|
|
154
210
|
const cacheCelebration = new CacheCelebrationController(() => requestRender?.());
|
|
155
211
|
let tracker: SessionWorktreeTracker | undefined;
|
|
@@ -157,6 +213,9 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
|
157
213
|
let cwdGit: GitRepositoryStatus | null = null;
|
|
158
214
|
let cwdStatusAbort: AbortController | undefined;
|
|
159
215
|
let cwdStatusInFlight: Promise<void> | undefined;
|
|
216
|
+
const home = homedir();
|
|
217
|
+
const settingsStore = new SettingsStore({ home });
|
|
218
|
+
let settings = settingsStore.get();
|
|
160
219
|
|
|
161
220
|
const runInBackground = (operation: Promise<void>): void => {
|
|
162
221
|
operation.catch(() => {
|
|
@@ -193,26 +252,128 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
|
193
252
|
|
|
194
253
|
const resetTracker = (ctx: ExtensionContext): void => {
|
|
195
254
|
tracker?.dispose();
|
|
255
|
+
tracker = undefined;
|
|
196
256
|
cwdStatusAbort?.abort();
|
|
197
257
|
cwdGit = null;
|
|
198
258
|
cwdStatusAbort = new AbortController();
|
|
199
259
|
cwdStatusInFlight = undefined;
|
|
260
|
+
runInBackground(refreshCwdStatus(ctx));
|
|
261
|
+
if (settings.showUsage) runInBackground(usageTracker.refresh());
|
|
262
|
+
// A hidden worktree line must not pay for git/gh polling.
|
|
263
|
+
if (!settings.showWorktrees) return;
|
|
200
264
|
const next = new SessionWorktreeTracker({
|
|
201
265
|
exec: (command, args, options) => pi.exec(command, args, options),
|
|
202
|
-
|
|
266
|
+
worktreeRoot: settings.worktreeRoot,
|
|
267
|
+
home,
|
|
203
268
|
onChange: () => requestRender?.(),
|
|
204
269
|
});
|
|
205
270
|
tracker = next;
|
|
206
|
-
runInBackground(refreshCwdStatus(ctx));
|
|
207
271
|
runInBackground(next.seedFromEntries(ctx.sessionManager.getBranch()));
|
|
208
272
|
runInBackground(next.includeCurrentWorktree(ctx.cwd));
|
|
209
|
-
runInBackground(usageTracker.refresh());
|
|
210
273
|
};
|
|
211
274
|
|
|
275
|
+
/** Adopt a new settings snapshot: apply it live, then persist in the background. */
|
|
276
|
+
const applySettings = (ctx: ExtensionContext, next: StatuslineSettings, persist = true): void => {
|
|
277
|
+
const previous = settings;
|
|
278
|
+
settings = next;
|
|
279
|
+
settingsStore.set(next);
|
|
280
|
+
|
|
281
|
+
if (!next.showWorktrees) {
|
|
282
|
+
tracker?.dispose();
|
|
283
|
+
tracker = undefined;
|
|
284
|
+
} else if (!previous.showWorktrees || !tracker || previous.worktreeRoot !== next.worktreeRoot) {
|
|
285
|
+
resetTracker(ctx);
|
|
286
|
+
}
|
|
287
|
+
if (next.showUsage && !previous.showUsage) runInBackground(usageTracker.refresh());
|
|
288
|
+
if (!next.showCacheCelebration) cacheCelebration.dispose();
|
|
289
|
+
// Dropping a row leaves a stale one behind in fullscreen mode.
|
|
290
|
+
if (previous.showSessionId !== next.showSessionId || previous.showWorktrees !== next.showWorktrees) {
|
|
291
|
+
fullRedraw.request();
|
|
292
|
+
}
|
|
293
|
+
requestRender?.();
|
|
294
|
+
|
|
295
|
+
if (!persist) return;
|
|
296
|
+
settingsStore.save(next).catch((error: unknown) => {
|
|
297
|
+
ctx.ui.notify(
|
|
298
|
+
`Could not save statusline settings: ${error instanceof Error ? error.message : String(error)}`,
|
|
299
|
+
"warning",
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
pi.registerCommand("statusline", {
|
|
305
|
+
description: "Configure the statusline",
|
|
306
|
+
handler: async (_args, ctx) => {
|
|
307
|
+
if (ctx.mode !== "tui") {
|
|
308
|
+
ctx.ui.notify("/statusline requires interactive TUI mode", "warning");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
|
313
|
+
const tracked = trackSelectedLabel(getSettingsListTheme());
|
|
314
|
+
const settingsTheme = tracked.theme;
|
|
315
|
+
const submenuHost = {
|
|
316
|
+
getSettings: () => settings,
|
|
317
|
+
commit: (next: StatuslineSettings) => applySettings(ctx, next),
|
|
318
|
+
notify: (message: string) => ctx.ui.notify(message, "warning"),
|
|
319
|
+
requestRender: () => tui.requestRender(),
|
|
320
|
+
settingsTheme,
|
|
321
|
+
selectTheme: getSelectListTheme(),
|
|
322
|
+
home,
|
|
323
|
+
};
|
|
324
|
+
const list = new SettingsList(
|
|
325
|
+
buildSettingItems(
|
|
326
|
+
settings,
|
|
327
|
+
{
|
|
328
|
+
worktreeRoot: createWorktreeRootSubmenu(submenuHost),
|
|
329
|
+
repoAliases: createAliasSubmenu(submenuHost),
|
|
330
|
+
},
|
|
331
|
+
home,
|
|
332
|
+
),
|
|
333
|
+
10,
|
|
334
|
+
settingsTheme,
|
|
335
|
+
(id, value) => {
|
|
336
|
+
const change = applySettingChange(settings, id, value, home);
|
|
337
|
+
if (change.kind === "error") ctx.ui.notify(change.message, "warning");
|
|
338
|
+
else if (change.kind === "settings") applySettings(ctx, change.settings);
|
|
339
|
+
tui.requestRender();
|
|
340
|
+
},
|
|
341
|
+
() => done(undefined),
|
|
342
|
+
{ enableSearch: true },
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// The preview animates the real footer, so it only needs to wrap the
|
|
346
|
+
// list's render to learn which row currently has focus.
|
|
347
|
+
return {
|
|
348
|
+
dispose(): void {
|
|
349
|
+
// Clear the fake badge from the footer the menu was drawn over.
|
|
350
|
+
celebrationPreview.dispose();
|
|
351
|
+
requestRender?.();
|
|
352
|
+
},
|
|
353
|
+
invalidate(): void {
|
|
354
|
+
list.invalidate();
|
|
355
|
+
},
|
|
356
|
+
handleInput(data: string): void {
|
|
357
|
+
list.handleInput(data);
|
|
358
|
+
},
|
|
359
|
+
render(width: number): string[] {
|
|
360
|
+
tracked.begin();
|
|
361
|
+
const lines = list.render(width);
|
|
362
|
+
celebrationPreview.setActive(tracked.selected() === CACHE_CELEBRATION_LABEL);
|
|
363
|
+
return lines;
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
212
370
|
pi.on("session_start", (_event, ctx) => {
|
|
213
371
|
cacheCelebration.dispose();
|
|
214
372
|
if (ctx.mode !== "tui") return;
|
|
215
373
|
|
|
374
|
+
// setFooter is synchronous, so the first frames render with defaults.
|
|
375
|
+
runInBackground(settingsStore.load().then((loaded) => applySettings(ctx, loaded, false)));
|
|
376
|
+
|
|
216
377
|
ctx.ui.setFooter((tui, _theme, footerData) => {
|
|
217
378
|
requestRender = () => tui.requestRender();
|
|
218
379
|
// Fullscreen mode never repaints unchanged rows; the session id line is
|
|
@@ -227,6 +388,7 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
|
227
388
|
dispose(): void {
|
|
228
389
|
stopBranchUpdates();
|
|
229
390
|
cacheCelebration.dispose();
|
|
391
|
+
celebrationPreview.dispose();
|
|
230
392
|
fullRedraw.detach();
|
|
231
393
|
requestRender = undefined;
|
|
232
394
|
},
|
|
@@ -246,10 +408,11 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
|
246
408
|
contextWindow: usage?.contextWindow ?? ctx.model?.contextWindow ?? 0,
|
|
247
409
|
worktrees: tracker?.getWorktrees() ?? [],
|
|
248
410
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
249
|
-
cacheCelebration: cacheCelebration.snapshot(),
|
|
411
|
+
cacheCelebration: celebrationPreview.snapshot() ?? cacheCelebration.snapshot(),
|
|
250
412
|
usage: usageTracker.snapshot(),
|
|
251
413
|
},
|
|
252
414
|
width,
|
|
415
|
+
settings,
|
|
253
416
|
),
|
|
254
417
|
);
|
|
255
418
|
},
|
|
@@ -263,12 +426,14 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
|
263
426
|
if (tracker) runInBackground(tracker.observeToolInput(event.toolName, event.input));
|
|
264
427
|
});
|
|
265
428
|
pi.on("turn_end", (event, ctx) => {
|
|
266
|
-
if (requestRender
|
|
429
|
+
if (requestRender && settings.showCacheCelebration) {
|
|
430
|
+
triggerCacheCelebrationForMessage(event.message, cacheCelebration);
|
|
431
|
+
}
|
|
267
432
|
fullRedraw.request();
|
|
268
433
|
requestRender?.();
|
|
269
434
|
runInBackground(refreshCwdStatus(ctx));
|
|
270
435
|
if (tracker) runInBackground(tracker.refresh());
|
|
271
|
-
runInBackground(usageTracker.refresh());
|
|
436
|
+
if (settings.showUsage) runInBackground(usageTracker.refresh());
|
|
272
437
|
});
|
|
273
438
|
pi.on("model_select", () => requestRender?.());
|
|
274
439
|
pi.on("session_tree", (_event, ctx) => resetTracker(ctx));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-statusline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Compact Pi footer statusline with Git/worktree context, token usage, and neon celebrations for exceptional prompt-cache hits.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"index.ts",
|
|
35
35
|
"cache-celebration.ts",
|
|
36
|
+
"celebration-preview.ts",
|
|
37
|
+
"celebration-styles.ts",
|
|
36
38
|
"redraw.ts",
|
|
39
|
+
"settings.ts",
|
|
40
|
+
"settings-menu.ts",
|
|
41
|
+
"themes.ts",
|
|
37
42
|
"usage.ts",
|
|
38
43
|
"worktrees.ts",
|
|
39
44
|
"README.md",
|