Declarative machine setup you can read before you run
kempt
A config repo is one kempt.toml plus the files
it references — the packages, dotfiles, and system settings that make a machine yours.
kempt plan shows you exactly what it would
install, link, or change; kempt apply makes only
those changes, and only after you've read them. That's the point: stand up a new
machine, or reproduce your setup on another, without hand-running a pile of shell scripts and
hoping they still do what you remember. The primitive set is closed and there is no
exec/script escape hatch — every action is a built-in, versioned primitive, so the whole
plan is reviewable before anything on your machine changes.
$ kempt plan plan · profile developer · darwin/arm64 packages.core ✓ install starship, atuin, ripgrep, jq (brew) ✓ symlink ~/.config/zsh → config/zsh packages.terminal ✓ git-clone ~/.tmux/plugins/tmux-resurrect packages.muster + install: schuettc/muster 0.7.1 (github-release) ✓ json-merge ~/.claude/settings.json ✓ service tools.muster.serve (launchd) ✓ verify muster · command-exists, version-current 1 changes, 61 ok, 0 blocked
what it's for
Machine setup today is imperative: a dotfiles installer, a bundle file, a shell script you
pipe into sh and hope. You run it and find out what it did afterward — there is
no honest way to read the full set of changes first, because the script decides as it goes.
kempt inverts that. The setup is declarative — a manifest of
the state you want, not a sequence of commands — so kempt can inspect the machine, compute
the difference, and show you the exact plan before it touches anything. Nothing changes until
you approve the diff.
The manifest describes the end state — this software present, these files linked, this service loaded — not the steps to get there. kempt derives the steps, and derives them against your machine as it actually is right now.
kempt plan is read-only. It prints every delta apply would make and changes
nothing. What you read is what apply does — there is no branch the plan can't see, because
every action is a known primitive.
There is no exec, no run, no script hook — not as a
discouraged option, but as an absent one. A manifest can only invoke kempt's built-in
primitives, so a plan can never hide an arbitrary command.
Every step computes current vs desired and no-ops on a match. Running apply twice is safe; converging a mostly-set-up machine is cheap; the plan is honest about how little is actually left to do.
install
The installer downloads the latest release for your platform, verifies its
checksum fail-closed, and drops the kempt binary in ~/.local/bin
(override with KEMPT_INSTALL_DIR). Then point kempt at your config repo, read the
plan, and apply it.
# 1. install the binary (fail-closed checksum, installs to ~/.local/bin) $ curl -fsSL https://kempt.tools/install.sh | sh # or build from source: go install ./cmd/kempt # 2. point kempt at your config — a git repo, or a tarball URL (no git needed) $ kempt init <repo-url> $ kempt init https://github.com/you/dotfiles/archive/refs/heads/main.tar.gz # 3. read the plan, then apply only what you approve $ kempt plan $ kempt apply
Binaries are served from the kempt.tools domain under the download family-URL contract, with GitHub releases as a durable backing store.
get started
The core loop of plan and apply, the read-only verify, and the split between kempt update and kempt refresh.
The kempt.toml manifest, where a config can come from, and the closed set of primitives a manifest can use.
The full kempt standard as it ships in a kempt release: the manifest shape, every primitive, rules, profiles, layers, versioning and kempt doctor.