55 "fmt"
66 "os"
77 "text/tabwriter"
8+ "time"
89
910 "github.com/spf13/cobra"
10- "github.com/wesm/msgvault/internal/query"
1111 "github.com/wesm/msgvault/internal/store"
1212)
1313
@@ -18,6 +18,8 @@ var listAccountsCmd = &cobra.Command{
1818 Short : "List synced email accounts" ,
1919 Long : `List all email accounts that have been added to msgvault.
2020
21+ Shows account email, message count, and last sync time.
22+
2123Examples:
2224 msgvault list-accounts
2325 msgvault list-accounts --json` ,
@@ -29,59 +31,121 @@ Examples:
2931 }
3032 defer s .Close ()
3133
32- engine := query .NewSQLiteEngine (s .DB ())
33-
34- accounts , err := engine .ListAccounts (cmd .Context ())
34+ sources , err := s .ListSources ()
3535 if err != nil {
3636 return fmt .Errorf ("list accounts: %w" , err )
3737 }
3838
39- if len (accounts ) == 0 {
39+ if len (sources ) == 0 {
4040 fmt .Println ("No accounts found. Use 'msgvault add-account <email>' to add one." )
4141 return nil
4242 }
4343
44+ // Gather stats for each account
45+ stats := make ([]accountStats , len (sources ))
46+ for i , src := range sources {
47+ count , err := s .CountMessagesForSource (src .ID )
48+ if err != nil {
49+ return fmt .Errorf ("count messages for %s: %w" , src .Identifier , err )
50+ }
51+
52+ var lastSync * time.Time
53+ if src .LastSyncAt .Valid {
54+ lastSync = & src .LastSyncAt .Time
55+ }
56+
57+ displayName := ""
58+ if src .DisplayName .Valid {
59+ displayName = src .DisplayName .String
60+ }
61+
62+ stats [i ] = accountStats {
63+ ID : src .ID ,
64+ Email : src .Identifier ,
65+ Type : src .SourceType ,
66+ DisplayName : displayName ,
67+ MessageCount : count ,
68+ LastSync : lastSync ,
69+ }
70+ }
71+
4472 if listAccountsJSON {
45- return outputAccountsJSON (accounts )
73+ return outputAccountsJSON (stats )
4674 }
47- outputAccountsTable (accounts )
75+ outputAccountsTable (stats )
4876 return nil
4977 },
5078}
5179
52- func outputAccountsTable (accounts []query. AccountInfo ) {
80+ func outputAccountsTable (stats []accountStats ) {
5381 w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
54- fmt .Fprintln (w , "ID\t EMAIL\t TYPE\t DISPLAY NAME" )
55- fmt .Fprintln (w , "──\t ─────\t ────\t ────────────" )
82+ fmt .Fprintln (w , "ID\t ACCOUNT\t TYPE\t DISPLAY NAME\t MESSAGES\t LAST SYNC" )
5683
57- for _ , acc := range accounts {
58- displayName := acc .DisplayName
84+ for _ , s := range stats {
85+ displayName := s .DisplayName
5986 if displayName == "" {
6087 displayName = "-"
6188 }
62- fmt .Fprintf (w , "%d\t %s\t %s\t %s\n " , acc .ID , acc .Identifier , acc .SourceType , displayName )
89+ lastSync := "-"
90+ if s .LastSync != nil && ! s .LastSync .IsZero () {
91+ lastSync = s .LastSync .Format ("2006-01-02 15:04" )
92+ }
93+ fmt .Fprintf (w , "%d\t %s\t %s\t %s\t %s\t %s\n " , s .ID , s .Email , s .Type , displayName , formatCount (s .MessageCount ), lastSync )
6394 }
6495
6596 w .Flush ()
66- fmt .Printf ("\n %d account(s)\n " , len (accounts ))
6797}
6898
69- func outputAccountsJSON (accounts []query.AccountInfo ) error {
70- output := make ([]map [string ]interface {}, len (accounts ))
71- for i , acc := range accounts {
72- output [i ] = map [string ]interface {}{
73- "id" : acc .ID ,
74- "email" : acc .Identifier ,
75- "type" : acc .SourceType ,
76- "display_name" : acc .DisplayName ,
99+ func outputAccountsJSON (stats []accountStats ) error {
100+ output := make ([]map [string ]interface {}, len (stats ))
101+ for i , s := range stats {
102+ entry := map [string ]interface {}{
103+ "id" : s .ID ,
104+ "email" : s .Email ,
105+ "type" : s .Type ,
106+ "display_name" : s .DisplayName ,
107+ "message_count" : s .MessageCount ,
108+ }
109+ if s .LastSync != nil && ! s .LastSync .IsZero () {
110+ entry ["last_sync" ] = s .LastSync .Format (time .RFC3339 )
111+ } else {
112+ entry ["last_sync" ] = nil
77113 }
114+ output [i ] = entry
78115 }
79116
80117 enc := json .NewEncoder (os .Stdout )
81118 enc .SetIndent ("" , " " )
82119 return enc .Encode (output )
83120}
84121
122+ // formatCount formats a number with thousand separators.
123+ func formatCount (n int64 ) string {
124+ if n < 1000 {
125+ return fmt .Sprintf ("%d" , n )
126+ }
127+
128+ // Format with commas
129+ s := fmt .Sprintf ("%d" , n )
130+ result := make ([]byte , 0 , len (s )+ (len (s )- 1 )/ 3 )
131+ for i , c := range s {
132+ if i > 0 && (len (s )- i )% 3 == 0 {
133+ result = append (result , ',' )
134+ }
135+ result = append (result , byte (c ))
136+ }
137+ return string (result )
138+ }
139+
140+ type accountStats struct {
141+ ID int64
142+ Email string
143+ Type string
144+ DisplayName string
145+ MessageCount int64
146+ LastSync * time.Time
147+ }
148+
85149func init () {
86150 rootCmd .AddCommand (listAccountsCmd )
87151 listAccountsCmd .Flags ().BoolVar (& listAccountsJSON , "json" , false , "Output as JSON" )
0 commit comments