feat: keep what was loaded between runs, and start where you left off

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-11 09:32:55 +02:00
1 parent 6fab41703e
commit d6c4b33d1d
11 files changed
+506 -13

No files matched your search

+40 -1
View File
@@ -68,6 +68,12 @@ type Options struct {
ClipboardClear time.Duration
// OpenURL opens a web page; defaults to the system browser.
OpenURL func(url string) error
// CacheFile keeps what was loaded between runs; "" keeps it for this
// run only.
CacheFile string
// Dir is the folder lazysecrets runs in: it goes back to where it was
// left there, unless it was started on a link. "" doesn't.
Dir string
// FlatSources lists sources without provider groups.
FlatSources bool
// CollapseGroups starts with the Sources pane's groups folded, when
@@ -156,7 +162,13 @@ type Model struct {
clip Clipboard
clipClear time.Duration
openURL func(string) error
now func() time.Time
// The cache file, whether the cache changed since it was saved, and
// when each provider's Sources list was loaded (persist.go).
cachePath string
cacheDirty bool
sourcesAt map[string]time.Time
placeDir string
now func() time.Time
// A pending clear of a copied value, counted down in the footer.
// clipToken identifies the latest copy so older ticks are ignored.
@@ -410,6 +422,17 @@ func New(providers []provider.Provider, opts Options) Model {
m.sourcesGen = 1
m.sourcesPending = len(providers)
m.spinning = true
m.cachePath, m.placeDir, m.sourcesAt = opts.CacheFile, opts.Dir, map[string]time.Time{}
if m.cachePath != "" {
m.loadCacheFile()
if len(m.sourceResults) > 0 {
m.rebuildSources()
}
}
if m.placeDir != "" && opts.Open == nil {
m.restorePlace()
}
return m
}
@@ -433,6 +456,9 @@ func (m *Model) setVersions(items []provider.Object) {
func (m Model) Init() tea.Cmd {
cmds := []tea.Cmd{m.sourcesCmd(), m.spin.Tick, m.checkClipboard()}
if m.cachePath != "" {
cmds = append(cmds, tea.Tick(persistEvery, func(time.Time) tea.Msg { return persistTickMsg{} }))
}
if m.fetchOnStart {
cmds = append(cmds, m.fetchLatest(false))
}
@@ -522,6 +548,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case commandDoneMsg:
m.handleCommandDone(msg)
case persistTickMsg:
cmds = append(cmds, m.persistTick())
case persistedMsg:
m.handlePersisted(msg)
case crawlMsg:
cmds = append(cmds, m.handleCrawl(msg))
@@ -552,6 +584,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.containers.SetError(msg.err)
} else {
m.cache.containers[msg.key] = entry[[]provider.Container]{msg.items, m.now()}
m.cacheDirty = true
m.containers.SetItems(m.starredContainersFirst(msg.items))
}
@@ -572,6 +605,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
msg.key = m.rebind(*msg.bound)
}
m.cache.objects[msg.key] = entry[[]provider.Object]{msg.items, m.now()}
m.cacheDirty = true
if m.versionsOf != nil {
m.setVersions(msg.items)
} else {
@@ -601,6 +635,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Errors and unsupported providers just leave the count out.
if msg.err == nil {
m.cache.objects[msg.key] = entry[[]provider.Object]{msg.items, m.now()}
m.cacheDirty = true
m.detail.SetVersions(msg.id, msg.items)
}
@@ -614,6 +649,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.detail.SetDescribeError(msg.err)
} else {
m.cache.details[msg.key] = entry[provider.Object]{msg.obj, m.now()}
m.cacheDirty = true
m.detail.SetDescribed(msg.obj)
}
@@ -1203,6 +1239,9 @@ func (m *Model) handleSources(msg sourcesMsg) tea.Cmd {
}
m.sourcesPending--
m.sourceResults[msg.provider] = msg
if msg.err == nil {
m.sourcesAt[msg.provider], m.cacheDirty = m.now(), true
}
var errs []string
for _, p := range m.providers {