From bdf949339bf26a99d37dc0b2f966490346a4451b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl-Einar=20Bergstr=C3=B8m?= Date: Wed, 14 Oct 2026 13:55:38 +0200 Subject: [PATCH] feat: lazysecrets open --action ends in the secrets list, like Go to --- docs/links.md | 6 +++-- internal/ui/app/app.go | 5 ++-- internal/ui/app/links.go | 45 ++++++++++++++++++++++++++++------- internal/ui/app/links_test.go | 8 +++---- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/docs/links.md b/docs/links.md index 562ae36..80d3326 100644 --- a/docs/links.md +++ b/docs/links.md @@ -121,7 +121,8 @@ account) and jumps to it, asking which account to use when several can see it, ending on a secret's details as `enter` on it would. Links say what, not what to do: opening a link to a secret asks whether to just go to it, also copy or show its value, or (`y`) copy its value without going there. -`lazysecrets open --action go|copy|show ` does one without asking, +`lazysecrets open --action go|copy|show ` does one without asking +and ends in the list, like Go to, and `--account ` picks the account (as [`list`](#listing-for-fzf-and-pickers) prints it) when several can see it. @@ -175,7 +176,8 @@ when several of yours can see it: without, `open` asks which, and `get` tries the one that last read it first. The link stays as it is, to copy and share; the account isn't part of it. `--action` tells `open` what to do rather than asking, as it does for a link clicked elsewhere: `go` to -it, `copy` its value too, or `show` it. +it, `copy` its value too, or `show` it. It ends in the secrets list, like +Go to, rather than on the details as a clicked link does. 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 diff --git a/internal/ui/app/app.go b/internal/ui/app/app.go index 048ba89..da6abdc 100644 --- a/internal/ui/app/app.go +++ b/internal/ui/app/app.go @@ -134,7 +134,8 @@ type Options struct { // Open is a reference to open at startup (lazysecrets open ). Open *ref.Ref // OpenAction, if set, is what to do with Open, rather than asking: - // go to it, and maybe copy or show its value (OpenActions). + // go to it, and maybe copy or show its value (OpenActions). It ends in + // the list, like Go to, rather than on the details. OpenAction string // Styles, if set, replaces the default theme (styles.FromTheme), and // DefaultFg the terminal's default text colour. @@ -602,7 +603,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case openRefMsg: m.deferHandlerSetup() // the link first if act, ok := OpenActions[msg.action]; ok && !msg.ref.IsShare() { - cmds = append(cmds, m.openRef(msg.ref, act)) + cmds = append(cmds, m.findRef(msg.ref, act)) } else { cmds = append(cmds, m.offerRef("Open link", msg.ref, false)) } diff --git a/internal/ui/app/links.go b/internal/ui/app/links.go index 87220fc..465e936 100644 --- a/internal/ui/app/links.go +++ b/internal/ui/app/links.go @@ -56,9 +56,12 @@ type resolvedMsg struct { act linkAction sourceRow string others []string // other accounts' rows that also found the vault - choices []choice // several candidates: ask - linked *provider.Container - err error + // find ends in the list, like Go to, not on the details: opened by a + // tool that found it (lazysecrets open --action, from fzf). + find bool + choices []choice // several candidates: ask + linked *provider.Container + err error } type choice struct { @@ -407,22 +410,22 @@ func (m *Model) handleResolved(msg resolvedMsg) { for i := range msg.choices { msg.choices[i].label = "Open in " + msg.choices[i].label } - m.chooseSource("Scope "+msg.ref.Scope+" is in several workspaces", msg.ref, msg.act, msg.choices, true) + m.chooseSource("Scope "+msg.ref.Scope+" is in several workspaces", msg.ref, msg.act, msg.choices, !msg.find) case len(msg.others) > 0: var choices []choice for _, row := range append([]string{msg.sourceRow}, msg.others...) { choices = append(choices, choice{label: "Open with " + m.sourceLabel(row), sourceRow: row}) } if row, ok := preferredRow(msg.ref, choices); ok { - m.startLinkNav(msg.ref, msg.act, row) + m.navResolved(msg, row) return } - m.chooseSource("Several accounts can see "+msg.ref.ContainerName(), msg.ref, msg.act, choices, true) + m.chooseSource("Several accounts can see "+msg.ref.ContainerName(), msg.ref, msg.act, choices, !msg.find) case msg.linked != nil: m.addLinked(*msg.linked) - m.startLinkNav(msg.ref, msg.act, linksRowID) + m.navResolved(msg, linksRowID) default: - m.startLinkNav(msg.ref, msg.act, msg.sourceRow) + m.navResolved(msg, msg.sourceRow) } } @@ -437,6 +440,32 @@ func preferredRow(r ref.Ref, choices []choice) (string, bool) { return "", false } +// navResolved goes to what a resolved link points at, under row. +func (m *Model) navResolved(msg resolvedMsg, row string) { + if msg.find { + m.startNav(msg.ref, msg.act, row) + return + } + m.startLinkNav(msg.ref, msg.act, row) +} + +// findRef is openRef ending in the list, like Go to: for a link a tool +// found (lazysecrets open --action). +func (m *Model) findRef(r ref.Ref, act linkAction) tea.Cmd { + cmd := m.openRef(r, act) + if cmd == nil { + return nil + } + return func() tea.Msg { + msg := cmd() + if rm, ok := msg.(resolvedMsg); ok { + rm.find = true + return rm + } + return msg + } +} + // startLinkNav opens a link: like startNav, ending on its object's // details. func (m *Model) startLinkNav(r ref.Ref, act linkAction, sourceRow string) { diff --git a/internal/ui/app/links_test.go b/internal/ui/app/links_test.go index a5f5071..f5c4d99 100644 --- a/internal/ui/app/links_test.go +++ b/internal/ui/app/links_test.go @@ -203,8 +203,8 @@ func TestOpenPromptAndStartupOpen(t *testing.T) { t.Fatal("open at startup didn't ask what to do") } d2.keys("o") - if _, _, s := d2.selected(); s != "api-key" { - t.Errorf("open at startup: selected %q", s) + if _, _, s := d2.selected(); s != "api-key" || d2.m.focus != paneDetail { + t.Errorf("open at startup: selected %q, focus %v, want the details", s, d2.m.focus) } // --action: no question, it does that. @@ -212,8 +212,8 @@ func TestOpenPromptAndStartupOpen(t *testing.T) { if d3.m.prompt != nil { t.Fatalf("--action go asked: %q", d3.m.prompt.title) } - if _, _, s := d3.selected(); s != "api-key" { - t.Errorf("--action go: selected %q", s) + if _, _, s := d3.selected(); s != "api-key" || d3.m.focus != paneSecrets { + t.Errorf("--action go: selected %q, focus %v, want the secrets list, like Go to", s, d3.m.focus) } d4 := newDriverOpts(t, Options{Open: &r, OpenAction: "copy"}) if _, _, s := d4.selected(); s != "api-key" || d4.clip().content == "" {