From baee1dfe615440ff05f2f6d4434a054abf28862a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl-Einar=20Bergstr=C3=B8m?= Date: Wed, 14 Oct 2026 12:51:53 +0200 Subject: [PATCH] fix: lazysecrets list puts the name first, and keeps errors out of fzf --- cmd/lazysecrets/list.go | 22 +++++++++++++++------- cmd/lazysecrets/main.go | 10 ++++++++-- docs/links.md | 21 ++++++++++++--------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/cmd/lazysecrets/list.go b/cmd/lazysecrets/list.go index 7d9dc52..d305b15 100644 --- a/cmd/lazysecrets/list.go +++ b/cmd/lazysecrets/list.go @@ -25,23 +25,31 @@ import ( // listCmd prints every source, vault and secret lazysecrets can find, one // per line, for fzf, editor pickers and scripts: // -// lazysecrets list | fzf --ansi --delimiter='\t' --with-nth=3 | cut -f1 +// lazysecrets list | fzf --ansi --delimiter='\t' --with-nth=1 | cut -f2 // -// The name is coloured from the theme, with the provider's icon (as in -// the TUI: --icons, LAZYSECRETS_ICONS, ui.icons); the link never is. +// Each line is the name, then the link: the name coloured from the theme, +// with the provider's icon (as in the TUI: --icons, LAZYSECRETS_ICONS, +// ui.icons); the link never is. // // What's cached prints at once; what's missing or older than --max-age -// follows as it loads. Lists that can't be read are reported on stderr. -func listCmd(providers []provider.Provider, cachePath string, timeout func(provider.Kind) time.Duration, theme styles.Styles, withIcons bool, args []string, out, errOut io.Writer) error { +// follows as it loads. Lists that can't be read are reported to errOut: +// stderr when the output is on the terminal, nothing when it goes into +// fzf, whose screen they'd draw over; --errors reports them to stderr +// either way. +func listCmd(providers []provider.Provider, cachePath string, timeout func(provider.Kind) time.Duration, theme styles.Styles, withIcons bool, args []string, out, errOut, stderr io.Writer) error { fs := flag.NewFlagSet("list", flag.ContinueOnError) cached := fs.Bool("cached", false, "only what's cached, and repos: load nothing from Azure or Databricks") maxAge := fs.Duration("max-age", 2*time.Minute, "load cached lists older than this again") - format := fs.String("format", "text", "text: link, kind and name, tab-separated; json: an object per line") + format := fs.String("format", "text", "text: name and link, tab-separated; json: an object per line") color := fs.String("color", "always", "colour the names (text): always or never; fzf needs --ansi") iconsOn := fs.Bool("icons", withIcons, "put the provider's Nerd Font icon before names (text)") + showErrors := fs.Bool("errors", false, "report lists that can't be read on stderr also when the output goes into a pipe") if err := fs.Parse(args); err != nil { return err } + if *showErrors { + errOut = stderr + } if fs.NArg() > 0 { return fmt.Errorf("usage: lazysecrets list [--cached] [--max-age 2m] [--format text|json]") } @@ -56,7 +64,7 @@ func listCmd(providers []provider.Provider, cachePath string, timeout func(provi switch *format { case "text": write = func(e listing.Entry) error { - _, err := fmt.Fprintf(out, "%s\t%s\t%s\n", e.Link, e.Kind, nameOf(e, theme, iconSet, *color == "always")) + _, err := fmt.Fprintf(out, "%s\t%s\n", nameOf(e, theme, iconSet, *color == "always"), e.Link) return err } case "json": diff --git a/cmd/lazysecrets/main.go b/cmd/lazysecrets/main.go index 1194ad8..0ab683c 100644 --- a/cmd/lazysecrets/main.go +++ b/cmd/lazysecrets/main.go @@ -17,6 +17,7 @@ import ( "time" tea "charm.land/bubbletea/v2" + "golang.org/x/term" "git.kebcloud.no/keb/lazysecrets" "git.kebcloud.no/keb/lazysecrets/internal/commands" @@ -122,7 +123,7 @@ func run() error { lazysecrets [flags] get print the value a lazysecrets:// link points at, for scripts: VAR=$(lazysecrets get ) lazysecrets [flags] list [--cached] [--max-age 2m] [--format text|json] - [--color always|never] [--icons=false] + [--color always|never] [--icons=false] [--errors] print every source, vault and secret it can find, one per line, for fzf and pickers: what's cached at once, then what loads @@ -357,7 +358,12 @@ Flags: if d, err := config.CacheDir(); err == nil && !*demo && config.On(cfg.Storage.Cache) { cachePath = filepath.Join(d, "cache.json") } - return listCmd(providers, cachePath, timeoutFor, theme, *icons, listArgs, os.Stdout, os.Stderr) + // Into a pipe (fzf), stderr would draw over what reads it. + errOut := io.Discard + if term.IsTerminal(int(os.Stdout.Fd())) { + errOut = os.Stderr + } + return listCmd(providers, cachePath, timeoutFor, theme, *icons, listArgs, os.Stdout, errOut, os.Stderr) } if shareArgs != nil { // The files from a repo, the key from whichever provider it's diff --git a/docs/links.md b/docs/links.md index 171eb19..d14f235 100644 --- a/docs/links.md +++ b/docs/links.md @@ -139,22 +139,24 @@ a newline only on a terminal. ### Listing, for fzf and pickers `lazysecrets list` prints every source, vault and secret, one per line: -its link, its kind (`source`, `vault`, `secret`, `key`, `certificate`) -and its name, tab-separated. It's what Go to searches, so fzf, or an -editor's picker, can search it instead: +its name (`platform-prod / kv-app-prod / db-password`), then its link, +tab-separated. It's what Go to searches, so fzf, or an editor's picker, +can search it instead, showing the names and acting on the links: ```sh -lazysecrets list | fzf --ansi --delimiter='\t' --with-nth=3 \ - --bind 'enter:become(lazysecrets open {1})' \ - --bind 'ctrl-y:execute-silent(lazysecrets get {1} | pbcopy)' +lazysecrets list | fzf --ansi --delimiter='\t' --with-nth=1 \ + --bind 'enter:become(lazysecrets open {2})' \ + --bind 'ctrl-y:execute-silent(lazysecrets get {2} | pbcopy)' ``` What's [cached](config.md#cache) prints at once. Then, like Go to, it loads what's missing or older than `--max-age` (2m), and what's new in it follows as it arrives: fzf shows it while you type. `--cached` loads nothing from Azure or Databricks, for an answer at once. Repos are always read, from -disk. What it loads is cached for the TUI too. Lists it can't read are -reported on stderr; it stops when the reader goes, after a pick in fzf. +disk. What it loads is cached for the TUI too. It stops when the reader +goes, after a pick in fzf. Lists it can't read are reported on stderr when +it prints to the terminal; into a pipe they'd draw over fzf, so only with +`--errors`. The name is coloured from your [theme](config.md#theme), with the provider's icon when [icons](config.md#theme) are on (`ui.icons`); fzf @@ -162,7 +164,8 @@ shows the colours with `--ansi`. `--color never` leaves them out, for scripts, and `--icons=false` the icons. The link is never coloured. `--format json` prints an object per line instead, for editor plugins, -without colours or icons: +without colours or icons, with each entry's kind (`source`, `vault`, +`secret`, `key` or `certificate`): ```json {"link":"lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password","kind":"secret","provider":"azure","path":["platform-prod","kv-app-prod","db-password"],"account":"alice"}