feat: keep what was loaded between runs, and start where you left off (#2)

Starting lazysecrets again now shows what you had loaded at once, and puts you back where you were.

- **Cache file** `~/.cache/lazysecrets/cache.json` (0600, written atomically): the Sources lists, vaults/scopes, secrets' metadata, Describe results and each list's cursor. **Never values**, and never repos (they're on disk anyway). Loaded on start; lists older than 2 minutes reload in the background as before. Saved every 30s when changed, and on quit.
- **Housekeeping:** entries not loaded for 30 days are dropped, as is everything from accounts no longer signed in (every cache key starts with the account ID). The file has a format version; another version is ignored and replaced. `cache: false` turns it off; `--demo` never uses it.
- **Sources are cached too:** Azure lists subscriptions over the network, so without this the Sources pane would still wait.
- **Go to** searches everything cached straight away, and its background loading now also refreshes lists from an earlier run instead of skipping them.
- **Where you left off:** the state file remembers the selected source, vault or secret per folder (the `--dir` or working directory, 100 most recent folders), and the next start in that folder goes back there quietly (no error if it's gone). Starting on a link skips it.

Docs: `cache:` in config.md, a Cache section, the place in State, and the README Privacy section now mentions the cache file.

Tests: cache round trip with a backend too slow to answer (lists and cursor come from the file), no value in the file, file mode 0600, old and signed-out entries dropped, back where it was left, not for another folder or a link.

This touches `app.go` near PR #1's changes (Model fields, the Update switch), so whichever merges second needs a small conflict fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
keb committed 2026-10-11 09:53:32 +02:00
1 parent 17d2705e12
commit 10dc747f0f
11 files changed
+512 -12

No files matched your search

+9 -5
View File
@@ -232,11 +232,12 @@ func (m *Model) crawlStart() tea.Cmd {
return cmd
}
// queueSource queues listing src's vaults or, with them listed and not
// force, loading their secrets.
// queueSource queues listing src's vaults or, with them listed lately
// and not force, loading their secrets. Lists from an earlier run (the
// cache file) are listed again, so searching brings them up to date.
func (m *Model) queueSource(src provider.Source, force bool) {
row := sourceRowKey(src.Provider, src.ID)
if e, ok := m.cache.containers[row]; ok && !force {
if e, ok := m.cache.containers[row]; ok && e.fresh(m.now()) && !force {
for _, c := range e.value {
m.queueContainer(c, row, false)
}
@@ -245,10 +246,11 @@ func (m *Model) queueSource(src provider.Source, force bool) {
m.enqueue(crawlJob{src: &src, row: row, force: force}, row)
}
// queueContainer queues loading c's secrets, unless loaded and not force.
// queueContainer queues loading c's secrets, unless loaded lately and not
// force.
func (m *Model) queueContainer(c provider.Container, row string, force bool) {
key := containerKey(c) + "|" + string(provider.TypeSecret)
if _, ok := m.cache.objects[key]; ok && !force {
if e, ok := m.cache.objects[key]; ok && e.fresh(m.now()) && !force {
return
}
m.enqueue(crawlJob{container: &c, row: row, force: force}, key)
@@ -319,12 +321,14 @@ func (m *Model) handleCrawl(msg crawlMsg) tea.Cmd {
case msg.job.src != nil:
delete(m.crawl.failed, key)
m.cache.containers[key] = entry[[]provider.Container]{msg.containers, m.now()}
m.cacheDirty = true
for _, c := range msg.containers {
m.queueContainer(c, key, msg.job.force)
}
default:
delete(m.crawl.failed, key)
m.cache.objects[key] = entry[[]provider.Object]{msg.objects, m.now()}
m.cacheDirty = true
}
cmd := m.crawlNext()
m.refreshSearch()