Ask, default no, before Remove Hermes deletes the user's data
This commit is contained in:
3 files changed
+94
-9
No files matched your search
@@ -25,11 +25,8 @@ omarchy-install-hermes-cli --remove || cli_removed=false
|
|||||||
# has whatever was there before, and none of it is ours to delete.
|
# has whatever was there before, and none of it is ours to delete.
|
||||||
if [[ -f $HOME/.hermes/hermes-agent/.hermes-bootstrap-complete ]]; then
|
if [[ -f $HOME/.hermes/hermes-agent/.hermes-bootstrap-complete ]]; then
|
||||||
# The checkout and venv, its own uv, its own node. None of it is any use once
|
# The checkout and venv, its own uv, its own node. None of it is any use once
|
||||||
# the app is gone. Not ~/.config/Hermes, which holds the gateway connections
|
# the app is gone, so it goes without asking; what the user made with the app
|
||||||
# and their encrypted tokens, the active profile and the update settings. Not
|
# is a different question, answered below.
|
||||||
# the rest of ~/.hermes either: the chats, memories and the skills Hermes
|
|
||||||
# wrote for itself are the user's, they are small, and finding them still
|
|
||||||
# there after a reinstall is the better surprise.
|
|
||||||
rm -rf \
|
rm -rf \
|
||||||
"$HOME/.hermes/hermes-agent" \
|
"$HOME/.hermes/hermes-agent" \
|
||||||
"$HOME/.hermes/bootstrap-cache" \
|
"$HOME/.hermes/bootstrap-cache" \
|
||||||
@@ -58,10 +55,35 @@ if [[ -f $HOME/.hermes/hermes-agent/.hermes-bootstrap-complete ]]; then
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# What is left is the user's: the chats, memories and skills in ~/.hermes,
|
||||||
|
# the connections and their encrypted tokens in ~/.config/Hermes. Keeping
|
||||||
|
# them stays the default -- they are small, and finding them intact after a
|
||||||
|
# reinstall is the better surprise -- but a removal meant to be complete
|
||||||
|
# should not leave credentials behind either, so the choice is put in front
|
||||||
|
# of the user, default no. Only here, behind the bootstrap marker: without
|
||||||
|
# it ~/.hermes is an install the app never owned, and offering to delete it
|
||||||
|
# would put the user's own Hermes on the chopping block. Without a terminal
|
||||||
|
# to ask in, keeping it is the answer.
|
||||||
|
data_removed=false
|
||||||
|
if [[ -t 0 ]] && command -v gum >/dev/null; then
|
||||||
|
# du answers non-zero when either directory is missing (the app makes
|
||||||
|
# ~/.config/Hermes, but nothing says it is still there), and pipefail
|
||||||
|
# would turn that into an aborted removal; the size is worth no such thing.
|
||||||
|
size=$(du -shc "$HOME/.hermes" "$HOME/.config/Hermes" 2>/dev/null | tail -1 | cut -f1 || true)
|
||||||
|
if gum confirm --default=false "Also delete your Hermes data ($size: chats, memories, skills, connections and tokens)?"; then
|
||||||
|
rm -rf "$HOME/.hermes" "$HOME/.config/Hermes"
|
||||||
|
data_removed=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Hermes Desktop has been removed."
|
echo "Hermes Desktop has been removed."
|
||||||
echo "Your chats, memories, and skills are still in ~/.hermes,"
|
if [[ $data_removed == true ]]; then
|
||||||
echo "and your connections and settings in ~/.config/Hermes."
|
echo "Its chats, memories, and settings in ~/.hermes and ~/.config/Hermes are gone too."
|
||||||
|
else
|
||||||
|
echo "Your chats, memories, and skills are still in ~/.hermes,"
|
||||||
|
echo "and your connections and settings in ~/.config/Hermes."
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
echo "Hermes Desktop has been removed."
|
echo "Hermes Desktop has been removed."
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ Crashes can also be silenced one program at a time, which is what the diagnosis
|
|||||||
|
|
||||||
The _Install > AI_ menu also carries a few graphical AI apps: the ChatGPT desktop app, Grok Bot for chatting with xAI's models, and Hermes Desktop.
|
The _Install > AI_ menu also carries a few graphical AI apps: the ChatGPT desktop app, Grok Bot for chatting with xAI's models, and Hermes Desktop.
|
||||||
|
|
||||||
Hermes Desktop is the one to know about, because there is only ever one Hermes on a machine. The app only runs against a runtime built from its own commit, so it installs one of its own under `~/.hermes` on first launch, which takes a few minutes and shows its own progress. From then on that is the Hermes the terminal `hermes` command and the default agent use too, whichever order you installed them in. Removing the app under _Remove > AI_ takes that runtime with it, and keeps your chats, memories, and the skills Hermes wrote for itself.
|
Hermes Desktop is the one to know about, because there is only ever one Hermes on a machine. The app only runs against a runtime built from its own commit, so it installs one of its own under `~/.hermes` on first launch, which takes a few minutes and shows its own progress. From then on that is the Hermes the terminal `hermes` command and the default agent use too, whichever order you installed them in. Removing the app under _Remove > AI_ takes that runtime with it, and keeps your chats, memories, and the skills Hermes wrote for itself unless you tell it otherwise: it asks, defaulting to no, whether that data and your connection settings should go too.
|
||||||
|
|
||||||
### Local LLMs
|
### Local LLMs
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ cat >"$mock_bin/omarchy-install-hermes-cli" <<'SH'
|
|||||||
printf '%s\0' "$@" >>"$OMARCHY_TEST_INSTALLER_LOG"
|
printf '%s\0' "$@" >>"$OMARCHY_TEST_INSTALLER_LOG"
|
||||||
exit "${OMARCHY_TEST_INSTALLER_STATUS:-0}"
|
exit "${OMARCHY_TEST_INSTALLER_STATUS:-0}"
|
||||||
SH
|
SH
|
||||||
|
|
||||||
|
# The remover asks through gum whether the user's data should go too. The stub
|
||||||
|
# answers "no" unless a test says otherwise, and logs every call: a real gum
|
||||||
|
# would hang a test run, and one that answered "yes" on its own would be the
|
||||||
|
# very data loss the default-no exists to prevent.
|
||||||
|
cat >"$mock_bin/gum" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
printf '%s\0' "$@" >>"$OMARCHY_TEST_GUM_LOG"
|
||||||
|
exit "${OMARCHY_TEST_GUM_STATUS:-1}"
|
||||||
|
SH
|
||||||
chmod +x "$mock_bin"/*
|
chmod +x "$mock_bin"/*
|
||||||
|
|
||||||
seed_install() {
|
seed_install() {
|
||||||
@@ -43,13 +53,30 @@ seed_install() {
|
|||||||
touch "$test_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
touch "$test_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# </dev/null pins stdin off a terminal, so these runs exercise the
|
||||||
|
# non-interactive path no matter where the suite itself is running.
|
||||||
remove() {
|
remove() {
|
||||||
: >"$test_tmp/installer-log"
|
: >"$test_tmp/installer-log"
|
||||||
|
: >"$test_tmp/gum-log"
|
||||||
OMARCHY_TEST_DROP_LOG="$test_tmp/drop-log" \
|
OMARCHY_TEST_DROP_LOG="$test_tmp/drop-log" \
|
||||||
OMARCHY_TEST_INSTALLER_LOG="$test_tmp/installer-log" \
|
OMARCHY_TEST_INSTALLER_LOG="$test_tmp/installer-log" \
|
||||||
OMARCHY_TEST_INSTALLER_STATUS="${OMARCHY_TEST_INSTALLER_STATUS:-0}" \
|
OMARCHY_TEST_INSTALLER_STATUS="${OMARCHY_TEST_INSTALLER_STATUS:-0}" \
|
||||||
|
OMARCHY_TEST_GUM_LOG="$test_tmp/gum-log" \
|
||||||
HOME="$test_home" PATH="$mock_bin:$PATH" \
|
HOME="$test_home" PATH="$mock_bin:$PATH" \
|
||||||
bash "$ROOT/bin/omarchy-remove-ai-hermes" >/dev/null 2>&1
|
bash "$ROOT/bin/omarchy-remove-ai-hermes" </dev/null >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# script(1) puts the remover on a pty, which is the only way -t 0 answers true
|
||||||
|
# without a person at a real one; the stubbed gum then supplies the answer.
|
||||||
|
remove_tty() {
|
||||||
|
: >"$test_tmp/installer-log"
|
||||||
|
: >"$test_tmp/gum-log"
|
||||||
|
OMARCHY_TEST_DROP_LOG="$test_tmp/drop-log" \
|
||||||
|
OMARCHY_TEST_INSTALLER_LOG="$test_tmp/installer-log" \
|
||||||
|
OMARCHY_TEST_GUM_LOG="$test_tmp/gum-log" \
|
||||||
|
OMARCHY_TEST_GUM_STATUS="${OMARCHY_TEST_GUM_STATUS:-1}" \
|
||||||
|
HOME="$test_home" PATH="$mock_bin:$PATH" \
|
||||||
|
script -qec "bash '$ROOT/bin/omarchy-remove-ai-hermes'" /dev/null >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
# The app brings its own uv and its own node; both are runtime, not data.
|
# The app brings its own uv and its own node; both are runtime, not data.
|
||||||
@@ -77,6 +104,12 @@ pass "removal clears only the managed Node links it stranded"
|
|||||||
[[ -f $test_home/.hermes/SOUL.md ]] || fail "SOUL.md survives removal"
|
[[ -f $test_home/.hermes/SOUL.md ]] || fail "SOUL.md survives removal"
|
||||||
pass "removal keeps what belongs to the user"
|
pass "removal keeps what belongs to the user"
|
||||||
|
|
||||||
|
# Without a terminal there is nobody to ask, so gum must not even be reached:
|
||||||
|
# a gum that answered "yes" on its own would be a data loss.
|
||||||
|
[[ ! -s $test_tmp/gum-log ]] ||
|
||||||
|
fail "removal does not ask about the user's data without a terminal"
|
||||||
|
pass "removal keeps the user's data unasked when there is no terminal"
|
||||||
|
|
||||||
[[ ! -e $test_home/.local/bin/hermes ]] || fail "the app's own hermes command is removed"
|
[[ ! -e $test_home/.local/bin/hermes ]] || fail "the app's own hermes command is removed"
|
||||||
pass "removal takes the command the app installed"
|
pass "removal takes the command the app installed"
|
||||||
|
|
||||||
@@ -134,6 +167,36 @@ remove || fail "remove succeeds with a wrapper pointing at a sibling directory"
|
|||||||
fail "a wrapper pointing at ~/xhermes is not mistaken for one pointing into ~/.hermes"
|
fail "a wrapper pointing at ~/xhermes is not mistaken for one pointing into ~/.hermes"
|
||||||
pass "removal matches the runtime path as a plain string"
|
pass "removal matches the runtime path as a plain string"
|
||||||
|
|
||||||
|
# On a terminal the user is asked, default no: declining leaves every piece of
|
||||||
|
# data where it was.
|
||||||
|
seed_install
|
||||||
|
remove_tty || fail "remove succeeds when the data question is declined"
|
||||||
|
tr '\0' '\n' <"$test_tmp/gum-log" | grep -qx 'confirm' ||
|
||||||
|
fail "removal asks about the user's data on a terminal"
|
||||||
|
[[ -f $test_home/.hermes/sessions/one.json && -d $test_home/.config/Hermes ]] ||
|
||||||
|
fail "declining the question keeps the user's data"
|
||||||
|
pass "removal asks on a terminal and declining keeps the data"
|
||||||
|
|
||||||
|
# An explicit yes is the one path that takes the data too.
|
||||||
|
seed_install
|
||||||
|
OMARCHY_TEST_GUM_STATUS=0 remove_tty || fail "remove succeeds when the data goes too"
|
||||||
|
[[ ! -e $test_home/.hermes && ! -e $test_home/.config/Hermes ]] ||
|
||||||
|
fail "a yes deletes ~/.hermes and ~/.config/Hermes"
|
||||||
|
pass "removal deletes the user's data only on an explicit yes"
|
||||||
|
|
||||||
|
# Without the bootstrap marker ~/.hermes is an install the app never owned, so
|
||||||
|
# it must not even be offered for deletion -- not to a terminal, not to a user
|
||||||
|
# who would say yes.
|
||||||
|
seed_install
|
||||||
|
rm -f "$test_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
||||||
|
OMARCHY_TEST_GUM_STATUS=0 remove_tty ||
|
||||||
|
fail "remove succeeds when the app never installed Hermes"
|
||||||
|
[[ ! -s $test_tmp/gum-log ]] ||
|
||||||
|
fail "a Hermes the app never installed is not offered for deletion"
|
||||||
|
[[ -d $test_home/.hermes/hermes-agent && -d $test_home/.config/Hermes ]] ||
|
||||||
|
fail "a Hermes the app never installed survives a would-be yes"
|
||||||
|
pass "removal never offers a Hermes the app did not install"
|
||||||
|
|
||||||
# A CLI teardown that fails must not stop the runtime handling, and must not be
|
# A CLI teardown that fails must not stop the runtime handling, and must not be
|
||||||
# papered over either: the data work still happens, and the failure reaches the
|
# papered over either: the data work still happens, and the failure reaches the
|
||||||
# caller's exit code.
|
# caller's exit code.
|
||||||
|
|||||||
Reference in new issue
Block a user