Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/omarchy-theme-set
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ post_theme_commands=(
omarchy-theme-set-gnome
omarchy-theme-set-pi
omarchy-theme-set-claude
omarchy-theme-set-grok
omarchy-theme-set-hermes
omarchy-theme-set-t3code
omarchy-theme-set-browser
Expand Down
134 changes: 134 additions & 0 deletions bin/omarchy-theme-set-grok
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/bin/bash

# omarchy:summary=Point Grok Build at the Omarchy terminal palette
# omarchy:args=[--activate]
# omarchy:hidden=true

# Grok has no custom theme file. Its terminal theme paints no canvas of its
# own and uses the terminal's default colors, which Omarchy already retints.
# Activate once so following is the default; a later /theme choice in Grok
# is left alone until --activate runs again.

set -euo pipefail

GROK_HOME="${GROK_HOME:-$HOME/.grok}"
GROK_CONFIG_PATH="${GROK_CONFIG_PATH:-$GROK_HOME/config.toml}"
GROK_ACTIVATE=0

usage() {
echo "Usage: omarchy-theme-set-grok [--activate]"
}

for arg in "$@"; do
case "$arg" in
--activate)
GROK_ACTIVATE=1
;;
-h | --help)
usage
exit 0
;;
*)
usage >&2
exit 1
;;
esac
done

if (( GROK_ACTIVATE == 0 )) && [[ ! -d $GROK_HOME ]]; then
exit 0
fi

write_terminal_theme() {
local force="$1"

python3 - "$GROK_CONFIG_PATH" "$force" <<'PY'
import pathlib
import re
import sys
import tomllib

path = pathlib.Path(sys.argv[1])
force = sys.argv[2] == "1"
terminal_themes = {"terminal", "terminal-default", "transparent", "native"}
section_re = re.compile(r"^\[([^]]+)\]\s*(#.*)?$")
key_re = re.compile(r"^([A-Za-z0-9_.-]+)\s*=")


def current_theme(data):
ui = data.get("ui")
if isinstance(ui, dict) and "theme" in ui:
value = ui["theme"]
return "" if value is None else str(value)
if "theme" in data:
value = data["theme"]
return "" if value is None else str(value)
return ""


def upsert(text, table, key, assignment):
raw = text.splitlines()
current = None
at_root = True
table_header = None
key_line = None
dotted_line = None
dotted = f"{table}.{key}"

for index, line in enumerate(raw):
stripped = line.strip()
if stripped == "" or stripped.startswith("#"):
continue

match = section_re.match(stripped)
if match:
at_root = False
current = match.group(1).strip()
if current == table:
table_header = index
continue

match = key_re.match(stripped)
if match is None:
continue

name = match.group(1)
if at_root and name == dotted:
dotted_line = index
elif current == table and name == key:
key_line = index

rhs = assignment.split("=", 1)[1].strip()
if key_line is not None:
raw[key_line] = assignment
elif dotted_line is not None:
raw[dotted_line] = f"{dotted} = {rhs}"
elif table_header is not None:
raw.insert(table_header + 1, assignment)
else:
if raw and raw[-1].strip() != "":
raw.append("")
raw.append(f"[{table}]")
raw.append(assignment)

return ("\n".join(raw) + "\n") if raw else f"[{table}]\n{assignment}\n"


text = path.read_text() if path.is_file() else ""
try:
data = tomllib.loads(text) if text.strip() else {}
except tomllib.TOMLDecodeError:
data = {}

theme = current_theme(data).strip().lower()
if not force and theme and theme not in terminal_themes:
raise SystemExit(0)

text = upsert(text, "features", "terminal_theme", "terminal_theme = true")
text = upsert(text, "ui", "theme", 'theme = "terminal"')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text)
PY
}

write_terminal_theme "$GROK_ACTIVATE"
3 changes: 2 additions & 1 deletion docs/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ After activation, `omarchy-theme-set` fires the `theme-set` hook
parallel retint of running apps — terminals, Hyprland, btop, browser, editors,
and the rest of the `post_theme_commands` list in `bin/omarchy-theme-set`.
Making a new app follow theme changes means adding its restart/retint command
to that list. Runs serialize on a `flock`, so scripted theme changes queue
to that list. Grok Build has no custom theme file; `omarchy-theme-set-grok`
sets `theme = "terminal"` so the TUI uses the terminal palette Omarchy already retints. Runs serialize on a `flock`, so scripted theme changes queue
instead of racing.

## What an installed theme may not ship
Expand Down
1 change: 1 addition & 0 deletions install/user/theme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ if [[ ! -s $HOME/.local/state/omarchy/current/theme.name ]]; then
fi
fi
omarchy-theme-set-pi --activate
omarchy-theme-set-grok --activate

