feat: opening a link to a secret ends on its details
CI / Checks (pull_request) Successful in 35s
PR title / Conventional title (pull_request) Successful in 1s

As enter on it would, instead of on the vault's list with the secret
selected: ctrl+o, a paste, a clicked link and the share failure
popup's g. Going back to where lazysecrets was left, and Go to, stay
as they were.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-13 14:06:16 +02:00
1 parent 2a3379b1c1
commit 7d6a095e42
4 files changed
+25 -10

No files matched your search

+3 -3
View File
@@ -118,9 +118,9 @@ read the clipboard over SSH.
lazysecrets finds the vault in your accounts (one Resource Graph query per lazysecrets finds the vault in your accounts (one Resource Graph query per
account) and jumps to it, asking which account to use when several can see account) and jumps to it, asking which account to use when several can see
it. Links say what, not what to do: opening a link to a secret asks whether it, ending on a secret's details as `enter` on it would. Links say what,
to just go to it, also copy or show its value, or (`y`) copy its value not what to do: opening a link to a secret asks whether to just go to it,
without going there. also copy or show its value, or (`y`) copy its value without going there.
## In scripts ## In scripts
+1 -1
View File
@@ -217,7 +217,7 @@ func (m *Model) jumpTo(r ref.Ref, n store.Node) tea.Cmd {
case 1: case 1:
m.startNav(r, actGo, choices[0].sourceRow) m.startNav(r, actGo, choices[0].sourceRow)
default: default:
m.chooseSource("Several accounts can see "+r.ContainerName(), r, actGo, choices) m.chooseSource("Several accounts can see "+r.ContainerName(), r, actGo, choices, false)
} }
return nil return nil
} }
+19 -5
View File
@@ -46,6 +46,9 @@ type nav struct {
// quiet gives up without a word: going back to where lazysecrets // quiet gives up without a word: going back to where lazysecrets
// was left, which may be gone. // was left, which may be gone.
quiet bool quiet bool
// link ends on the object's details, as enter on it would: opening
// a link to it opens it, rather than just finding it in its list.
link bool
} }
type resolvedMsg struct { type resolvedMsg struct {
@@ -401,23 +404,30 @@ func (m *Model) handleResolved(msg resolvedMsg) {
for i := range msg.choices { for i := range msg.choices {
msg.choices[i].label = "Open in " + msg.choices[i].label 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) m.chooseSource("Scope "+msg.ref.Scope+" is in several workspaces", msg.ref, msg.act, msg.choices, true)
case len(msg.others) > 0: case len(msg.others) > 0:
var choices []choice var choices []choice
for _, row := range append([]string{msg.sourceRow}, msg.others...) { for _, row := range append([]string{msg.sourceRow}, msg.others...) {
choices = append(choices, choice{label: "Open with " + m.sourceLabel(row), sourceRow: row}) choices = append(choices, choice{label: "Open with " + m.sourceLabel(row), sourceRow: row})
} }
m.chooseSource("Several accounts can see "+msg.ref.ContainerName(), msg.ref, msg.act, choices) m.chooseSource("Several accounts can see "+msg.ref.ContainerName(), msg.ref, msg.act, choices, true)
case msg.linked != nil: case msg.linked != nil:
m.addLinked(*msg.linked) m.addLinked(*msg.linked)
m.startNav(msg.ref, msg.act, linksRowID) m.startLinkNav(msg.ref, msg.act, linksRowID)
default: default:
m.startNav(msg.ref, msg.act, msg.sourceRow) m.startLinkNav(msg.ref, msg.act, msg.sourceRow)
} }
} }
// startLinkNav opens a link: like startNav, ending on its object's
// details.
func (m *Model) startLinkNav(r ref.Ref, act linkAction, sourceRow string) {
m.startNav(r, act, sourceRow)
m.nav.link = true
}
// chooseSource asks where to open r. // chooseSource asks where to open r.
func (m *Model) chooseSource(title string, r ref.Ref, act linkAction, choices []choice) { func (m *Model) chooseSource(title string, r ref.Ref, act linkAction, choices []choice, link bool) {
var opts []option var opts []option
for i, c := range choices { for i, c := range choices {
key := "" key := ""
@@ -427,6 +437,7 @@ func (m *Model) chooseSource(title string, r ref.Ref, act linkAction, choices []
row := c.sourceRow row := c.sourceRow
opts = append(opts, option{key: key, label: c.label, run: func(m *Model) tea.Cmd { opts = append(opts, option{key: key, label: c.label, run: func(m *Model) tea.Cmd {
m.startNav(r, act, row) m.startNav(r, act, row)
m.nav.link = link
return nil return nil
}}) }})
} }
@@ -577,6 +588,9 @@ func (m *Model) advanceNav() bool {
m.pendingCmd = m.toggleReveal() m.pendingCmd = m.toggleReveal()
} }
} }
if n.link {
m.focus = paneDetail // after: copying acts on the focused pane
}
return true return true
} }
return false return false
+2 -1
View File
@@ -40,7 +40,8 @@ func TestOpenSecretLink(t *testing.T) {
if src, c, s := d.selected(); src != "platform-prod" || c != "kv-app-prod" || s != "api-key" { if src, c, s := d.selected(); src != "platform-prod" || c != "kv-app-prod" || s != "api-key" {
t.Fatalf("opened %s/%s/%s", src, c, s) t.Fatalf("opened %s/%s/%s", src, c, s)
} }
if d.m.focus != paneSecrets || d.m.detail.State() != panes.ValueHidden { // Opened: on its details, as enter on it would; its value hidden.
if d.m.focus != paneDetail || d.m.detail.State() != panes.ValueHidden {
t.Errorf("focus %d, value state %d", d.m.focus, d.m.detail.State()) t.Errorf("focus %d, value state %d", d.m.focus, d.m.detail.State())
} }
} }