feat: add rolling 30-day window to menubar status

Add a '30 Days' section to the menubar format output, positioned between
'7 Days' and 'Month' to match the tab order in the interactive report.

The rolling 30-day date range logic already existed (used by report and
export commands) - this wires it into the status menubar renderer.

Both 30 Days (rolling window) and Month (calendar month) are shown,
giving useful context early in the month when the calendar month total
is nearly empty.
This commit is contained in:
Christopher Sant 2026-04-14 14:09:42 -07:00
parent 475ab0da61
commit 5cdcd2c2ee
2 changed files with 21 additions and 1 deletions

View file

@ -127,6 +127,7 @@ program
const todayRange = getDateRange('today').range
const todayData = buildPeriodData('Today', await parseAllSessions(todayRange, pf))
const weekData = buildPeriodData('7 Days', await parseAllSessions(getDateRange('week').range, pf))
const thirtyDayData = buildPeriodData('30 Days', await parseAllSessions(getDateRange('30days').range, pf))
const monthData = buildPeriodData('Month', await parseAllSessions(getDateRange('month').range, pf))
const todayProviders: ProviderCost[] = []
for (const p of await getAllProviders()) {
@ -134,7 +135,7 @@ program
const cost = data.reduce((s, proj) => s + proj.totalCostUSD, 0)
if (cost > 0) todayProviders.push({ name: p.displayName, cost })
}
console.log(renderMenubarFormat(todayData, weekData, monthData, todayProviders))
console.log(renderMenubarFormat(todayData, weekData, thirtyDayData, monthData, todayProviders))
return
}

View file

@ -68,6 +68,7 @@ export type ProviderCost = {
export function renderMenubarFormat(
today: PeriodData,
week: PeriodData,
thirtyDays: PeriodData,
month: PeriodData,
todayProviders?: ProviderCost[],
): string {
@ -130,6 +131,24 @@ export function renderMenubarFormat(
lines.push(`--${bar} ${name} ${formatCost(model.cost).padStart(8)} ${String(model.calls).padStart(5)} calls | font=Menlo size=11`)
}
lines.push(`30 Days ${formatCost(thirtyDays.cost)} ${thirtyDays.calls.toLocaleString()} calls | size=14`)
const tdMaxCat = Math.max(...thirtyDays.categories.map(c => c.cost), 0.01)
const tdMaxModel = Math.max(...thirtyDays.models.filter(m => m.name !== '<synthetic>').map(m => m.cost), 0.01)
lines.push(`--Activity | size=12 color=#FF8C42`)
for (const cat of thirtyDays.categories.slice(0, 8)) {
const bar = miniBar(cat.cost, tdMaxCat)
const name = cat.name.padEnd(14)
lines.push(`--${bar} ${name} ${formatCost(cat.cost).padStart(8)} ${String(cat.turns).padStart(4)} turns | font=Menlo size=11`)
}
lines.push(`-----`)
lines.push(`--Models | size=12 color=#FF8C42`)
for (const model of thirtyDays.models.slice(0, 5)) {
if (model.name === '<synthetic>') continue
const bar = miniBar(model.cost, tdMaxModel)
const name = model.name.padEnd(14)
lines.push(`--${bar} ${name} ${formatCost(model.cost).padStart(8)} ${String(model.calls).padStart(5)} calls | font=Menlo size=11`)
}
lines.push(`Month ${formatCost(month.cost)} ${month.calls.toLocaleString()} calls | size=14`)
const monthMaxCat = Math.max(...month.categories.map(c => c.cost), 0.01)
const monthMaxModel = Math.max(...month.models.filter(m => m.name !== '<synthetic>').map(m => m.cost), 0.01)