mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-16 19:44:14 +00:00
renderStatusBar computed `today` via `new Date().toISOString().slice(0,10)`, which is the UTC date. Session timestamps are also UTC ISO strings, but the user's expectation of "today" is their wall-clock day. During the window between local midnight and UTC midnight (e.g. 17:00 PDT on 2026-04-17, which is already 00:00 UTC on 2026-04-18), every session bucketed under local April 17 missed the UTC-April-18 filter and the status bar read `Today $0.00 0 calls` even while `--format json` and the menubar app correctly showed the spend. Both sides of the comparison now use the local date of each session timestamp, so the terminal status and the JSON / menubar paths agree. Verified at UTC midnight (the regression moment that surfaced the bug): Before: Today $0.0000 0 calls After: Today $339.87 1839 calls Caught during the fresh-clone review of the menubar PR.
56 lines
2.5 KiB
TypeScript
56 lines
2.5 KiB
TypeScript
import chalk from 'chalk'
|
|
import type { ProjectSummary } from './types.js'
|
|
|
|
// Re-exported from currency.ts so existing imports from './format.js' keep working.
|
|
// The currency-aware version applies exchange rate and symbol automatically.
|
|
// Imported locally too since renderStatusBar below uses it directly.
|
|
import { formatCost } from './currency.js'
|
|
export { formatCost }
|
|
|
|
export function formatTokens(n: number): string {
|
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
|
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
|
return n.toString()
|
|
}
|
|
|
|
/// Returns YYYY-MM-DD for the given date in the process-local timezone. Cheaper than shelling
|
|
/// out to Intl.DateTimeFormat for every turn in a loop and avoids the UTC drift that bites
|
|
/// `Date.toISOString().slice(0,10)` whenever the user runs this between local midnight and
|
|
/// UTC midnight.
|
|
function localDateString(d: Date): string {
|
|
const y = d.getFullYear()
|
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
const day = String(d.getDate()).padStart(2, '0')
|
|
return `${y}-${m}-${day}`
|
|
}
|
|
|
|
export function renderStatusBar(projects: ProjectSummary[]): string {
|
|
const now = new Date()
|
|
const today = localDateString(now)
|
|
const monthStart = `${today.slice(0, 7)}-01`
|
|
|
|
let todayCost = 0, todayCalls = 0, monthCost = 0, monthCalls = 0
|
|
|
|
for (const project of projects) {
|
|
for (const session of project.sessions) {
|
|
for (const turn of session.turns) {
|
|
if (!turn.timestamp) continue
|
|
// Bucket by the session timestamp's local date so the user's "today" and "this month"
|
|
// match the wall clock on their machine. Session timestamps are stored as UTC ISO
|
|
// strings; naively slicing `timestamp.slice(0,10)` bucketed them by UTC date, which
|
|
// showed `Today $0` during the UTC-midnight-to-local-midnight window.
|
|
const day = localDateString(new Date(turn.timestamp))
|
|
const turnCost = turn.assistantCalls.reduce((s, c) => s + c.costUSD, 0)
|
|
const turnCalls = turn.assistantCalls.length
|
|
if (day === today) { todayCost += turnCost; todayCalls += turnCalls }
|
|
if (day >= monthStart) { monthCost += turnCost; monthCalls += turnCalls }
|
|
}
|
|
}
|
|
}
|
|
|
|
const lines: string[] = ['']
|
|
lines.push(` ${chalk.bold('Today')} ${chalk.yellowBright(formatCost(todayCost))} ${chalk.dim(`${todayCalls} calls`)} ${chalk.bold('Month')} ${chalk.yellowBright(formatCost(monthCost))} ${chalk.dim(`${monthCalls} calls`)}`)
|
|
lines.push('')
|
|
|
|
return lines.join('\n')
|
|
}
|