mkdir -p ~/.config/btop/themes
ln -snf "$HOME/.local/state/omarchy/current/theme/btop.theme" ~/.config/btop/themes/current.theme
4 changes: 4 additions & 0 deletions migrations/1789095456.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
echo "Make Grok Build follow the Omarchy terminal palette"

[[ -d ${GROK_HOME:-$HOME/.grok} ]] || exit 0
omarchy-theme-set-grok --activate
90 changes: 90 additions & 0 deletions test/cli
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,96 @@ HOME="$PI_TMPDIR" CLAUDE_CONFIG_DIR="$CLAUDE_TEST_CONFIG" "$ROOT/bin/omarchy-the
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 GROK_TMPDIR
GROK_HOME_DIR="$GROK_TMPDIR/grok"

HOME="$GROK_TMPDIR" GROK_HOME="$GROK_HOME_DIR" "$ROOT/bin/omarchy-theme-set-grok"
[[ ! -f $GROK_HOME_DIR/config.toml ]] || fail "grok theme sync is a no-op before activate"
pass "grok theme sync is a no-op before activate"

HOME="$GROK_TMPDIR" GROK_HOME="$GROK_TMPDIR/no-such-grok" "$ROOT/bin/omarchy-theme-set-grok" --activate
[[ -f $GROK_TMPDIR/no-such-grok/config.toml ]] || fail "grok activate writes config when Grok home is missing"
awk '
/^\[features\]$/ { in_features = 1; in_ui = 0; next }
/^\[ui\]$/ { in_ui = 1; in_features = 0; next }
/^\[/ { in_ui = 0; in_features = 0 }
in_features && $0 == "terminal_theme = true" { flag = 1 }
in_ui && $0 == "theme = \"terminal\"" { theme = 1 }
END { exit(flag && theme ? 0 : 1) }
' "$GROK_TMPDIR/no-such-grok/config.toml" || fail "grok activate enables the terminal theme"
pass "grok activate writes the terminal theme"

mkdir -p "$GROK_HOME_DIR"
cat >"$GROK_HOME_DIR/config.toml" <<'EOF'
[cli]
installer = "internal"

[marketplace]
default_skills_installs_purged = true
official_marketplace_auto_installed = true

[[marketplace.sources]]
name = "xAI Official"
git = "https://github.com/xai-org/plugin-marketplace.git"

[ui]
max_thoughts_width = 120
fork_secondary_model = "grok-4.6"
yolo = false
EOF
HOME="$GROK_TMPDIR" GROK_HOME="$GROK_HOME_DIR" "$ROOT/bin/omarchy-theme-set-grok"
awk '
/^\[cli\]$/ { in_cli = 1; in_ui = 0; in_src = 0; in_features = 0; next }
/^\[marketplace\]$/ { in_mkt = 1; in_cli = 0; in_ui = 0; in_src = 0; in_features = 0; next }
/^\[\[marketplace\.sources\]\]$/ { in_src = 1; in_mkt = 0; in_cli = 0; in_ui = 0; in_features = 0; next }
/^\[ui\]$/ { in_ui = 1; in_cli = 0; in_mkt = 0; in_src = 0; in_features = 0; next }
/^\[features\]$/ { in_features = 1; in_cli = 0; in_mkt = 0; in_src = 0; in_ui = 0; next }
/^\[/ { in_ui = 0; in_cli = 0; in_mkt = 0; in_src = 0; in_features = 0 }
in_cli && $0 == "installer = \"internal\"" { cli = 1 }
in_mkt && $0 == "official_marketplace_auto_installed = true" { mkt = 1 }
in_src && $0 == "name = \"xAI Official\"" { src_name = 1 }
in_src && index($0, "plugin-marketplace") { src_git = 1 }
in_ui && $0 == "max_thoughts_width = 120" { width = 1 }
in_ui && $0 == "fork_secondary_model = \"grok-4.6\"" { model = 1 }
in_ui && $0 == "theme = \"terminal\"" { theme = 1 }
in_features && $0 == "terminal_theme = true" { flag = 1 }
END { exit(cli && mkt && src_name && src_git && width && model && theme && flag ? 0 : 1) }
' "$GROK_HOME_DIR/config.toml" || fail "grok follow writes terminal theme without dropping marketplace tables"
pass "grok follow writes terminal theme without dropping marketplace tables"

cat >"$GROK_HOME_DIR/config.toml" <<'EOF'
[ui]
theme = "tokyonight"
max_thoughts_width = 120
EOF
HOME="$GROK_TMPDIR" GROK_HOME="$GROK_HOME_DIR" "$ROOT/bin/omarchy-theme-set-grok"
awk '
/^\[ui\]$/ { in_ui = 1; next }
/^\[/ { in_ui = 0 }
in_ui && $0 == "theme = \"tokyonight\"" { kept = 1 }
in_ui && $0 == "theme = \"terminal\"" { overwritten = 1 }
END { exit(kept && !overwritten ? 0 : 1) }
' "$GROK_HOME_DIR/config.toml" || fail "grok follow leaves a manual theme alone"
pass "grok follow leaves a manual theme alone"

HOME="$GROK_TMPDIR" GROK_HOME="$GROK_HOME_DIR" "$ROOT/bin/omarchy-theme-set-grok" --activate
awk '
/^\[ui\]$/ { in_ui = 1; in_features = 0; next }
/^\[features\]$/ { in_features = 1; in_ui = 0; next }
/^\[/ { in_ui = 0; in_features = 0 }
in_ui && $0 == "theme = \"terminal\"" { theme = 1 }
in_ui && $0 == "max_thoughts_width = 120" { width = 1 }
in_features && $0 == "terminal_theme = true" { flag = 1 }
END { exit(theme && width && flag ? 0 : 1) }
' "$GROK_HOME_DIR/config.toml" || fail "grok activate replaces a manual theme"
pass "grok activate replaces a manual theme"

ln -s "$GROK_HOME_DIR/config.toml" "$GROK_TMPDIR/linked.toml"
HOME="$GROK_TMPDIR" GROK_HOME="$GROK_HOME_DIR" GROK_CONFIG_PATH="$GROK_TMPDIR/linked.toml" \
"$ROOT/bin/omarchy-theme-set-grok" --activate
[[ -L $GROK_TMPDIR/linked.toml ]] || fail "grok theme write keeps a config symlink"
pass "grok theme write keeps a config symlink"

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)
Expand Down
24 changes: 19 additions & 5 deletions test/shell.d/user-theme-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,39 @@ printf '%s\n' "$*" >>"$OMARCHY_TEST_THEME_CALLS"
SH
chmod +x "$mock_bin/omarchy-theme-set"

for command in omarchy-theme-set-pi; do
cat >"$mock_bin/$command" <<'SH'
# Record name + args so we assert finalize always activates pi/grok follow mode.
for command in omarchy-theme-set-pi omarchy-theme-set-grok; do
cat >"$mock_bin/$command" <<SH
#!/bin/bash
exit 0
printf '%s %s\n' "$command" "\$*" >>"\$OMARCHY_TEST_ACTIVATE_CALLS"
SH
chmod +x "$mock_bin/$command"
done

calls="$test_tmp/theme-calls"
activate_calls="$test_tmp/activate-calls"
touch "$test_tmp/home/.config/chromium/SingletonLock"
HOME="$test_tmp/home" PATH="$mock_bin:$PATH" OMARCHY_TEST_THEME_CALLS="$calls" \
: >"$activate_calls"
HOME="$test_tmp/home" PATH="$mock_bin:$PATH" \
OMARCHY_TEST_THEME_CALLS="$calls" OMARCHY_TEST_ACTIVATE_CALLS="$activate_calls" \
bash "$ROOT/install/user/theme.sh"
grep -Fx 'Tokyo Night' "$calls" >/dev/null || fail "user theme setup seeds Tokyo Night when no theme exists"
[[ -f $test_tmp/home/.config/chromium/SingletonLock ]] || fail "runtime user theme setup preserves Chromium's singleton lock"
grep -Fx 'omarchy-theme-set-pi --activate' "$activate_calls" >/dev/null || \
fail "user theme setup activates pi theme follow"
grep -Fx 'omarchy-theme-set-grok --activate' "$activate_calls" >/dev/null || \
fail "user theme setup activates grok theme follow"

: >"$calls"
: >"$activate_calls"
printf 'Solitude\n' >"$test_tmp/home/.local/state/omarchy/current/theme.name"
HOME="$test_tmp/home" PATH="$mock_bin:$PATH" OMARCHY_TEST_THEME_CALLS="$calls" \
HOME="$test_tmp/home" PATH="$mock_bin:$PATH" \
OMARCHY_TEST_THEME_CALLS="$calls" OMARCHY_TEST_ACTIVATE_CALLS="$activate_calls" \
bash "$ROOT/install/user/theme.sh"
[[ ! -s $calls ]] || fail "user theme setup preserves an existing theme"
grep -Fx 'omarchy-theme-set-pi --activate' "$activate_calls" >/dev/null || \
fail "user theme setup re-activates pi when a theme already exists"
grep -Fx 'omarchy-theme-set-grok --activate' "$activate_calls" >/dev/null || \
fail "user theme setup re-activates grok when a theme already exists"

pass "user theme setup only seeds the default theme once"