Search and Discovery
Three distinct tools, each answering a different question. Picking the wrong one is the most common KV mistake:
listbrowses key names - by prefix, tag, owner, or a substring of the key path.searchruns full-text search over values - but only values that were written withindexed.statsaggregates counts and sizes under a prefix.
Listing keys
snug kv list --prefix config/
snug kv list --prefix config/ --depth 0 # immediate children only
snug kv list --filter 'tags[eq]=ui' --filter 'private[eq]=true' --mine
snug kv list --prefix logs/ --sort -updated_at --limit 50 # then --cursor <page.next_cursor>, or --all
snug kv list -q "session" --prefix cache/ # substring on key NAMESThe JSON payload is a keys array of metadata (no values) plus the standard
page object; captured live:
{
"keys": [
{
"key": "docs/indexed",
"content_type": "application/json",
"size_bytes": 33,
"owner": "docs-writer",
"private": false,
"tags": [],
"created_at": "2026-08-28T03:48:16.356982Z",
"updated_at": "2026-08-28T03:48:16.356982Z"
}
],
"page": { "has_more": false }
}list speaks the standard list contract:
filters on tags, owner, private, content_type, namespace,
size_bytes, expires_at, created_at, and updated_at, sorts on
created_at (newest first by default), updated_at, and size_bytes, and
25 entries per page by default. prefix and depth stay named parameters,
and --mine (mine=true) keeps only your own keys. Other users' private keys
simply do not appear. With --depth, keys deeper than that fold into
namespace entries, the entries are ordered by key, and the page counts
entries rather than keys.
Full-text search over values
Only values written with --indexed are searchable, and indexing is not
retroactive - re-set a key to index it. Verified: two keys with identical
content, one indexed, and search returns only the indexed one.
snug kv set docs/indexed '{"content":"searchable words here"}' --indexed
snug kv search -q "searchable words" --prefix docs/{
"results": [
{
"key": "docs/indexed",
"snippet": "searchable words here",
"score": 1.0,
"content_type": "application/json",
"tags": [],
"updated_at": "2026-08-28T03:48:16.356982Z"
}
],
"page": { "has_more": false }
}search takes the same filters and paging as list, plus prefix.
Remember the split: list -q "dark" will not find {"theme":"dark"} -
it looks at key paths, not value bodies. That is what search is for.
Aggregate stats
snug --output json kv stats --prefix docs/{
"total_keys": 5,
"total_size_bytes": 132,
"keys_by_namespace": { "docs": 5 },
"keys_by_content_type": { "application/json": 5 },
"keys_with_ttl": 0,
"private_keys": 1,
"indexed_keys": 1
}A scripting pattern
List, review, then batch-delete - JSON mode prints the bare payload, so fields are addressed directly:
snug --output json kv list --filter 'tags[eq]=deprecated' --mine --all \
| jq -r '.keys[].key' | tee /tmp/to-delete.txt
# review, then:
snug kv batch-delete "$(paste -sd, /tmp/to-delete.txt)" --forceThe exact parameters are in the KV Store API reference.
