diff --git a/README.md b/README.md
index 097a7b6..f95caa9 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ and more, written in pure Go, and inspired by [skate](https://github.com/charmbr
-`pda` stores key-value pairs natively as [newline-delimited JSON](https://en.wikipedia.org/wiki/JSON_streaming#Newline-delimited_JSON) files. [`pda list`](#listing) outputs tabular data by default, but also supports [CSV](https://en.wikipedia.org/wiki/Comma-separated_values), [TSV](https://en.wikipedia.org/wiki/Tab-separated_values), [Markdown](https://en.wikipedia.org/wiki/Markdown) and [HTML](https://en.wikipedia.org/wiki/HTML) tables, [JSON](https://en.wikipedia.org/wiki/JSON), and raw NDJSON. Everything is in plaintext to make version control easy, and to avoid tying anybody to using this tool forever.
+`pda` stores key-value pairs natively as [newline-delimited JSON](https://en.wikipedia.org/wiki/JSON_streaming#Newline-delimited_JSON) files. [`pda list`](#listing) outputs tabular data by default, but also supports [CSV](https://en.wikipedia.org/wiki/Comma-separated_values), [TSV](), [Markdown]() and [HTML]() tables, [JSON](), and raw NDJSON. Everything is in plaintext to make version control easy, and to avoid tying anybody to using this tool forever.
Git versioning can be initiated with [`pda init`](#git), and varying levels of automation can be toggled via the [config](#config): `git.autocommit`, `git.autofetch`, and `git.autopush`. Running Git operations on every change can be slow, but a commit is fast. A happy middle-ground is enabling `git.autocommit` and doing the rest manually via [`pda sync`](#git) when changing devices.
@@ -497,57 +497,60 @@ Additionally, `interactive` being passed or `key.always_prompt_overwrite` being
↑ ·
- pda remove
+ pda help remove
-[`pda remove`](#removing) (alias: [`rm`](#removing)) deletes one or more keys. Any number of keys can be deleted in a single call, with keys from differing stores able to be mixed freely.
+[`pda remove`](#removing) (alias: [`rm`](#removing)) deletes one or more keys.
```bash
-# delete a single key
-pda remove kitty
-
-# delete multiple keys at once
-pda remove kitty doggy
-
-# delete across stores
-pda remove kitty secret@private
+pda rm kitty
```
-Exact positional keys can be combined with [glob patterns](#filtering) via `key`, `store`, and `value` to widen the scope of a deletion. Glob-matched deletions prompt for confirmation by default due to their more error-prone nature. This is configurable with `key.always_prompt_glob_delete` in the [config](#config).
+Remove multiple keys at once:
```bash
-# delete "kitty" and everything matching the key "?og"
-❯ pda rm kitty --key "?og"
- ??? remove 'cog'? (y/n)
- ==> y
- ??? remove 'dog'? (y/n)
-
-# delete keys matching a store and key pattern
-❯ pda rm --store "temp*" --key "session*"
+pda rm kitty dog@animals
```
-Passing `interactive` prompts before each deletion, including exact keys. This behaviour can be made permanent with `key.always_prompt_delete` in the [config](#config). Inversely, `yes` auto-accepts all confirmation prompts.
+Mix exact keys with [glob patterns](#filtering) using [`--key`](#removing):
```bash
-# prompt before each deletion
-❯ pda rm kitty -i
- ??? remove 'kitty'? (y/n)
- ==> y
-
-# auto-accept all prompts
-❯ pda rm kitty -y
+pda set cog "cogs"
+pda set dog "doggy"
+pda set kitty "cat"
+pda rm kitty --key "?og"
```
-[Read-only](#read-only) keys cannot be deleted without explicitly passing `force`.
+Filter by store with [`--store`](#removing) / `-s` and by value with [`--value`](#removing) / `-v`:
+
+```bash
+pda rm --store "temp*" --key "session*"
+```
+
+[`--interactive`](#removing) / `-i` prompts before each deletion (or set `key.always_prompt_delete` in [config](#config)):
+
+```bash
+pda rm kitty -i
+# ??? remove 'kitty'? (y/n)
+# ==> y
+```
+
+Glob-matched deletions prompt by default (configurable with `key.always_prompt_glob_delete`).
+
+[`--yes`](#removing) / `-y` auto-accepts all confirmation prompts:
+
+```bash
+pda rm kitty -y
+```
+
+[Read-only](#read-only) keys can't be deleted without [`--force`](#removing):
```bash
-# remove a read-only key
❯ pda rm protected-key
FAIL cannot remove 'protected-key': key is read-only
-# force-remove a read-only key
-❯ pda rm protected-key --force
+pda rm protected-key --force
```
### Metadata
@@ -555,7 +558,7 @@ FAIL cannot remove 'protected-key': key is read-only
↑ ·
- pda meta,
+ pda meta,
TTL,
Encryption,
Read-Only,
@@ -563,41 +566,47 @@ FAIL cannot remove 'protected-key': key is read-only
-[`pda meta`](#metadata) can be used to view or modify metadata for a given key without touching its value. It always takes one argument: the desired key.
-
-If no flags are passed, [`pda meta`](#metadata) will display the key's current metadata. Any flags passed can be used to modify metadata in-place: [`ttl`](#ttl), [`encrypt`](#encryption) or [`decrypt`](#encryption), [`readonly`](#read-only) or [`writable`](#read-only), and [`pin`](#pinned) or [`unpin`](#pinned). Multiple changes can be combined in a single command.
-
-In [`pda list`](#listing) output, metadata is demonstrated via a `Meta` column. The presence of each type of metadata is marked by a character, or a dash if unset: [(e)ncrypted](#encryption), [(w)ritable](#read-only), [(t)ime-to-live](#ttl), and [(p)inned](#pinned).
+[`pda meta`](#metadata) views or modifies metadata for a key without changing its value. With no flags, it displays the key's current metadata:
```bash
-# view a key's underlying metadata
❯ pda meta session
key: session@store
secret: false
writable: true
pinned: false
expires: 59m30s
-
-# make a key read-only
-❯ pda meta session --readonly
-
-# remove a key's expiration time
-❯ pda meta session --ttl never
```
-Modifying a [read-only](#read-only) key's metadata requires `force` or by first making it `writable`. A [read-only](#read-only) key can still be [pinned or unpinned](#pinned) as pin state only determines where a key is on `list output`[#listing], and does not change the actual key state.
+Pass flags to modify: [`--ttl`](#ttl), [`--encrypt`](#encryption) / [`--decrypt`](#encryption), [`--readonly`](#read-only) / [`--writable`](#read-only), [`--pin`](#pinned) / [`--unpin`](#pinned).
+
+Multiple metadata changes can be combined in one call:
+
+```bash
+pda meta session --ttl 2h --encrypt --pin
+```
+
+Modifying a [read-only](#read-only) key's metadata requires [`--force`](#metadata) (except for toggling the read-only flag itself, and pin/unpin):
+
+```bash
+❯ pda meta api-url --ttl 1h
+FAIL cannot meta 'api-url': key is read-only
+
+pda meta api-url --ttl 1h --force
+```
#### TTL
↑ ·
- pda set,
- pda meta
+ pda help set,
+ pda help meta
-Keys can be given an expiration time. Expired keys are marked for garbage collection and deleted on the next access to the [store](#stores). [TTL](#ttl) can be set at creation time via [`pda set --ttl`](#setting), or toggled later with [`pda meta --ttl`](#metadata) and [`pda edit --ttl`](#editing).
+Keys can be given an expiration time. Expired keys are marked for garbage collection and deleted on the next access to the store.
+
+Set a TTL at creation time with [`pda set --ttl`](#setting):
```bash
# expire after 1 hour
@@ -605,70 +614,91 @@ pda set session "123" --ttl 1h
# expire after 54 minutes and 10 seconds
pda set session2 "xyz" --ttl 54m10s
-
-# remove an expiration time
-pda meta session --ttl never
```
-TTL can be displayed with [`pda list`](#listing) in the [TTL](#ttl) column, or with [`pda meta`](#metadata).
+[`pda list`](#listing) shows expiration in the TTL column:
```bash
-# view ttl in a store's list output
❯ pda ls
TTL Key Value
59m30s session 123
51m40s session2 xyz
-
-# view the metadata of a specific key
-❯ pda meta session
```
-Expiration time is preserved on [`import`](#import--export) and [`export`](#import--export) and [moving or copying](#moving--copying). TTL is stored as a timestamp rather than a timer; keys with a TTL are checked on access to the store they reside in, and any with an expiry that has already passed are deleted. If a key expires while having been exported, it will be deleted on import the next time `pda` touches the file.
+Change or clear the TTL on an existing key with [`pda meta --ttl`](#metadata):
+
+```bash
+❯ pda meta session --ttl 2h
+ ok set ttl to 2h session
+
+❯ pda meta session --ttl never
+ ok cleared ttl session
+```
+
+The [`edit`](#editing) command also accepts `--ttl`:
+
+```bash
+pda edit session --ttl 30m
+```
+
+[`export`](#import--export) and [`import`](#import--export) preserve the expiry date. Expirations are stored as a timestamp, not a timer — they continue ticking down regardless of whether the key is in an active store or sitting in a backup file.
#### Encryption
↑ ·
- pda set,
- pda meta,
- pda identity
+ pda help set,
+ pda help meta,
+ pda help identity
-[`pda set --encrypt`](#setting) encrypts values at rest using [age](https://github.com/FiloSottile/age). Values are stored on disk as age ciphertext and decrypted automatically at run-time by commands like [`pda get`](#getting) and [`pda list`](#listing) when the correct identity file is present. An X25519 [identity](#identity) is generated on first use.
-
-By default, the only recipient for encrypted keys is your own [identity](#identity) file. Additional recipients can be added or removed via [`pda identity`](#identity).
+[`pda set --encrypt`](#setting) encrypts values at rest using [age](https://github.com/FiloSottile/age). Values are stored on disk as age ciphertext and decrypted automatically by commands like [`get`](#getting) and [`list`](#listing) when the correct identity file is present. An X25519 identity is generated on first use.
```bash
-# create a key called "api-key" and encrypt it
-❯ pda set --encrypt api-key "sk-live-abc123"
- ok created identity at ~/.local/share/pda/identity.txt
+pda set --encrypt api-key "sk-live-abc123"
+# ok created identity at ~/.config/pda/identity.txt
-# encrypt a key after editing in $EDITOR
-❯ pda edit --encrypt api-key
-
-# decrypt a key via meta
-❯ pda meta --decrypt api-key
+pda set --encrypt token "ghp_xxxx"
```
-Because the on-disk value of an encrypted key is ciphertext, encrypted entries are safe to commit and push with [Git](#git).
+[`pda get`](#getting) decrypts automatically:
+
+```bash
+❯ pda get api-key
+sk-live-abc123
+```
+
+Toggle encryption on an existing key with [`pda meta`](#metadata):
+
+```bash
+❯ pda meta api-key --encrypt
+ ok encrypted api-key
+
+❯ pda meta api-key --decrypt
+ ok decrypted api-key
+```
+
+The on-disk value is ciphertext, so encrypted entries are safe to commit and push with [Git](#git):
```bash
❯ pda export
{"key":"api-key","value":"YWdlLWVuY3J5cHRpb24u...","encoding":"secret"}
```
-[`mv`](#moving--copying), [`cp`](#moving--copying), and [`import`](#import--export) all preserve encryption, read-only, and pinned flags. Overwriting an encrypted key by setting a new value without `--encrypt` will warn you.
+[`mv`](#moving--copying), [`cp`](#moving--copying), and [`import`](#import--export) all preserve encryption, read-only, and pinned flags. Overwriting an encrypted key without `--encrypt` will warn you:
```bash
-# setting a new key and forgetting the "--encrypt" flag
+pda cp api-key api-key-backup
+# still encrypted
+
❯ pda set api-key "oops"
WARN overwriting encrypted key 'api-key' as plaintext
hint pass --encrypt to keep it encrypted
```
-If your [identity](#identity) file does not match an intended recipient of an encrypted key, the value will be inaccessible. It will display `locked` on fetch until a matching [identity](#identity) is found.
+If the identity file is missing, encrypted values are inaccessible but not lost. Keys remain visible, and the ciphertext is preserved through reads and writes:
```bash
❯ pda ls
@@ -679,76 +709,93 @@ ew-- api-key locked (identity file missing)
FAIL cannot get 'api-key': secret is locked (identity file missing)
```
-Encrypted keys can be made the default by enabling `key.always_encrypt` in the [config](#config).
+All encryption operations can be set as default with `key.always_encrypt` in [config](#config), so every [`pda set`](#setting) automatically encrypts.
#### Read-Only
↑ ·
- pda set,
- pda meta
+ pda help set,
+ pda help meta
-Keys marked [read-only](#read-only) are protected from accidental modification. A [read-only](#read-only) flag can be set at creation time, toggled later with [`pda meta`](#metadata), or applied alongside an [`edit`](#editing). Making a key [`writable`](#metadata) again or explicitly passing [`force`](#metadata) allows changes through. A key being made writable is a permanent change, whereas the `force` flag is a one-off.
+Keys marked read-only are protected from accidental modification. You can modify a read-only key again by making it [`--writable`](#metadata) or by explicitly bypassing with [`--force`](#metadata).
+
+Set a key as read-only at creation time:
```bash
-# create a read-only key
pda set api-url "https://prod.example.com" --readonly
+```
-# set a key to read-only with meta
+Toggle with [`pda meta`](#metadata):
+
+```bash
❯ pda meta api-url --readonly
ok made readonly api-url
-# set a key as writable with meta
❯ pda meta api-url --writable
ok made writable api-url
-
-# edit a key, and set as readonly on save
-❯ pda edit notes --readonly
```
-Read-only keys are protected from [`setting`](#setting), [`removing`](#removing), [`moving`](#moving--copying), and [`editing`](#editing). They are *not protected* from the deletion of an entire [store](#stores).
+Or alongside an edit:
+
+```bash
+pda edit notes --readonly
+```
+
+Read-only keys are protected from [`set`](#setting), [`rm`](#removing), [`mv`](#moving--copying), and [`edit`](#editing). Use `--force` to bypass:
```bash
-# set a new value to a read-only key
❯ pda set api-url "new value"
FAIL cannot set 'api-url': key is read-only
-# force changes to a read-only key with the force flag
-❯ pda set api-url "new value" --force
-❯ pda remove api-url --force
-❯ pda move api-url new-name --force
+pda set api-url "new value" --force
+pda rm api-url --force
+pda mv api-url new-name --force
```
-[`pda copy`](#moving--copying) can copy a read-only key freely (since the source isn't modified), and the copy preserves the read-only flag. Overwriting a read-only destination is blocked without `force`.
+Modifying a read-only key's metadata also requires `--force` (except for toggling the read-only flag itself, and pin/unpin):
+
+```bash
+❯ pda meta api-url --ttl 1h
+FAIL cannot meta 'api-url': key is read-only
+
+pda meta api-url --ttl 1h --force
+```
+
+[`cp`](#moving--copying) can copy a read-only key freely (since the source isn't modified), and the copy preserves the read-only flag. Overwriting a read-only destination is blocked without `--force`.
#### Pinned
↑ ·
- pda set,
- pda meta
+ pda help set,
+ pda help meta
-Pinned keys sort to the top of [`pda list`](#listing) output, preserving alphabetical order within the pinned and unpinned groups. A pin can be set at creation time, toggled with [`pda meta`](#metadata), or applied alongside an [`edit`](#editing).
+Pinned keys sort to the top of [`pda list`](#listing) output, preserving alphabetical order within the pinned and unpinned groups.
+
+Pin a key at creation time:
```bash
-# pin a key at creation time
pda set important "remember this" --pin
+```
-# pin a key with meta
+Toggle with [`pda meta`](#metadata):
+
+```bash
❯ pda meta todo --pin
ok pinned todo
-# unpin a key with meta
❯ pda meta todo --unpin
ok unpinned todo
+```
-# view pinned keys in list output, at the top
+```bash
❯ pda ls
Meta Key Value
-w-p important remember this
@@ -761,66 +808,95 @@ Meta Key Value
↑ ·
- pda list-stores,
- pda move-store,
- pda remove-store
+ pda list-stores,
+ pda move-store,
+ pda remove-store
-Stores are saved on disk as NDJSON files. `pda` supports any number of stores, and creating them is automatic. If a key is created with a `@STORE` suffix, and the named store does not already exist, it will be created automatically to support the new key.
+You can have as many stores as you want. Stores are created implicitly when you set a key with a `@STORE` suffix. Each store is a separate NDJSON file on disk.
-[`pda list-stores`](#stores) (alias: [`lss`](#stores)) shows all stores with their respective key counts and file sizes. Passing `short` prints only the store names.
+[`pda list-stores`](#stores) (alias: [`lss`](#stores)) shows all stores with key counts and file sizes:
```bash
-# list all stores
❯ pda list-stores
Keys Size Store
2 1.8k @birthdays
12 4.2k @store
+```
-# list all store names
+[`--short`](#stores) prints only the names:
+
+```bash
❯ pda list-stores --short
@birthdays
@store
```
-[`pda move-store`](#stores) (alias: [`mvs`](#stores)) renames a store. Passing `copy` keeps the source intact.
+Save to a specific store with the `@STORE` syntax:
```bash
-# rename a store
-pda move-store birthdays bdays
+pda set alice@birthdays "11/11/1998"
+```
-# copy a store
+List a specific store:
+
+```bash
+❯ pda ls @birthdays
+ Store Key Value
+birthdays alice 11/11/1998
+birthdays bob 05/12/1980
+```
+
+[`pda move-store`](#stores) (alias: [`mvs`](#stores)) renames a store:
+
+```bash
+pda move-store birthdays bdays
+```
+
+Copy a store with `--copy`:
+
+```bash
pda move-store birthdays bdays --copy
```
-[`pda remove-store`](#stores) (alias: [`rms`](#stores)) deletes a store. A confirmation prompt is shown by default (configurable with `store.always_prompt_delete` in the [config](#config)). Deleting an entire store does not require unsetting [read-only](#read-only) on contained keys: if a [read-only](#read-only) key is within a store, it **will be deleted** if the store is removed.
+[`--safe`](#stores) skips if the destination already exists:
+
+```bash
+pda move-store birthdays bdays --safe
+```
+
+[`pda remove-store`](#stores) (alias: [`rms`](#stores)) deletes a store:
```bash
-# delete a store
pda remove-store birthdays
```
-As with changeful key operations, store commands support `interactive` and `safe` flags where they make sense. Moving or removing a store interactively will generate a confirmation prompt if anything would be lost by the action being taken. The `safe` flag will prevent moving a store from ever overwriting another store.
+[`--yes`](#stores) / `-y` skips confirmation prompts:
-Inversely, `yes` can be passed to bypass any confirmation prompts.
+```bash
+pda remove-store birthdays -y
+```
#### Import & Export
↑ ·
- pda export,
- pda import
+ pda help export,
+ pda help import
-[`pda export`](#import--export) dumps entries as NDJSON (it is functionally an alias for `list --format ndjson`). The [filtering](#filtering) flags `key`, `value`, and `store` all work with exports. It shares functionality with [`pda list`](#listing) in regard to which stores get exported: if `list.always_show_all_stores` is set and no store name is specified as an argument, all stores will be exported.
+[`pda export`](#import--export) exports everything as NDJSON (it's an alias for `list --format ndjson`):
```bash
-# export everything
pda export > my_backup
+```
+Filter exports with [`--key`](#filtering), [`--value`](#filtering), and [`--store`](#filtering):
+
+```bash
# export only matching keys
pda export --key "a*"
@@ -828,28 +904,46 @@ pda export --key "a*"
pda export --value "**https**"
```
-[`pda import`](#import--export) restores entries from an NDJSON dump. The default behaviour for an import is to merge with any existing stores of the same name. To completely replace existing stores instead of merging, `drop` can be passed.
-
-[Importing](#import--export) takes one or zero arguments. On export each key saves the name of the store it came from in its metadata; on import, by default, each key will be returned to that same store. If a store name is passed as an argument to [`pda import`](#import--export), this behaviour will be overriden and all keys will be imported into the specified store.
-
-As with [exporting](#import--export), `key`, `value`, and `store` flags can be passed to filter which keys will be imported from the input file.
+[`pda import`](#import--export) restores entries from an NDJSON dump. By default, each entry is routed to the store it came from (via the `"store"` field in the NDJSON). If no `"store"` field is present, entries go to `store.default_store_name`.
```bash
# entries are routed to their original stores
pda import -f my_backup
# ok restored 5 entries
+```
-# force all entries into a single store
+Pass a store name as a positional argument to force all entries into one store:
+
+```bash
pda import mystore -f my_backup
# ok restored 5 entries into @mystore
+```
-# read from stdin
+Read from stdin:
+
+```bash
pda import < my_backup
```
-`interactive` can be passed to [`pda import`](#import--export) to prompt on potential overwrite, and is generally recommended if an import is ever being routed to a specific store, as it is likely to cause collisions.
+Filter imports with [`--key`](#filtering) and [`--store`](#filtering):
-[`pda export`](#import--export) encodes [binary data](#binary-data) as base64. All [metadata](#metadata) is preserved through export and import.
+```bash
+# import only matching keys
+pda import --key "a*" -f my_backup
+
+# import only entries from matching stores
+pda import --store "prod*" -f my_backup
+```
+
+[`--drop`](#import--export) does a full replace — drops all existing entries before importing:
+
+```bash
+pda import --drop -f my_backup
+```
+
+[`--interactive`](#import--export) / `-i` prompts before overwriting existing keys.
+
+[`export`](#import--export) encodes [binary data](#binary-data) as base64. [Encryption](#encryption), [read-only](#read-only), [pinned](#pinned) flags, and [TTL](#ttl) are all preserved through export and import.
### Templates
@@ -863,11 +957,9 @@ pda import < my_backup
Values support Go's [`text/template`](https://pkg.go.dev/text/template) syntax. Templates are evaluated on [`pda get`](#getting) and [`pda run`](#running).
-`text/template` is a Turing-complete templating library that supports pipelines, nested templates, conditionals, loops, and more. Actions are given with `{{ action }}` syntax. To better accomodate `text/template`, `pda` adds a small set of built-in functions on top of the standard library.
+`text/template` is a Turing-complete templating library that supports pipelines, nested templates, conditionals, loops, and more. Actions are given with `{{ action }}` syntax. To fit `text/template` into a CLI key-value tool, `pda!` adds a small set of built-in functions on top of the standard library.
-These same functions are also available in `git.default_commit_message` templates, in addition to `summary`, which returns the action that triggered the commit (e.g. "set foo", "removed bar").
-
-`no-template` can be passed to output a raw value without resolving the template.
+These same functions are also available in `git.default_commit_message` templates, along with `summary` which returns the action that triggered the commit (e.g. "set foo", "removed bar").
#### Basic Substitution
@@ -992,15 +1084,20 @@ FAIL cannot get 'level': ...invalid value 'debug', allowed: [info warn error]
-`int` parses a variable as an integer. Useful mostly for loops or arithmetic.
+`int` parses a variable as an integer, useful for loops and arithmetic:
```bash
-❯ pda set number "{{ int .N }}"
+pda set number "{{ int .N }}"
+
❯ pda get number N=3
3
+```
+
+Use it in a range loop:
+
+```bash
+pda set meows "{{ range int .COUNT }}meow! {{ end }}"
-# using "int" in a loop
-❯ pda set meows "{{ range int .COUNT }}meow! {{ end }}"
❯ pda get meows COUNT=4
meow! meow! meow! meow!
```
@@ -1032,19 +1129,23 @@ Hi Bob. Hi Alice.
-`shell` executes a command and returns its stdout. Commands executed by the `shell` function are executed by `$SHELL`. If it is somehow unset, it defaults to using `/usr/sh`.
+`shell` executes a command and returns its stdout:
```bash
-❯ pda set rev '{{ shell "git rev-parse --short HEAD" }}'
+pda set rev '{{ shell "git rev-parse --short HEAD" }}'
+
❯ pda get rev
a1b2c3d
+```
+
+```bash
+pda set today '{{ shell "date +%Y-%m-%d" }}'
-❯ pda set today '{{ shell "date +%Y-%m-%d" }}'
❯ pda get today
2025-06-15
```
-#### `pda`
+#### `pda` (Recursive)
@@ -1053,22 +1154,47 @@ a1b2c3d
-`pda` returns the output of [`pda get`](#getting) on a key.
+`pda` gets another key's value, enabling recursive composition:
```bash
-# use the value of "base_url" in another key
-❯ pda set base_url "https://api.example.com"
-❯ pda set endpoint '{{ pda "base_url" }}/users/{{ require .ID }}'
+pda set base_url "https://api.example.com"
+pda set endpoint '{{ pda "base_url" }}/users/{{ require .ID }}'
+
❯ pda get endpoint ID=42
https://api.example.com/users/42
+```
+
+Cross-store references work too:
+
+```bash
+pda set host@urls "https://example.com"
+pda set api '{{ pda "host@urls" }}/api'
-# use the value of a key from another store
-❯ pda set host@urls "https://example.com"
-❯ pda set api '{{ pda "host@urls" }}/api'
❯ pda get api
https://example.com/api
```
+#### `no-template`
+
+
+
+ ↑ ·
+ pda help get
+
+
+
+Pass [`--no-template`](#getting) to [`pda get`](#getting) to output the raw value without evaluating templates:
+
+```bash
+pda set hello "{{ if .MORNING }}Good morning.{{ end }}"
+
+❯ pda get hello MORNING=1
+Good morning.
+
+❯ pda get hello --no-template
+{{ if .MORNING }}Good morning.{{ end }}
+```
+
### Filtering
@@ -1081,11 +1207,11 @@ https://example.com/api
-[`key`](#filtering), [`value`](#filtering), and [`store`](#filtering) flags can be used to filter entries via globs. All three flags are repeatable, with results matching one-or-more of the patterns per flag. When multiple flags are combined, results must satisfy all of them (AND across flags, OR within the same flag).
+[`--key`](#filtering) / `-k`, [`--value`](#filtering) / `-v`, and [`--store`](#filtering) / `-s` filter entries with glob support. All three flags are repeatable, with results matching one-or-more of the patterns per flag. When multiple flags are combined, results must satisfy all of them (AND across flags, OR within the same flag).
-These filters work with [`pda list`](#listing), [`pda export`](#import--export), [`pda import`](#import--export), and [`pda remove`](#removing).
+These filters work with [`list`](#listing), [`export`](#import--export), [`import`](#import--export), and [`remove`](#removing). [`--value`](#filtering) is not available on [`import`](#import--export) or [`remove`](#removing).
-[`gobwas/glob`](https://github.com/gobwas/glob) is used for matching. The default separators are `/-_.@:` and space. For a detailed guide to globbing, I highly recommend taking a look at the documentation there directly.
+[`gobwas/glob`](https://github.com/gobwas/glob) is used for matching. The default separators are `/-_.@:` and space.
#### Glob Patterns
@@ -1096,69 +1222,63 @@ These filters work with [`pda list`](#listing), [`pda export`](#import--export),
-`*` wildcards a word or series of characters, stopping at separator boundaries.
+`*` wildcards a word or series of characters, stopping at separator boundaries:
```bash
-# list all store contents
❯ pda ls
cat
+dog
+cog
+mouse hotdog
mouse house
foo.bar.baz
-# match any single-word key
-❯ pda ls --key "*"
-cat
+pda ls --key "*"
+# cat, dog, cog (single-segment keys only)
-# match any two-word key
-❯ pda ls --key "* *"
-mouse house
+pda ls --key "* *"
+# mouse hotdog, mouse house
-# match any key starting with "foo." and ending with ".baz", with one word between
-❯ pda ls --key "foo.*.baz"
-foo.bar.baz
+pda ls --key "foo.*.baz"
+# foo.bar.baz
```
-`**` super-wildcards ignore word boundaries.
+`**` super-wildcards ignore word boundaries:
```bash
-# match anything beginning with "foo"
-❯ pda ls --key "foo**"
-foo.bar.baz
+pda ls --key "foo**"
+# foo.bar.baz
+
+pda ls --key "**g"
+# dog, cog, mouse hotdog
```
`?` matches a single character:
```bash
-# match anything beginning with any letter, and ending with "og"
-❯ pda ls --key "?og"
-dog
-cog
+pda ls --key "?og"
+# dog, cog
```
-`[abc]` matches one of the characters in the brackets.
+`[abc]` matches one of the characters in the brackets:
```bash
-# match anything beginning with "d" or "c", and ending with "og"
-❯ pda ls --key "[dc]og"
-dog
-cog
+pda ls --key "[dc]og"
+# dog, cog
# negate with '!'
-❯ pda ls --key "[!dc]og"
-bog
+pda ls --key "[!dc]og"
+# bog (if it exists)
```
`[a-c]` matches a range:
```bash
-# match anything beginning with "a" to "g", and ending with "ag"
-❯ pda ls --key "[a-g]ag"
-bag
-gag
+pda ls --key "[a-g]ag"
+# bag, gag
-# negate with '!'
-❯ pda ls --key "[!a-g]ag"
-wag
+pda ls --key "[!a-g]ag"
+# wag
```
#### Filtering by Key
@@ -1171,13 +1291,14 @@ wag
-[`key`](#filtering) filters entries by key name. Multiple `key` patterns are OR'd. An entry matches if it matches any of them.
+[`--key`](#filtering) / `-k` filters entries by key name:
```bash
pda ls --key "db*"
pda ls --key "session*" --key "token*"
```
+Multiple `--key` patterns are OR'd — an entry matches if it matches any of them.
#### Filtering by Value
@@ -1189,14 +1310,24 @@ pda ls --key "session*" --key "token*"
-[`value`](#filtering) filters by value. Multiple `value` patterns are OR'd.
+[`--value`](#filtering) / `-v` filters by value content using the same glob syntax:
```bash
❯ pda ls --value "**localhost**"
-❯ pda ls --value "**world**" --value "42"
+Key Value
+db-url postgres://localhost:5432
```
-Locked (encrypted without an available identity) and non-UTF-8 (binary) entries are silently excluded from `value` matching.
+Multiple `--value` patterns are OR'd:
+
+```bash
+❯ pda ls --value "**world**" --value "42"
+Key Value
+greeting hello world
+number 42
+```
+
+Locked (encrypted without an available identity) and non-UTF-8 (binary) entries are silently excluded from `--value` matching.
#### Filtering by Store
@@ -1208,7 +1339,7 @@ Locked (encrypted without an available identity) and non-UTF-8 (binary) entries
-[`store`](#filtering) filters by store name. Multiple `store` patterns are OR'd.
+[`--store`](#filtering) / `-s` filters by store name:
```bash
pda ls --store "prod*"
@@ -1224,13 +1355,13 @@ pda export --store "dev*"
-`key`, `value`, and `store` filters can be combined. Results must match at least one of each category of filter used. For example, checking for `key` and two different `value` globs on the same filter: the results must match `key` and at least one of the two `value` globs; the results do not need to match both values.
+Combine key, value, and store filters. Results must match all flags (AND), with OR within each flag:
```bash
pda ls --key "db*" --value "**localhost**"
```
-Globs can be combined to create some deeply complex queries. For example, [`key`](#filtering) can be combined with exact positional args on [`rm`](#removing) to remove exactly the "cat" key, and any keys beginning with "cat", "dog", or "mouse" followed by zero-or-more additional words.
+Globs can be arbitrarily complex, and [`--key`](#filtering) can be combined with exact positional args on [`rm`](#removing):
```bash
pda rm cat --key "{mouse,[cd]og}**"
@@ -1251,59 +1382,75 @@ pda rm cat --key "{mouse,[cd]og}**"
-`pda!` supports all binary data. Values can be read from a file with `--file` or piped in via stdin. Retrieval works the same way: pipe or redirect the output to get the raw bytes.
+`pda!` supports all binary data. Save it with [`pda set`](#setting):
```bash
-# store binary data from a file
pda set logo < logo.png
pda set logo -f logo.png
+```
-# retrieve binary data
+And retrieve it with [`pda get`](#getting):
+
+```bash
pda get logo > output.png
```
-On a TTY, [`get`](#getting) and [`list`](#listing) show a summary instead of printing raw bytes (which can cause undefined terminal behaviour). In a non-TTY setting (piped or redirected), the raw bytes are returned as expected. Passing `base64` provides a safe way to view binary data in a terminal.
+On a TTY, [`get`](#getting) and [`list`](#listing) show a summary for binary data. If piped or run outside of a TTY, raw bytes are output:
```bash
-# TTY shows a summary
❯ pda get logo
(binary: 4.2 KB, image/png)
+```
-# base64 view
+[`--base64`](#getting) / `-b` views binary data as base64:
+
+```bash
❯ pda get logo --base64
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVQI12...
```
-[`pda export`](#import--export) encodes binary data as base64 in the NDJSON, and [`pda edit`](#editing) presents binary values as base64 for editing and decodes them back on save.
+[`pda export`](#import--export) encodes binary data as base64 in the NDJSON:
```bash
❯ pda export
{"key":"logo","value":"89504E470D0A1A0A0000000D4948445200000001000000010802000000","encoding":"base64"}
```
+[`pda edit`](#editing) presents binary values as base64 for editing and decodes them back on save.
+
### Git
↑ ·
- pda init,
- pda sync,
- pda git,
+ pda init,
+ pda sync,
Config
-[`pda init`](#git) initialises version control in the data directory. Without arguments, this creates a local-only repository. Passing a remote URL clones from an existing repository instead, useful for syncing `pda` across machines. For anything that [`pda sync`](#sync) doesn't cover, [`pda git`](#pda-git) passes arguments directly to `git` from within the data directory (this generally should be avoided unless things are very broken somehow).
+`pda!` supports automatic version control backed by Git, either in a local-only repository or by initialising from a remote.
+
+#### Init
+
+
+
+ ↑ ·
+ pda help init
+
+
+
+[`pda init`](#git) initialises version control:
```bash
-# initialise an empty local repository
+# initialise an empty repository
pda init
-# or clone from an existing remote
+# or clone an existing one
pda init https://github.com/llywelwyn/my-repository
```
-Passing `clean` removes any existing `.git` directory first. This is primarily useful for reinitialising a broken repository or switching to a different remote.
+[`--clean`](#git) removes the existing `.git` directory first, useful for reinitialising or switching remotes:
```bash
pda init --clean
@@ -1315,76 +1462,108 @@ pda init https://github.com/llywelwyn/my-repository --clean
↑ ·
- pda sync
+ pda help sync
-[`pda sync`](#sync) conducts a best-effort sync of local data with the Git repository. Any time you swap machine or know you've made changes outside of `pda!`, syncing is recommended.
+[`pda sync`](#sync) conducts a best-effort sync of your local data with your Git repository. Any time you swap machine or know you've made changes outside of `pda!`, syncing is recommended.
-If the local repository is ahead, syncing will commit and push. If it is behind, syncing will detect this and prompt: either stash local changes and pull, or abort and resolve manually. Running [`pda sync`](#sync) will always fetch, commit, and push regardless of the automation settings in [config](#config).
+If you're ahead, syncing will commit and push. If you're behind, syncing will detect this and prompt you: either stash local changes and pull, or abort and fix manually.
```bash
# sync with Git
pda sync
# with a custom commit message
-pda sync -m "new bookmarks"
+pda sync -m "added production credentials"
```
-#### Auto-Commit, Push, and Fetch
+Running [`pda sync`](#sync) manually will always fetch, commit, and push — or stash and pull if behind — regardless of config.
+
+#### Auto-Commit & Auto-Push
↑ ·
- pda config
+ pda help config
-The amount of Git automation can be configured via `git.auto_commit`, `git.auto_fetch`, and `git.auto_push` in the [config](#config). All three are disabled by default.
+`pda!` supports automation via its [config](#config). There are options for `git.auto_commit`, `git.auto_fetch`, and `git.auto_push`.
-`git.auto_commit` commits changes to the local repository any time data is changed. `git.auto_fetch` fetches from the remote before committing, though this incurs a noticeable slowdown due to network round-trips. `git.auto_push` pushes committed changes to the remote after each commit.
+**`git.auto_commit`** commits changes immediately to the local Git repository any time data is changed.
-`auto_fetch` and `auto_push` are additional steps that happen during the commit process, so they have no effect if `auto_commit` is disabled. Running all Git operations on every change can be slow, but a commit is fast. A happy middle-ground is enabling `git.auto_commit` and doing the rest manually via [`pda sync`](#sync) when changing devices.
+**`git.auto_fetch`** fetches before committing any changes. This incurs a noticeable slowdown due to network round-trips.
+**`git.auto_push`** automatically pushes committed changes to the remote repository, if one is configured.
+
+If `auto_commit` is false, `auto_fetch` and `auto_push` have no effect. They are additional steps in the commit process.
+
+A recommended setup is to enable `git.auto_commit` and run [`pda sync`](#sync) manually when switching machines.
### Identity
↑ ·
- pda identity,
+ pda identity,
Encryption
-[`pda identity`](#identity) (alias: [`id`](#identity)) manages the [age](https://github.com/FiloSottile/age) identity used for [encryption](#encryption). An identity is generated automatically the first time [`--encrypt`](#encryption) is used, but one can also be created manually with `--new`. [`--new`](#identity) will error if an identity already exists; delete the file manually to replace it.
+[`pda identity`](#identity) (alias: [`id`](#identity)) manages the age encryption identity used for [encryption](#encryption).
-```bash
-# create a new identity manually
-pda identity --new
-```
+#### Viewing Identity
-With no flags, [`pda identity`](#identity) displays your public key, identity file path, and any additional recipients. Passing `path` prints only the identity file path, useful for scripting.
+
+
+ ↑ ·
+ pda help identity
+
+
+
+With no flags, [`pda identity`](#identity) shows your public key, identity file path, and any additional recipients:
```bash
❯ pda identity
ok pubkey age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
ok identity ~/.config/pda/identity.txt
+```
+[`--path`](#identity) prints only the identity file path:
+
+```bash
❯ pda identity --path
~/.config/pda/identity.txt
```
+#### Creating an Identity
+
+
+
+ ↑ ·
+ pda help identity
+
+
+
+An identity is generated automatically the first time you use [`--encrypt`](#encryption). To create one manually:
+
+```bash
+pda identity --new
+```
+
+[`--new`](#identity) errors if an identity already exists. Delete the file manually to replace it.
+
#### Recipients
↑ ·
- pda identity
+ pda help identity
-By default, secrets are encrypted only for your own identity. To encrypt for additional recipients (e.g. another device or a friend), use [`--add-recipient`](#identity) with their age public key. All existing secrets are automatically re-encrypted for every recipient.
+By default, secrets are encrypted only for your own identity. To encrypt for additional recipients (e.g. a teammate or another device), use [`--add-recipient`](#identity) with their age public key. All existing secrets are automatically re-encrypted for every recipient:
```bash
❯ pda identity --add-recipient age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
@@ -1393,11 +1572,15 @@ By default, secrets are encrypted only for your own identity. To encrypt for add
ok re-encrypted 1 secret(s)
```
-Removing a recipient with `--remove-recipient` re-encrypts all secrets without their key. Additional recipients are shown in the default [`pda identity`](#identity) output.
+Remove a recipient with [`--remove-recipient`](#identity). Secrets are re-encrypted without their key:
```bash
pda identity --remove-recipient age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
+```
+Additional recipients are shown in the default identity display:
+
+```bash
❯ pda identity
ok pubkey age1abc...
ok identity ~/.local/share/pda/identity.txt
@@ -1409,12 +1592,23 @@ pda identity --remove-recipient age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2k
↑ ·
- pda config,
- pda doctor
+ pda config,
+ pda doctor
-Config is stored at `~/.config/pda/config.toml` (Linux/macOS) or `%LOCALAPPDATA%/pda/config.toml` (Windows). All values have sensible defaults, so a config file is entirely optional. [`pda config`](#config) manages configuration without needing to edit files by hand, and [`pda doctor`](#doctor) will warn about unrecognised keys (typos, removed options) and show any non-default values, so it doubles as a config audit.
+Config is stored at `~/.config/pda/config.toml` (Linux/macOS) or `%LOCALAPPDATA%/pda/config.toml` (Windows). All values have sensible defaults, so a config file is entirely optional.
+
+#### Config Commands
+
+
+
+ ↑ ·
+ pda help config
+
+
+
+[`pda config`](#config) manages configuration without editing files by hand:
```bash
# list all config values and their current settings
@@ -1443,13 +1637,15 @@ pda config init --new
pda config init --update
```
-#### Default Config
+[`pda doctor`](#doctor) will warn about unrecognised keys (typos, removed options) and show any non-default values, so it doubles as a config audit.
+
+#### Example config.toml
↑ ·
Config,
- pda config
+ pda help config
@@ -1507,28 +1703,39 @@ default_commit_message = "{{ summary }} {{ time }}"
↑ ·
Config,
- pda doctor
+ pda doctor
-`pda!` respects a small set of environment variables for overriding paths and tools. These are primarily useful for isolating stores across environments or for scripting.
-
-`PDA_CONFIG` overrides the config directory, causing `pda!` to look for `config.toml` here instead of the default XDG location. `PDA_DATA` overrides the data storage directory where stores and the Git repository live. Default data locations follow XDG conventions: `~/.local/share/pda/` on Linux, `~/Library/Application Support/pda/` on macOS, and `%LOCALAPPDATA%/pda/` on Windows.
+`PDA_CONFIG` overrides the config directory. `pda!` will look for `config.toml` in this directory:
```bash
-# use an alternative config directory
PDA_CONFIG=/tmp/config/ pda set key value
+```
-# use an alternative data directory
+`PDA_DATA` overrides the data storage directory:
+
+```bash
PDA_DATA=/tmp/stores pda set key value
```
-`EDITOR` is used by [`pda edit`](#editing) and [`pda config edit`](#config) to open values in a text editor, and must be set for these commands to work. `SHELL` is used by [`pda run`](#running) (or [`pda get --run`](#getting)) for command execution, falling back to `/bin/sh` if unset.
+Default data locations:
+- Linux: `~/.local/share/pda/`
+- macOS: `~/Library/Application Support/pda/`
+- Windows: `%LOCALAPPDATA%/pda/`
+
+`EDITOR` is used by [`pda edit`](#editing) and [`pda config edit`](#config) to open values in a text editor. Must be set for these commands to work:
```bash
EDITOR=nvim pda edit mykey
```
+`SHELL` is used by [`pda run`](#running) (or [`pda get --run`](#getting)) for command execution. Falls back to `/bin/sh` if unset:
+
+```bash
+pda run script
+```
+
### Doctor
@@ -1539,11 +1746,11 @@ EDITOR=nvim pda edit mykey
-[`pda doctor`](#doctor) runs a set of health checks against your [environment](#environment), covering installed tools, [config](#config) validity, [store](#stores) integrity, and [Git](#git) status. As mentioned in [config](#config), it also doubles as a config audit by warning about unrecognised keys and surfacing any non-default values.
+[`pda doctor`](#doctor) runs a set of health checks of your environment:
```bash
❯ pda doctor
- ok pda! 2026.14 (linux/amd64)
+ ok pda! 2025.52 Christmas release (linux/amd64)
ok OS: Linux 6.18.7-arch1-1
ok Go: go1.23.0
ok Git: 2.45.0
@@ -1561,28 +1768,28 @@ EDITOR=nvim pda edit mykey
ok No issues found
```
-Severity levels are colour-coded: `ok` (green), `WARN` (yellow), and `FAIL` (red). Only `FAIL` produces a non-zero exit code. `WARN` is generally not a problem, but may indicate that some functionality isn't being made use of (like [version control](#git) not having been initialised yet).
+Severity levels are colour-coded: `ok` (green), `WARN` (yellow), and `FAIL` (red). Only `FAIL` produces a non-zero exit code. `WARN` is generally not a problem, but may mean some functionality isn't being made use of, like version control not having been initialised yet.
### Version
↑ ·
- pda version
+ pda help version
-[`pda version`](#version) displays the current version. `pda!` uses calendar versioning in the format `YYYY.WW`. Passing `short` prints just the release string without ASCII art, useful for scripting. The ASCII art can also be permanently disabled with `display_ascii_art = false` in the [config](#config).
-
```bash
# display the full version output
pda version
# or just the release
❯ pda version --short
-pda! 2026.14
+pda! 2025.52 Christmas release
```
+`pda!` uses calendar versioning: `YYYY.WW`. ASCII art can be permanently disabled with `display_ascii_art = false` in [config](#config).
+
### Help
diff --git a/cmd/completions.go b/cmd/completions.go
deleted file mode 100644
index 0f8b856..0000000
--- a/cmd/completions.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package cmd
-
-import (
- "strings"
-
- "github.com/spf13/cobra"
-)
-
-// completeKeys returns key[@store] completions for the current toComplete prefix.
-// It handles three cases:
-// - No "@" typed yet: return all keys from all stores (as "key@store")
-// - "@" typed with partial store: return store-scoped completions
-// - "key@store" with known store: return keys from that store
-func completeKeys(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- store := &Store{}
- stores, err := store.AllStores()
- if err != nil || len(stores) == 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
-
- var completions []string
- parts := strings.SplitN(toComplete, "@", 2)
-
- if len(parts) == 2 {
- // User typed "something@" — complete keys within matching stores.
- prefix := parts[0]
- dbFilter := strings.ToLower(parts[1])
- for _, db := range stores {
- if !strings.HasPrefix(db, dbFilter) {
- continue
- }
- keys, err := store.Keys(db)
- if err != nil {
- continue
- }
- for _, k := range keys {
- if prefix == "" || strings.HasPrefix(k, strings.ToLower(prefix)) {
- completions = append(completions, k+"@"+db)
- }
- }
- }
- } else {
- // No "@" yet — offer key@store for every key in every store.
- lowerPrefix := strings.ToLower(toComplete)
- for _, db := range stores {
- keys, err := store.Keys(db)
- if err != nil {
- continue
- }
- for _, k := range keys {
- full := k + "@" + db
- if strings.HasPrefix(full, lowerPrefix) || strings.HasPrefix(k, lowerPrefix) {
- completions = append(completions, full)
- }
- }
- }
- }
-
- return completions, cobra.ShellCompDirectiveNoFileComp
-}
-
-// completeStores returns store name completions.
-func completeStores(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- store := &Store{}
- stores, err := store.AllStores()
- if err != nil {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
-
- var completions []string
- lowerPrefix := strings.ToLower(toComplete)
- for _, db := range stores {
- if strings.HasPrefix(db, lowerPrefix) {
- completions = append(completions, db)
- }
- }
- return completions, cobra.ShellCompDirectiveNoFileComp
-}
-
-// completeStoreFlag is a completion function for --store / -s string slice flags.
-func completeStoreFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return completeStores(cmd, args, toComplete)
-}
diff --git a/cmd/del-db.go b/cmd/del-db.go
index e094370..31fe227 100644
--- a/cmd/del-db.go
+++ b/cmd/del-db.go
@@ -33,13 +33,12 @@ import (
// delStoreCmd represents the set command
var delStoreCmd = &cobra.Command{
- Use: "remove-store STORE",
- Short: "Delete a store",
- Aliases: []string{"rms"},
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: completeStores,
- RunE: delStore,
- SilenceUsage: true,
+ Use: "remove-store STORE",
+ Short: "Delete a store",
+ Aliases: []string{"rms"},
+ Args: cobra.ExactArgs(1),
+ RunE: delStore,
+ SilenceUsage: true,
}
func delStore(cmd *cobra.Command, args []string) error {
diff --git a/cmd/del.go b/cmd/del.go
index fba342c..3dfda52 100644
--- a/cmd/del.go
+++ b/cmd/del.go
@@ -34,10 +34,9 @@ import (
var delCmd = &cobra.Command{
Use: "remove KEY[@STORE] [KEY[@STORE] ...]",
Short: "Delete one or more keys",
- Aliases: []string{"rm"},
- Args: cobra.ArbitraryArgs,
- ValidArgsFunction: completeKeys,
- RunE: del,
+ Aliases: []string{"rm"},
+ Args: cobra.ArbitraryArgs,
+ RunE: del,
SilenceUsage: true,
}
@@ -146,7 +145,6 @@ func init() {
delCmd.Flags().Bool("force", false, "bypass read-only protection")
delCmd.Flags().StringSliceP("key", "k", nil, "delete keys matching glob pattern (repeatable)")
delCmd.Flags().StringSliceP("store", "s", nil, "target stores matching glob pattern (repeatable)")
- delCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
delCmd.Flags().StringSliceP("value", "v", nil, "delete entries matching value glob pattern (repeatable)")
rootCmd.AddCommand(delCmd)
}
diff --git a/cmd/edit.go b/cmd/edit.go
index a5cbe03..96c31ad 100644
--- a/cmd/edit.go
+++ b/cmd/edit.go
@@ -22,10 +22,9 @@ Binary values are presented as base64 for editing and decoded back on save.
Metadata flags (--ttl, --encrypt, --decrypt) can be passed alongside the edit
to modify metadata in the same operation.`,
- Aliases: []string{"e"},
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: completeKeys,
- RunE: edit,
+ Aliases: []string{"e"},
+ Args: cobra.ExactArgs(1),
+ RunE: edit,
SilenceUsage: true,
}
diff --git a/cmd/export.go b/cmd/export.go
index 80dade8..94a22eb 100644
--- a/cmd/export.go
+++ b/cmd/export.go
@@ -31,7 +31,6 @@ var exportCmd = &cobra.Command{
Short: "Export store as NDJSON (alias for list --format ndjson)",
Aliases: []string{},
Args: cobra.MaximumNArgs(1),
- ValidArgsFunction: completeStores,
RunE: func(cmd *cobra.Command, args []string) error {
listFormat = "ndjson"
return list(cmd, args)
@@ -42,7 +41,6 @@ var exportCmd = &cobra.Command{
func init() {
exportCmd.Flags().StringSliceP("key", "k", nil, "filter keys with glob pattern (repeatable)")
exportCmd.Flags().StringSliceP("store", "s", nil, "filter stores with glob pattern (repeatable)")
- exportCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
exportCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
rootCmd.AddCommand(exportCmd)
}
diff --git a/cmd/get.go b/cmd/get.go
index d4d9116..ff1f5a8 100644
--- a/cmd/get.go
+++ b/cmd/get.go
@@ -46,10 +46,9 @@ additional argument after the initial KEY being fetched.
For example:
pda set greeting 'Hello, {{ .NAME }}!'
pda get greeting NAME=World`,
- Aliases: []string{"g"},
- Args: cobra.MinimumNArgs(1),
- ValidArgsFunction: completeKeys,
- RunE: get,
+ Aliases: []string{"g"},
+ Args: cobra.MinimumNArgs(1),
+ RunE: get,
SilenceUsage: true,
}
@@ -64,9 +63,8 @@ additional argument after the initial KEY being fetched.
For example:
pda set greeting 'Hello, {{ .NAME }}!'
pda run greeting NAME=World`,
- Args: cobra.MinimumNArgs(1),
- ValidArgsFunction: completeKeys,
- RunE: run,
+ Args: cobra.MinimumNArgs(1),
+ RunE: run,
SilenceUsage: true,
}
diff --git a/cmd/list.go b/cmd/list.go
index 50e319b..86259f1 100644
--- a/cmd/list.go
+++ b/cmd/list.go
@@ -141,11 +141,10 @@ glob pattern to filter by store name.
Use --key/-k and --value/-v to filter by key or value glob, and --store/-s
to filter by store name. All filters are repeatable and OR'd within the
same flag.`,
- Aliases: []string{"ls"},
- Args: cobra.MaximumNArgs(1),
- ValidArgsFunction: completeStores,
- RunE: list,
- SilenceUsage: true,
+ Aliases: []string{"ls"},
+ Args: cobra.MaximumNArgs(1),
+ RunE: list,
+ SilenceUsage: true,
}
func list(cmd *cobra.Command, args []string) error {
@@ -786,7 +785,6 @@ func init() {
listCmd.Flags().VarP(&listFormat, "format", "o", "output format (table|tsv|csv|markdown|html|ndjson|json)")
listCmd.Flags().StringSliceP("key", "k", nil, "filter keys with glob pattern (repeatable)")
listCmd.Flags().StringSliceP("store", "s", nil, "filter stores with glob pattern (repeatable)")
- listCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
listCmd.Flags().StringSliceP("value", "v", nil, "filter values with glob pattern (repeatable)")
rootCmd.AddCommand(listCmd)
}
diff --git a/cmd/meta.go b/cmd/meta.go
index 91efc26..62b862a 100644
--- a/cmd/meta.go
+++ b/cmd/meta.go
@@ -14,9 +14,8 @@ var metaCmd = &cobra.Command{
without changing its value.
With no flags, displays the key's current metadata. Pass flags to modify.`,
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: completeKeys,
- RunE: meta,
+ Args: cobra.ExactArgs(1),
+ RunE: meta,
SilenceUsage: true,
}
diff --git a/cmd/mv-db.go b/cmd/mv-db.go
index 1e1db1e..f3a360e 100644
--- a/cmd/mv-db.go
+++ b/cmd/mv-db.go
@@ -33,13 +33,12 @@ import (
// mvStoreCmd represents the move-store command
var mvStoreCmd = &cobra.Command{
- Use: "move-store FROM TO",
- Short: "Rename a store",
- Aliases: []string{"mvs"},
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: completeStores,
- RunE: mvStore,
- SilenceUsage: true,
+ Use: "move-store FROM TO",
+ Short: "Rename a store",
+ Aliases: []string{"mvs"},
+ Args: cobra.ExactArgs(2),
+ RunE: mvStore,
+ SilenceUsage: true,
}
func mvStore(cmd *cobra.Command, args []string) error {
diff --git a/cmd/mv.go b/cmd/mv.go
index 1594962..fa7b9d4 100644
--- a/cmd/mv.go
+++ b/cmd/mv.go
@@ -30,23 +30,21 @@ import (
)
var cpCmd = &cobra.Command{
- Use: "copy FROM[@STORE] TO[@STORE]",
- Aliases: []string{"cp"},
- Short: "Make a copy of a key",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: completeKeys,
- RunE: cp,
- SilenceUsage: true,
+ Use: "copy FROM[@STORE] TO[@STORE]",
+ Aliases: []string{"cp"},
+ Short: "Make a copy of a key",
+ Args: cobra.ExactArgs(2),
+ RunE: cp,
+ SilenceUsage: true,
}
var mvCmd = &cobra.Command{
- Use: "move FROM[@STORE] TO[@STORE]",
- Aliases: []string{"mv"},
- Short: "Move a key",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: completeKeys,
- RunE: mv,
- SilenceUsage: true,
+ Use: "move FROM[@STORE] TO[@STORE]",
+ Aliases: []string{"mv"},
+ Short: "Move a key",
+ Args: cobra.ExactArgs(2),
+ RunE: mv,
+ SilenceUsage: true,
}
func cp(cmd *cobra.Command, args []string) error {
diff --git a/cmd/restore.go b/cmd/restore.go
index 03d1d30..70948ba 100644
--- a/cmd/restore.go
+++ b/cmd/restore.go
@@ -37,13 +37,12 @@ import (
)
var restoreCmd = &cobra.Command{
- Use: "import [STORE]",
- Short: "Restore key/value pairs from an NDJSON dump",
- Aliases: []string{},
- Args: cobra.MaximumNArgs(1),
- ValidArgsFunction: completeStores,
- RunE: restore,
- SilenceUsage: true,
+ Use: "import [STORE]",
+ Short: "Restore key/value pairs from an NDJSON dump",
+ Aliases: []string{},
+ Args: cobra.MaximumNArgs(1),
+ RunE: restore,
+ SilenceUsage: true,
}
func restore(cmd *cobra.Command, args []string) error {
@@ -324,7 +323,6 @@ func init() {
restoreCmd.Flags().StringP("file", "f", "", "path to an NDJSON dump (defaults to stdin)")
restoreCmd.Flags().StringSliceP("key", "k", nil, "restore keys matching glob pattern (repeatable)")
restoreCmd.Flags().StringSliceP("store", "s", nil, "restore entries from stores matching glob pattern (repeatable)")
- restoreCmd.RegisterFlagCompletionFunc("store", completeStoreFlag)
restoreCmd.Flags().BoolP("interactive", "i", false, "prompt before overwriting existing keys")
restoreCmd.Flags().Bool("drop", false, "drop existing entries before restoring (full replace)")
rootCmd.AddCommand(restoreCmd)
diff --git a/cmd/set.go b/cmd/set.go
index 9435c94..d81f41b 100644
--- a/cmd/set.go
+++ b/cmd/set.go
@@ -50,10 +50,9 @@ For example:
'Hello, {{ default "World" .NAME }}' will default to World if NAME is blank.
'Hello, {{ require .NAME }}' will error if NAME is blank.
'{{ enum .NAME "Alice" "Bob" }}' allows only NAME=Alice or NAME=Bob.`,
- Aliases: []string{"s"},
- Args: cobra.RangeArgs(1, 2),
- ValidArgsFunction: completeKeys,
- RunE: set,
+ Aliases: []string{"s"},
+ Args: cobra.RangeArgs(1, 2),
+ RunE: set,
SilenceUsage: true,
}
diff --git a/cmd/version.go b/cmd/version.go
index 5e27c90..8c46579 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -28,7 +28,7 @@ import (
)
var (
- version = "pda! 2026.14"
+ version = "pda! 2025.52 Christmas release"
)
// versionCmd represents the version command