Capture a change you made by hand
Flip something in System Settings or install something directly, then read the line niwa offers and decide where it lands.
When to use this
Use this when the machine is already right and the config has not caught up: you flipped a setting in System Settings, or ran an install because it was faster than opening an editor. Nobody hand-writes two hundred settings, so niwa proposes the line and you curate it.
What must already be true
- The config repo is not mid-merge. niwa refuses to edit a config it cannot read one side of.
- A terminal is attached. The walk asks questions.
niwa notices
The watcher is a launchd job with no state and no protocol. It sees that
preferences or the config repo changed, waits five seconds so a burst is
one event, and runs niwa check --notify. It notifies. It never applies.
What counts as interesting is a filter, not a guess. niwa watches a short list of System Settings domains, plus whatever your config touches. A key it has never seen is learned quietly; only a change to a known key becomes a proposal. Drift is computed per governed identity, so an app that rewrites its whole preference file on every launch, while the one key you govern sits untouched, is silence.
You can ask at any time with niwa check. Six things come out of a
survey: a governed file whose bytes moved, a governed rendered file, a
governed preference whose value moved, an ungoverned key in a watched
domain, a package with an install receipt and no declaration, and a
declaration that vanished while its work is still on the machine.
The walk
niwa pullpull is apply's inverse: machine to config. It shows one difference at a
time and offers four answers, which mean the same thing everywhere.
- apply accepts the edit into the config.
- edit opens the proposed lines in
$EDITORfirst. What you save is what lands. - never is the permanent no. It is remembered per machine in the journal's declined list, keyed to the exact proposal with its value, so the same key at a different value asks again.
- skip is not now. It returns the next time the difference is seen.
The line you are shown is the line that lands.
niwa.defaults("com.apple.WindowManager", {
StandardHideWidgets = true,
})This is also how you learn the keys. Nobody knows that hiding desktop
widgets is StandardHideWidgets in com.apple.WindowManager, and no list
of preference keys survives a macOS release. Flip it, read the line.
Where the line lands
Packages are matched by provider, preferences by domain. A proposal goes
to the one module that already declares that kind: a formula joins the
module your formulae live in, a com.apple.dock key joins the module that
already governs com.apple.dock.
Anything that matches nowhere, or matches more than one module, lands in
modules/inbox.luau. niwa creates it on first use and adds the require
to init.luau so it loads. The inbox is a permanent home, not a queue:
unsorted and working beats blocked on tidying.
When the key is already declared, the proposal edits that declaration in
place instead of appending a second opinion, so the config never holds
two answers about one key. If the existing line cannot be edited with
confidence, niwa names it and leaves it to you. niwa add places its
lines by exactly these rules, and niwa fmt normalizes them, so a line
niwa wrote reads like one you wrote.
Everything at once
niwa pull --allThis stages every finding without the walk and leaves the review to git.
modules/inbox.luau ← brew: jq 1 change staged in your config · review with `git diff`
pull writes to the working tree and stops there. Staging and committing
are yours, which is what makes git diff the review step.
One finding is never staged by --all: a removal. A declaration you
deleted leaves work behind on the machine, and taking that work back
changes the machine rather than the config, so it waits for an
interactive yes. The secret gate runs on every pull, so a line that reads
like a credential is named and held back while the rest goes through.
Next
- Drift and the write-back loop — it explains why proposals run in this direction, and what the four answers mean elsewhere
