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