diff --git a/bin/omarchy-install-hermes-cli b/bin/omarchy-install-hermes-cli index 7ae9410a..4a6923a3 100755 --- a/bin/omarchy-install-hermes-cli +++ b/bin/omarchy-install-hermes-cli @@ -101,8 +101,8 @@ hermes_prompt_ready() { local help hermes_runs && help=$(timeout 15 "$HOME/.local/bin/hermes" chat --help 2>/dev/null) && - grep -qE -- '--tui([^[:alnum:]-]|$)' <<<"$help" && - grep -qE -- '--query([^[:alnum:]-]|$)' <<<"$help" + grep -qE -- '--tui([^[:alnum:]_-]|$)' <<<"$help" && + grep -qE -- '--query([^[:alnum:]_-]|$)' <<<"$help" } # --owns answers whether the wrapper on PATH is the one this command wrote, so @@ -113,18 +113,20 @@ if [[ $mode == "--owns" ]]; then fi # --remove tears down a Hermes CLI this installer put in place -- the mise tool -# it installs and the stub it marks -- so Remove Hermes clears a CLI the app +# it installed and the stub it marked -- so Remove Hermes clears a CLI the app # never superseded (an interrupted install, or the terminal CLI from before the -# app existed) rather than leaving it stranded on PATH. Scoped to what is ours: -# the tool spec is this installer's, and the stub goes only when it carries the -# marker, so a Hermes the user installed themselves is untouched. Idempotent -- -# nothing installed means nothing to do -- and it is the same teardown the -# desktop takeover above performs, kept in one place so the two cannot drift. +# app existed) rather than leaving it stranded on PATH. The whole teardown +# turns on the marked stub, exactly as replacement does further down: without +# it nothing proves the mise environment is Omarchy's rather than one the user +# built against the same spec, and a user's stays theirs. The desktop takeover +# removes the environment without asking, but that is its own bargain -- a +# second Hermes has to go whoever built it, and the app still provides the +# command afterwards; here nothing would. Idempotent: nothing owned, nothing +# to do. if [[ $mode == "--remove" ]]; then - mise rm -g "$tool" >/dev/null 2>&1 || true - mise uninstall --all "$tool" >/dev/null 2>&1 || true - if ours; then + mise rm -g "$tool" >/dev/null 2>&1 || true + mise uninstall --all "$tool" >/dev/null 2>&1 || true rm -f "$HOME/.local/bin/hermes" fi diff --git a/test/shell.d/hermes-cli-test.sh b/test/shell.d/hermes-cli-test.sh index a70589ef..69a52f61 100755 --- a/test/shell.d/hermes-cli-test.sh +++ b/test/shell.d/hermes-cli-test.sh @@ -421,6 +421,10 @@ run_remove() { rm -f "$remove_home/.local/bin/hermes" : >"$mise_log" run_remove || fail "--remove succeeds when there is nothing to remove" +# No stub means no proof the mise environment -- if one even exists -- is +# Omarchy's, so nothing may reach mise at all. +tr '\0' '\n' <"$mise_log" | grep -Eq '^(rm|uninstall)$' && + fail "--remove leaves mise alone when nothing proves ownership" pass "--remove is idempotent when no Hermes CLI is present" printf '%s\n' "#!/bin/bash" "$stub_marker" >"$remove_home/.local/bin/hermes" @@ -436,9 +440,14 @@ foreign_remove_body="#!/bin/bash exec /usr/local/bin/my-own-hermes \"\$@\"" printf '%s\n' "$foreign_remove_body" >"$remove_home/.local/bin/hermes" chmod +x "$remove_home/.local/bin/hermes" -run_remove || fail "--remove succeeds with a foreign hermes present" +: >"$mise_log" +OMARCHY_TEST_MISE_WHERE_OK=1 run_remove || fail "--remove succeeds with a foreign hermes present" [[ -f $remove_home/.local/bin/hermes && $(cat "$remove_home/.local/bin/hermes") == "$foreign_remove_body" ]] || fail "--remove leaves a hermes it does not own untouched" +# The wrapper may front a mise environment the user built against the very same +# spec; without the marker there is no telling, so the environment stays too. +tr '\0' '\n' <"$mise_log" | grep -Eq '^(rm|uninstall)$' && + fail "--remove never removes a mise environment it cannot prove is Omarchy's" pass "--remove leaves a Hermes the user installed themselves" # The app's marker says its install once landed, not that it is still there. A @@ -501,3 +510,34 @@ SH chmod +x "$ready_home/.hermes/hermes-agent/venv/bin/hermes" run_ready_check && fail "--check accepts a release whose flags only contain --tui/--query as a substring" pass "a flag that merely contains --tui or --query is not prompt-ready" + +# Each flag answers for itself: a release that kept --tui but dropped --query, +# or the reverse, cannot run the seeded session either, so neither grep may +# ride on the other's match. +for kept in '--tui' '-q QUERY, --query QUERY'; do + cat >"$ready_home/.hermes/hermes-agent/venv/bin/hermes" <"$ready_home/.hermes/hermes-agent/venv/bin/hermes" <<'SH' +#!/bin/bash +if [[ ${1:-} == "chat" && ${2:-} == "--help" ]]; then + echo "[--tui_mode MODE] [--query_log FILE]" +else + echo "hermes-agent 0.0.0-test" +fi +SH +chmod +x "$ready_home/.hermes/hermes-agent/venv/bin/hermes" +run_ready_check && fail "--check accepts flags that extend --tui/--query with an underscore" +pass "an underscore continuation is not the bare flag" diff --git a/test/shell.d/hermes-remove-test.sh b/test/shell.d/hermes-remove-test.sh index c590f0d3..6045c9a1 100755 --- a/test/shell.d/hermes-remove-test.sh +++ b/test/shell.d/hermes-remove-test.sh @@ -104,6 +104,10 @@ printf 'my local edit\n' >"$test_home/.hermes/hermes-agent/PATCH" printf '%s\n' "#!/bin/bash" "exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\"" \ >"$test_home/.local/bin/hermes" remove || fail "remove succeeds when the app never finished installing Hermes" +# The stranded pre-desktop CLI is exactly the interrupted-install case, so the +# teardown must be asked for here too, not only when the app's runtime landed. +tr '\0' '\n' <"$test_tmp/installer-log" | grep -qx -- '--remove' || + fail "removal tears down the CLI even when the app never finished installing" [[ -d $test_home/.hermes/hermes-agent ]] || fail "a Hermes runtime the app never installed survives removal" [[ -f $test_home/.hermes/hermes-agent/PATCH ]] ||