diff --git a/CHANGELOG.md b/CHANGELOG.md index 30024de..0cc78e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## 0.4.4 - 2026-04-15 + +### Added +- Auto-refresh flag. `codeburn report --refresh 60` reloads data at a set + interval. Works on `report`, `today`, and `month` commands. Default off. +- Readable project names. Strips home directory prefix from encoded paths, + shows 3 path segments for more context. Home dir sessions display as "home". +- Responsive dashboard reflows on terminal resize via Ink's useWindowSize + hook. Width cap raised from 104 to 160 columns. Contributed by @AleBles. +- Total downloads and install size badges in README. + +### Fixed +- Agent/subagent session files were excluded, dropping ~46% of API calls. + Subagent sessions live in separate subagents/ directories with unique + message IDs and are now included. Closes #17. +- Codex cache hit always showed 100%. OpenAI includes cached tokens inside + input_tokens (unlike Anthropic). Normalized to prevent double-counting + in cost calculation and cache hit display. Closes #21. +- CSV formula injection. Cells starting with =, +, -, @ are prefixed with + an apostrophe before CSV escaping. Contributed by @serabi. +- Menubar "Open Full Report" and "Export CSV" actions broken for npm-installed + users. Invokes resolved binary directly instead of assuming ~/codeburn + checkout. Currency picker used nonexistent `config currency` subcommand. + Contributed by @MukundaKatta. Closes #32, #27. +- Activity panel moved from full-width to half-width row for better space + usage on wide terminals. + ## 0.4.1 - 2026-04-14 ### Added diff --git a/README.md b/README.md index 449adea..f18b330 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@

npm version - npm downloads + total downloads + monthly downloads + install size license node version

@@ -45,6 +47,7 @@ codeburn # interactive dashboard (default: 7 days) codeburn today # today's usage codeburn month # this month's usage codeburn report -p 30days # rolling 30-day window +codeburn report --refresh 60 # auto-refresh every 60 seconds codeburn status # compact one-liner (today + month) codeburn status --format json codeburn export # CSV with today, 7 days, 30 days diff --git a/assets/dashboard.jpg b/assets/dashboard.jpg index b27c7ee..7006631 100644 Binary files a/assets/dashboard.jpg and b/assets/dashboard.jpg differ diff --git a/src/cli.ts b/src/cli.ts index d3c5c45..f422239 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -67,8 +67,9 @@ program .description('Interactive usage dashboard') .option('-p, --period ', 'Starting period: today, week, month, 30days', 'week') .option('--provider ', 'Filter by provider: all, claude, codex', 'all') + .option('--refresh ', 'Auto-refresh interval in seconds', parseInt) .action(async (opts) => { - await renderDashboard(toPeriod(opts.period), opts.provider) + await renderDashboard(toPeriod(opts.period), opts.provider, opts.refresh) }) function buildPeriodData(label: string, projects: ProjectSummary[]): PeriodData { @@ -153,16 +154,18 @@ program .command('today') .description('Today\'s usage dashboard') .option('--provider ', 'Filter by provider: all, claude, codex', 'all') + .option('--refresh ', 'Auto-refresh interval in seconds', parseInt) .action(async (opts) => { - await renderDashboard('today', opts.provider) + await renderDashboard('today', opts.provider, opts.refresh) }) program .command('month') .description('This month\'s usage dashboard') .option('--provider ', 'Filter by provider: all, claude, codex', 'all') + .option('--refresh ', 'Auto-refresh interval in seconds', parseInt) .action(async (opts) => { - await renderDashboard('month', opts.provider) + await renderDashboard('month', opts.provider, opts.refresh) }) program diff --git a/src/dashboard.tsx b/src/dashboard.tsx index 2130c0f..04ed622 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -194,18 +194,16 @@ const _homeEncoded = homedir().replace(/\//g, '-') function shortProject(encoded: string): string { let path = encoded.replace(/^-/, '') - // Strip home dir prefix (e.g. "Users-torukmakto-" → "") if (path.startsWith(_homeEncoded.replace(/^-/, ''))) { path = path.slice(_homeEncoded.replace(/^-/, '').length).replace(/^-/, '') } - // Strip common system prefixes path = path .replace(/^private-tmp-[^-]+-[^-]+-/, '') // /private/tmp/// .replace(/^private-tmp-/, '') .replace(/^tmp-/, '') - if (!path) return '~' + if (!path) return 'home' const parts = path.split('-').filter(Boolean) if (parts.length <= 3) return parts.join('/') @@ -485,10 +483,11 @@ function DashboardContent({ projects, period, columns }: { projects: ProjectSumm ) } -function InteractiveDashboard({ initialProjects, initialPeriod, initialProvider }: { +function InteractiveDashboard({ initialProjects, initialPeriod, initialProvider, refreshSeconds }: { initialProjects: ProjectSummary[] initialPeriod: Period initialProvider: string + refreshSeconds?: number }) { const { exit } = useApp() const [period, setPeriod] = useState(initialPeriod) @@ -528,6 +527,12 @@ function InteractiveDashboard({ initialProjects, initialPeriod, initialProvider setLoading(false) }, []) + useEffect(() => { + if (!refreshSeconds || refreshSeconds <= 0) return + const id = setInterval(() => { reloadData(period, activeProvider) }, refreshSeconds * 1000) + return () => clearInterval(id) + }, [refreshSeconds, period, activeProvider, reloadData]) + const switchPeriod = useCallback(async (newPeriod: Period) => { if (newPeriod === period) return setPeriod(newPeriod) @@ -592,7 +597,7 @@ function StaticDashboard({ projects, period }: { projects: ProjectSummary[]; per ) } -export async function renderDashboard(period: Period = 'week', provider: string = 'all'): Promise { +export async function renderDashboard(period: Period = 'week', provider: string = 'all', refreshSeconds?: number): Promise { await loadPricing() const range = getDateRange(period) const projects = await parseAllSessions(range, provider) @@ -601,7 +606,7 @@ export async function renderDashboard(period: Period = 'week', provider: string if (isTTY) { const { waitUntilExit } = render( - + ) await waitUntilExit() } else {