The config directory

Where it is

What it is for is the concept page; development secrets is the file you will actually edit; pithy doctor reports what it found here; and accepted limits covers what this directory does not defend against.

~/.config/pithy on Linux and macOS, following the platform’s own convention elsewhere.

Created 0700, with 0600 files.

pithy doctor prints every path below, so you never have to remember them.

The files

PathHoldsWritten by
state.jsonThe update notifier’s statepithy doctor, the notifier
dev-ports.jsonThe machine-wide port registryAny command taking a port
cloudflare.<name>.jsonA Cloudflare API tokenpithy init, pithy token
<project>/secrets.jsoncYour dev secret valuespithy add, pithy secrets edit
<project>/dev.jsonThe dev-login sessionpithy dev

Why it is outside every checkout

Nothing to gitignore. Nothing git add -A can reach. Nothing npm pack can carry.

Delete the whole clone and the secrets are still there.

It is also what lets several projects, and several worktrees of each, run at once with no per-checkout setup step.

secrets.jsonc

Keyed by the registry secret name verbatim — <capability>-<what>, kebab — because that name is the join key into the registry, and a mapping table between the file’s keys and the registry’s names would be one more thing to rot.

Nothing in it names a destination. The registry already knows each secret’s backend, so the seeder derives where a value goes and the file and the registry cannot disagree.

Every entry is a full envelope, even a single-version text secret — which is what makes a JSON secret’s own object distinguishable from an envelope without a heuristic.

Unknown keys are refused, not stripped. A stripping parser once accepted the master key’s own value written bare, dropped a field, and left a base64 string where a nested object belongs — surfacing three frames later, naming neither the file nor the secret.

The Worker’s .dev.vars is generated from this file, not the other way round. Generation is idempotent by content, so a run that changes nothing writes no bytes.

dev-ports.json

One registry for the whole machine.

pithy doctor shows this checkout’s blocks first and unqualified — which ports am I on, and on which branch — then the machine-wide view, each block named by the checkout that holds it. Which is what turns 8847, and I don’t know why into because other-app holds 8827–8846.

Ranges, never block indices. A block’s ports are base + index × size, so the index is the whole answer only while every entry is the same width — and a registry written before the width changed keeps its old entries verbatim.

cloudflare.<name>.json

Mode 0600, in a 0700 directory, outside every checkout.

Nothing secret goes into your project. The account is discovered rather than asked for — the token lists the accounts it can see, one is a confirmation and several are a picker, and choosing the account supplies both its id and the nickname for its credentials file.

A narrowly scoped token that cannot list falls back to asking for an account id, and init still completes.

<project>/dev.json

The dev-login session. Absent means sign-in stays magic-link only — which is a state, not a fault.

The rule the whole directory is held to

One optional line’s failure must not cost every other line.

pithy doctor discards every read failure it meets — an unreadable config, a file that will not parse, a .dev.vars it cannot open — and it discards the write to its own notifier cache too, since a config directory that will not take a write is exactly the machine somebody runs doctor on.

A diagnostic has to work in the environment it diagnoses.

Overrides

PITHY_NO_UPDATE_NOTIFIER turns the notifier off without touching the state file.

ESC