#!/bin/bash set -euo pipefail ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) CLI="$ROOT/bin/omarchy" TMPDIRS=() export PATH="$ROOT/bin:$PATH" # Assigns a fresh temporary directory to the named variable and registers it for # removal on exit, so every scratch directory this suite makes is cleaned up. make_tmpdir() { local -n dir_ref="$1" dir_ref=$(mktemp -d) TMPDIRS+=("$dir_ref") } pass() { printf 'ok - %s\n' "$1" } fail() { printf 'not ok - %s\n' "$1" >&2 exit 1 } assert_output_contains() { local description="$1" local output="$2" local expected="$3" if [[ $output != *"$expected"* ]]; then printf 'Expected output to contain: %s\n' "$expected" >&2 printf 'Actual output:\n%s\n' "$output" >&2 fail "$description" fi pass "$description" } cleanup() { local dir for dir in ${TMPDIRS[@]+"${TMPDIRS[@]}"}; do [[ -d $dir ]] && rm -rf "$dir" done return 0 } trap cleanup EXIT output=$("$CLI" --help) assert_output_contains "main help renders" "$output" "Omarchy command center" assert_output_contains "main help includes hardware group" "$output" "hw" assert_output_contains "main help includes package group" "$output" "pkg" if grep -Eq '^ [a-z0-9-]+[[:space:]].*\([0-9]+\)$' <<<"$output"; then fail "main help does not show group counts" fi pass "main help does not show group counts" output=$("$CLI" commands) assert_output_contains "commands lists documented commands" "$output" "omarchy theme set " "$CLI" commands --json | jq -e '.ok == true and (.commands | length >= 200)' >/dev/null pass "commands --json is valid JSON with full bin coverage" "$CLI" commands --json | jq -e 'all(.commands[]; .summary != "undocumented")' >/dev/null pass "all included commands have summaries" "$CLI" commands --json | jq -e 'all(.commands[]; has("binary") and has("filename_route") and has("routes") and (has("legacy") | not) and (has("usage") | not) and (has("visibility") | not) and (has("mutates") | not) and (has("interactive") | not))' >/dev/null pass "JSON uses binary/routes and omits legacy/usage/extra metadata" "$CLI" commands --check >/dev/null pass "commands --check passes" "$CLI" commands --all >/dev/null pass "commands --all does not crash" "$CLI" commands --all --json | jq -e '.commands[] | select(.route == "omarchy hyprland window gaps toggle" and .summary != "undocumented")' >/dev/null pass "fallback commands are inferred and documented" "$CLI" commands --all --json | jq -e '.commands[] | select(.route == "omarchy dev benchmark cli")' >/dev/null pass "benchmark command is discoverable in all commands" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-pkg-add" and .route == "omarchy pkg add" and .filename_route == "omarchy pkg add" and (.routes | index("omarchy pkg add")))' >/dev/null pass "JSON exposes direct pkg add route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-install-gaming-xbox-cloud" and .route == "omarchy install gaming xbox-cloud" and .filename_route == "omarchy install gaming xbox cloud" and (.routes | index("omarchy install gaming xbox cloud")))' >/dev/null pass "Xbox Cloud installer keeps hyphenated canonical route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-install-gaming-xbox-controllers" and .route == "omarchy install gaming xbox-controllers" and .filename_route == "omarchy install gaming xbox controllers" and (.routes | index("omarchy install gaming xbox controllers")))' >/dev/null pass "Xbox controller installer keeps hyphenated canonical route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-remove-gaming-xbox-controllers" and .route == "omarchy remove gaming xbox-controllers" and .filename_route == "omarchy remove gaming xbox controllers" and (.routes | index("omarchy remove gaming xbox controllers")))' >/dev/null pass "Xbox controller remover keeps hyphenated canonical route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-install-gaming-geforce-now" and .route == "omarchy install gaming geforce-now" and .filename_route == "omarchy install gaming geforce now" and (.routes | index("omarchy install gaming geforce now")))' >/dev/null pass "GeForce NOW installer keeps hyphenated canonical route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-remove-gaming-geforce-now" and .route == "omarchy remove gaming geforce-now" and .filename_route == "omarchy remove gaming geforce now" and (.routes | index("omarchy remove gaming geforce now")))' >/dev/null pass "GeForce NOW remover keeps hyphenated canonical route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-install-gaming-gpu-lib32" and .route == "omarchy install gaming gpu-lib32" and .filename_route == "omarchy install gaming gpu lib32" and (.routes | index("omarchy install gaming gpu lib32")))' >/dev/null pass "GPU lib32 installer keeps hyphenated canonical route" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-refresh-pacman" and .requires_sudo == true)' >/dev/null pass "sudo metadata marks sudo commands" output=$("$CLI" theme --help) assert_output_contains "group help renders" "$output" "Theme commands" output=$("$CLI" install --help) assert_output_contains "install group help renders" "$output" "Install commands" assert_output_contains "install group includes browser route" "$output" "omarchy install browser" output=$("$CLI" install) assert_output_contains "bare group renders help instead of picker" "$output" "Install commands" assert_output_contains "bare group includes browser route" "$output" "omarchy install browser" output=$("$CLI" toggle) assert_output_contains "bare root command with children renders help" "$output" "Toggle commands" assert_output_contains "bare toggle help includes child route" "$output" "omarchy toggle nightlight" output=$("$CLI" pkg --help) assert_output_contains "package group includes pkg add fallback route" "$output" "omarchy pkg add " output=$("$CLI" restart --help) assert_output_contains "restart group includes inferred commands" "$output" "omarchy restart btop" assert_output_contains "restart group includes all restart commands" "$output" "omarchy restart wifi" output=$("$CLI" hw --help) assert_output_contains "hardware group help renders" "$output" "omarchy hw asus rog" assert_output_contains "hardware group includes touchpad" "$output" "omarchy hw touchpad" output=$("$CLI" hw asus) assert_output_contains "partial hardware prefix renders matching commands" "$output" "omarchy hw asus rog" assert_output_contains "partial hardware prefix includes nested match" "$output" "omarchy hw asus zenbook ux5406aa" output=$("$CLI" menu --help) assert_output_contains "menu group includes share fallback route" "$output" "omarchy menu share" output=$("$CLI" share) assert_output_contains "bare required-arg alias renders CLI help" "$output" "Usage:" assert_output_contains "bare share help uses canonical route" "$output" "omarchy share [path...]" output=$("$CLI" menu share) assert_output_contains "bare required-arg filename route renders CLI help" "$output" "omarchy share [path...]" CLI="$CLI" python3 <<'PY' import json import os import subprocess import sys cli = os.environ['CLI'] commands = json.loads(subprocess.check_output([cli, 'commands', '--json'], text=True))['commands'] by_group = {} for command in commands: binary = command['binary'] stem = binary.removeprefix('omarchy-') group = stem.split('-', 1)[0] filename_route = 'omarchy ' + stem.replace('-', ' ') by_group.setdefault(group, []).append((binary, filename_route, command['route'])) missing = [] for group, rows in sorted(by_group.items()): proc = subprocess.run([cli, group, '--help'], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = proc.stdout + proc.stderr if proc.returncode != 0: missing.append((group, '', f'exit {proc.returncode}')) continue for binary, filename_route, canonical_route in rows: if filename_route not in output and canonical_route not in output and binary not in output: missing.append((group, binary, filename_route)) if missing: for row in missing: print('\t'.join(row), file=sys.stderr) sys.exit(1) PY pass "every filename-derived group help represents its bins" output=$(timeout 5 "$CLI" theme set --help) assert_output_contains "command help renders without executing" "$output" "Binary:" assert_output_contains "theme set help names binary" "$output" "omarchy-theme-set" output=$(timeout 5 "$CLI" update --help) assert_output_contains "mutating command help does not execute target" "$output" "omarchy-update" if [[ $output == *"omarchy update perform"* ]]; then fail "hidden update perform compatibility wrapper is not shown" fi pass "hidden update perform compatibility wrapper is not shown" # These three are install-time plumbing the ISO and install leaves call, so # they stay out of the listing a user browses -- while still routing, because # those callers use the CLI. A GROUP_DESCRIPTIONS entry would put the group # back in the top-level listing even with every command in it hidden, which is # why apply has none. output=$(timeout 5 "$CLI") if [[ $output == *"apply"* ]]; then fail "a group whose commands are all hidden stays out of the top-level listing" fi pass "a group whose commands are all hidden stays out of the top-level listing" for internal in omarchy-apply-system omarchy-apply-hardware omarchy-apply-lock; do if "$CLI" commands --json | jq -e --arg binary "$internal" \ '.commands[] | select(.binary == $binary)' >/dev/null; then fail "$internal stays out of the default command listing" fi "$CLI" commands --all --json | jq -e --arg binary "$internal" \ '.commands[] | select(.binary == $binary) | select(.hidden)' >/dev/null || fail "$internal is still listed by commands --all" done pass "install-time setup plumbing is hidden from the setup wizards" output=$(timeout 5 "$CLI" apply hardware --help) assert_output_contains "hidden setup plumbing still routes" "$output" "omarchy-apply-hardware" output=$("$CLI" screenshot --help) assert_output_contains "root alias resolves to command help" "$output" "omarchy-capture-screenshot" "$CLI" commands --json | jq -e '.commands[] | select(.binary == "omarchy-capture-screenshot") | .aliases | index("omarchy screenshot")' >/dev/null pass "aliases are included in JSON metadata" output=$("$CLI" pkg add --help) assert_output_contains "pkg add help resolves" "$output" "omarchy-pkg-add" assert_output_contains "pkg add help shows direct route" "$output" "omarchy pkg add " output=$("$CLI" system reboot --help) assert_output_contains "system command help is safe" "$output" "omarchy-system-reboot" output=$("$CLI" dev benchmark cli --repeat=1) assert_output_contains "benchmark command runs" "$output" "Omarchy CLI benchmark" "$CLI" theme list >/dev/null pass "safe dispatch works for theme list" "$CLI" theme current >/dev/null pass "safe dispatch works for theme current" "$CLI" font list >/dev/null pass "safe dispatch works for font list" "$CLI" font current >/dev/null pass "safe dispatch works for font current" make_tmpdir PI_TMPDIR NEXT_THEME="$PI_TMPDIR/.local/state/omarchy/current/next-theme" CURRENT_THEME="$PI_TMPDIR/.local/state/omarchy/current/theme" USER_THEMED="$PI_TMPDIR/.config/omarchy/themed" mkdir -p "$NEXT_THEME" "$CURRENT_THEME" "$USER_THEMED" cat >"$NEXT_THEME/colors.toml" <<'EOF' mode = "light" accent = "#336699" hyprland_active_border = "rgba(010203ee) rgba(040506ee) 45deg" background = "#000000" dark_background = "#010101" darker_background = "#020202" lighter_background = "#030303" foreground = "#ffffff" dark_foreground = "#cccccc" light_foreground = "#dddddd" selection = "#808080" red = "#ff0000" green = "#00ff00" yellow = "#ffff00" blue = "#0000ff" magenta = "#ff00ff" cyan = "#00ffff" muted = "#111111" bright_red = "#ff1111" bright_green = "#11ff11" bright_yellow = "#ffff11" bright_blue = "#1111ff" bright_magenta = "#ff11ff" bright_cyan = "#11ffff" bright_foreground = "#eeeeee" # Canonical values must win when a transitional theme defines both forms. bg = "#abcdef" dark_bg = "#abcdef" darker_bg = "#abcdef" lighter_bg = "#abcdef" fg = "#abcdef" dark_fg = "#abcdef" light_fg = "#abcdef" bright_fg = "#abcdef" EOF cat >"$USER_THEMED/mix-test.txt.tpl" <<'EOF' mix={{ mix background foreground 50% }} strip={{ mix_strip background foreground 50% }} rgb={{ mix_rgb background foreground 50% }} EOF cat >"$USER_THEMED/gradient-test.txt.tpl" <<'EOF' hypr={{ hypr_gradient hyprland_active_border accent }} shell={{ gradient_start hyprland_active_border accent }} shell-gradient={{ shell_gradient hyprland_active_border accent }} fallback-hypr={{ hypr_gradient missing accent }} fallback-shell={{ gradient_start missing accent }} fallback-shell-gradient={{ shell_gradient missing accent }} EOF cat >"$USER_THEMED/alias-test.txt.tpl" <<'EOF' color={{ color1 }} strip={{ color1_strip }} rgb={{ color1_rgb }} magenta={{ magenta }} legacy-purple={{ purple }} bright-magenta={{ bright_magenta }} legacy-bright-purple={{ bright_purple }} background={{ background }} foreground={{ foreground }} legacy-background={{ bg }} legacy-dark-background={{ dark_bg }} legacy-darker-background={{ darker_bg }} legacy-lighter-background={{ lighter_bg }} legacy-foreground={{ fg }} legacy-dark-foreground={{ dark_fg }} legacy-light-foreground={{ light_fg }} legacy-bright-foreground={{ bright_fg }} legacy-mix={{ mix bg fg 50% }} selection-background={{ selection_background }} selection-foreground={{ selection_foreground }} cursor-alias={{ cursor }} EOF cat >"$NEXT_THEME/shell.lock.toml" <<'EOF' text = "#ffffff" placeholder = "#ffffff" border = "#ffffff" EOF OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-templates" grep -qx 'mix=#808080' "$NEXT_THEME/mix-test.txt" || fail "theme template mix helper works" grep -qx 'strip=808080' "$NEXT_THEME/mix-test.txt" || fail "theme template mix_strip helper works" grep -qx 'rgb=128,128,128' "$NEXT_THEME/mix-test.txt" || fail "theme template mix_rgb helper works" pass "theme template color mix helpers work" grep -qx 'color=#ff0000' "$NEXT_THEME/alias-test.txt" || fail "theme template semantic aliases expose legacy colorN" grep -qx 'strip=ff0000' "$NEXT_THEME/alias-test.txt" || fail "theme template legacy colorN_strip works" grep -qx 'rgb=255,0,0' "$NEXT_THEME/alias-test.txt" || fail "theme template legacy colorN_rgb works" grep -qx 'magenta=#ff00ff' "$NEXT_THEME/alias-test.txt" || fail "theme template magenta helper works" grep -qx 'legacy-purple=#ff00ff' "$NEXT_THEME/alias-test.txt" || fail "theme template purple alias works" grep -qx 'bright-magenta=#ff11ff' "$NEXT_THEME/alias-test.txt" || fail "theme template bright magenta helper works" grep -qx 'legacy-bright-purple=#ff11ff' "$NEXT_THEME/alias-test.txt" || fail "theme template bright purple alias works" grep -qx 'background=#000000' "$NEXT_THEME/alias-test.txt" || fail "theme template resolves background" grep -qx 'foreground=#ffffff' "$NEXT_THEME/alias-test.txt" || fail "theme template resolves foreground" grep -qx 'legacy-background=#000000' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy bg alias" grep -qx 'legacy-dark-background=#010101' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy dark_bg alias" grep -qx 'legacy-darker-background=#020202' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy darker_bg alias" grep -qx 'legacy-lighter-background=#030303' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy lighter_bg alias" grep -qx 'legacy-foreground=#ffffff' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy fg alias" grep -qx 'legacy-dark-foreground=#cccccc' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy dark_fg alias" grep -qx 'legacy-light-foreground=#dddddd' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy light_fg alias" grep -qx 'legacy-bright-foreground=#eeeeee' "$NEXT_THEME/alias-test.txt" || fail "theme template exposes legacy bright_fg alias" grep -qx 'legacy-mix=#808080' "$NEXT_THEME/alias-test.txt" || fail "theme template mix helper accepts legacy palette aliases" grep -qx 'selection-background=#808080' "$NEXT_THEME/alias-test.txt" || fail "theme template derives selection background from selection" grep -qx 'selection-foreground=#eeeeee' "$NEXT_THEME/alias-test.txt" || fail "theme template derives selection foreground from bright_foreground" grep -qx 'cursor-alias=#eeeeee' "$NEXT_THEME/alias-test.txt" || fail "theme template derives cursor alias from bright_foreground" pass "theme template semantic aliases expose legacy palette helpers" LEGACY_COLORS_FILE="$PI_TMPDIR/legacy-colors.toml" cat >"$LEGACY_COLORS_FILE" <<'EOF' mode = "dark" accent = "#336699" selection = "#292929" muted = "#404040" bg = "#101010" dark_bg = "#111111" darker_bg = "#121212" lighter_bg = "#131313" fg = "#f0f0f0" dark_fg = "#d0d0d0" light_fg = "#e0e0e0" bright_fg = "#ffffff" red = "#ff0000" green = "#00ff00" yellow = "#ffff00" blue = "#0000ff" magenta = "#ff00ff" cyan = "#00ffff" bright_red = "#ff1111" bright_green = "#11ff11" bright_yellow = "#ffff11" bright_blue = "#1111ff" bright_magenta = "#ff11ff" bright_cyan = "#11ffff" EOF while read -r canonical expected; do actual=$("$ROOT/bin/omarchy-theme-color" --file "$LEGACY_COLORS_FILE" "$canonical") [[ $actual == "$expected" ]] || fail "legacy palette resolves $canonical" done <<'EOF' background #101010 dark_background #111111 darker_background #121212 lighter_background #131313 foreground #f0f0f0 dark_foreground #d0d0d0 light_foreground #e0e0e0 bright_foreground #ffffff EOF pass "legacy short palette resolves every canonical neutral color" LEGACY_THEME_HOME="$PI_TMPDIR/legacy-theme-home" LEGACY_NEXT_THEME="$LEGACY_THEME_HOME/.local/state/omarchy/current/next-theme" mkdir -p "$LEGACY_NEXT_THEME" cp "$LEGACY_COLORS_FILE" "$LEGACY_NEXT_THEME/colors.toml" OMARCHY_PATH="$ROOT" HOME="$LEGACY_THEME_HOME" "$ROOT/bin/omarchy-theme-set-templates" grep -qx 'background = #101010' "$LEGACY_NEXT_THEME/ghostty.conf" || fail "legacy theme generates terminal background" grep -qx 'foreground = #f0f0f0' "$LEGACY_NEXT_THEME/ghostty.conf" || fail "legacy theme generates terminal foreground" grep -Fq 'dark_bg = "#111111"' "$LEGACY_NEXT_THEME/neovim.lua" || fail "legacy theme preserves dark_bg in generated editor theme" grep -Fq 'lighter_bg = "#131313"' "$LEGACY_NEXT_THEME/neovim.lua" || fail "legacy theme preserves lighter_bg in generated editor theme" grep -Fq 'dark_fg = "#d0d0d0"' "$LEGACY_NEXT_THEME/neovim.lua" || fail "legacy theme preserves dark_fg in generated editor theme" grep -Fq 'bright_fg = "#ffffff"' "$LEGACY_NEXT_THEME/neovim.lua" || fail "legacy theme preserves bright_fg in generated editor theme" if rg -q '\{\{[^}]+\}\}' "$LEGACY_NEXT_THEME"; then fail "legacy theme renders every template placeholder" fi pass "legacy short palette renders the complete generated theme" osc_summary=$("$ROOT/bin/omarchy-theme-osc" "$NEXT_THEME/colors.toml" | python -c 'import sys; data = sys.stdin.buffer.read(); print(data.count(b"]4;"), b"]4;1;#ff0000" in data, b"]4;7;#ffffff" in data, b"]4;15;#eeeeee" in data, b"]12;#eeeeee" in data, b"]17;#808080" in data, b"]19;#eeeeee" in data)') [[ $osc_summary == "16 True True True True True True" ]] || fail "theme OSC aliases semantic colors to ANSI palette" pass "theme OSC aliases semantic colors to ANSI palette" preview_output=$(OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-dev-theme-preview" "$NEXT_THEME/colors.toml" --no-color) assert_output_contains "theme preview shows background to bright_foreground gradient" "$preview_output" "background -> bright_foreground gradient" assert_output_contains "theme preview orders light mode ramp light to dark" "$preview_output" "Neutral ramp (lightest -> darkest)" assert_output_contains "theme preview includes dark background slots" "$preview_output" "darker_background" assert_output_contains "theme preview shows selected text sample" "$preview_output" "This is some [selected text] in a sentence" assert_output_contains "theme preview shows practical terminal samples" "$preview_output" "Terminal/UI samples" assert_output_contains "theme preview shows ANSI palette strips" "$preview_output" "ANSI palette strips" tokyo_fg=$(awk -F'"' '/^foreground =/ { print $2; exit }' "$ROOT/themes/tokyo-night/colors.toml") tokyo_light_fg=$(awk -F'"' '/^light_foreground =/ { print $2; exit }' "$ROOT/themes/tokyo-night/colors.toml") tokyo_bright_fg=$(awk -F'"' '/^bright_foreground =/ { print $2; exit }' "$ROOT/themes/tokyo-night/colors.toml") [[ $tokyo_fg == "#a9b1d6" && $tokyo_light_fg == "#b4bee6" && $tokyo_bright_fg == "#c0caf5" ]] || fail "tokyo night foreground ramp stays ordered" pass "tokyo night foreground ramp stays ordered" if rg -q '^cursor\s*=' "$ROOT/themes" -g'colors.toml'; then fail "built-in colors.toml files omit cursor token" fi pass "built-in colors.toml files omit cursor token" grep -qx 'hypr={ colors = { "rgba(010203ee)", "rgba(040506ee)" }, angle = 45 }' "$NEXT_THEME/gradient-test.txt" || fail "theme template hypr_gradient helper works" grep -qx 'shell=#010203' "$NEXT_THEME/gradient-test.txt" || fail "theme template gradient_start helper works" grep -qx 'shell-gradient=rgba(010203ee) rgba(040506ee) 45deg' "$NEXT_THEME/gradient-test.txt" || fail "theme template shell_gradient helper works" grep -qx 'fallback-hypr="#336699"' "$NEXT_THEME/gradient-test.txt" || fail "theme template hypr_gradient fallback works" grep -qx 'fallback-shell=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template gradient_start fallback works" grep -qx 'fallback-shell-gradient=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template shell_gradient fallback works" pass "theme template gradient helpers work" awk ' /^\[lock\]$/ { in_lock = 1; next } /^\[/ { in_lock = 0 } in_lock && /^text = "#ffffff"$/ { text = 1 } in_lock && /^placeholder = "#ffffff"$/ { placeholder = 1 } in_lock && /^border = "#ffffff"$/ { border = 1 } END { exit(text && placeholder && border ? 0 : 1) } ' "$NEXT_THEME/shell.toml" || fail "theme shell section override is merged" pass "theme shell section override is merged" jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f" and .vars.border == "#4d4d4d" and .colors.accent == "accent"' "$NEXT_THEME/pi.json" >/dev/null pass "pi theme template is generated from standard themed templates" cp "$NEXT_THEME/pi.json" "$CURRENT_THEME/pi.json" echo '{"name":"omarchy-system","vars":{"custom":"yes"}}' >"$NEXT_THEME/pi.json" OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-templates" jq -e '.name == "omarchy-system" and .vars.custom == "yes"' "$NEXT_THEME/pi.json" >/dev/null pass "theme-specific pi.json overrides the default template" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-pi" [[ ! -d $PI_TMPDIR/.pi ]] || fail "pi theme sync skips missing Pi agent when not activating" pass "pi theme sync skips missing Pi agent when not activating" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-pi" --activate [[ -f $PI_TMPDIR/.pi/agent/themes/omarchy-system.json ]] || fail "pi theme file is synced" pass "pi theme file is synced" [[ -f $PI_TMPDIR/.pi/agent/settings.json ]] || fail "pi settings file is generated" pass "pi settings file is generated" jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f"' "$PI_TMPDIR/.pi/agent/themes/omarchy-system.json" >/dev/null pass "pi synced theme JSON is valid" jq -e '.theme == "omarchy-system"' "$PI_TMPDIR/.pi/agent/settings.json" >/dev/null pass "pi generated theme is activated" jq '.model = "keep-me"' "$PI_TMPDIR/.pi/agent/settings.json" >"$PI_TMPDIR/settings.json.tmp" mv "$PI_TMPDIR/settings.json.tmp" "$PI_TMPDIR/.pi/agent/settings.json" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-pi" --activate jq -e '.theme == "omarchy-system" and .model == "keep-me"' "$PI_TMPDIR/.pi/agent/settings.json" >/dev/null pass "pi theme activation preserves existing settings" jq -e '.name == "Omarchy" and .base == "light" and .overrides.text == "#ffffff"' "$NEXT_THEME/claude.json" >/dev/null pass "claude theme template is generated from standard themed templates" cp "$NEXT_THEME/claude.json" "$CURRENT_THEME/claude.json" CLAUDE_TEST_CONFIG="$PI_TMPDIR/.claude-work" mkdir -p "$CLAUDE_TEST_CONFIG" HOME="$PI_TMPDIR" CLAUDE_CONFIG_DIR="$CLAUDE_TEST_CONFIG" "$ROOT/bin/omarchy-theme-set-claude" [[ -f $CLAUDE_TEST_CONFIG/themes/omarchy.json ]] || fail "claude theme is synced to the configured directory" [[ ! -d $PI_TMPDIR/.claude ]] || fail "claude theme sync avoids the default directory when configured" pass "claude theme sync respects CLAUDE_CONFIG_DIR" echo '{"model":"keep-me"}' >"$CLAUDE_TEST_CONFIG/settings.json" HOME="$PI_TMPDIR" CLAUDE_CONFIG_DIR="$CLAUDE_TEST_CONFIG" "$ROOT/bin/omarchy-theme-set-claude" --activate jq -e '.theme == "custom:omarchy" and .model == "keep-me"' "$CLAUDE_TEST_CONFIG/settings.json" >/dev/null pass "claude theme activation preserves configured settings" make_tmpdir THEME_TMPDIR OMARCHY_PATH="$ROOT" HOME="$THEME_TMPDIR" OMARCHY_THEME_HEADLESS=1 "$ROOT/bin/omarchy-theme-set" "Tokyo Night" background_path=$(readlink -f "$THEME_TMPDIR/.local/state/omarchy/current/background" 2>/dev/null || true) expected_background="$THEME_TMPDIR/.local/state/omarchy/current/theme/backgrounds/0-winding-road.jpg" [[ $background_path == "$expected_background" ]] || fail "headless theme set creates current background symlink" [[ -f $background_path ]] || fail "headless theme background target exists" [[ ! -e $THEME_TMPDIR/.config/omarchy/current ]] || fail "headless theme set avoids config current state" pass "headless theme set creates current background symlink" make_tmpdir MIGRATION_TMPDIR mkdir -p \ "$MIGRATION_TMPDIR/.config/omarchy/current/theme" \ "$MIGRATION_TMPDIR/.config/alacritty" \ "$MIGRATION_TMPDIR/.config/hypr" \ "$MIGRATION_TMPDIR/.config/hyprland-preview-share-picker" \ "$MIGRATION_TMPDIR/.config/btop/themes" printf 'tokyo-night\n' >"$MIGRATION_TMPDIR/.config/omarchy/current/theme.name" printf 'x\n' >"$MIGRATION_TMPDIR/.config/omarchy/current/theme/colors.toml" printf 'general.import = [ "~/.config/omarchy/current/theme/alacritty.toml" ]\n' >"$MIGRATION_TMPDIR/.config/alacritty/alacritty.toml" printf 'stylesheets: ["../omarchy/current/theme/hyprland-preview-share-picker.css"]\n' >"$MIGRATION_TMPDIR/.config/hyprland-preview-share-picker/config.yaml" cat >"$MIGRATION_TMPDIR/.config/hypr/hyprland.lua" <<'LUA' package.path = os.getenv("HOME") .. "/.config/?.lua;" .. (os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/?.lua;" .. package.path LUA ln -s "$MIGRATION_TMPDIR/.config/omarchy/current/theme/btop.theme" "$MIGRATION_TMPDIR/.config/btop/themes/current.theme" HOME="$MIGRATION_TMPDIR" bash -euo pipefail "$ROOT/migrations/1781043107.sh" >/dev/null [[ -f $MIGRATION_TMPDIR/.local/state/omarchy/current/theme.name ]] || fail "current theme migration moves theme name" [[ ! -e $MIGRATION_TMPDIR/.config/omarchy/current ]] || fail "current theme migration removes legacy config state" grep -Fq '~/.local/state/omarchy/current/theme/alacritty.toml' "$MIGRATION_TMPDIR/.config/alacritty/alacritty.toml" || fail "current theme migration updates alacritty import" grep -Fq '../../.local/state/omarchy/current/theme/hyprland-preview-share-picker.css' "$MIGRATION_TMPDIR/.config/hyprland-preview-share-picker/config.yaml" || fail "current theme migration updates relative stylesheet" grep -Fq '/.local/state/?.lua;' "$MIGRATION_TMPDIR/.config/hypr/hyprland.lua" || fail "current theme migration keeps Hyprland theme modules discoverable" [[ $(readlink "$MIGRATION_TMPDIR/.config/btop/themes/current.theme") == "$MIGRATION_TMPDIR/.local/state/omarchy/current/theme/btop.theme" ]] || fail "current theme migration updates btop symlink" pass "current theme migration moves state and rewrites shipped references" make_tmpdir NVIM_MIGRATION_TMPDIR nvim_theme_link="$NVIM_MIGRATION_TMPDIR/.config/nvim/lua/plugins/theme.lua" nvim_expected_target="../../../../.local/state/omarchy/current/theme/neovim.lua" nvim_expected_resolved_target="$NVIM_MIGRATION_TMPDIR/.local/state/omarchy/current/theme/neovim.lua" mkdir -p \ "$(dirname "$nvim_theme_link")" \ "$NVIM_MIGRATION_TMPDIR/.config/omarchy/current/theme" \ "$NVIM_MIGRATION_TMPDIR/.local/state/omarchy/current/theme" touch "$NVIM_MIGRATION_TMPDIR/.local/state/omarchy/current/theme/neovim.lua" for nvim_legacy_target in \ "../../../omarchy/current/theme/neovim.lua" \ "$NVIM_MIGRATION_TMPDIR/.config/omarchy/current/theme/neovim.lua"; do ln -sfn "$nvim_legacy_target" "$nvim_theme_link" HOME="$NVIM_MIGRATION_TMPDIR" bash -euo pipefail "$ROOT/migrations/1781158082.sh" >/dev/null nvim_actual_target=$(readlink "$nvim_theme_link") nvim_resolved_target=$(readlink -f "$nvim_theme_link") [[ $nvim_actual_target == $nvim_expected_target ]] || fail "nvim theme migration updates theme symlink" [[ $nvim_resolved_target == $nvim_expected_resolved_target ]] || fail "nvim theme migration points to current theme" done pass "nvim theme migration relinks current theme" for nvim_legacy_target in \ "../../../omarchy/current/theme/neovim.lua" \ "../../../../.config/omarchy/current/theme/neovim.lua" \ "~/.config/omarchy/current/theme/neovim.lua" \ "$NVIM_MIGRATION_TMPDIR/.config/omarchy/current/theme/neovim.lua"; do ln -sfn "$nvim_legacy_target" "$nvim_theme_link" HOME="$NVIM_MIGRATION_TMPDIR" bash -euo pipefail "$ROOT/migrations/1785002349.sh" >/dev/null nvim_actual_target=$(readlink "$nvim_theme_link") nvim_resolved_target=$(readlink -f "$nvim_theme_link") [[ $nvim_actual_target == $nvim_expected_target ]] || fail "nvim theme repair migration updates $nvim_legacy_target" [[ $nvim_resolved_target == $nvim_expected_resolved_target ]] || fail "nvim theme repair migration points to current theme" done HOME="$NVIM_MIGRATION_TMPDIR" bash -euo pipefail "$ROOT/migrations/1785002349.sh" >/dev/null [[ $(readlink "$nvim_theme_link") == $nvim_expected_target ]] || fail "nvim theme repair migration is idempotent" touch "$NVIM_MIGRATION_TMPDIR/custom-theme.lua" ln -sfn "../../../../custom-theme.lua" "$nvim_theme_link" HOME="$NVIM_MIGRATION_TMPDIR" bash -euo pipefail "$ROOT/migrations/1785002349.sh" >/dev/null [[ $(readlink "$nvim_theme_link") == "../../../../custom-theme.lua" ]] || fail "nvim theme repair migration leaves custom symlinks alone" pass "nvim theme repair migration relinks every legacy spelling" make_tmpdir HYPR_MIGRATION_TMPDIR mkdir -p "$HYPR_MIGRATION_TMPDIR/.config/hypr" cat >"$HYPR_MIGRATION_TMPDIR/.config/hypr/hyprland.lua" <<'LUA' -- Learn how to configure Hyprland: https://wiki.hypr.land/Configuring/Start/ -- Load user modules from ~/.config and Omarchy defaults from $OMARCHY_PATH. package.path = os.getenv("HOME") .. "/.config/?.lua;" .. (os.getenv("OMARCHY_PATH") or (os.getenv("HOME") .. "/.local/share/omarchy")) .. "/?.lua;" .. package.path -- All Omarchy default setups require("default.hypr.omarchy") -- Add any other personal Hyprland configuration below. o.window("Example", { workspace = "1" }) LUA HOME="$HYPR_MIGRATION_TMPDIR" bash -euo pipefail "$ROOT/migrations/1781063758.sh" >/dev/null grep -Fq 'dofile((os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/default/hypr/bootstrap.lua")' "$HYPR_MIGRATION_TMPDIR/.config/hypr/hyprland.lua" || fail "hyprland bootstrap migration adds bootstrap" ! grep -Fq 'package.path = os.getenv("HOME")' "$HYPR_MIGRATION_TMPDIR/.config/hypr/hyprland.lua" || fail "hyprland bootstrap migration removes legacy path block" grep -Fq 'o.window("Example", { workspace = "1" })' "$HYPR_MIGRATION_TMPDIR/.config/hypr/hyprland.lua" || fail "hyprland bootstrap migration preserves custom config" pass "hyprland bootstrap migration updates stale user entrypoint" cp "$NEXT_THEME/colors.toml" "$CURRENT_THEME/colors.toml" cp "$NEXT_THEME/vscode-theme.json" "$CURRENT_THEME/vscode-theme.json" FAKE_BIN="$PI_TMPDIR/fake-bin" mkdir -p "$FAKE_BIN" cat >"$FAKE_BIN/tmux" <<'EOF' #!/bin/bash case "$1" in list-sessions) [[ $2 == "-F" ]] && echo "session-1" exit 0 ;; set-environment|set-option|refresh-client) echo "$*" >>"${TMUX_LOG:-/dev/null}" exit 0 ;; list-panes|list-clients) exit 0 ;; *) exit 0 ;; esac EOF chmod +x "$FAKE_BIN/tmux" TMUX_LOG="$PI_TMPDIR/tmux.log" PATH="$FAKE_BIN:$ROOT/bin:$PATH" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-tmux" grep -q 'set-environment -g COLORFGBG 0;15' "$PI_TMPDIR/tmux.log" || fail "tmux sync reads mode from colors.toml" pass "tmux sync reads mode from colors.toml" cat >"$FAKE_BIN/gsettings" <<'EOF' #!/bin/bash echo "$*" >>"${GSETTINGS_LOG:-/dev/null}" EOF chmod +x "$FAKE_BIN/gsettings" GSETTINGS_LOG="$PI_TMPDIR/gsettings.log" DBUS_SESSION_BUS_ADDRESS="unix:path=$PI_TMPDIR/bus" PATH="$FAKE_BIN:$ROOT/bin:$PATH" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-gnome" grep -q 'org.gnome.desktop.interface color-scheme prefer-light' "$PI_TMPDIR/gsettings.log" || fail "gnome sync reads mode from colors.toml" pass "gnome sync reads mode from colors.toml" cat >"$FAKE_BIN/omarchy-cmd-present" <<'EOF' #!/bin/bash [[ $1 == "code" ]] EOF chmod +x "$FAKE_BIN/omarchy-cmd-present" cat >"$FAKE_BIN/omarchy-toggle-enabled" <<'EOF' #!/bin/bash exit 1 EOF chmod +x "$FAKE_BIN/omarchy-toggle-enabled" mkdir -p "$PI_TMPDIR/.vscode/extensions" echo '{"local.omarchy-theme-1.0.0":true}' >"$PI_TMPDIR/.vscode/extensions/.obsolete" PATH="$FAKE_BIN:$ROOT/bin:$PATH" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-vscode" jq -e '.contributes.themes[0].uiTheme == "vs"' "$PI_TMPDIR/.vscode/extensions/omarchy-theme/package.json" >/dev/null || fail "vscode generated light theme uses VS Code light uiTheme" pass "vscode generated light theme uses VS Code light uiTheme" [[ -L $PI_TMPDIR/.vscode/extensions/omarchy-theme/themes/omarchy-color-theme.json ]] || fail "vscode generated theme references current theme file" [[ $(readlink "$PI_TMPDIR/.vscode/extensions/omarchy-theme/themes/omarchy-color-theme.json") == "$PI_TMPDIR/.local/state/omarchy/current/theme/vscode-theme.json" ]] || fail "vscode generated theme references current theme file" pass "vscode generated theme references current theme file" [[ ! -f $PI_TMPDIR/.vscode/extensions/.obsolete ]] || fail "vscode generated theme clears obsolete extension marker" pass "vscode generated theme clears obsolete extension marker" jq -e '.[] | select(.identifier.id == "local.omarchy-theme" and .relativeLocation == "omarchy-theme")' "$PI_TMPDIR/.vscode/extensions/extensions.json" >/dev/null || fail "vscode generated theme registers local extension" pass "vscode generated theme registers local extension" for binary in \ omarchy-update \ omarchy-theme-set \ omarchy-capture-screenshot \ omarchy-system-reboot \ omarchy-pkg-add; do [[ -x $ROOT/bin/$binary ]] || fail "binary is executable: $binary" pass "binary is executable: $binary" done while IFS= read -r binary_path; do header=$(awk ' NR == 1 && /^#!/ { next } /^[[:space:]]*$/ { if (seen) print; next } /^[[:space:]]*#/ { seen=1; print; next } { exit } ' "$binary_path") grep -q '^# omarchy:summary=' <<<"$header" || fail "metadata summary is present: $binary_path" ! grep -q '^# omarchy:binary=' <<<"$header" || fail "metadata does not repeat inferred binary: $binary_path" ! grep -q '^# omarchy:args=$' <<<"$header" || fail "metadata does not include empty args: $binary_path" ! grep -Eq '^# omarchy:(legacy|usage|visibility|mutates|interactive)=' <<<"$header" || fail "metadata avoids removed fields: $binary_path" ! grep -Eq '^# omarchy:requires-sudo=false$' <<<"$header" || fail "metadata omits false booleans: $binary_path" done < <(find "$ROOT/bin" -maxdepth 1 -type f -executable -name 'omarchy-*' | sort) pass "all executable bins have slim self-documenting metadata" make_tmpdir TMPDIR ln -s "$CLI" "$TMPDIR/omarchy" { printf '#!/bin/bash\n\n' printf '# ordinary comments are fine\n' printf '# omarchy:this malformed line should be ignored\n' printf '# omarchy:group=weird\n' printf '# omarchy:name=test\n' printf '# omarchy:summary=Survives malformed metadata comments\n' printf '# omarchy:made-up=value\n' printf 'echo weird-ok\n' } >"$TMPDIR/omarchy-weird-test" chmod +x "$TMPDIR/omarchy-weird-test" { printf '#!/bin/bash\n\n' printf '# a partial metadata header should not destroy fallback routing\n' printf '# omarchy:summary=Partial metadata keeps inferred route\n' printf '# omarchy:made-up=value\n' printf 'echo partial-ok\n' } >"$TMPDIR/omarchy-partial-meta-test" chmod +x "$TMPDIR/omarchy-partial-meta-test" { printf '#!/bin/bash\n\n' printf 'echo body-metadata-ok\n' printf '# omarchy:group=wrong\n' printf '# omarchy:name=wrong\n' } >"$TMPDIR/omarchy-body-metadata-test" chmod +x "$TMPDIR/omarchy-body-metadata-test" "$TMPDIR/omarchy" commands --all --json | jq -e '.commands[] | select(.route == "omarchy weird test" and .summary == "Survives malformed metadata comments")' >/dev/null pass "unknown metadata values are non-fatal" "$TMPDIR/omarchy" commands --all --json | jq -e '.commands[] | select(.route == "omarchy partial meta test" and .summary == "Partial metadata keeps inferred route")' >/dev/null pass "partial metadata keeps inferred fallback route" "$TMPDIR/omarchy" commands --all --json | jq -e '.commands[] | select(.route == "omarchy body metadata test" and .summary == "Run the body metadata test command")' >/dev/null pass "metadata-looking comments after script body are ignored" output=$("$TMPDIR/omarchy" weird test) assert_output_contains "temporary metadata command dispatches" "$output" "weird-ok" output=$("$TMPDIR/omarchy" partial meta test) assert_output_contains "partial metadata command dispatches" "$output" "partial-ok" output=$("$TMPDIR/omarchy" body metadata test) assert_output_contains "body metadata command dispatches by filename" "$output" "body-metadata-ok" # A trailing --help must render help even when the route only partially # resolves, leaving unresolved words ahead of the flag (e.g. `update aur --help` # used to start a real update because only the first leftover token was checked). { printf '#!/bin/bash\n\n' printf '# omarchy:summary=Partial resolution help test parent\n' printf '# omarchy:alias=omarchy parenthelp-alias\n' printf 'echo "parenthelp-parent-ran args=[$*]"\n' } >"$TMPDIR/omarchy-parenthelp" chmod +x "$TMPDIR/omarchy-parenthelp" { printf '#!/bin/bash\n\n' printf '# omarchy:summary=Partial resolution help test child\n' printf 'echo parenthelp-child-ran\n' } >"$TMPDIR/omarchy-parenthelp-child" chmod +x "$TMPDIR/omarchy-parenthelp-child" output=$("$TMPDIR/omarchy" parenthelp bogus --help) assert_output_contains "trailing --help after an unresolved word renders help" "$output" "omarchy-parenthelp" assert_output_contains "partial resolution help lists related commands" "$output" "omarchy parenthelp child" if [[ $output == *"parenthelp-parent-ran"* ]]; then fail "trailing --help after an unresolved word does not execute the command" fi pass "trailing --help after an unresolved word does not execute the command" output=$("$TMPDIR/omarchy" parenthelp bogus -h) assert_output_contains "trailing -h after an unresolved word renders help" "$output" "omarchy-parenthelp" output=$("$TMPDIR/omarchy" parenthelp-alias bogus --help) assert_output_contains "trailing --help after an unresolved word renders help via alias route" "$output" "omarchy-parenthelp" if [[ $output == *"parenthelp-parent-ran"* ]]; then fail "trailing --help on an alias route does not execute the command" fi pass "trailing --help on an alias route does not execute the command" output=$("$TMPDIR/omarchy" parenthelp bogus --json --help) grep -q '"ok": true' <<<"$output" || fail "--json with --help renders JSON help" pass "--json with --help renders JSON help" output=$("$TMPDIR/omarchy" parenthelp "explain --json output" --help) assert_output_contains "a --json embedded inside one argument does not switch help to JSON" "$output" "Partial resolution help test parent" output=$("$TMPDIR/omarchy" parenthelp bogus trailing) assert_output_contains "leftover args without a help flag still forward" "$output" "parenthelp-parent-ran args=[bogus trailing]" output=$("$TMPDIR/omarchy" parenthelp run -- --help) assert_output_contains "a --help behind -- belongs to the command and still forwards" "$output" "parenthelp-parent-ran args=[run -- --help]" output=$("$TMPDIR/omarchy" parenthelp --helpme x--help) assert_output_contains "help lookalike tokens do not trigger help" "$output" "parenthelp-parent-ran args=[--helpme x--help]"