feat: add configurable pi statusbar extension

This commit is contained in:
2026-05-03 11:31:46 -04:00
commit c96da624fe
21 changed files with 6150 additions and 0 deletions

106
modules/basic.ts Normal file
View File

@@ -0,0 +1,106 @@
import { homedir } from "node:os";
import type { ModuleContext, RenderedModule } from "../types";
function formatTokens(count: number): string {
if (count < 1000) return count.toString();
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
if (count < 1000000) return `${Math.round(count / 1000)}k`;
if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
return `${Math.round(count / 1000000)}M`;
}
function sessionEntries(ctx: any): any[] {
try {
return ctx.sessionManager?.getEntries?.() ?? [];
} catch {
return [];
}
}
function totalCost(ctx: any): number {
return sessionEntries(ctx).reduce((total, entry) => {
const cost =
entry?.type === "message" && entry.message?.role === "assistant"
? entry.message.usage?.cost?.total
: undefined;
return (
total + (typeof cost === "number" && Number.isFinite(cost) ? cost : 0)
);
}, 0);
}
function isUsingSubscription(ctx: any): boolean {
const provider = ctx.model?.provider;
if (!provider) return false;
try {
return Boolean(ctx.modelRegistry?.isUsingOAuth?.(ctx.model));
} catch {
return provider === "anthropic" || provider === "openai-codex";
}
}
export function directoryModule(moduleCtx: ModuleContext): RenderedModule {
const cwd = moduleCtx.ctx.sessionManager?.getCwd?.() ?? process.cwd();
const home = homedir();
let text = home && cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd;
// Git And Session Name
const options = moduleCtx.config.modules.directory;
if (options?.showGitBranch) {
const branch = moduleCtx.footerData?.getGitBranch?.();
if (branch) text = `${text} (${branch})`;
}
if (options?.showSessionName) {
const sessionName = moduleCtx.ctx.sessionManager?.getSessionName?.();
if (sessionName) text = `${text}${sessionName}`;
}
return { text };
}
export function contextModule(moduleCtx: ModuleContext): RenderedModule {
const usage = moduleCtx.ctx.getContextUsage?.();
const contextWindow =
usage?.contextWindow ?? moduleCtx.ctx.model?.contextWindow ?? 0;
const percent = usage?.percent;
const showWindow = moduleCtx.config.modules.context?.showWindow ?? false;
const value =
percent === null || percent === undefined
? "?"
: `${Number(percent).toFixed(1)}%`;
return {
text: showWindow ? `${value} / ${formatTokens(contextWindow)}` : `${value}`,
};
}
export function modelModule(moduleCtx: ModuleContext): RenderedModule {
const model = moduleCtx.ctx.model;
if (!model) return { text: "no-model" };
// Model Label
const options = moduleCtx.config.modules.model;
const provider = options?.showProvider ? `(${model.provider}) ` : "";
let text = `${provider}${model.id}`;
if (options?.showThinking && model.reasoning) {
const thinking = moduleCtx.state.thinkingLevel ?? "off";
text = thinking === "off" ? `${text} thinking off` : `${text} ${thinking}`;
}
return { text };
}
export function thinkingModule(moduleCtx: ModuleContext): RenderedModule {
if (!moduleCtx.ctx.model?.reasoning) return { text: "" };
const level = moduleCtx.state.thinkingLevel ?? "off";
if (level === "off" && moduleCtx.config.modules.thinking?.hideWhenOff)
return { text: "" };
return { text: `think ${level}` };
}
export function costModule(moduleCtx: ModuleContext): RenderedModule {
const cost = totalCost(moduleCtx.ctx);
const subscription = isUsingSubscription(moduleCtx.ctx);
if (!cost && !subscription) return { text: "" };
if (subscription && moduleCtx.config.modules.cost?.showSubscription !== false)
return { text: cost ? `$${cost.toFixed(3)} sub` : "sub" };
return { text: `$${cost.toFixed(3)}` };
}

152
modules/usage.ts Normal file
View File

