Add a theme pack
One command themes everything: bin/theme <name> renders the pack into
~/.local/state/theme/current/theme/ and nudges every consumer. A pack is
themes/<name>/ and needs only:
colors.toml — the contract. Either style:
semantic (mode, accent, background, foreground, muted, red,
bright_red, dark_background, ...) or ANSI (accent, background,
foreground, color0-color15). bin/theme-color -f colors.toml --all
shows the full set after aliasing and derivation; anything missing is
mixed from what is there.
backgrounds/ — images. theme <name> [N] picks the Nth (sorted by
name), theme bg next cycles. Name them <index>-<description>.<ext>;
the fzf picker previews the filename.
theme.conf — TAGLINE="one line of whimsy" (printed on switch, shown
in the tmux cheat sheet). Optional but every pack has one.
icons.theme — optional, a GTK icon theme name (Yaru-* is common);
applied on Linux only if that icon set is installed.
Every app file (ghostty, kitty, wezterm, tmux, nvim, btop, starship, pi,
claude, hyprland borders, hyprlock, wofi, sketchybar, borders, slack) is
rendered from themes/templates/*.tpl. A pack may ship a file under the
same name to hand-tune one app (hyprland.lua for a gradient border,
btop.theme, ...); it wins over the template. Do NOT hand-author the rest.
Work directly in the repo (subagent worktrees don't see uncommitted
theme files and have flipped the user's live theme by running theme from
the wrong tree — do NOT run theme for validation until told, and never
from a worktree).
Sources
- An existing theme repo (any
themes/<name>/ laid out as above):
copy colors.toml, backgrounds/,
icons.theme. Skip neovim.lua, vscode.json, preview*.png,
unlock.png, shell*.toml, keyboard.rgb, chromium.theme. Keep a
shipped btop.theme; keep a shipped hyprland.lua only if it is plain
hl.config (the o.window(...) helper style some repos use does not
exist here). An
older theme with only alacritty.toml and no colors.toml: transcribe
its [colors.*] into ANSI-style colors.toml.
- Just a wallpaper: first run
bin/theme adopt <image>. It compares the
extracted palette with every existing pack and, when the closest score is
60 or lower, copies the image into that pack's backgrounds/ with the next
numeric prefix. A weaker match changes nothing and prints the three closest
packs. Then use bin/theme from-image <name> <image> to create a new pack;
it writes colors.toml + backgrounds/ + an empty theme.conf. Both
commands use heuristic hue mapping, so eyeball the result.
Then write theme.conf with a TAGLINE in the house style (see
themes/*/theme.conf: short, lower-case, a little wry).
Validate (no theme run)
bin/theme-color -f themes/<name>/colors.toml --all # no error, sane mode
bin/theme-color -f themes/<name>/colors.toml --name <name> --render /tmp/x themes/templates/*.tpl
jq empty /tmp/x/*.json; bash -n /tmp/x/*.sh
ghostty +validate-config --config-file=/tmp/x/ghostty.conf
A rendered file containing {{ means a template names a key the resolver
does not produce; fix the template, not the pack.
Hand verification to the user or ask before switching
theme <name> [N], then: tmux show-option -gqv @thm_pink == accent;
cat ~/.local/state/theme/current/mode; jq -r .theme ~/.claude/settings.json
== custom:<name>. Linux: hyprctl getoption general:col.active_border,
gsettings get org.gnome.desktop.interface color-scheme. macOS: sketchybar
border colour, wallpaper after a few seconds. Pi applies on next launch.
Commit on the active theme branch and push.
Removing a theme
git rm -r themes/<name>/, then theme <other>. The rendered
~/.pi/agent/themes/<name>.json and ~/.claude/themes/<name>.json can go
too.
1---2name: add-theme3description: Add a theme pack or match a wallpaper to an existing theme in the dotfiles theme system (bin/theme). Use when the user pastes a theme repo URL or image, says "add this theme", "new theme pack", or wants another entry for theme next to cycle.4---56# Add a theme pack78One command themes everything: `bin/theme <name>` renders the pack into9`~/.local/state/theme/current/theme/` and nudges every consumer. A pack is10`themes/<name>/` and needs only:1112- `colors.toml` — the contract. Either style:13 semantic (`mode`, `accent`, `background`, `foreground`, `muted`, `red`,14 `bright_red`, `dark_background`, ...) or ANSI (`accent`, `background`,15 `foreground`, `color0`-`color15`). `bin/theme-color -f colors.toml --all`16 shows the full set after aliasing and derivation; anything missing is17 mixed from what is there.18- `backgrounds/` — images. `theme <name> [N]` picks the Nth (sorted by19 name), `theme bg next` cycles. Name them `<index>-<description>.<ext>`;20 the fzf picker previews the filename.21- `theme.conf` — `TAGLINE="one line of whimsy"` (printed on switch, shown22 in the tmux cheat sheet). Optional but every pack has one.23- `icons.theme` — optional, a GTK icon theme name (Yaru-* is common);24 applied on Linux only if that icon set is installed.2526Every app file (ghostty, kitty, wezterm, tmux, nvim, btop, starship, pi,27claude, hyprland borders, hyprlock, wofi, sketchybar, borders, slack) is28rendered from `themes/templates/*.tpl`. A pack may ship a file under the29same name to hand-tune one app (`hyprland.lua` for a gradient border,30`btop.theme`, ...); it wins over the template. Do NOT hand-author the rest.3132Work directly in the repo (subagent worktrees don't see uncommitted33theme files and have flipped the user's live theme by running theme from34the wrong tree — do NOT run theme for validation until told, and never35from a worktree).3637## Sources3839- **An existing theme repo** (any `themes/<name>/` laid out as above):40 copy `colors.toml`, `backgrounds/`,41 `icons.theme`. Skip `neovim.lua`, `vscode.json`, `preview*.png`,42 `unlock.png`, `shell*.toml`, `keyboard.rgb`, `chromium.theme`. Keep a43 shipped `btop.theme`; keep a shipped `hyprland.lua` only if it is plain44 `hl.config` (the `o.window(...)` helper style some repos use does not45 exist here). An46 older theme with only `alacritty.toml` and no `colors.toml`: transcribe47 its `[colors.*]` into ANSI-style `colors.toml`.48- **Just a wallpaper**: first run `bin/theme adopt <image>`. It compares the49 extracted palette with every existing pack and, when the closest score is50 60 or lower, copies the image into that pack's `backgrounds/` with the next51 numeric prefix. A weaker match changes nothing and prints the three closest52 packs. Then use `bin/theme from-image <name> <image>` to create a new pack;53 it writes `colors.toml` + `backgrounds/` + an empty `theme.conf`. Both54 commands use heuristic hue mapping, so eyeball the result.5556Then write `theme.conf` with a TAGLINE in the house style (see57`themes/*/theme.conf`: short, lower-case, a little wry).5859## Validate (no theme run)6061```62bin/theme-color -f themes/<name>/colors.toml --all # no error, sane mode63bin/theme-color -f themes/<name>/colors.toml --name <name> --render /tmp/x themes/templates/*.tpl64jq empty /tmp/x/*.json; bash -n /tmp/x/*.sh65ghostty +validate-config --config-file=/tmp/x/ghostty.conf66```67A rendered file containing `{{` means a template names a key the resolver68does not produce; fix the template, not the pack.6970## Hand verification to the user or ask before switching7172`theme <name> [N]`, then: `tmux show-option -gqv @thm_pink` == accent;73`cat ~/.local/state/theme/current/mode`; `jq -r .theme ~/.claude/settings.json`74== `custom:<name>`. Linux: `hyprctl getoption general:col.active_border`,75`gsettings get org.gnome.desktop.interface color-scheme`. macOS: sketchybar76border colour, wallpaper after a few seconds. Pi applies on next launch.7778## Commit on the active theme branch and push.7980## Removing a theme8182`git rm -r themes/<name>/`, then `theme <other>`. The rendered83`~/.pi/agent/themes/<name>.json` and `~/.claude/themes/<name>.json` can go84too.