feat: add CLI commands for usage analysis, cost breakdown, and setup
Implement the complete Cobra command tree (11 subcommands): - cmd/root.go: Root command with persistent flags (--days, --project, --model, --no-cache, --data-dir, --quiet, --no-subagents). Shared loadData() orchestrates the full pipeline: tries cache-assisted loading first, falls back to uncached parse on cache failure, reports progress to stderr. applyFilters() applies project/model substring filters and computes the time window. - cmd/summary.go: Default command (also "cburn summary"). Renders a bordered metrics table with token breakdown (5 types), cost with cache savings, and per-day rates with period-over-period deltas. - cmd/costs.go: Detailed cost analysis — breaks down costs by token type (output, cache_write_1h, input, cache_write_5m, cache_read) with share percentages, period comparison bar chart, and per-model cost breakdown (input/output/cache/total columns). - cmd/daily.go: Daily usage table (date, weekday, sessions, prompts, tokens, cost) sorted most-recent-first. - cmd/hourly.go: Activity heatmap showing prompt distribution across 24 hours with Unicode block bars, reports peak hour. - cmd/models.go: Model usage ranking with API call counts, token volumes, cost, and usage share percentage. - cmd/projects.go: Project ranking by cost with session/prompt/token counts. - cmd/sessions.go: Session list sorted by recency with --limit flag (default 20). Shows start time, project, duration, tokens, cost. Marks subagent sessions with "(sub)" suffix. - cmd/config_cmd.go: Displays current configuration across all sections (general, admin API, appearance, budget) with auto- detected plan ceiling. - cmd/setup.go: Interactive first-run wizard — configures Admin API key, default time range (7/30/90 days), and color theme (Flexoki Dark, Catppuccin Mocha, Tokyo Night, Terminal). Saves to ~/.config/cburn/config.toml. - cmd/tui.go: Launches the interactive Bubble Tea TUI dashboard, passing through all filter flags and applying the configured theme. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
95
cmd/summary.go
Normal file
95
cmd/summary.go
Normal file
@@ -0,0 +1,95 @@
|
||||
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{
|
||||
{"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 := fmt.Sprintf("%s/day", cli.FormatCost(stats.CostPerDay))
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user