1+ package cmd
2+
3+ import (
4+ "fmt"
5+ "sort"
6+ "time"
7+
8+ "github.com/dfanso/commit-msg/cmd/cli/store"
9+ "github.com/dfanso/commit-msg/pkg/types"
10+ "github.com/pterm/pterm"
11+ "github.com/spf13/cobra"
12+ )
13+
14+ // statsCmd represents the statistics command
15+ var statsCmd = & cobra.Command {
16+ Use : "stats" ,
17+ Short : "Display usage statistics" ,
18+ Long : `Display comprehensive usage statistics including:
19+ - Most used LLM provider
20+ - Average generation time
21+ - Success/failure rates
22+ - Token usage per provider
23+ - Cache hit rates
24+ - Cost tracking` ,
25+ RunE : func (cmd * cobra.Command , args []string ) error {
26+ Store , err := store .NewStoreMethods ()
27+ if err != nil {
28+ return fmt .Errorf ("failed to initialize store: %w" , err )
29+ }
30+
31+ reset , _ := cmd .Flags ().GetBool ("reset" )
32+ if reset {
33+ if err := resetStatistics (Store ); err != nil {
34+ return err
35+ }
36+ return nil
37+ }
38+
39+ detailed , _ := cmd .Flags ().GetBool ("detailed" )
40+ return displayStatistics (Store , detailed )
41+ },
42+ }
43+
44+ func init () {
45+ statsCmd .Flags ().Bool ("detailed" , false , "Show detailed per-provider statistics" )
46+ statsCmd .Flags ().Bool ("reset" , false , "Reset all usage statistics" )
47+ }
48+
49+ func displayStatistics (store * store.StoreMethods , detailed bool ) error {
50+ stats := store .GetUsageStats ()
51+
52+ if stats .TotalGenerations == 0 {
53+ pterm .Info .Println ("No usage statistics available yet." )
54+ pterm .Info .Println ("Statistics will be collected as you use the commit message generator." )
55+ return nil
56+ }
57+
58+ // Header
59+ pterm .DefaultHeader .WithFullWidth ().
60+ WithBackgroundStyle (pterm .NewStyle (pterm .BgBlue )).
61+ WithTextStyle (pterm .NewStyle (pterm .FgWhite , pterm .Bold )).
62+ Println ("Usage Statistics" )
63+
64+ pterm .Println ()
65+
66+ // Overall Statistics
67+ pterm .DefaultSection .WithLevel (2 ).Println ("Overall Statistics" )
68+
69+ overallData := [][]string {
70+ {"Total Generations" , fmt .Sprintf ("%d" , stats .TotalGenerations )},
71+ {"Successful Generations" , fmt .Sprintf ("%d (%.1f%%)" , stats .SuccessfulGenerations , store .GetOverallSuccessRate ())},
72+ {"Failed Generations" , fmt .Sprintf ("%d (%.1f%%)" , stats .FailedGenerations , float64 (stats .FailedGenerations )/ float64 (stats .TotalGenerations )* 100 )},
73+ {"Average Generation Time" , fmt .Sprintf ("%.1f ms" , stats .AverageGenerationTime )},
74+ {"Total Cost" , fmt .Sprintf ("$%.4f" , stats .TotalCost )},
75+ {"Total Tokens Used" , fmt .Sprintf ("%d" , stats .TotalTokensUsed )},
76+ }
77+
78+ if stats .CacheHits > 0 || stats .CacheMisses > 0 {
79+ cacheRate := store .GetCacheHitRate ()
80+ overallData = append (overallData , []string {"Cache Hit Rate" , fmt .Sprintf ("%.1f%% (%d hits, %d misses)" , cacheRate , stats .CacheHits , stats .CacheMisses )})
81+ }
82+
83+ if stats .FirstUse != "" {
84+ if firstUse , err := time .Parse (time .RFC3339 , stats .FirstUse ); err == nil {
85+ overallData = append (overallData , []string {"First Use" , firstUse .Local ().Format ("Jan 2, 2006 15:04" )})
86+ }
87+ }
88+
89+ if stats .LastUse != "" {
90+ if lastUse , err := time .Parse (time .RFC3339 , stats .LastUse ); err == nil {
91+ overallData = append (overallData , []string {"Last Use" , lastUse .Local ().Format ("Jan 2, 2006 15:04" )})
92+ }
93+ }
94+
95+ pterm .DefaultTable .WithHasHeader (false ).WithData (overallData ).Render ()
96+ pterm .Println ()
97+
98+ // Provider Rankings
99+ if len (stats .ProviderStats ) > 0 {
100+ pterm .DefaultSection .WithLevel (2 ).Println ("Provider Rankings" )
101+
102+ ranking := store .GetProviderRanking ()
103+ rankingData := [][]string {{"Rank" , "Provider" , "Uses" , "Success Rate" , "Avg Time (ms)" , "Total Cost" }}
104+
105+ for i , provider := range ranking {
106+ providerStats := stats .ProviderStats [provider ]
107+ rankingData = append (rankingData , []string {
108+ fmt .Sprintf ("#%d" , i + 1 ),
109+ string (provider ),
110+ fmt .Sprintf ("%d" , providerStats .TotalUses ),
111+ fmt .Sprintf ("%.1f%%" , providerStats .SuccessRate ),
112+ fmt .Sprintf ("%.1f" , providerStats .AverageGenerationTime ),
113+ fmt .Sprintf ("$%.4f" , providerStats .TotalCost ),
114+ })
115+ }
116+
117+ pterm .DefaultTable .WithHasHeader (true ).WithData (rankingData ).Render ()
118+ pterm .Println ()
119+ }
120+
121+ // Detailed Provider Statistics
122+ if detailed && len (stats .ProviderStats ) > 0 {
123+ pterm .DefaultSection .WithLevel (2 ).Println ("Detailed Provider Statistics" )
124+
125+ // Sort providers alphabetically for consistent display
126+ var providers []types.LLMProvider
127+ for provider := range stats .ProviderStats {
128+ providers = append (providers , provider )
129+ }
130+ sort .Slice (providers , func (i , j int ) bool {
131+ return string (providers [i ]) < string (providers [j ])
132+ })
133+
134+ for _ , provider := range providers {
135+ providerStats := stats .ProviderStats [provider ]
136+
137+ pterm .DefaultSection .WithLevel (3 ).Printf ("%s Details" , provider )
138+
139+ providerData := [][]string {
140+ {"Total Uses" , fmt .Sprintf ("%d" , providerStats .TotalUses )},
141+ {"Successful Uses" , fmt .Sprintf ("%d" , providerStats .SuccessfulUses )},
142+ {"Failed Uses" , fmt .Sprintf ("%d" , providerStats .FailedUses )},
143+ {"Success Rate" , fmt .Sprintf ("%.1f%%" , providerStats .SuccessRate )},
144+ {"Average Generation Time" , fmt .Sprintf ("%.1f ms" , providerStats .AverageGenerationTime )},
145+ {"Total Cost" , fmt .Sprintf ("$%.4f" , providerStats .TotalCost )},
146+ {"Total Tokens Used" , fmt .Sprintf ("%d" , providerStats .TotalTokensUsed )},
147+ }
148+
149+ if providerStats .FirstUsed != "" {
150+ if firstUsed , err := time .Parse (time .RFC3339 , providerStats .FirstUsed ); err == nil {
151+ providerData = append (providerData , []string {"First Used" , firstUsed .Local ().Format ("Jan 2, 2006 15:04" )})
152+ }
153+ }
154+
155+ if providerStats .LastUsed != "" {
156+ if lastUsed , err := time .Parse (time .RFC3339 , providerStats .LastUsed ); err == nil {
157+ providerData = append (providerData , []string {"Last Used" , lastUsed .Local ().Format ("Jan 2, 2006 15:04" )})
158+ }
159+ }
160+
161+ pterm .DefaultTable .WithHasHeader (false ).WithData (providerData ).Render ()
162+ pterm .Println ()
163+ }
164+ }
165+
166+ // Show tips
167+ pterm .DefaultSection .WithLevel (2 ).Println ("Tips" )
168+ pterm .Info .Println ("• Use --detailed flag to see comprehensive per-provider statistics" )
169+ pterm .Info .Println ("• Statistics help identify your most reliable and cost-effective providers" )
170+ pterm .Info .Println ("• Cache hits save both time and API costs" )
171+ pterm .Info .Println ("• Use --reset flag to clear all statistics (irreversible)" )
172+
173+ return nil
174+ }
175+
176+ func resetStatistics (store * store.StoreMethods ) error {
177+ pterm .Warning .Println ("This will permanently delete all usage statistics." )
178+
179+ confirm , _ := pterm .DefaultInteractiveConfirm .
180+ WithDefaultValue (false ).
181+ WithDefaultText ("Are you sure you want to reset all statistics?" ).
182+ Show ()
183+
184+ if ! confirm {
185+ pterm .Info .Println ("Statistics reset cancelled." )
186+ return nil
187+ }
188+
189+ if err := store .ResetUsageStats (); err != nil {
190+ return fmt .Errorf ("failed to reset statistics: %w" , err )
191+ }
192+
193+ pterm .Success .Println ("All usage statistics have been reset." )
194+ return nil
195+ }
0 commit comments