Files
keb 39454d47a8 feat: lazysecrets list prints everything lazysecrets can find, for fzf and editor pickers (#70)
`lazysecrets list` prints every source, vault and secret lazysecrets can find, one per line, for fzf, editor pickers and scripts: what Go to searches, without the TUI. What's cached prints at once; what's missing or older than `--max-age` loads in the background and streams in. It stops when the reader goes (a pick in fzf), saving what it loaded.

- Text: kind, mark (✗/⚠, ★, ◷ and the provider icon), name, status (why it couldn't be read, or expired / expires in 12d), note, account and link, tab-separated; kind first and account and link last, so new fields go between. Coloured from the theme; `--color never`, `--icons=false`, `--stars=false`, `--recent`, `--notes=false`, `--expiry=false`.
- `--format json`: an object per line, with starred, recent, lastUsed and uses, expires, enabled, note, reference and url too.
- `--kind` lists only some kinds, and doesn't load lists only other kinds need; `--cached` loads nothing.
- `lazysecrets open --action go|copy|show` skips the "what to do" menu and ends in the list, like Go to; `open` and `get` take `--account`, the account list printed, rather than asking which.
- The cache file moved to `internal/cachefile`: saves merge with the file under a lock, so list and the TUI don't undo each other.
- `ui.recentIcon` marks items used lately with ◷ in the TUI's lists too; Recent now means the last 90 days.
- --demo has stars, notes, recent use, and expired, expiring and disabled secrets.
- docs/integration.md documents get, list and an fzf picker; docs/links.md is docs/sharing.md.

Reviewed-on: #70
Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
2026-10-14 21:30:44 +02:00

97 lines
3.3 KiB
Go

package app
import (
"context"
"fmt"
"slices"
"git.kebcloud.no/keb/lazysecrets/internal/cachefile"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
)
// containerKey identifies a container in the UI and caches. The same vault
// seen through two accounts must not share cached lists, since each
// account may see different things.
func containerKey(c provider.Container) string { return cachefile.ContainerKey(c) }
// providerFor finds the provider instance a source came from. Sources
// remembered from an earlier run (favourites) may name an account that
// isn't signed in any more, or predate accounts; they fall back to the
// first provider of the same kind.
func (m Model) providerFor(src provider.Source) provider.Provider {
if p, ok := m.byID[src.Provider]; ok {
return p
}
for _, p := range m.providers {
if p.Kind() == src.Kind {
return p
}
}
return missingProvider{src}
}
// candidates lists the providers that may serve src, in configured order:
// its own provider when that is known, otherwise every provider of its kind
// that lists the source (all of that kind if their sources haven't loaded).
// Favourites saved before accounts existed, or by an account that is no
// longer signed in, take the second path.
func (m Model) candidates(src provider.Source) []provider.Provider {
if p, ok := m.byID[src.Provider]; ok {
return []provider.Provider{p}
}
var seeing, ofKind []provider.Provider
for _, p := range m.providers {
if p.Kind() != src.Kind {
continue
}
ofKind = append(ofKind, p)
r, loaded := m.sourceResults[p.ID()]
if !loaded || slices.ContainsFunc(r.sources, func(s provider.Source) bool { return s.ID == src.ID }) {
seeing = append(seeing, p)
}
}
switch {
case len(seeing) > 0:
return seeing
case len(ofKind) > 0:
return ofKind
}
return []provider.Provider{missingProvider{src}}
}
// boundTo returns src as served by p.
func boundTo(src provider.Source, p provider.Provider) provider.Source {
src.Provider, src.ProviderLabel = p.ID(), p.Label()
return src
}
// missingProvider answers for a provider that isn't configured, e.g. a
// favourite from a provider disabled in the config.
type missingProvider struct{ src provider.Source }
func (p missingProvider) err() error {
return fmt.Errorf("%s isn't available (disabled in the config or not signed in)", p.src.Kind)
}
func (p missingProvider) Kind() provider.Kind { return p.src.Kind }
func (p missingProvider) ID() string { return p.src.Provider }
func (p missingProvider) Label() string { return string(p.src.Kind) }
func (p missingProvider) Sources(context.Context) ([]provider.Source, error) {
return nil, p.err()
}
func (p missingProvider) Containers(context.Context, provider.Source) ([]provider.Container, error) {
return nil, p.err()
}
func (p missingProvider) Objects(context.Context, provider.Container, provider.ObjectType) ([]provider.Object, error) {
return nil, p.err()
}
func (p missingProvider) Describe(_ context.Context, o provider.Object) (provider.Object, error) {
return o, p.err()
}
func (p missingProvider) Versions(context.Context, provider.Object) ([]provider.Object, error) {
return nil, p.err()
}
func (p missingProvider) Value(context.Context, provider.Object) (provider.SecretValue, error) {
return provider.SecretValue{}, p.err()
}