@@ -230,6 +230,18 @@ func collectEntries(vault *enpass.Vault, args *Args, includeSensitive bool) ([]e
230230 if c .IsTrashed () && ! * args .trashed {
231231 continue
232232 }
233+ // Non-password field values are stored in cleartext; Decrypt() returns
234+ // them as-is. For password fields, Decrypt() actually decrypts.
235+ value , derr := c .Decrypt ()
236+ if derr != nil {
237+ return nil , fmt .Errorf ("could not decrypt %s/%s: %w" , c .Title , c .Label , derr )
238+ }
239+ // Match the Enpass native apps' view mode: hide empty-value template
240+ // placeholders that a user never filled in (e.g. "Date Mod", "Field 6").
241+ // Sections are visual dividers and stay even when empty.
242+ if value == "" && c .Type != "section" {
243+ continue
244+ }
233245 g , ok := groups [c .UUID ]
234246 if ! ok {
235247 g = & entryView {
@@ -247,12 +259,6 @@ func collectEntries(vault *enpass.Vault, args *Args, includeSensitive bool) ([]e
247259 Label : c .Label ,
248260 Sensitive : c .Sensitive ,
249261 }
250- // Non-password field values are stored in cleartext; Decrypt() returns
251- // them as-is. For password fields, Decrypt() actually decrypts.
252- value , derr := c .Decrypt ()
253- if derr != nil {
254- return nil , fmt .Errorf ("could not decrypt %s/%s: %w" , c .Title , c .Label , derr )
255- }
256262 isTOTP := c .Type == "totp"
257263 hasValue := value != ""
258264 // TOTP fields are classified as sensitive: in list mode neither the
@@ -372,40 +378,53 @@ func outputDetailed(logger *logrus.Logger, entries []entryView, args *Args) {
372378 if name == "" {
373379 name = f .Type
374380 }
381+ // Three-level hierarchy: record header (no indent), section header
382+ // (4 spaces), regular field (8 spaces). Regular fields are at the
383+ // same depth whether the record has sections or not, so columns
384+ // stay aligned across records.
385+ indent := fieldIndent
386+ if f .Type == "section" {
387+ indent = sectionIndent
388+ }
375389 if f .Type == "totp" && (f .TOTPCode != "" || f .TOTPError != "" ) {
376- renderTOTPField (logger , name , f )
390+ renderTOTPField (logger , indent , name , f )
377391 continue
378392 }
379393 switch {
380394 case f .Sensitive && f .Value == "" :
381- logger .Printf (" %s (%s): ********" , name , f .Type )
395+ logger .Printf ("%s%s (%s): ********" , indent , name , f .Type )
382396 case f .Value != "" :
383- logger .Printf (" %s (%s): %s" , name , f .Type , f .Value )
397+ logger .Printf ("%s%s (%s): %s" , indent , name , f .Type , f .Value )
384398 default :
385- logger .Printf (" %s (%s)" , name , f .Type )
399+ logger .Printf ("%s%s (%s)" , indent , name , f .Type )
386400 }
387401 }
388402 }
389403}
390404
405+ const (
406+ sectionIndent = " "
407+ fieldIndent = " "
408+ )
409+
391410// renderTOTPField prints a TOTP field. When the code could be computed we
392411// show it; otherwise we tell the user the value is dynamic. The secret is
393412// only included when collectEntries chose to expose it (i.e. show mode).
394- func renderTOTPField (logger * logrus.Logger , name string , f fieldView ) {
413+ func renderTOTPField (logger * logrus.Logger , indent , name string , f fieldView ) {
395414 parts := []string {}
396415 switch {
397416 case f .TOTPCode != "" :
398417 parts = append (parts , "code " + f .TOTPCode )
399418 case f .TOTPError != "" :
400419 parts = append (parts , "<dynamic TOTP value>" )
401420 default :
402- logger .Printf (" %s (%s)" , name , f .Type )
421+ logger .Printf ("%s%s (%s)" , indent , name , f .Type )
403422 return
404423 }
405424 if f .Value != "" {
406425 parts = append (parts , "secret: " + f .Value )
407426 }
408- logger .Printf (" %s (%s): %s" , name , f .Type , strings .Join (parts , " " ))
427+ logger .Printf ("%s%s (%s): %s" , indent , name , f .Type , strings .Join (parts , " " ))
409428}
410429
411430// anchorField picks the field that represents the entry in compact mode.
0 commit comments