The logic lives in the packages and upgrades with them. What lands in your repository is thin enough to read in a sitting — a config file, a wrangler file, and a mount. And every command is drivable by an agent.
It installs with your project's own package manager, reads the real manifest out of node_modules rather than trusting a catalog, resolves what the capability composes against as a graph, and writes the bindings into every environment stanza your wrangler file declares.
$ pithy add payments --with-prerequisites
▸ secrets installed, wired, migrated
▸ auth installed, wired, migrated
▸ payments installed, wired, migrated
▸ wrangler.jsonc updated: staging, prod
Done.
This is the part most CLIs bolt on later and never finish. Every command supports --json, with structured errors that name the failure and the fix — so a coding agent can install a capability, read what happened, and decide what to do next without parsing prose.
{
"error": {
"code": "cli/prerequisites_missing",
"message": "auth requires secrets and email, which myapp does not compose.",
"action": "Run pithy add auth --with-prerequisites, or compose them first."
}
}
One command cuts a git worktree and its branch, reserves a port block so several features run at once, installs, migrates and seeds a local backend. A second provisions that branch its own ephemeral Cloudflare resources — its D1, its KV, its R2, its own master key.
Every name is derived from the branch, so an already-provisioned resource is recovered by looking it up rather than by anything stored. That is what makes it idempotent, resumable, and safe to run from CI. And the ids land in a build artifact, never in your tracked config, so git add -A cannot commit them and a branch abandoned without teardown strands nothing.
$ pithy feature create checkout-flow --issue 214
▸ worktree .worktrees/214-checkout-flow
▸ branch feature/214-checkout-flow
▸ ports reserved 8110-8119
▸ backend migrated, seeded
$ cd .worktrees/214-checkout-flow
$ pithy provision --feature
▸ d1 myapp-214-checkout-flow
▸ kv myapp-214-checkout-flow-sessions
▸ r2 myapp-214-checkout-flow-uploads
Done.
An isolated worktree, a real environment, and structured output on every command is the loop a coding agent already wants: take an issue, get a branch and infrastructure of its own, build, deploy a preview, and tear the whole thing down when the pull request closes.
Teardown deletes by recomputed name regardless of what anyone recorded, so a half-finished run cleans up too. Put the same two commands in CI and every pull request gets a real environment rather than a shared staging everyone queues for.
$ pithy feature sync --json
$ pithy provision --feature --json
$ pithy deploy --env feature --json
# and when the pull request closes
$ pithy feature destroy --json
pithy remove is destructive, so it is interactive only. Passing --json fast-fails with a clear error before anything changes, and its confirmations are typed at a real terminal.
Tearing down an ephemeral environment is a different command, because that is a different intent.
$ pithy remove payments --drop --json
error remove is interactive only; --json is refused.
Nothing has changed.
Run it in a terminal, or use the feature lifecycle
to tear down an environment.
In a terminal it asks one question for the whole cascade. With --with-prerequisites it proceeds. Anything non-interactive exits and names the exact commands to run, in order — and nothing is wired.
Composing something nobody asked for is not a thing to do behind your back, and reporting success on a project that cannot boot is worse.
$ pithy deploy --env prod
▸ typecheck ok
▸ test ok
▸ migrate 3 applied
▸ deploy myapp-api → prod
Done.
The surface
init · add · remove · worker · ui · alias
dev · migrate · seed · deploy · env · doctor
secrets · token · provision · feature · upgrade · dashboard
payments · email · storage · media · vector · support · testers · turnstile
Every one of them takes --json, except the one that deletes things.
Install a capability, wire its bindings, run its migrations. Then deploy.