docs: a majority of the README has been renewed

This commit is contained in:
Lewis Wynne 2026-02-14 05:38:55 +00:00
parent 940c3d694d
commit 3923d20ae9

515
README.md
View file

@ -497,60 +497,57 @@ Additionally, `interactive` being passed or `key.always_prompt_overwrite` being
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-remove"><code>pda help remove</code></a> <a href="#pda-remove"><code>pda remove</code></a>
</sup> </sup>
</p> </p>
[`pda remove`](#removing) (alias: [`rm`](#removing)) deletes one or more keys. [`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.
```bash ```bash
pda rm kitty # delete a single key
pda remove kitty
# delete multiple keys at once
pda remove kitty doggy
# delete across stores
pda remove kitty secret@private
``` ```
Remove multiple keys at once: 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).
```bash ```bash
pda rm kitty dog@animals # 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*"
``` ```
Mix exact keys with [glob patterns](#filtering) using [`--key`](#removing): 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.
```bash ```bash
pda set cog "cogs" # prompt before each deletion
pda set dog "doggy" pda rm kitty -i
pda set kitty "cat" ??? remove 'kitty'? (y/n)
pda rm kitty --key "?og" ==> y
# auto-accept all prompts
pda rm kitty -y
``` ```
Filter by store with [`--store`](#removing) / `-s` and by value with [`--value`](#removing) / `-v`: [Read-only](#read-only) keys cannot be deleted without explicitly passing `force`.
```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 ```bash
# remove a read-only key
pda rm protected-key pda rm protected-key
FAIL cannot remove 'protected-key': key is read-only FAIL cannot remove 'protected-key': key is read-only
pda rm protected-key --force # force-remove a read-only key
pda rm protected-key --force
``` ```
### Metadata ### Metadata
@ -558,7 +555,7 @@ pda rm protected-key --force
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#metadata"><code>pda meta</code></a>, <a href="#pda-meta"><code>pda meta</code></a>,
<a href="#ttl">TTL</a>, <a href="#ttl">TTL</a>,
<a href="#encryption">Encryption</a>, <a href="#encryption">Encryption</a>,
<a href="#read-only">Read-Only</a>, <a href="#read-only">Read-Only</a>,
@ -566,47 +563,41 @@ pda rm protected-key --force
</sup> </sup>
</p> </p>
[`pda meta`](#metadata) views or modifies metadata for a key without changing its value. With no flags, it displays the key's current metadata: [`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).
```bash ```bash
# view a key's underlying metadata
pda meta session pda meta session
key: session@store key: session@store
secret: false secret: false
writable: true writable: true
pinned: false pinned: false
expires: 59m30s expires: 59m30s
# make a key read-only
pda meta session --readonly
# remove a key's expiration time
pda meta session --ttl never
``` ```
Pass flags to modify: [`--ttl`](#ttl), [`--encrypt`](#encryption) / [`--decrypt`](#encryption), [`--readonly`](#read-only) / [`--writable`](#read-only), [`--pin`](#pinned) / [`--unpin`](#pinned). 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.
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 #### TTL
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-set"><code>pda help set</code></a>, <a href="#pda-set"><code>pda set</code></a>,
<a href="#pda-meta"><code>pda help meta</code></a> <a href="#pda-meta"><code>pda meta</code></a>
</sup> </sup>
</p> </p>
Keys can be given an expiration time. Expired keys are marked for garbage collection and deleted on the next access to the store. 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).
Set a TTL at creation time with [`pda set --ttl`](#setting):
```bash ```bash
# expire after 1 hour # expire after 1 hour
@ -614,91 +605,70 @@ pda set session "123" --ttl 1h
# expire after 54 minutes and 10 seconds # expire after 54 minutes and 10 seconds
pda set session2 "xyz" --ttl 54m10s pda set session2 "xyz" --ttl 54m10s
# remove an expiration time
pda meta session --ttl never
``` ```
[`pda list`](#listing) shows expiration in the TTL column: TTL can be displayed with [`pda list`](#listing) in the [TTL](#ttl) column, or with [`pda meta`](#metadata).
```bash ```bash
# view ttl in a store's list output
pda ls pda ls
TTL Key Value TTL Key Value
59m30s session 123 59m30s session 123
51m40s session2 xyz 51m40s session2 xyz
# view the metadata of a specific key
pda meta session
``` ```
Change or clear the TTL on an existing key with [`pda meta --ttl`](#metadata): 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.
```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 #### Encryption
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-set"><code>pda help set</code></a>, <a href="#pda-set"><code>pda set</code></a>,
<a href="#pda-meta"><code>pda help meta</code></a>, <a href="#pda-meta"><code>pda meta</code></a>,
<a href="#pda-identity"><code>pda help identity</code></a> <a href="#pda-identity"><code>pda identity</code></a>
</sup> </sup>
</p> </p>
[`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. [`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).
```bash ```bash
pda set --encrypt api-key "sk-live-abc123" # create a key called "api-key" and encrypt it
# ok created identity at ~/.config/pda/identity.txt pda set --encrypt api-key "sk-live-abc123"
ok created identity at ~/.local/share/pda/identity.txt
pda set --encrypt token "ghp_xxxx" # encrypt a key after editing in $EDITOR
pda edit --encrypt api-key
# decrypt a key via meta
pda meta --decrypt api-key
``` ```
[`pda get`](#getting) decrypts automatically: Because the on-disk value of an encrypted key is ciphertext, encrypted entries are safe to commit and push with [Git](#git).
```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 ```bash
pda export pda export
{"key":"api-key","value":"YWdlLWVuY3J5cHRpb24u...","encoding":"secret"} {"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 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 by setting a new value without `--encrypt` will warn you.
```bash ```bash
pda cp api-key api-key-backup # setting a new key and forgetting the "--encrypt" flag
# still encrypted
pda set api-key "oops" pda set api-key "oops"
WARN overwriting encrypted key 'api-key' as plaintext WARN overwriting encrypted key 'api-key' as plaintext
hint pass --encrypt to keep it encrypted hint pass --encrypt to keep it encrypted
``` ```
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: 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.
```bash ```bash
pda ls pda ls
@ -709,93 +679,76 @@ ew-- api-key locked (identity file missing)
FAIL cannot get 'api-key': secret is locked (identity file missing) FAIL cannot get 'api-key': secret is locked (identity file missing)
``` ```
All encryption operations can be set as default with `key.always_encrypt` in [config](#config), so every [`pda set`](#setting) automatically encrypts. Encrypted keys can be made the default by enabling `key.always_encrypt` in the [config](#config).
#### Read-Only #### Read-Only
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-set"><code>pda help set</code></a>, <a href="#pda-set"><code>pda set</code></a>,
<a href="#pda-meta"><code>pda help meta</code></a> <a href="#pda-meta"><code>pda meta</code></a>
</sup> </sup>
</p> </p>
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). 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.
Set a key as read-only at creation time:
```bash ```bash
# create a read-only key
pda set api-url "https://prod.example.com" --readonly pda set api-url "https://prod.example.com" --readonly
```
Toggle with [`pda meta`](#metadata): # set a key to read-only with meta
```bash
pda meta api-url --readonly pda meta api-url --readonly
ok made readonly api-url ok made readonly api-url
# set a key as writable with meta
pda meta api-url --writable pda meta api-url --writable
ok made writable api-url ok made writable api-url
# edit a key, and set as readonly on save
pda edit notes --readonly
``` ```
Or alongside an edit: 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).
```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 ```bash
# set a new value to a read-only key
pda set api-url "new value" pda set api-url "new value"
FAIL cannot set 'api-url': key is read-only FAIL cannot set 'api-url': key is read-only
pda set api-url "new value" --force # force changes to a read-only key with the force flag
pda rm api-url --force pda set api-url "new value" --force
pda mv api-url new-name --force pda remove api-url --force
pda move api-url new-name --force
``` ```
Modifying a read-only key's metadata also requires `--force` (except for toggling the read-only flag itself, and pin/unpin): [`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`.
```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 #### Pinned
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-set"><code>pda help set</code></a>, <a href="#pda-set"><code>pda set</code></a>,
<a href="#pda-meta"><code>pda help meta</code></a> <a href="#pda-meta"><code>pda meta</code></a>
</sup> </sup>
</p> </p>
Pinned keys sort to the top of [`pda list`](#listing) output, preserving alphabetical order within the pinned and unpinned groups. 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).
Pin a key at creation time:
```bash ```bash
# pin a key at creation time
pda set important "remember this" --pin pda set important "remember this" --pin
```
Toggle with [`pda meta`](#metadata): # pin a key with meta
```bash
pda meta todo --pin pda meta todo --pin
ok pinned todo ok pinned todo
# unpin a key with meta
pda meta todo --unpin pda meta todo --unpin
ok unpinned todo ok unpinned todo
```
```bash # view pinned keys in list output, at the top
pda ls pda ls
Meta Key Value Meta Key Value
-w-p important remember this -w-p important remember this
@ -808,95 +761,66 @@ Meta Key Value
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#stores"><code>pda list-stores</code></a>, <a href="#pda-list-stores"><code>pda list-stores</code></a>,
<a href="#stores"><code>pda move-store</code></a>, <a href="#pda-move-store"><code>pda move-store</code></a>,
<a href="#stores"><code>pda remove-store</code></a> <a href="#pda-remove-store"><code>pda remove-store</code></a>
</sup> </sup>
</p> </p>
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. 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.
[`pda list-stores`](#stores) (alias: [`lss`](#stores)) shows all stores with key counts and file sizes: [`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.
```bash ```bash
# list all stores
pda list-stores pda list-stores
Keys Size Store Keys Size Store
2 1.8k @birthdays 2 1.8k @birthdays
12 4.2k @store 12 4.2k @store
```
[`--short`](#stores) prints only the names: # list all store names
```bash
pda list-stores --short pda list-stores --short
@birthdays @birthdays
@store @store
``` ```
Save to a specific store with the `@STORE` syntax: [`pda move-store`](#stores) (alias: [`mvs`](#stores)) renames a store. Passing `copy` keeps the source intact.
```bash
pda set alice@birthdays "11/11/1998"
```
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 ```bash
# rename a store
pda move-store birthdays bdays pda move-store birthdays bdays
```
Copy a store with `--copy`: # copy a store
```bash
pda move-store birthdays bdays --copy pda move-store birthdays bdays --copy
``` ```
[`--safe`](#stores) skips if the destination already exists: [`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.
```bash
pda move-store birthdays bdays --safe
```
[`pda remove-store`](#stores) (alias: [`rms`](#stores)) deletes a store:
```bash ```bash
# delete a store
pda remove-store birthdays pda remove-store birthdays
``` ```
[`--yes`](#stores) / `-y` skips confirmation prompts: 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.
```bash Inversely, `yes` can be passed to bypass any confirmation prompts.
pda remove-store birthdays -y
```
#### Import & Export #### Import & Export
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-export"><code>pda help export</code></a>, <a href="#pda-export"><code>pda export</code></a>,
<a href="#pda-import"><code>pda help import</code></a> <a href="#pda-import"><code>pda import</code></a>
</sup> </sup>
</p> </p>
[`pda export`](#import--export) exports everything as NDJSON (it's an alias for `list --format ndjson`): [`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.
```bash ```bash
# export everything
pda export > my_backup pda export > my_backup
```
Filter exports with [`--key`](#filtering), [`--value`](#filtering), and [`--store`](#filtering):
```bash
# export only matching keys # export only matching keys
pda export --key "a*" pda export --key "a*"
@ -904,46 +828,28 @@ pda export --key "a*"
pda export --value "**https**" pda export --value "**https**"
``` ```
[`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`. [`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.
```bash ```bash
# entries are routed to their original stores # entries are routed to their original stores
pda import -f my_backup pda import -f my_backup
# ok restored 5 entries # ok restored 5 entries
```
Pass a store name as a positional argument to force all entries into one store: # force all entries into a single store
```bash
pda import mystore -f my_backup pda import mystore -f my_backup
# ok restored 5 entries into @mystore # ok restored 5 entries into @mystore
```
Read from stdin: # read from stdin
```bash
pda import < my_backup pda import < my_backup
``` ```
Filter imports with [`--key`](#filtering) and [`--store`](#filtering): `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.
```bash [`pda export`](#import--export) encodes [binary data](#binary-data) as base64. All [metadata](#metadata) is preserved through export and import.
# 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 ### Templates
@ -957,9 +863,11 @@ pda import --drop -f 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). 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 fit `text/template` into a CLI key-value tool, `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 better accomodate `text/template`, `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, along with `summary` which returns the action that triggered the commit (e.g. "set foo", "removed bar"). 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.
#### Basic Substitution #### Basic Substitution
@ -1084,20 +992,15 @@ FAIL cannot get 'level': ...invalid value 'debug', allowed: [info warn error]
</sup> </sup>
</p> </p>
`int` parses a variable as an integer, useful for loops and arithmetic: `int` parses a variable as an integer. Useful mostly for loops or arithmetic.
```bash ```bash
pda set number "{{ int .N }}" pda set number "{{ int .N }}"
pda get number N=3 pda get number N=3
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 pda get meows COUNT=4
meow! meow! meow! meow! meow! meow! meow! meow!
``` ```
@ -1129,23 +1032,19 @@ Hi Bob. Hi Alice.
</sup> </sup>
</p> </p>
`shell` executes a command and returns its stdout: `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`.
```bash ```bash
pda set rev '{{ shell "git rev-parse --short HEAD" }}' pda set rev '{{ shell "git rev-parse --short HEAD" }}'
pda get rev pda get rev
a1b2c3d a1b2c3d
```
```bash
pda set today '{{ shell "date +%Y-%m-%d" }}'
pda set today '{{ shell "date +%Y-%m-%d" }}'
pda get today pda get today
2025-06-15 2025-06-15
``` ```
#### `pda` (Recursive) #### `pda`
<p> <p>
<sup> <sup>
@ -1154,47 +1053,22 @@ pda set today '{{ shell "date +%Y-%m-%d" }}'
</sup> </sup>
</p> </p>
`pda` gets another key's value, enabling recursive composition: `pda` returns the output of [`pda get`](#getting) on a key.
```bash ```bash
pda set base_url "https://api.example.com" # use the value of "base_url" in another key
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 pda get endpoint ID=42
https://api.example.com/users/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 pda get api
https://example.com/api https://example.com/api
``` ```
#### `no-template`
<p>
<sup>
<a href="#overview"></a> ·
<a href="#pda-get"><code>pda help get</code></a>
</sup>
</p>
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 ### Filtering
<p> <p>
@ -1382,49 +1256,43 @@ pda rm cat --key "{mouse,[cd]og}**"
</sup> </sup>
</p> </p>
`pda!` supports all binary data. Save it with [`pda set`](#setting): `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.
```bash ```bash
# store binary data from a file
pda set logo < logo.png pda set logo < logo.png
pda set logo -f logo.png pda set logo -f logo.png
```
And retrieve it with [`pda get`](#getting): # retrieve binary data
```bash
pda get logo > output.png pda get logo > output.png
``` ```
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: 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.
```bash ```bash
# TTY shows a summary
pda get logo pda get logo
(binary: 4.2 KB, image/png) (binary: 4.2 KB, image/png)
```
[`--base64`](#getting) / `-b` views binary data as base64: # base64 view
```bash
pda get logo --base64 pda get logo --base64
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVQI12... iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVQI12...
``` ```
[`pda export`](#import--export) encodes binary data as base64 in the NDJSON: [`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.
```bash ```bash
pda export pda export
{"key":"logo","value":"89504E470D0A1A0A0000000D4948445200000001000000010802000000","encoding":"base64"} {"key":"logo","value":"89504E470D0A1A0A0000000D4948445200000001000000010802000000","encoding":"base64"}
``` ```
[`pda edit`](#editing) presents binary values as base64 for editing and decodes them back on save.
### Git ### Git
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#git"><code>pda init</code></a>, <a href="#pda-init"><code>pda init</code></a>,
<a href="#sync"><code>pda sync</code></a>, <a href="#pda-sync"><code>pda sync</code></a>,
<a href="#config">Config</a> <a href="#config">Config</a>
</sup> </sup>
</p> </p>
@ -1436,21 +1304,21 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVQI12...
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-init"><code>pda help init</code></a> <a href="#pda-init"><code>pda init</code></a>
</sup> </sup>
</p> </p>
[`pda init`](#git) initialises version control: [`pda init`](#git) initialises version control. With no arguments it creates a local-only repository in the data directory. Pass a remote URL to clone from an existing repository instead.
```bash ```bash
# initialise an empty repository # initialise an empty local repository
pda init pda init
# or clone an existing one # or clone from an existing remote
pda init https://github.com/llywelwyn/my-repository pda init https://github.com/llywelwyn/my-repository
``` ```
[`--clean`](#git) removes the existing `.git` directory first, useful for reinitialising or switching remotes: Passing `clean` removes any existing `.git` directory first, useful for reinitialising or switching remotes.
```bash ```bash
pda init --clean pda init --clean
@ -1462,7 +1330,7 @@ pda init https://github.com/llywelwyn/my-repository --clean
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-sync"><code>pda help sync</code></a> <a href="#pda-sync"><code>pda sync</code></a>
</sup> </sup>
</p> </p>
@ -1485,7 +1353,7 @@ Running [`pda sync`](#sync) manually will always fetch, commit, and push — or
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-config"><code>pda help config</code></a> <a href="#pda-config"><code>pda config</code></a>
</sup> </sup>
</p> </p>
@ -1506,7 +1374,7 @@ A recommended setup is to enable `git.auto_commit` and run [`pda sync`](#sync) m
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#identity"><code>pda identity</code></a>, <a href="#pda-identity"><code>pda identity</code></a>,
<a href="#encryption">Encryption</a> <a href="#encryption">Encryption</a>
</sup> </sup>
</p> </p>
@ -1518,21 +1386,17 @@ A recommended setup is to enable `git.auto_commit` and run [`pda sync`](#sync) m
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-identity"><code>pda help identity</code></a> <a href="#pda-identity"><code>pda identity</code></a>
</sup> </sup>
</p> </p>
With no flags, [`pda identity`](#identity) shows your public key, identity file path, and any additional recipients: With no flags, [`pda identity`](#identity) shows your public key, identity file path, and any additional recipients. Passing `path` prints only the identity file path, useful for scripting.
```bash ```bash
pda identity pda identity
ok pubkey age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p ok pubkey age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
ok identity ~/.config/pda/identity.txt ok identity ~/.config/pda/identity.txt
```
[`--path`](#identity) prints only the identity file path:
```bash
pda identity --path pda identity --path
~/.config/pda/identity.txt ~/.config/pda/identity.txt
``` ```
@ -1542,7 +1406,7 @@ With no flags, [`pda identity`](#identity) shows your public key, identity file
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-identity"><code>pda help identity</code></a> <a href="#pda-identity"><code>pda identity</code></a>
</sup> </sup>
</p> </p>
@ -1559,7 +1423,7 @@ pda identity --new
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-identity"><code>pda help identity</code></a> <a href="#pda-identity"><code>pda identity</code></a>
</sup> </sup>
</p> </p>
@ -1572,15 +1436,11 @@ By default, secrets are encrypted only for your own identity. To encrypt for add
ok re-encrypted 1 secret(s) ok re-encrypted 1 secret(s)
``` ```
Remove a recipient with [`--remove-recipient`](#identity). Secrets are re-encrypted without their key: Removing a recipient with `--remove-recipient` re-encrypts all secrets without their key. Additional recipients are shown in the default identity display.
```bash ```bash
pda identity --remove-recipient age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p pda identity --remove-recipient age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
```
Additional recipients are shown in the default identity display:
```bash
pda identity pda identity
ok pubkey age1abc... ok pubkey age1abc...
ok identity ~/.local/share/pda/identity.txt ok identity ~/.local/share/pda/identity.txt
@ -1592,8 +1452,8 @@ Additional recipients are shown in the default identity display:
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#config"><code>pda config</code></a>, <a href="#pda-config"><code>pda config</code></a>,
<a href="#doctor"><code>pda doctor</code></a> <a href="#pda-doctor"><code>pda doctor</code></a>
</sup> </sup>
</p> </p>
@ -1604,7 +1464,7 @@ Config is stored at `~/.config/pda/config.toml` (Linux/macOS) or `%LOCALAPPDATA%
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-config"><code>pda help config</code></a> <a href="#pda-config"><code>pda config</code></a>
</sup> </sup>
</p> </p>
@ -1645,7 +1505,7 @@ pda config init --update
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#config">Config</a>, <a href="#config">Config</a>,
<a href="#pda-config"><code>pda help config</code></a> <a href="#pda-config"><code>pda config</code></a>
</sup> </sup>
</p> </p>
@ -1703,39 +1563,28 @@ default_commit_message = "{{ summary }} {{ time }}"
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#config">Config</a>, <a href="#config">Config</a>,
<a href="#doctor"><code>pda doctor</code></a> <a href="#pda-doctor"><code>pda doctor</code></a>
</sup> </sup>
</p> </p>
`PDA_CONFIG` overrides the config directory. `pda!` will look for `config.toml` in this directory: `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 — `pda!` will 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.
```bash ```bash
# use an alternative config directory
PDA_CONFIG=/tmp/config/ pda set key value PDA_CONFIG=/tmp/config/ pda set key value
```
`PDA_DATA` overrides the data storage directory: # use an alternative data directory
```bash
PDA_DATA=/tmp/stores pda set key value PDA_DATA=/tmp/stores pda set key value
``` ```
Default data locations: `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. `SHELL` is used by [`pda run`](#running) (or [`pda get --run`](#getting)) for command execution, falling back to `/bin/sh` if unset.
- 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 ```bash
EDITOR=nvim pda edit mykey 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 ### Doctor
<p> <p>
@ -1746,7 +1595,7 @@ pda run script
</sup> </sup>
</p> </p>
[`pda doctor`](#doctor) runs a set of health checks of your environment: [`pda doctor`](#doctor) runs a set of health checks against your environment, covering installed tools, config validity, store integrity, and Git status.
```bash ```bash
pda doctor pda doctor
@ -1775,10 +1624,12 @@ Severity levels are colour-coded: `ok` (green), `WARN` (yellow), and `FAIL` (red
<p> <p>
<sup> <sup>
<a href="#overview"></a> · <a href="#overview"></a> ·
<a href="#pda-version"><code>pda help version</code></a> <a href="#pda-version"><code>pda version</code></a>
</sup> </sup>
</p> </p>
[`pda version`](#version) displays the current version. Passing `short` prints just the release string without ASCII art, useful for scripting. `pda!` uses calendar versioning: `YYYY.WW`. ASCII art can be permanently disabled with `display_ascii_art = false` in [config](#config).
```bash ```bash
# display the full version output # display the full version output
pda version pda version
@ -1788,8 +1639,6 @@ pda version
pda! 2025.52 Christmas release 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 ### Help
<p> <p>