Hermes Desktop installed under Install > AI kept its own palette while every other agent app retinted with the theme. Hermes' skin is its one theme unit for the desktop app, the TUI and the CLI, and its gateway watches the active skin file and broadcasts changes to every surface, so Omarchy publishes a skin named omarchy from a template on every theme switch and nothing Omarchy-specific goes upstream.
Activation goes through hermes config set, which writes the active profile's config and touches the skin so a running gateway repaints at once, and it only replaces Hermes' default skin so a choice made in Hermes stays. A theme switch runs that activation too when the desktop package is present and Hermes is still on its default, so a hand-over the installer missed is finished by the next switch; once the config names the skin a switch never starts Hermes. The desktop adopts a skin from a change broadcast rather than from the config it finds at connect time, and its first launch builds the runtime over minutes, so the installer starts --wait as a transient user unit that outlives the install terminal, activates once the runtime marker appears, republishes after the gateway is up, and reports to the journal. A migration hands the skin to existing Hermes Desktop installs through --activate, which also renders the skin for a theme applied before the template existed.
The generated file is validated before it is published, because Hermes parses it as YAML: only the name, a plain description and #rrggbb colours pass, so an unresolved palette key or a cloned theme's own hermes.yaml leaves the previous skin in place.
🤖 Generated by Fable 5.1 in Claude Code. Reviewed by Fable 5.1 code-review at high.
229 lines
8.6 KiB
Bash
Executable File
229 lines
8.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
# A theme installed with `omarchy theme install` is a stranger's git repo, so
|
|
# omarchy-theme-set drops the files that would run its code -- Lua, terminal
|
|
# configs, vscode.json -- and keeps the colour. A theme the user wrote themselves
|
|
# is not filtered at all.
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
test_tmp=$(mktemp -d)
|
|
trap 'rm -rf "$test_tmp"' EXIT
|
|
|
|
home="$test_tmp/home"
|
|
state="$home/.local/state/omarchy/current"
|
|
themes="$home/.config/omarchy/themes"
|
|
mkdir -p "$state" "$themes"
|
|
|
|
marker="omarchy-theme-staging-marker"
|
|
|
|
set_theme() {
|
|
HOME="$home" OMARCHY_PATH="$ROOT" PATH="$ROOT/bin:$PATH" \
|
|
OMARCHY_THEME_HEADLESS=1 OMARCHY_THEME_SKIP_BACKGROUND=1 \
|
|
XDG_RUNTIME_DIR="$test_tmp" \
|
|
bash "$ROOT/bin/omarchy-theme-set" "$1" 2>"$test_tmp/stderr" || return $?
|
|
}
|
|
|
|
staged() {
|
|
printf '%s' "$state/theme/$1"
|
|
}
|
|
|
|
assert_staged() {
|
|
[[ -f $(staged "$1") ]] || fail "$2"
|
|
}
|
|
|
|
assert_not_staged() {
|
|
[[ ! -e $(staged "$1") ]] || fail "$2"
|
|
}
|
|
|
|
assert_no_marker() {
|
|
! grep -q "$marker" "$(staged "$1")" || fail "$2"
|
|
}
|
|
|
|
write_colors() {
|
|
cat >"$1" <<TOML
|
|
mode = "light"
|
|
|
|
accent = "#7aa2f7"
|
|
selection = "#292e42"
|
|
muted = "#414868"
|
|
|
|
background = "#1a1b26"
|
|
foreground = "#a9b1d6"
|
|
|
|
color0 = "#1a1b26"
|
|
color1 = "#f7768e"
|
|
color2 = "#9ece6a"
|
|
color3 = "#e0af68"
|
|
color4 = "#7aa2f7"
|
|
color5 = "#bb9af7"
|
|
color6 = "#7dcfff"
|
|
color7 = "#a9b1d6"
|
|
TOML
|
|
}
|
|
|
|
# A theme that ships everything it is not allowed to ship.
|
|
hostile="$themes/hostile"
|
|
mkdir -p "$hostile/backgrounds" "$hostile/.git"
|
|
write_colors "$hostile/colors.toml"
|
|
touch "$hostile/light.mode"
|
|
printf 'os.execute("%s")\n' "$marker" >"$hostile/hyprland.lua"
|
|
printf 'vim.cmd("%s")\n' "$marker" >"$hostile/neovim.lua"
|
|
printf 'shell %s\n' "$marker" >"$hostile/kitty.conf"
|
|
printf '[terminal.shell]\nprogram = "%s"\n' "$marker" >"$hostile/alacritty.toml"
|
|
printf 'shell = "%s"\n' "$marker" >"$hostile/foot.ini"
|
|
printf 'command = "%s"\n' "$marker" >"$hostile/ghostty.conf"
|
|
printf 'hl.env("GUM_INPUT_PROMPT", "%s")\n' "$marker" >"$hostile/gum_env.lua"
|
|
printf '[bar]\nbackground = "#%s"\n' "000000" >"$hostile/shell.toml"
|
|
printf '{}\n' >"$hostile/vscode.json"
|
|
printf 'Yaru-red\n' >"$hostile/icons.theme"
|
|
printf 'theme[main_bg]="#000000"\n' >"$hostile/btop.theme"
|
|
printf 'png\n' >"$hostile/preview.png"
|
|
printf 'png\n' >"$hostile/backgrounds/1-real.png"
|
|
printf '%s\n' "$marker" >"$hostile/backgrounds/payload.sh"
|
|
printf '# notes\n' >"$hostile/README.md"
|
|
ln -s /etc/hostname "$hostile/unlock.png"
|
|
|
|
set_theme hostile || fail "omarchy-theme-set applies a theme that ships disallowed files"
|
|
|
|
assert_staged colors.toml "the theme's colors.toml is staged"
|
|
grep -q '#7aa2f7' "$(staged colors.toml)" || fail "the staged colors.toml is the theme's palette"
|
|
assert_staged light.mode "the light mode marker is staged"
|
|
assert_staged preview.png "the theme's preview image is staged"
|
|
assert_staged backgrounds/1-real.png "an image in backgrounds/ is staged"
|
|
|
|
assert_not_staged unlock.png "a symlink is not followed out of the theme"
|
|
assert_not_staged vscode.json "vscode.json names an extension to install and is not staged"
|
|
|
|
assert_staged icons.theme "the theme's icon set name is staged"
|
|
grep -q 'Yaru-red' "$(staged icons.theme)" || fail "the staged icons.theme is the theme's"
|
|
assert_staged btop.theme "the theme's btop colours are staged"
|
|
grep -q 'main_bg' "$(staged btop.theme)" || fail "the staged btop.theme is the theme's"
|
|
assert_not_staged .git "the clone's own git directory is never staged"
|
|
|
|
# These run code, so the theme's versions must lose to Omarchy's generated ones
|
|
# rather than merely be absent.
|
|
for generated in hyprland.lua neovim.lua gum_env.lua kitty.conf alacritty.toml foot.ini ghostty.conf; do
|
|
assert_staged "$generated" "$generated is generated from Omarchy's template"
|
|
assert_no_marker "$generated" "an installed theme cannot supply $generated"
|
|
done
|
|
|
|
# Colour is kept, including a file Omarchy would otherwise have generated.
|
|
assert_staged shell.toml "shell.toml is staged"
|
|
grep -q '000000' "$(staged shell.toml)" || fail "an installed theme's shell.toml colours are kept"
|
|
|
|
grep -q 'hyprland.lua' "$test_tmp/stderr" || fail "omarchy-theme-set names the files it ignored"
|
|
! grep -q 'README.md' "$test_tmp/stderr" || fail "omarchy-theme-set does not report a theme's documentation"
|
|
|
|
pass "an installed theme keeps its colour and loses everything that runs code"
|
|
|
|
# icons.theme is staged verbatim and handed to gsettings, so a symlinked one
|
|
# would stage a copy of whatever it points at.
|
|
linked="$themes/linked"
|
|
mkdir -p "$linked/.git"
|
|
write_colors "$linked/colors.toml"
|
|
ln -s /etc/hostname "$linked/icons.theme"
|
|
|
|
set_theme linked || fail "omarchy-theme-set applies a theme whose icons.theme is a symlink"
|
|
assert_not_staged icons.theme "a symlinked icons.theme is not followed"
|
|
|
|
pass "a symlinked icon set name is refused like any other symlink"
|
|
|
|
# A theme predating colors.toml still gets a palette, without its alacritty.toml
|
|
# reaching the staged theme.
|
|
legacy="$themes/legacy"
|
|
mkdir -p "$legacy/.git"
|
|
cat >"$legacy/alacritty.toml" <<TOML
|
|
[terminal.shell]
|
|
program = "$marker"
|
|
|
|
[colors.primary]
|
|
background = "#102030"
|
|
foreground = "#a0b0c0"
|
|
|
|
[colors.normal]
|
|
black = "#102030"
|
|
red = "#ff0000"
|
|
green = "#00ff00"
|
|
yellow = "#ffff00"
|
|
blue = "#0000ff"
|
|
magenta = "#ff00ff"
|
|
cyan = "#00ffff"
|
|
white = "#a0b0c0"
|
|
TOML
|
|
|
|
set_theme legacy || fail "omarchy-theme-set applies a theme that only ships alacritty.toml"
|
|
assert_staged colors.toml "a legacy theme's palette is recovered from its alacritty.toml"
|
|
grep -q '#102030' "$(staged colors.toml)" || fail "the recovered palette is the theme's"
|
|
assert_no_marker alacritty.toml "a legacy theme's alacritty.toml is not staged"
|
|
|
|
pass "a theme older than colors.toml keeps its palette and loses its terminal config"
|
|
|
|
# An overlay on a stock theme still repaints it, and still cannot add code.
|
|
mkdir -p "$themes/tokyo-night/.git"
|
|
write_colors "$themes/tokyo-night/colors.toml"
|
|
sed -i 's/#7aa2f7/#abcdef/' "$themes/tokyo-night/colors.toml"
|
|
printf 'os.execute("%s")\n' "$marker" >"$themes/tokyo-night/hyprland.lua"
|
|
|
|
set_theme "Tokyo Night" || fail "omarchy-theme-set applies a stock theme with a user overlay"
|
|
grep -q '#abcdef' "$(staged colors.toml)" || fail "a user overlay still replaces the stock palette"
|
|
assert_no_marker hyprland.lua "a user overlay cannot add Lua to a stock theme"
|
|
|
|
pass "an overlay on a stock theme repaints it without adding code"
|
|
|
|
# A theme the user wrote themselves has no git repo behind it and is theirs.
|
|
mine="$themes/mine"
|
|
mkdir -p "$mine"
|
|
write_colors "$mine/colors.toml"
|
|
printf 'os.execute("%s")\n' "$marker" >"$mine/hyprland.lua"
|
|
printf '{"name":"Mine","extension":"pub.ext"}\n' >"$mine/vscode.json"
|
|
|
|
set_theme mine || fail "omarchy-theme-set applies a theme the user wrote"
|
|
grep -q "$marker" "$(staged hyprland.lua)" || fail "a theme the user wrote keeps its own hyprland.lua"
|
|
assert_staged vscode.json "a theme the user wrote keeps every file it ships"
|
|
[[ ! -s $test_tmp/stderr ]] || fail "a theme the user wrote reports nothing ignored" "$(cat "$test_tmp/stderr")"
|
|
|
|
pass "a theme the user wrote is not held to the installed-theme list"
|
|
|
|
# A working copy symlinked into the themes folder is the user's own too.
|
|
ln -s "$mine" "$themes/mine-link"
|
|
set_theme mine-link || fail "omarchy-theme-set applies a symlinked working copy"
|
|
grep -q "$marker" "$(staged hyprland.lua)" || fail "a symlinked working copy is the user's own"
|
|
|
|
pass "a symlinked working copy is the user's own"
|
|
|
|
# The name is joined into paths that get removed and copied into.
|
|
for name in .. . "../../evil"; do
|
|
if set_theme "$name" >/dev/null; then
|
|
fail "omarchy-theme-set rejects the theme name '$name'"
|
|
fi
|
|
done
|
|
|
|
pass "a theme name cannot climb out of the theme directories"
|
|
|
|
# A denylist is only correct while someone adding a template classifies what it
|
|
# generates. Every generated theme file is either denied to an installed theme or
|
|
# recorded here as carrying colour, so a new template fails until it is placed.
|
|
denied=(alacritty.toml foot.ini ghostty.conf kitty.conf gum_env.lua hyprland.lua neovim.lua vscode.json)
|
|
colour_only=(btop.theme chromium.theme claude.json helix.toml hermes.yaml hyprland-preview-share-picker.css keyboard.rgb obsidian.css pi.json shell.toml vscode-theme.json)
|
|
|
|
for tpl in "$ROOT"/default/themed/*.tpl; do
|
|
generated=$(basename "$tpl" .tpl)
|
|
classified=""
|
|
|
|
for name in "${denied[@]}" "${colour_only[@]}"; do
|
|
if [[ $generated == "$name" ]]; then
|
|
classified=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
[[ -n $classified ]] ||
|
|
fail "every generated theme file is classified as code or colour" \
|
|
"$generated has a template but is in neither list in $(basename "$0"); decide whether an installed theme may ship it"
|
|
done
|
|
|
|
pass "every file Omarchy generates is classified as code or colour"
|