@@ -68,6 +68,7 @@ type Args struct {
6868 pinEnable * bool
6969 sort * bool
7070 trashed * bool
71+ detailed * bool
7172 and * bool
7273 clipboardPrimary * bool
7374 // write command flags
@@ -91,6 +92,7 @@ func (args *Args) parse() {
9192 args .and = flag .Bool ("and" , false , "Combines filters with AND instead of default OR." )
9293 args .sort = flag .Bool ("sort" , false , "Sort the output by title and username of the 'list' and 'show' command." )
9394 args .trashed = flag .Bool ("trashed" , false , "Show trashed items in the 'list' and 'show' command." )
95+ args .detailed = flag .Bool ("detailed" , false , "Show every field of each entry in 'list' and 'show'. Without this flag, only the original summary fields (title, login, category, label, type) are displayed." )
9496 args .clipboardPrimary = flag .Bool ("clipboardPrimary" , false , "Use primary X selection instead of clipboard for the 'copy' command." )
9597 // write command flags
9698 args .title = flag .String ("title" , "" , "Entry title (for create/edit)." )
@@ -154,85 +156,221 @@ func sortEntries(cards []enpass.Card) {
154156}
155157
156158func listEntries (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
157- cards , err := vault . GetEntries ( * args . cardType , args . filters )
159+ entries , err := collectEntries ( vault , args , false )
158160 if err != nil {
159- logger .WithError (err ).Fatal ("could not retrieve cards" )
160- }
161- if * args .sort {
162- sortEntries (cards )
161+ logger .WithError (err ).Fatal (err .Error ())
163162 }
163+ outputEntriesOrLog (logger , entries , args )
164+ }
164165
165- data , err := prepareCardData (cards , false , args )
166+ func showEntries (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
167+ entries , err := collectEntries (vault , args , true )
166168 if err != nil {
167169 logger .WithError (err ).Fatal (err .Error ())
168170 }
171+ outputEntriesOrLog (logger , entries , args )
172+ }
169173
170- outputDataOrLog (logger , data , args )
174+ // entryView is one Enpass item with all of its fields grouped together.
175+ type entryView struct {
176+ UUID string `json:"uuid"`
177+ Title string `json:"title"`
178+ Subtitle string `json:"subtitle,omitempty"`
179+ Category string `json:"category,omitempty"`
180+ Trashed bool `json:"trashed,omitempty"`
181+ Fields []fieldView `json:"fields"`
171182}
172183
173- func showEntries (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
174- cards , err := vault .GetEntries (* args .cardType , args .filters )
184+ // fieldView is a single field of an entry (username, email, password, ...).
185+ // Value is empty when the field is sensitive and the caller didn't ask for
186+ // decrypted output (list mode).
187+ type fieldView struct {
188+ Type string `json:"type"`
189+ Label string `json:"label,omitempty"`
190+ Sensitive bool `json:"sensitive,omitempty"`
191+ Value string `json:"value,omitempty"`
192+ }
193+
194+ // collectEntries fetches every field for matching entries and groups them by
195+ // item UUID. When includeSensitive is false, values of sensitive fields
196+ // (passwords) are omitted while non-sensitive fields like username/email are
197+ // still populated — this is what powers the "list shows usernames and emails
198+ // but not passwords" behavior.
199+ func collectEntries (vault * enpass.Vault , args * Args , includeSensitive bool ) ([]entryView , error ) {
200+ // The -type flag defaults to "password" for the copy/pass commands. For
201+ // list/show we want every field type, so treat the default as "no filter".
202+ // Any other explicit value still filters server-side.
203+ typeFilter := * args .cardType
204+ if typeFilter == "password" {
205+ typeFilter = ""
206+ }
207+
208+ cards , err := vault .GetAllFields (typeFilter , args .filters )
175209 if err != nil {
176- logger .WithError (err ).Fatal ("could not retrieve cards" )
177- }
178- if * args .sort {
179- sortEntries (cards )
210+ return nil , fmt .Errorf ("could not retrieve cards: %w" , err )
180211 }
181212
182- data , err := prepareCardData (cards , true , args )
183- if err != nil {
184- logger .WithError (err ).Fatal (err .Error ())
213+ order := make ([]string , 0 )
214+ groups := make (map [string ]* entryView )
215+ for _ , c := range cards {
216+ if c .IsDeleted () {
217+ continue
218+ }
219+ if c .IsTrashed () && ! * args .trashed {
220+ continue
221+ }
222+ g , ok := groups [c .UUID ]
223+ if ! ok {
224+ g = & entryView {
225+ UUID : c .UUID ,
226+ Title : c .Title ,
227+ Subtitle : c .Subtitle ,
228+ Category : c .Category ,
229+ Trashed : c .IsTrashed (),
230+ }
231+ groups [c .UUID ] = g
232+ order = append (order , c .UUID )
233+ }
234+ f := fieldView {
235+ Type : c .Type ,
236+ Label : c .Label ,
237+ Sensitive : c .Sensitive ,
238+ }
239+ // Non-password field values are stored in cleartext; Decrypt() returns
240+ // them as-is. For password fields, Decrypt() actually decrypts.
241+ value , derr := c .Decrypt ()
242+ if derr != nil {
243+ return nil , fmt .Errorf ("could not decrypt %s/%s: %w" , c .Title , c .Label , derr )
244+ }
245+ if includeSensitive || ! c .Sensitive {
246+ f .Value = value
247+ }
248+ g .Fields = append (g .Fields , f )
185249 }
186250
187- outputDataOrLog (logger , data , args )
251+ entries := make ([]entryView , 0 , len (order ))
252+ for _ , uuid := range order {
253+ entries = append (entries , * groups [uuid ])
254+ }
255+ if * args .sort {
256+ sort .SliceStable (entries , func (i , j int ) bool {
257+ return strings .ToLower (entries [i ].Title ) < strings .ToLower (entries [j ].Title )
258+ })
259+ }
260+ return entries , nil
188261}
189262
190- func prepareCardData (cards []enpass.Card , includeDecrypted bool , args * Args ) ([]map [string ]string , error ) {
191- data := make ([]map [string ]string , 0 )
192- for _ , card := range cards {
193- if card .IsTrashed () && ! * args .trashed {
194- continue
195- }
263+ func outputEntriesOrLog (logger * logrus.Logger , entries []entryView , args * Args ) {
264+ if * args .detailed {
265+ outputDetailed (logger , entries , args )
266+ return
267+ }
268+ outputCompact (logger , entries , args )
269+ }
196270
197- cardMap := map [string ]string {
198- "title" : card .Title ,
199- "login" : card .Subtitle ,
200- "category" : card .Category ,
201- "label" : card .Label ,
202- "type" : card .Type ,
271+ // outputCompact reproduces the original list/show output: one row per entry
272+ // with the summary fields title, login, category, label, type — plus password
273+ // when present (show mode).
274+ func outputCompact (logger * logrus.Logger , entries []entryView , args * Args ) {
275+ type compactRow struct {
276+ Title string `json:"title"`
277+ Login string `json:"login"`
278+ Category string `json:"category"`
279+ Label string `json:"label"`
280+ Type string `json:"type"`
281+ Password string `json:"password,omitempty"`
282+ }
283+
284+ rows := make ([]compactRow , 0 , len (entries ))
285+ for _ , e := range entries {
286+ anchor := anchorField (e .Fields )
287+ row := compactRow {
288+ Title : e .Title ,
289+ Login : e .Subtitle ,
290+ Category : e .Category ,
203291 }
204-
205- if includeDecrypted {
206- decrypted , err := card . Decrypt ()
207- if err != nil {
208- return nil , fmt . Errorf ( "could not decrypt %s: %w" , card . Title , err )
292+ if anchor != nil {
293+ row . Label = anchor . Label
294+ row . Type = anchor . Type
295+ if anchor . Sensitive {
296+ row . Password = anchor . Value
209297 }
210- cardMap ["password" ] = decrypted
211298 }
299+ rows = append (rows , row )
300+ }
212301
213- data = append (data , cardMap )
302+ if * args .jsonOutput {
303+ jsonData , err := json .Marshal (rows )
304+ if err != nil {
305+ logger .WithError (err ).Fatal ("could not marshal JSON data" )
306+ }
307+ fmt .Println (string (jsonData ))
308+ return
309+ }
310+ for _ , r := range rows {
311+ format := "> title: %s login: %s cat.: %s label: %s type: %s"
312+ vals := []any {r .Title , r .Login , r .Category , r .Label , r .Type }
313+ if r .Password != "" {
314+ format += " password: %s"
315+ vals = append (vals , r .Password )
316+ }
317+ logger .Printf (format , vals ... )
214318 }
215- return data , nil
216319}
217320
218- func outputDataOrLog (logger * logrus.Logger , data []map [string ]string , args * Args ) {
321+ // outputDetailed emits the grouped per-field view: one header line per entry
322+ // followed by an indented line per field.
323+ func outputDetailed (logger * logrus.Logger , entries []entryView , args * Args ) {
219324 if * args .jsonOutput {
220- jsonData , jsonErr := json .Marshal (data )
221- if jsonErr != nil {
222- logger .WithError (jsonErr ).Fatal ("could not marshal JSON data" )
325+ jsonData , err := json .Marshal (entries )
326+ if err != nil {
327+ logger .WithError (err ).Fatal ("could not marshal JSON data" )
223328 }
224329 fmt .Println (string (jsonData ))
225- } else {
226- for _ , card := range data {
227- logger . Printf (
228- "> title: %s login: %s cat.: %s label: %s" ,
229- card [ "title" ],
230- card [ "login" ],
231- card [ "category" ],
232- card [ "label" ],
233- )
330+ return
331+ }
332+ for _ , e := range entries {
333+ header := "> " + e . Title
334+ if e . Subtitle != "" {
335+ header += " (" + e . Subtitle + ")"
336+ }
337+ if e . Category != "" {
338+ header += " cat.: " + e . Category
234339 }
340+ if e .Trashed {
341+ header += " [trashed]"
342+ }
343+ logger .Print (header )
344+ for _ , f := range e .Fields {
345+ name := f .Label
346+ if name == "" {
347+ name = f .Type
348+ }
349+ switch {
350+ case f .Sensitive && f .Value == "" :
351+ logger .Printf (" %s (%s): ********" , name , f .Type )
352+ case f .Value != "" :
353+ logger .Printf (" %s (%s): %s" , name , f .Type , f .Value )
354+ default :
355+ logger .Printf (" %s (%s)" , name , f .Type )
356+ }
357+ }
358+ }
359+ }
360+
361+ // anchorField picks the field that represents the entry in compact mode.
362+ // Mirrors the original GetEntries dedup: prefer the sensitive (password)
363+ // field, fall back to the first field.
364+ func anchorField (fields []fieldView ) * fieldView {
365+ for i := range fields {
366+ if fields [i ].Sensitive {
367+ return & fields [i ]
368+ }
369+ }
370+ if len (fields ) > 0 {
371+ return & fields [0 ]
235372 }
373+ return nil
236374}
237375
238376func copyEntry (logger * logrus.Logger , vault * enpass.Vault , args * Args ) {
0 commit comments