` | A wrapper per built-in command (e.g. `pounce-clipboard`) |
Inside nebelhaus, set the palette up via [`nebelhaus.pounce`](/reference/options/#nebelhauspounce)
instead — the module handles the daemon, hotkey, and permission survival for you.
---
# The nebelung palette
Source: https://nebelhaus.com/reference/palette/
Every colour in the silver-mist palette, with hex values, plus how to use nebelung in your own tools.
export const NOTES = { mauve: 'the family default' };
{/* The chip paints itself with the live CSS variable and the label prints the
value parsed out of palette.css, so neither can drift from the stylesheet.
Nothing on this page states a hex. */}
export const Swatch = ({ name }) => (
{hex(name)}
{NOTES[name] ? `${name} — ${NOTES[name]}` : name}
);
**nebelung** is a silver-mist Catppuccin variant — the blue pulled out of the
neutral ramp and the accents calmed (chroma ×0.9), keeping perceptual lightness
identical. The result is a fog-grey scheme named for a cat breed the colour of
high fog. Grey is the point.
The source of truth is `palette/nebelung.hex.json` in the
[nebelung repo](https://github.com/nebelhaus/nebelung).
## The four variants
Those two rules never mention "dark", so they work in either polarity: applied to
Catppuccin **Latte** instead of Mocha they give the same theme as a light one.
Crossed with a contrast axis, that's four palettes — picked in the rice with
[`theme.flavor` and `theme.contrast`](/guides/theming/#light-mode):
| Variant | Source | Rendered under | text on base |
|---|---|---|---|
| `nebelung` | Mocha | *(the tree root)* | 11.3:1 |
| `nebelung-high-contrast` | Mocha | `high-contrast/` | 19.9:1 |
| `nebelung-latte` | Latte | `latte/` | 7.0:1 |
| `nebelung-latte-high-contrast` | Latte | `latte-high-contrast/` | 9.9:1 |
Every variant keeps its flavor's accents unchanged — contrast is a property of the
**neutrals** only, so a high-contrast rice is the same theme read more easily
rather than a different one. Each ratio is asserted in nebelung's CI, and there's
a test that all twelve ramp steps stay distinct, because a boost pushed too far
clamps two of them into the same colour and silently costs a level of hierarchy.
Each variant also renders as its own Catppuccin flavor, so its files are named
after it — Ghostty's light theme is
`latte/ghostty/themes/catppuccin-latte.conf`. The swatches below are the default
variant; view any of them in the
[interactive preview](https://htmlpreview.github.io/?https://github.com/nebelhaus/nebelung/blob/main/preview/nebelung-latte.html).
## The neutral ramp
Twelve steps from near-black to soft white — the bones of the theme.
{RAMP.map((name) => )}
## The accents
Fourteen calmed accents. **mauve** is the family default; set another with
[`nebelhaus.theme.accent`](/reference/options/#nebelhaustheme).
{ACCENTS.map((name) => )}
## Using it in your own tools
nebelung ships a rendered theme for **50+ tools**, generated from the palette
with [Whiskers](https://github.com/catppuccin/whiskers). A sampling:
| Tool | Tool | Tool | Tool |
|---|---|---|---|
| Ghostty | Kitty | Alacritty | Starship |
| zellij | tmux | btop | bat |
| delta | fzf | lsd | yazi |
| lazygit | glow | helix | Zen browser |
| Slack | Obsidian | VS Code / Cursor | zsh-syntax-highlighting |
Two ways to consume them:
- **Copy a rendered file.** Build the flake's output tree and grab the file for
your tool — e.g. Ghostty's theme, bat's `.tmTheme`, lazygit's per-accent
`.yml`. Each tool's README section shows where its config expects the file.
- **Consume the flake** from your own Nix config:
```nix
inputs.nebelung.url = "github:nebelhaus/nebelung";
# rendered themes:
# ${nebelung.packages.${system}.default}//...
# raw palette (name → "#hex") for configs you generate yourself:
# nebelung.palette
```
**Per-accent tools**
lazygit, yazi, and the Zen browser render **all 14 accents** as separate files;
consumers pick one (the rice uses `mauve`). The single-file tools ship one theme
with the default accent baked in.
## Tuning the palette
The palette is computed in [OKLCH](https://oklch.com/) space, so you can rewrite
hue and chroma while preserving perceptual lightness. Three knobs at the top of
`scripts/generate-palette.mjs` control the look:
| Knob | Meaning | Default |
|---|---|---|
| `neutralHue` | Hue (degrees) of the grey tint — 70° is a faint warm graphite | `70` |
| `neutralChroma` | Strength of that tint; `0` is pure neutral grey | `0` |
| `accentChromaScale` | How much accents are calmed (`0.9` = 10% less saturated) | `0.9` |
Then regenerate and re-render:
```sh
./build.sh # regenerate palette + render every tool → dist/
./build.sh --no-gen # render only (palette unchanged)
```
This is a change in the nebelung repo, not your host file — see the nebelung
[README](https://github.com/nebelhaus/nebelung) for the full workflow.
---
# The haus CLI
Source: https://nebelhaus.com/reference/haus/
The end-user haus command — rebuild, update, rollback, and diagnose your machine.
`haus` is the command that drives your machine after install. It wraps the Nix
and `darwin-rebuild` invocations you'd otherwise type by hand, with safe
defaults (it always builds before switching). It lands on your `PATH` after the
first `switch`.
For the day-to-day workflow, see [Keeping in sync](/guides/staying-in-sync/).
## Commands
| Command | What it does |
|---|---|
| `haus rebuild` | Build, then `darwin-rebuild switch`. Your everyday apply. A failed build never touches the running system. |
| `haus update` | Update the `nebelhaus` pin in `~/.config/nix/flake.lock`, then rebuild — pulls new rice versions. |
| `haus rollback [N]` | Atomically return to the previous generation — or to generation `N`. |
| `haus generations` | List the generations you can roll back to. |
| `haus status` | Show the current generation and how stale the pinned rice is. |
| `haus edit` | Open your host file (`~/.config/nix/hosts//default.nix`) in `$EDITOR`. |
| `haus doctor` | Health check: Determinate Nix, Xcode CLT, the GUI login agents, Homebrew cask drift (casks installed that no rebuild will manage), and whether an [agent](/guides/ai-agent/) can change this machine. |
| `haus btm` | On macOS 26 Tahoe+, check whether Background Task Management is blocking the nix login agents, and print the one-time fix. A no-op on earlier macOS. See [Troubleshooting](/reference/troubleshooting/#after-a-macos-upgrade-all-my-agents-are-dead-macos-26-tahoe). |
### One thing `haus rebuild` refuses
If your host file sets `system.defaults.universalaccess.*`, `haus rebuild`
refuses to run from an AI agent session that lacks Full Disk Access — that write
would abort activation partway and skip every background service the rice
installs. Run the same command yourself instead. See [Changing your Mac with an
agent](/guides/ai-agent/#the-one-rebuild-it-will-refuse).
## Typical sessions
**Change a setting and apply it:**
```sh
haus edit # tweak your host file
haus rebuild
```
**Pull the latest rice:**
```sh
haus update # bumps the pin and rebuilds
```
**Recover from a bad change:**
```sh
haus rollback # back to the previous generation
haus doctor # if something still looks off
```
## The other CLIs
`haus` is the only command an end user needs. The rice and workshop ship three
more, each for a different job — see [the CLIs at a glance](/start/the-family/#the-clis-at-a-glance)
for the full map:
- **[`wt`](/guides/claude-agents/)** — agent worktrees (Claude Code, Codex, OpenCode) for any repo.
Also on your `PATH` (it ships in the rice), and useful to anyone who runs Claude
Code, contributor or not.
- **[`bench`](/internals/contributing/)** — the contributor CLI in the workshop
checkout: `try`, `ship`, `release` for moving changes between the family's repos.
- **`zscratch`** — feel-test a zellij edit without a rebuild (for rice
contributors); ships in the rice.
---
# Troubleshooting
Source: https://nebelhaus.com/reference/troubleshooting/
The handful of things that go sideways on macOS — a wedged agent, a missing grant, a stubborn cask — and the exact command to unstick each one.
Most nebelhaus trouble is one of a few known macOS quirks with a known fix. Work
top-down; `haus doctor` is a good first stop for any of them.
```sh
haus doctor # Nix, the Xcode CLT, the GUI agents, and cask drift, in one shot
```
## ⌘Space / Pounce does nothing
The launcher is a launchd **user agent**. If it isn't answering:
```sh
# Is it running?
launchctl list | grep pounce
# Bounce it (the clean recover for any wedged user agent):
launchctl bootout gui/$(id -u)/org.nixos.pounce 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.nixos.pounce.plist
```
If it opens but **clipboard/emoji paste** don't work, it's missing the
Accessibility grant:
```sh
pounce --request-accessibility # approve the system dialog
pounce --check-accessibility # prints true when granted
```
Set `nebelhaus.pounce.signingIdentity` so this grant survives rebuilds — see
[Pounce config](/reference/pounce/). If ⌘Space still opens **Spotlight**, log
out and back in once so the symbolic-hotkey reassignment takes.
**Palette opens, but slowly?** An external hotkey tool — skhd, AeroSpace, Raycast —
bound to the same key grabs it with an event tap *ahead* of pounce, so every summon
spawns a fresh client instead of hitting the daemon's fast in-process path.
Registration still reports success and the palette still opens, which is what makes
this one so hard to spot. One command names the culprit:
```sh
pounce doctor
```
It combines the daemon's live hotkey self-report with outside-in probes — a macOS
system-shortcut clash, running external hotkey daemons, and their configs scanned
for a binding on pounce's key — and tells you what to unbind.
## Windows won't tile / AeroSpace isn't running
This is almost always the **cold-boot GUI race**: an agent launched before the
desktop session was ready parked itself (exit 78) instead of starting. The rice
guards against it, but if you land in this state, bounce the agent:
```sh
launchctl bootout gui/$(id -u)/org.nixos.aerospace 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.nixos.aerospace.plist
```
Other quick fixes:
- **Windows scrambled after sleep?** They self-heal after a couple seconds; force
it by tapping ⇪, then `Backtick`.
- **A binding you changed didn't take?** A rebuild rewrites the config but the
live daemon caches the old one — normally auto-reloaded, but you can force it:
`aerospace reload-config`.
- **A window landed on the wrong workspace?** Tap ⇪, then `Backtick` to re-sort
everything to its roster home.
## The bar (SketchyBar) is blank or missing
```sh
launchctl bootout gui/$(id -u)/org.nixos.sketchybar 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.nixos.sketchybar.plist
# or, if it's running but stale:
sketchybar --reload
```
If you'd rather not run a custom bar at all, set `nebelhaus.sill.enable = false`
and the native macOS menu bar comes back.
## After a macOS upgrade, *all* my agents are dead (macOS 26 Tahoe+)
If the bar, tiling, and ⌘Space all come up dead at once right after upgrading to
**macOS 26 Tahoe or later**, the culprit is usually **Background Task Management
(BTM)**. Tahoe gates login items whose executable isn't Apple-signed, and every
nix agent launches through `/bin/sh -c "…"` — which BTM files under "unidentified
developer" and can silently refuse to start. The agents register fine; they just
never run. `haus doctor` flags this on Tahoe+; confirm and get the fix with:
```sh
haus btm # no-op before Tahoe; on Tahoe+ it reads the BTM store and instructs
```
There's **no declarative fix** — the toggle lives in macOS's BTM store, which has
no CLI to set it (`sfltool` can only dump it). So it's a one-time manual step:
1. **System Settings → General → Login Items & Extensions**
2. Scroll to **"Allow in the Background"**
3. Find the entries named **`sh`** — subtitle *"Item from unidentified developer"*
4. Toggle them **on**, then reboot
*(already on but still blocked? flip off then on to force a database write)*
Inspect the store by hand any time with:
```sh
sudo sfltool dumpbtm | grep -B2 -A8 -iE "nixos|darwin-store" # look for "disallowed"
```
## Touch ID for sudo beachballs (inside tmux/zellij)
If the Touch ID prompt hangs when you `sudo` inside a multiplexer, the
`pam_reattach` shim isn't loading. It ships with the **collar** room and is on by
default; confirm the order in `/etc/pam.d/sudo_local` (reattach must come *before*
the Touch ID line). Outside a multiplexer, Touch ID should just work; cancel the
prompt to fall back to your password.
## "Refusing to load cask … from an untrusted tap"
Third-party taps fail Homebrew's trust check under sudo-driven activation. The
rice disables that requirement globally (`HOMEBREW_NO_REQUIRE_TAP_TRUST=1` in
`/etc/homebrew/brew.env`). If you see this, that file didn't land — re-run
`haus rebuild`, or set the variable in your shell for the one-off.
## An app I removed from my config is still installed
By design. `nebelhaus.homebrew.cleanup` defaults to `"none"`, so nothing you
installed is ever auto-deleted, and **`haus rollback` doesn't rewind Homebrew
apps** (they're not in Nix generations). Remove one by hand:
```sh
brew uninstall --zap
```
Want a fully declarative machine where undeclared casks get removed on rebuild?
Set `nebelhaus.homebrew.cleanup = "zap";` — see
[Making it yours](/guides/making-it-yours/#homebrew-behaviour).
## A rebuild broke something — get back fast
```sh
haus rollback # atomically return to the previous generation
haus generations # see what you can roll back to
```
`haus rebuild` always builds *before* switching, so a config with an error can't
activate — you just see the build fail. For macOS **settings** (not packages),
the APFS/Time-Machine snapshot the installer took is the coarser rewind.
Want to go further than one generation — disable a room, or remove nebelhaus
entirely? [Leaving nebelhaus](/guides/leaving/) walks each exit, smallest first.
## The installer refuses to run — "expects Determinate Nix"
nebelhaus is built on [Determinate Nix](https://docs.determinate.systems/) and
won't install on top of a stock/single-user Nix rather than risk breaking it. If
you have an existing Nix you don't need, uninstall it and re-run the one-liner;
if you want to keep it, migrating to Determinate is the supported path. Fresh
Macs need none of this — the bootstrap installs Determinate for you.
## Still stuck?
- [Keybindings cheatsheet](/reference/keybindings/) — or tap `⇪` then `/` for the
live, roster-aware version.
- [nebelhaus.* options](/reference/options/) — every knob and its default.
- [Open an issue](https://github.com/nebelhaus/nebelhaus/issues) with the output
of `haus doctor`.
---
# How the flakes fit together
Source: https://nebelhaus.com/internals/flakes/
The consumer config, the mkNebelhaus builder, and why pinned flake inputs mean nothing changes until you say so.
nebelhaus is built on [Nix flakes](https://nixos.org/). You don't need to
understand Nix deeply to use the rice, but knowing how the pieces fit makes the
`update` / `rebuild` model click — and it's essential if you want to
[contribute](/internals/contributing/).
## Your config is a thin consumer
The installer scaffolds `~/.config/nix` as a **thin consumer** of the rice. Its
`flake.nix` imports nebelhaus as an input and calls the `mkNebelhaus` builder:
```nix
{
inputs.nebelhaus.url = "github:nebelhaus/nebelhaus";
outputs = { nebelhaus, ... }: {
darwinConfigurations. = nebelhaus.mkNebelhaus {
username = "";
hostname = "";
host = ./hosts/;
};
};
}
```
All your personal choices live in `hosts//default.nix` — your
[options](/reference/options/), your app roster, your casks. The rice stays
upstream; you never edit it directly.
## The mkNebelhaus builder
`mkNebelhaus` is the rice's entry point. Given a username, hostname, and host
file, it returns a full `darwin-darwinSystem` with every room wired up:
```nix
nebelhaus.mkNebelhaus {
username = "ada";
hostname = "fog";
host = ./hosts/fog; # your identity + choices
system = "aarch64-darwin"; # Apple Silicon only
extraModules = [ ]; # extra nix-darwin modules, optional
}
```
It composes the rooms — `den`, `theme`, `hearth`, `prowl`, `sill`, `collar`,
`pounce`, `trill`, `perch`, `hush`, `secrets` — and layers in home-manager, the
[nebelung](/reference/palette/) theme, and the [pounce](/guides/pounce/) app.
Most rooms are also exported individually under `darwinModules` (`den`,
`hearth`, `prowl`, `sill`, `collar`, `pounce`, `hush`, `secrets`, and `default`
for all of them) if you'd rather cherry-pick; `theme`, `trill`, and `perch` ride
along only with the full house.
## The inputs
The rice flake pulls together:
- `nixpkgs` (unstable) — the package set
- `nix-darwin` — macOS system modules
- `home-manager` — your user environment
- `catppuccin` — the theme framework
- `nebelung` — the silver-mist palette (a family input)
- `pounce` — the launcher app (a family input)
- `trill` — the Messages client (a family input; tracks trill *releases*, not `main`)
- `perch` — the notch file shelf (a family input; tracks perch *releases*, not `main`)
- `nix-index-database` — for `nix-index` + `comma`
## Why pins mean "nothing changes until you say so"
Each input is recorded in `flake.lock` as an **exact commit hash**, not "latest".
That's what makes a rebuild reproducible — the same lock produces the same system
on any machine, any day.
The flip side, and the thing that surprises everyone: pushing a change to
nebelung or the rice changes **nothing** on your machine until your `flake.lock`
is bumped to point at the new commit. A colour change therefore ripples like
this across the family:

**haus update does the ripple for you**
As an end user you never walk this by hand — `haus update` bumps your pin to the
latest nebelhaus (which already has its own inputs pinned) and rebuilds. Family
*contributors* use the workshop's `bench ship` to ripple a change all the way
down the chain; see [Contributing](/internals/contributing/).
## Cold-boot safety
One subtlety worth knowing if you ever debug a login issue: Determinate Nix
lives on a separate APFS volume, and macOS sometimes tries to launch the rice's
GUI agents (AeroSpace, SketchyBar, Pounce) before that volume mounts or before
the GUI event manager is ready. The rice wraps each agent in a small
`gui-wait` shim that polls for Dock/Finder/SystemUIServer readiness before
exec'ing — which is why your bar and tiling reliably come back after a reboot.
---
# Contributing & worktrees
Source: https://nebelhaus.com/internals/contributing/
How the family repos fit together, the workshop bench CLI, and the hack → test → ship → release flow.
Want to hack on nebelhaus itself — the rice, the launcher, or the palette? This
page covers how the family is developed. (To just *use* nebelhaus, you never need
any of this — see [Keeping in sync](/guides/staying-in-sync/).)
## The workshop
The [workshop repo](https://github.com/nebelhaus/workshop) is a parent directory
that holds every family repo checked out side by side, plus a `bench` dev CLI (a
different tool from the [end-user `haus`](/reference/haus/)). Clone it and pull
the family:
```sh
git clone https://github.com/nebelhaus/workshop.git
cd workshop
./bench clone
```
You end up with `nebelung/`, `pounce/`, `nebelhaus/`, and friends as independent
git repos, with the `bench` script sitting above them.
## Where a change goes
Each change belongs to exactly one repo. Start in the right one:
| Change | Repo |
|---|---|
| colours / palette / how a tool is themed | `nebelung` |
| the launcher app or a generic command | `pounce` |
| the Messages client (trill) | `trill` |
| the notch file shelf (perch) | `perch` |
| macOS defaults, tiling, bar, shell, Touch ID | `nebelhaus` |
| your own machine's apps / identity / secrets | `~/.config/nix` |
## The dev loop: try without pushing
The important trick is that you never have to push to *see* a change. The
workshop `bench try` builds your real machine config against the **local
checkouts** — uncommitted edits and all — using Nix `--override-input`:
```sh
# edit anything in nebelung/, pounce/, nebelhaus/…
./bench try # does it build? (nothing pushed, nothing activated)
./bench try switch # run it on this Mac (still nothing pushed)
```
When you're happy, commit in the repos you touched and let `bench ship` ripple
the change downstream — pushing each repo and bumping every `flake.lock` in
dependency order, so a colour change in nebelung reaches your Mac without you
hand-walking the [pin chain](/internals/flakes/#why-pins-mean-nothing-changes-until-you-say-so):
```sh
./bench ship
```
`./bench status` shows every repo's git state and every stale lock edge at a
glance.
## Parallel agents & worktrees
nebelhaus is built to be hacked on by several [Claude Code](https://claude.com/claude-code)
agents at once. Each agent runs in its own **git worktree** — an isolated checkout
on a `worktree-` branch, living outside the repo — so agents never yank the
branch out from under each other. That whole mechanism (spawning with `Super c`,
never losing work when a pane closes, resuming a parked session, the statusline
HUD) is the rice's [`wt` tool](/guides/claude-agents/), and it works for any repo.
Two things matter specifically when the repo is a *family* one:
- **`bench try` is worktree-aware.** Run it from inside a worktree and that repo's
flake override points at *your branch* instead of the main checkout — so an
agent can prove its branch builds before anyone merges it. (`bench try switch`
refuses from a worktree: only the main checkouts get activated.)
- **Land work through a PR — never a push or local merge into `main`.** Parallel
agents pushing straight to `main` clobber each other; a PR is conflict-detected
and atomic. Push the branch, open a PR with a **What / Why / Verify / Watch-out**
body, and merge it from GitHub. `bench status` lists every agent worktree and
unmerged `worktree-*` branch so nothing rots silently.
Working from the workshop on a task that belongs to a *sub*-repo? Resolve the
workshop's main checkout from Git, then make the child worktree — this works no
matter where you cloned the workshop:
```sh
workshop_root="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"
cd "$(wt child "$workshop_root/nebelung")" # replace nebelung with the repo you need
```
That registers the child so its PR shows in the statusline, which a raw
`git worktree add` would not.
## Feel-testing the whole PR queue (try-batch)
Activating a Mac is **serial** — one `darwin-rebuild switch` is one machine state
— so with a stack of PRs waiting you can only feel-test one at a time. The
tempting shortcut is to merge them all to `main` first, then rebuild and tick them
off — but now unverified code is on `main` before you've felt it.
`bench try-batch` inverts that. For each repo it builds a throwaway integration
tree (the committed `main` plus every open PR merged in), overrides the flake at
those trees, and builds — or activates — the whole queue in **one** rebuild,
`main` untouched:
```sh
./bench try-batch # build every open PR together; prints a tick-off checklist
./bench try-batch switch # …and activate the combined tree on this Mac
```
Verify each PR (its body carries the Verify steps), then merge only the ones that
pass and leave the rest open. This is test-then-merge, not merge-then-test.
## Feel-testing a zellij edit (zscratch)
One rice change escapes the ripple entirely. A zellij edit — a keybind, a theme
colour, a freshly-built plugin `.wasm` — doesn't need `bench try switch` at all:
that restarts the `main` zellij session and nukes every open tab. The rice ships
**`zscratch`** for exactly this. It renders your candidate over a copy of your
live `~/.config/zellij` into a temp config dir and boots a throwaway session in
its **own Ghostty window**, so your working multiplexer is untouched:
```sh
zscratch --config config.kdl # try a candidate config.kdl
zscratch --layout my-layout.kdl # …or a layout
zscratch --theme nebelung.kdl # …or a theme
zscratch --plugin tab-bar=tab-bar.wasm # …or swap a freshly-built plugin
zscratch clean # kill the scratch session + temp dir
```
Feel it there; the real `bench try switch` happens once, at the end, already
knowing it works. After that activation, press `Super R` (or run `zreload`) to
gracefully quit/reopen Ghostty and load the installed zellij config and plugin
wasm through a fresh server without rebuilding the workspace by hand. The
destructive half runs in a launchd agent outside the terminal, so it survives
both processes going away. Tabs, panes and working directories return; live
Claude Code panes resume their exact conversations. Pane identity comes from a
generation-scoped manifest rather than zellij's foreground-command label (which
can temporarily be an LSP or tool); an unclassified command pane aborts before
the launchd handoff.
## The whole life of a change
```
hack ──► test ──► PR ──► batch-test ──► merge ──► ship ──► release
```
1. **hack** — edit in place, or let `Super c` agents draft on `worktree-*`
branches in parallel; the main checkouts never move.
2. **test** — `./bench try` builds your real machine against the local checkouts
(from inside a worktree, against *that* branch). `./bench try switch` activates
it — main checkouts only.
3. **PR** — push the branch and open a PR against `main`; never push to or
`git merge` into `main` directly.
4. **batch-test** — `./bench try-batch` feels the whole open-PR queue in one
rebuild, `main` untouched — so you verify *before* landing anything.
5. **merge** — merge the PRs that passed (on GitHub, or `gh pr merge`); leave the
rest open.
6. **ship** — `./bench ship` pushes upstream→downstream, rippling every
`flake.lock` in dependency order.
7. **release** — `./bench release `; CI does the rest. Four repos are
releasable: **pounce** (GitHub release + Homebrew formula bump), **trill** and
**perch** (Homebrew cask bump + the rice's flake pin), and **nebelhaus** (the
tag becomes what `nebelhaus.com/init.sh` serves to new installs).
**Releases ride tags**
Versions are date-based (CalVer). `bench release ` stamps today's date —
`YYYY.MM.DD`, or `YYYY.MM.DD-N` on a same-day repeat — into the repo's version
source, commits it, and tags `v`. CI does the rest — the GitHub release
and the `homebrew-tap` bump. No number is typed by hand, and you never hand-edit
the formula's url/sha. And because the installer serves the latest **rice**
release, user-visible rice changes aren't
really "out" until nebelhaus gets a new tag.
Each repo carries its own `CLAUDE.md` with the deep rules for that boundary —
start there when working inside one.