@@ -0,0 +1,152 @@
import type { ModuleContext, ModuleSpec, RenderedModule, UsageTextPart, UsageWindows } from "../types";
import type { UsageLimit, UsageReport } from "../usage";
function usageFraction(limit: UsageLimit): number | undefined {
if (limit.amount.usedFraction !== undefined) return Math.min(Math.max(limit.amount.usedFraction, 0), 1);
if (limit.amount.used !== undefined && limit.amount.limit !== undefined && limit.amount.limit > 0) {
return Math.min(Math.max(limit.amount.used / limit.amount.limit, 0), 1);
}
return undefined;
}
function duration(valueMs: number | undefined): string | undefined {
if (valueMs === undefined || !Number.isFinite(valueMs)) return undefined;
const ms = Math.max(0, valueMs);
const days = Math.floor(ms / 86_400_000);
const hours = Math.floor((ms % 86_400_000) / 3_600_000);
const minutes = Math.round((ms % 3_600_000) / 60_000);
if (days > 0) return `${days}d${hours}h`;
if (hours > 0) return `${hours}h${minutes}m`;
return `${minutes}m`;
}
function compactNumber(value: number | undefined): string | undefined {
if (value === undefined) return undefined;
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`;
return `${Math.round(value)}`;
}
function lerp(start: number, end: number, amount: number): number {
return Math.round(start + (end - start) * amount);
}
function fgRgb(text: string, r: number, g: number, b: number): string {
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
}
function usageColor(fraction: number): { r: number; g: number; b: number } {
const green = { r: 34, g: 197, b: 94 };
const yellow = { r: 234, g: 179, b: 8 };
const red = { r: 239, g: 68, b: 68 };
// Continuous Green → Yellow → Red Gradient
if (fraction <= 0.5) {
const amount = fraction / 0.5;
return {
r: lerp(green.r, yellow.r, amount),
g: lerp(green.g, yellow.g, amount),
b: lerp(green.b, yellow.b, amount),
};
}
const amount = (fraction - 0.5) / 0.5;
return {
r: lerp(yellow.r, red.r, amount),
g: lerp(yellow.g, red.g, amount),
b: lerp(yellow.b, red.b, amount),
};
}
function findWindowLimits(report: UsageReport): UsageWindows {
const windowId = (limit: UsageLimit) => (limit.window?.id ?? limit.scope.windowId ?? "").toLowerCase();
const current = report.limits.find(limit => ["5h", "primary"].includes(windowId(limit))) ?? report.limits[0];
const week = report.limits.find(limit => ["7d", "secondary"].includes(windowId(limit))) ?? report.limits.find(limit => limit !== current);
return { current, week };
}
function lineBar(limit: UsageLimit | undefined, width: number, theme?: any, color = true): string {
if (width <= 0) return "";
const fraction = limit ? usageFraction(limit) ?? 0 : 0;
const filled = Math.round(fraction * width);
const empty = width - filled;
const filledText = "━".repeat(filled);
const emptyText = "─".repeat(empty);
if (!color) return `${filledText}${emptyText}`;
const { r, g, b } = usageColor(fraction);
const coloredFilled = filledText ? fgRgb(filledText, r, g, b) : "";
const dimEmpty = emptyText && theme?.fg ? theme.fg("dim", emptyText) : emptyText;
return `${coloredFilled}${dimEmpty}`;
}
function selectedLimit(moduleCtx: ModuleContext, spec: ModuleSpec): UsageLimit | undefined {
if (!moduleCtx.state.report || moduleCtx.state.error) return undefined;
const windows = findWindowLimits(moduleCtx.state.report);
const window = typeof spec === "string" ? "both" : spec.window ?? "both";
if (window === "week") return windows.week;
return windows.current ?? windows.week;
}
function textPart(limit: UsageLimit, part: UsageTextPart): string | undefined {
const fraction = usageFraction(limit);
switch (part) {
case "percent":
return fraction === undefined ? undefined : `${Math.round(fraction * 100)}%`;
case "time":
return duration(limit.window?.resetsAt !== undefined ? limit.window.resetsAt - Date.now() : undefined);
case "used":
return compactNumber(limit.amount.used);
case "limit":
return compactNumber(limit.amount.limit);
case "remaining":
return compactNumber(limit.amount.remaining);
}
}
function fallbackTextPart(part: UsageTextPart): string | undefined {
switch (part) {
case "percent":
return "0%";
case "time":
return "∞";
case "used":
return "0";
case "remaining":
return "∞";
case "limit":
return undefined;
}
}
function textUsage(moduleCtx: ModuleContext, spec: ModuleSpec): string {
// Usage Text Parts
const parts = typeof spec === "string"
? moduleCtx.config.modules.usage?.parts ?? ["percent", "time"]
: spec.parts ?? moduleCtx.config.modules.usage?.parts ?? ["percent", "time"];
const separator = typeof spec === "string"
? moduleCtx.config.modules.usage?.separator ?? " | "
: spec.separator ?? moduleCtx.config.modules.usage?.separator ?? " | ";
const limit = !moduleCtx.state.report || moduleCtx.state.error ? undefined : selectedLimit(moduleCtx, spec);
return parts
.map(part => limit ? textPart(limit, part) : fallbackTextPart(part))
.filter((part): part is string => Boolean(part))
.join(separator);
}
export function usageModule(moduleCtx: ModuleContext, spec: ModuleSpec): RenderedModule {
const style = typeof spec === "string" ? "line" : spec.style ?? "line";
const grow = typeof spec === "string" ? 1 : spec.grow ?? 1;
if (style === "text") return { text: textUsage(moduleCtx, spec) };
return {
grow,
render(width: number, theme?: any): string {
const limit = !moduleCtx.state.report || moduleCtx.state.error ? undefined : selectedLimit(moduleCtx, spec);
return lineBar(limit, width, theme, moduleCtx.config.modules.usage?.colorBars !== false);
},
};
}