Files
cburn/cmd/summary.go
teernisse 892f578565 fix: linter compliance and code quality improvements across codebase
Address golangci-lint findings and improve error handling throughout:

Package doc comments:
- Add canonical "// Package X ..." comments to source, model, config,
  pipeline, cli, store, and main packages for godoc compliance.

Security & correctness:
- Fix directory permissions 0o755 -> 0o750 in store/cache.go Open()
  (gosec G301: restrict group write on cache directory)
- Fix config.Save() to check encoder error before closing file, preventing
  silent data loss on encode failure
- Add //nolint:gosec annotations with justifications on intentional
  patterns (constructed file paths, manual bounds checking, config fields)
- Add //nolint:nilerr on intentional error-swallowing in scanner WalkDir
- Add //nolint:revive on stuttering type names (ModelStats, ModelUsage)
  that would break too many call sites to rename

Performance (perfsprint):
- Replace fmt.Sprintf("%d", n) with strconv.FormatInt(n, 10) in format.go
  FormatTokens() and FormatNumber() hot paths
- Clean up redundant fmt.Sprintf patterns in FormatCost and FormatDelta

Code cleanup:
- Convert if-else chain to switch in parser.go skipJSONString() for clarity
- Remove unused indexedResult struct from pipeline/loader.go
- Add deferred cache.Close() in pipeline/bench_test.go to prevent leaks
- Add deferred cache.Close() in cmd/root.go data loading path
- Fix doc comment alignment in scanner.go decodeProjectName
- Remove trailing blank line in cmd/costs.go
- Fix duplicate "/day" suffix in cmd/summary.go cost-per-day formatting
- Rename shadowed variable 'max' -> 'maxVal' in cli/render.go Sparkline
2026-02-20 16:07:26 -05:00

96 lines
2.6 KiB
Go

package cmd
import (
"fmt"
"os"
"cburn/internal/cli"
"cburn/internal/pipeline"
"github.com/spf13/cobra"
)
var summaryCmd = &cobra.Command{
Use: "summary",
Short: "Detailed usage summary with costs",
RunE: runSummary,
}
func init() {
rootCmd.AddCommand(summaryCmd)
}
func runSummary(_ *cobra.Command, _ []string) error {
result, err := loadData()
if err != nil {
return err
}
if len(result.Sessions) == 0 {
fmt.Println("\n No Claude Code sessions found.")
fmt.Println(" Use Claude Code first, then come back!")
return nil
}
filtered, since, until := applyFilters(result.Sessions)
stats := pipeline.Aggregate(filtered, since, until)
if stats.TotalSessions == 0 {
fmt.Println("\n No sessions found in the selected time range.")
return nil
}
// Compute previous period for comparison
prevDuration := until.Sub(since)
prevSince := since.Add(-prevDuration)
prevStats := pipeline.Aggregate(filtered, prevSince, since)
// Render output
fmt.Println()
fmt.Println(cli.RenderTitle(fmt.Sprintf("CLAUDE USAGE Last %dd", flagDays)))
fmt.Println()
// Build the summary table
rows := [][]string{ //nolint:prealloc // appended conditionally below
{"Sessions", cli.FormatNumber(int64(stats.TotalSessions))},
{"Prompts", cli.FormatNumber(int64(stats.TotalPrompts))},
{"Total Time", cli.FormatDuration(stats.TotalDurationSecs)},
{"---"},
{"Input Tokens", cli.FormatTokens(stats.InputTokens)},
{"Output Tokens", cli.FormatTokens(stats.OutputTokens)},
{"Cache Write (5m)", cli.FormatTokens(stats.CacheCreation5mTokens)},
{"Cache Write (1h)", cli.FormatTokens(stats.CacheCreation1hTokens)},
{"Cache Read", cli.FormatTokens(stats.CacheReadTokens)},
{"Total Billed", cli.FormatTokens(stats.TotalBilledTokens)},
{"---"},
{"Cost (est)", cli.FormatCost(stats.EstimatedCost)},
{"Cache Savings", cli.FormatCost(stats.CacheSavings)},
{"Cache Hit Rate", cli.FormatPercent(stats.CacheHitRate)},
{"---"},
}
// Cost per day with delta
costDayStr := cli.FormatCost(stats.CostPerDay) + "/day"
if prevStats.CostPerDay > 0 {
costDayStr += fmt.Sprintf(" (%s vs prev %dd)",
cli.FormatDelta(stats.CostPerDay, prevStats.CostPerDay), flagDays)
}
rows = append(rows, []string{"Cost/day", costDayStr})
rows = append(rows, []string{"Tokens/day", cli.FormatTokens(stats.TokensPerDay)})
rows = append(rows, []string{"Sessions/day", fmt.Sprintf("%.1f", stats.SessionsPerDay)})
table := cli.Table{
Headers: []string{"Metric", "Value"},
Rows: rows,
}
fmt.Print(cli.RenderTable(table))
// Print warnings
if result.FileErrors > 0 {
fmt.Fprintf(os.Stderr, "\n %d files could not be parsed\n", result.FileErrors)
}
return nil
}