Merge pull request #36 from AgentSeal/fix/refresh-readme-changelog

feat: auto-refresh, updated README, changelog for 0.4.4
This commit is contained in:
AgentSeal 2026-04-15 10:41:40 +02:00 committed by GitHub
commit 4c07c52fc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 48 additions and 10 deletions

View file

@ -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

View file

@ -8,7 +8,9 @@
<p align="center">
<a href="https://www.npmjs.com/package/codeburn"><img src="https://img.shields.io/npm/v/codeburn.svg" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/codeburn"><img src="https://img.shields.io/npm/dm/codeburn.svg" alt="npm downloads" /></a>
<a href="https://www.npmjs.com/package/codeburn"><img src="https://img.shields.io/npm/dt/codeburn.svg" alt="total downloads" /></a>
<a href="https://www.npmjs.com/package/codeburn"><img src="https://img.shields.io/npm/dm/codeburn.svg" alt="monthly downloads" /></a>
<a href="https://bundlephobia.com/package/codeburn"><img src="https://img.shields.io/bundlephobia/min/codeburn" alt="install size" /></a>
<a href="https://github.com/agentseal/codeburn/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/codeburn.svg" alt="license" /></a>
<a href="https://github.com/agentseal/codeburn"><img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg" alt="node version" /></a>
</p>
@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

After

Width:  |  Height:  |  Size: 497 KiB

Before After
Before After

View file

@ -67,8 +67,9 @@ program
.description('Interactive usage dashboard')
.option('-p, --period <period>', 'Starting period: today, week, month, 30days', 'week')
.option('--provider <provider>', 'Filter by provider: all, claude, codex', 'all')
.option('--refresh <seconds>', '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 <provider>', 'Filter by provider: all, claude, codex', 'all')
.option('--refresh <seconds>', '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 <provider>', 'Filter by provider: all, claude, codex', 'all')
.option('--refresh <seconds>', 'Auto-refresh interval in seconds', parseInt)
.action(async (opts) => {
await renderDashboard('month', opts.provider)
await renderDashboard('month', opts.provider, opts.refresh)
})
program

View file

@ -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/<org>/<env>/
.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<Period>(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<void> {
export async function renderDashboard(period: Period = 'week', provider: string = 'all', refreshSeconds?: number): Promise<void> {
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(
<InteractiveDashboard initialProjects={projects} initialPeriod={period} initialProvider={provider} />
<InteractiveDashboard initialProjects={projects} initialPeriod={period} initialProvider={provider} refreshSeconds={refreshSeconds} />
)
await waitUntilExit()
} else {