diff --git a/bin/omarchy-remove-ai-chatgpt b/bin/omarchy-remove-ai-chatgpt new file mode 100755 index 00000000..d5d41a24 --- /dev/null +++ b/bin/omarchy-remove-ai-chatgpt @@ -0,0 +1,17 @@ +#!/bin/bash + +# omarchy:summary=Remove the ChatGPT desktop app along with its configuration and caches. +# omarchy:requires-sudo=true + +set -e + +omarchy-pkg-drop openai-codex-desktop + +# Not ~/.cache/codex-runtimes: the Codex CLI resolves its own runtime under +# there, and it ships in a different package that survives this one. +rm -rf \ + "$HOME/.config/Codex" \ + "$HOME/.cache/Codex" + +echo "" +echo "ChatGPT has been removed." diff --git a/bin/omarchy-remove-ai-grok-bot b/bin/omarchy-remove-ai-grok-bot new file mode 100755 index 00000000..19072e06 --- /dev/null +++ b/bin/omarchy-remove-ai-grok-bot @@ -0,0 +1,16 @@ +#!/bin/bash + +# omarchy:summary=Remove Grok Bot along with its settings and data. +# omarchy:requires-sudo=true + +set -e + +omarchy-pkg-drop grok-bot + +# Not ~/.grok: that is the Grok CLI, which is installed and used on its own. +rm -rf \ + "$HOME/.config/Grok Bot" \ + "$HOME/.grokbot" + +echo "" +echo "Grok Bot has been removed." diff --git a/bin/omarchy-remove-ai-lm-studio b/bin/omarchy-remove-ai-lm-studio new file mode 100755 index 00000000..e4fbe6db --- /dev/null +++ b/bin/omarchy-remove-ai-lm-studio @@ -0,0 +1,29 @@ +#!/bin/bash + +# omarchy:summary=Remove LM Studio along with its configuration and every model it downloaded. +# omarchy:requires-sudo=true + +set -e + +omarchy-pkg-drop lmstudio-bin + +# LM Studio's home is relocatable and the pointer is the only record of where +# the models went, so it has to be read before it is deleted with everything else. +lmstudio_home="" +if [[ -f $HOME/.lmstudio-home-pointer ]]; then + read -r lmstudio_home <"$HOME/.lmstudio-home-pointer" || true +fi + +rm -rf \ + "$HOME/.config/LM Studio" \ + "$HOME/.config/LM-Studio" \ + "$HOME/.lmstudio" \ + "$HOME/.lmstudio-home-pointer" + +if [[ -n $lmstudio_home && $lmstudio_home == /* && -d $lmstudio_home ]] && + [[ $lmstudio_home != "/" && $lmstudio_home != "$HOME" ]]; then + rm -rf "$lmstudio_home" +fi + +echo "" +echo "LM Studio and its models have been removed." diff --git a/bin/omarchy-remove-ai-ollama b/bin/omarchy-remove-ai-ollama new file mode 100755 index 00000000..6644d5fd --- /dev/null +++ b/bin/omarchy-remove-ai-ollama @@ -0,0 +1,18 @@ +#!/bin/bash + +# omarchy:summary=Remove Ollama along with every model it pulled. +# omarchy:requires-sudo=true + +set -e + +# The service is what holds the models open, so it goes before the package. +sudo systemctl disable --now ollama.service 2>/dev/null || true + +# Whichever acceleration variant is installed provides the same command. +omarchy-pkg-drop ollama ollama-cuda ollama-rocm ollama-vulkan + +sudo rm -rf /var/lib/ollama +rm -rf "$HOME/.ollama" + +echo "" +echo "Ollama and its models have been removed." diff --git a/bin/omarchy-remove-ai-t3-code b/bin/omarchy-remove-ai-t3-code new file mode 100755 index 00000000..d9d6df20 --- /dev/null +++ b/bin/omarchy-remove-ai-t3-code @@ -0,0 +1,18 @@ +#!/bin/bash + +# omarchy:summary=Remove T3 Code along with its configuration and workspaces. +# omarchy:requires-sudo=true + +set -e + +omarchy-pkg-drop t3code-bin + +# T3 Code drives agents that keep their own state -- ~/.claude.json, ~/.grok, +# ~/.local/share/opencode -- and those outlive it, so only its own goes. +rm -rf \ + "$HOME/.config/t3code" \ + "$HOME/.t3" +rm -f "$HOME/.config/t3code-flags.conf" + +echo "" +echo "T3 Code has been removed." diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 438ce8e6..598b6abb 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -280,7 +280,7 @@ "remove.development": {"icon":"󰵮","label":"Development","title":"Remove"}, "remove.theme": {"icon":"󰸌","label":"Theme","action":"omarchy-theme-remove"}, "remove.browser": {"icon":"","label":"Browser","title":"Remove"}, - "remove.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"}, + "remove.ai": {"icon":"󱚤","label":"AI","title":"Remove"}, "remove.gaming": {"icon":"","label":"Gaming","title":"Remove"}, "remove.service": {"icon":"","label":"Services","title":"Remove"}, "remove.windows": {"icon":"󰍲","label":"Windows","when":"[[ -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm remove'"}, @@ -297,6 +297,12 @@ "remove.browser.zen": {"icon":"","label":"Zen","when":"omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser zen'"}, "remove.service.dropbox": {"icon":"","label":"Dropbox","when":"omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-dropbox"}, "remove.service.tailscale": {"icon":"","label":"Tailscale","when":"omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-tailscale"}, + "remove.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-chatgpt"}, + "remove.ai.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"}, + "remove.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"omarchy-pkg-present grok-bot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-grok-bot"}, + "remove.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"omarchy-pkg-present lmstudio-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-lm-studio"}, + "remove.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"omarchy-pkg-present ollama","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-ollama"}, + "remove.ai.t3-code": {"icon":"","iconFont":"omarchy","label":"T3 Code","when":"omarchy-pkg-present t3code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-t3-code"}, "remove.gaming.steam": {"icon":"","label":"Steam","when":"omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-gaming-steam"}, "remove.gaming.retroarch": {"icon":"","label":"RetroArch","when":"omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-gaming-retroarch"}, "remove.gaming.minecraft": {"icon":"󰍳","label":"Minecraft","when":"omarchy-pkg-present minecraft-launcher","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-gaming-minecraft"}, diff --git a/test/shell.d/remove-ai-test.sh b/test/shell.d/remove-ai-test.sh new file mode 100644 index 00000000..8c7cb153 --- /dev/null +++ b/test/shell.d/remove-ai-test.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT + +mkdir -p "$tmp_dir/bin" + +cat >"$tmp_dir/bin/omarchy-pkg-drop" <<'SCRIPT' +#!/bin/bash +printf 'drop:%s\n' "$*" >>"$TEST_LOG" +SCRIPT +chmod +x "$tmp_dir/bin/omarchy-pkg-drop" + +export TEST_LOG="$tmp_dir/log" +export PATH="$tmp_dir/bin:$PATH" + +fresh_home() { + rm -rf "$tmp_dir/home" + mkdir -p "$tmp_dir/home" + export HOME="$tmp_dir/home" +} + +# The Codex CLI ships in its own package and resolves its runtime out of +# ~/.cache/codex-runtimes, so removing the desktop app must not take it. +fresh_home +mkdir -p "$HOME/.config/Codex" "$HOME/.cache/Codex" "$HOME/.cache/codex-runtimes/codex-primary-runtime" "$HOME/.codex" +"$ROOT/bin/omarchy-remove-ai-chatgpt" >/dev/null + +[[ ! -e $HOME/.config/Codex ]] || fail "ChatGPT removal deletes the desktop app's config" +pass "ChatGPT removal deletes the desktop app's config" + +[[ -d $HOME/.cache/codex-runtimes/codex-primary-runtime ]] || fail "ChatGPT removal keeps the Codex CLI's runtime cache" +pass "ChatGPT removal keeps the Codex CLI's runtime cache" + +[[ -d $HOME/.codex ]] || fail "ChatGPT removal keeps the Codex CLI's config" +pass "ChatGPT removal keeps the Codex CLI's config" + +# LM Studio's models follow a relocatable home, named only by the pointer file. +fresh_home +mkdir -p "$tmp_dir/relocated-models/models" +printf '%s' "$tmp_dir/relocated-models" >"$HOME/.lmstudio-home-pointer" +mkdir -p "$HOME/.config/LM Studio" +"$ROOT/bin/omarchy-remove-ai-lm-studio" >/dev/null + +[[ ! -e $tmp_dir/relocated-models ]] || fail "LM Studio removal follows a relocated home pointer" +pass "LM Studio removal follows a relocated home pointer" + +[[ ! -e "$HOME/.config/LM Studio" ]] || fail "LM Studio removal deletes its config" +pass "LM Studio removal deletes its config" + +# A pointer that resolves to the home directory itself would take everything. +fresh_home +printf '%s' "$HOME" >"$HOME/.lmstudio-home-pointer" +mkdir -p "$HOME/Documents" +"$ROOT/bin/omarchy-remove-ai-lm-studio" >/dev/null + +[[ -d $HOME/Documents ]] || fail "LM Studio removal refuses a pointer aimed at the home directory" +pass "LM Studio removal refuses a pointer aimed at the home directory" + +# T3 Code bootstraps the agents it drives; their state outlives it. +fresh_home +mkdir -p "$HOME/.config/t3code" "$HOME/.t3" "$HOME/.grok" "$HOME/.local/share/opencode" "$HOME/.npm" +touch "$HOME/.claude.json" +"$ROOT/bin/omarchy-remove-ai-t3-code" >/dev/null + +[[ ! -e $HOME/.t3 ]] || fail "T3 Code removal deletes its own data" +pass "T3 Code removal deletes its own data" + +for kept in .grok .claude.json .npm .local/share/opencode; do + [[ -e $HOME/$kept ]] || fail "T3 Code removal keeps the agent state it bootstrapped" "$kept" +done +pass "T3 Code removal keeps the agent state it bootstrapped" + +# ~/.grok belongs to the Grok CLI that omarchy-default-agent installs. +fresh_home +mkdir -p "$HOME/.config/Grok Bot" "$HOME/.grokbot" "$HOME/.grok" +"$ROOT/bin/omarchy-remove-ai-grok-bot" >/dev/null + +[[ ! -e $HOME/.grokbot ]] || fail "Grok Bot removal deletes its own data" +pass "Grok Bot removal deletes its own data" + +[[ -d $HOME/.grok ]] || fail "Grok Bot removal keeps the Grok CLI's state" +pass "Grok Bot removal keeps the Grok CLI's state" + +# Every acceleration variant depends on the base package, so package presence is +# the test the remover can actually act on; the command alone is also provided by +# builds omarchy-pkg-drop will not touch. +ollama_row=$(grep '^ "remove.ai.ollama":' "$ROOT/default/omarchy/omarchy-menu.jsonc") +[[ $ollama_row == *'"when":"omarchy-pkg-present ollama"'* ]] || + fail "Ollama removal is offered only where the package is installed" "$ollama_row" +pass "Ollama removal is offered only where the package is installed"