Hermes is only ever installed through the app. Choosing it as the default agent used to stand aside for a hermes that worked but came from somewhere else, and to refuse one that predated seeded sessions; both left the machine on a Hermes that was not the app's, which is the one Omarchy prepares for in-app updates and hands the theme to. Now --check answers only for the app's own Hermes, and --now installs the app whatever answered to hermes before, saving the previous command aside as it always did for the app path. The desktop installer no longer adds the package itself, since the shared install does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
260 lines
12 KiB
Bash
Executable File
260 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
test_tmp=$(mktemp -d)
|
|
trap 'rm -rf "$test_tmp"' EXIT
|
|
|
|
mock_bin="$test_tmp/bin"
|
|
test_home="$test_tmp/home"
|
|
hermes="$test_home/.local/bin/hermes"
|
|
ran="$test_tmp/ran"
|
|
mise_log="$test_tmp/mise-log"
|
|
pkg_log="$test_tmp/pkg-log"
|
|
mkdir -p "$mock_bin" "$test_home/.local/bin"
|
|
: >"$mise_log"
|
|
|
|
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
|
|
#!/bin/bash
|
|
[[ ${OMARCHY_TEST_DESKTOP_INSTALLED:-0} == 1 ]]
|
|
SH
|
|
|
|
# Installing the package is the first thing the runtime setup does, so a mock
|
|
# that fails there proves the installer would have installed without running
|
|
# the rest of the setup, which hermes-desktop-install-test.sh covers.
|
|
cat >"$mock_bin/omarchy-pkg-add" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >>"$OMARCHY_TEST_PKG_LOG"
|
|
exit 1
|
|
SH
|
|
|
|
# Hermes never comes from mise any more; the only thing the installer asks
|
|
# mise is to remove what the retired wrapper built. `where` finds that
|
|
# environment and `ls -g` lists it as requested when a test says it was built,
|
|
# until uninstalled and unrequested respectively. Every call is logged.
|
|
cat >"$mock_bin/mise" <<'SH'
|
|
#!/bin/bash
|
|
printf 'mise %s\n' "$*" >>"$OMARCHY_TEST_MISE_LOG"
|
|
case "$1" in
|
|
where)
|
|
[[ ${OMARCHY_TEST_MISE_BUILT:-0} == 1 && ! -e $OMARCHY_TEST_MISE_LOG.removed ]]
|
|
;;
|
|
ls)
|
|
if [[ ${OMARCHY_TEST_MISE_BUILT:-0} == 1 && ! -e $OMARCHY_TEST_MISE_LOG.unrequested ]]; then
|
|
echo '{"pipx:hermes-agent[extras=all]": [{"version": "latest"}]}'
|
|
elif [[ ${OMARCHY_TEST_MISE_SIBLING:-0} == 1 ]]; then
|
|
echo '{"pipx:hermes-agent-tools": [{"version": "latest"}]}'
|
|
else
|
|
echo '{}'
|
|
fi
|
|
;;
|
|
rm)
|
|
touch "$OMARCHY_TEST_MISE_LOG.unrequested"
|
|
;;
|
|
uninstall)
|
|
touch "$OMARCHY_TEST_MISE_LOG.removed"
|
|
rm -f "${OMARCHY_TEST_SHIM:-}"
|
|
;;
|
|
esac
|
|
SH
|
|
|
|
chmod +x "$mock_bin"/*
|
|
|
|
# ~/.local/bin is on PATH the way Omarchy puts it there, after the mocks, so
|
|
# `hermes` resolves to the command under test unless a test shadows it.
|
|
run_installer() {
|
|
OMARCHY_TEST_DESKTOP_INSTALLED="${OMARCHY_TEST_DESKTOP_INSTALLED:-0}" \
|
|
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
|
OMARCHY_TEST_PKG_LOG="$pkg_log" \
|
|
OMARCHY_TEST_RAN="$ran" \
|
|
HOME="$test_home" \
|
|
PATH="$mock_bin:$test_home/.local/bin:$PATH" \
|
|
bash "$ROOT/bin/omarchy-install-hermes-cli" "$@" >"$test_tmp/output" 2>&1
|
|
}
|
|
|
|
# A hermes that runs, defines the flags omarchy-agent passes unless a test says
|
|
# otherwise, and records that it ran. With "ours" it names the runtime the way
|
|
# upstream's launcher does; without, it is a command from somewhere else.
|
|
write_hermes() {
|
|
cat >"$hermes" <<'SH'
|
|
#!/bin/bash
|
|
touch "$OMARCHY_TEST_RAN"
|
|
if [[ ${1:-} == "chat" && ${2:-} == "--help" ]]; then
|
|
printf '%s\n' "${OMARCHY_TEST_HERMES_HELP-[-q QUERY, --query QUERY] [--tui]}"
|
|
else
|
|
echo "hermes-agent 0.0.0-test"
|
|
fi
|
|
SH
|
|
if [[ ${1:-} == "ours" ]]; then
|
|
printf '# stands in for: exec "%s/.hermes/hermes-agent/venv/bin/python" "$@"\n' "$test_home" >>"$hermes"
|
|
fi
|
|
chmod +x "$hermes"
|
|
}
|
|
|
|
legacy_marker="# Written by omarchy-install-hermes-cli."
|
|
|
|
write_legacy_stub() {
|
|
printf '%s\n' "#!/bin/bash" "$legacy_marker" 'touch "$OMARCHY_TEST_RAN"' >"$1"
|
|
chmod +x "$1"
|
|
}
|
|
|
|
# The app's launcher used to call this with no arguments; a default of --now
|
|
# would turn every launch into an install.
|
|
: >"$pkg_log"
|
|
run_installer && fail "the installer runs without a mode"
|
|
grep -q 'Usage' "$test_tmp/output" || fail "a missing mode prints usage"
|
|
[[ ! -s $pkg_log ]] || fail "a missing mode installs nothing"
|
|
pass "the installer needs its mode named"
|
|
|
|
rm -f "$hermes"
|
|
run_installer --check && fail "--check reports a Hermes with nothing at the command's path"
|
|
pass "--check is false with no hermes command"
|
|
|
|
# The retired wrapper built Hermes through mise when run, so it is never run to
|
|
# find out whether Hermes is there, whether it is the file or a link to it.
|
|
write_legacy_stub "$hermes"
|
|
rm -f "$ran"
|
|
run_installer --check && fail "--check reports the retired mise wrapper as a Hermes"
|
|
[[ ! -e $ran ]] || fail "--check runs the retired mise wrapper"
|
|
rm -f "$hermes"
|
|
write_legacy_stub "$test_tmp/stub"
|
|
ln -s "$test_tmp/stub" "$hermes"
|
|
run_installer --check && fail "--check reports a link to the retired wrapper as a Hermes"
|
|
[[ ! -e $ran ]] || fail "--check runs the retired wrapper through a link"
|
|
rm -f "$hermes"
|
|
pass "--check never runs the retired mise wrapper"
|
|
|
|
# Hermes is only ever installed through the app: a hermes that works but did
|
|
# not come from it is not the app's, so --check says no and --now installs the
|
|
# app over it, the command saved aside by the runtime setup further on.
|
|
write_hermes
|
|
run_installer --check && fail "--check calls a hermes without the app installed"
|
|
: >"$pkg_log"
|
|
run_installer --now && fail "--now carries on past the mocked package failure"
|
|
grep -qx 'hermes-desktop' "$pkg_log" || fail "--now installs the app over a hermes from elsewhere" "$(cat "$test_tmp/output")"
|
|
pass "a hermes that did not come from the app is not the app's; choosing Hermes installs the app"
|
|
|
|
rm -f "$hermes"
|
|
: >"$pkg_log"
|
|
run_installer --now && fail "--now carries on past the mocked package failure"
|
|
grep -qx 'hermes-desktop' "$pkg_log" || fail "--now installs the hermes-desktop package" "$(cat "$test_tmp/output")"
|
|
pass "--now installs Hermes Desktop when no Hermes answers"
|
|
|
|
# The retired wrapper is not a Hermes for --now either, and still is not run.
|
|
write_legacy_stub "$hermes"
|
|
rm -f "$ran"
|
|
: >"$pkg_log"
|
|
run_installer --now && fail "--now carries on past the mocked package failure"
|
|
grep -qx 'hermes-desktop' "$pkg_log" || fail "--now installs over the retired wrapper"
|
|
[[ ! -e $ran ]] || fail "--now runs the retired wrapper"
|
|
rm -f "$hermes"
|
|
pass "--now installs over the retired mise wrapper without running it"
|
|
|
|
# With the app installed, a command that runs is not the whole answer: until
|
|
# the runtime carries the marker upstream writes last and the packaged app has
|
|
# been seeded, --now still has minutes of work, so --check says no and the menu
|
|
# opens a terminal for it rather than running it where nobody can see.
|
|
runtime="$test_home/.hermes/hermes-agent"
|
|
native="$runtime/apps/desktop/release/linux-unpacked/resources"
|
|
write_hermes ours
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check calls an app with no runtime installed"
|
|
mkdir -p "$runtime" "$native"
|
|
touch "$runtime/.hermes-bootstrap-complete"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check calls an app whose packaged build is not seeded installed"
|
|
touch "$native/app.asar" "$native/install-stamp.json"
|
|
printf '#!/bin/bash\nexit 0\n' >"$native/../Hermes"
|
|
chmod +x "$native/../Hermes"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check || fail "--check follows a finished install"
|
|
pass "--check says no while --now still has work to do behind the app"
|
|
|
|
# With the app installed, the terminal has to be on the app's Hermes: a working
|
|
# command from somewhere else beside a finished runtime is not installed, so
|
|
# --now gets to put the runtime's own command back.
|
|
write_hermes
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check calls the app installed while the command is somebody else's"
|
|
write_hermes ours
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check || fail "--check follows the runtime's own command"
|
|
pass "--check needs the app's own command once the app is installed"
|
|
|
|
# The flags have to be defined by the help, not merely mentioned in it, and
|
|
# both of them: omarchy-agent passes --query to seed the session and --tui to
|
|
# keep it interactive.
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 OMARCHY_TEST_HERMES_HELP='Run with --tui for a terminal session; see --query in the docs.' run_installer --check &&
|
|
fail "--check accepts flags that are only mentioned"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 OMARCHY_TEST_HERMES_HELP='[--tui]' run_installer --check && fail "--check accepts a hermes without --query"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 OMARCHY_TEST_HERMES_HELP='[-q QUERY, --query QUERY]' run_installer --check && fail "--check accepts a hermes without --tui"
|
|
pass "--check needs both flags defined, not mentioned"
|
|
|
|
chmod -x "$hermes"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check accepts a hermes that is not executable"
|
|
rm -f "$hermes"
|
|
mkdir "$hermes"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check accepts a directory at the command's path"
|
|
rmdir "$hermes"
|
|
ln -s "$test_tmp/nowhere" "$hermes"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check accepts a dangling link"
|
|
rm -f "$hermes"
|
|
write_hermes ours
|
|
pass "--check rejects what is not a command that runs"
|
|
|
|
# Installed and finished: nothing to do, and quickly, because choosing the
|
|
# agent from the menu runs this.
|
|
: >"$pkg_log"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --now || fail "--now accepts a finished install" "$(cat "$test_tmp/output")"
|
|
[[ ! -s $pkg_log && ! -s $test_tmp/output ]] || fail "a finished install is set up again" "$(cat "$test_tmp/output")"
|
|
pass "--now has nothing to do once the app and its runtime are in"
|
|
|
|
[[ ! -s $mise_log ]] || fail "the installer touched mise" "$(cat "$mise_log")"
|
|
pass "Hermes never goes through mise"
|
|
|
|
# A PATH that finds no hermes at all is not in the way: the theme unit asks
|
|
# from a service whose PATH has no ~/.local/bin and runs the command by path.
|
|
run_installer_bare_path() {
|
|
OMARCHY_TEST_DESKTOP_INSTALLED="${OMARCHY_TEST_DESKTOP_INSTALLED:-0}" \
|
|
OMARCHY_TEST_MISE_LOG="$mise_log" OMARCHY_TEST_PKG_LOG="$pkg_log" OMARCHY_TEST_RAN="$ran" \
|
|
HOME="$test_home" PATH="$mock_bin:/usr/bin" \
|
|
bash "$ROOT/bin/omarchy-install-hermes-cli" "$@" >"$test_tmp/output" 2>&1
|
|
}
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer_bare_path --check || fail "--check from a PATH without ~/.local/bin still follows a finished install" "$(cat "$test_tmp/output")"
|
|
pass "--check does not need ~/.local/bin on the caller's PATH"
|
|
|
|
# What omarchy-agent runs is whichever hermes PATH finds first, and Omarchy
|
|
# puts mise's shims ahead of ~/.local/bin. A command ahead of the one the probe
|
|
# vets means the agent would run something else, so it is not installed, and
|
|
# --now says what is in the way rather than reporting a Hermes that is not the
|
|
# one the agent gets.
|
|
cp "$hermes" "$mock_bin/hermes"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check && fail "--check calls a shadowed command installed"
|
|
rm -f "$mock_bin/hermes"
|
|
OMARCHY_TEST_DESKTOP_INSTALLED=1 run_installer --check || fail "--check follows the command once nothing shadows it"
|
|
pass "a hermes ahead of ~/.local/bin on PATH is not the one the agent gets"
|
|
|
|
# The retired wrapper's environment goes with the wrapper's proof, wherever
|
|
# that is. --retire-mise is the migration's whole job; --now does the same once
|
|
# the runtime installer has saved the wrapper aside, so a user who chose Hermes
|
|
# before their migration ran is not left with mise's shim answering `hermes`.
|
|
write_legacy_stub "$hermes"
|
|
: >"$mise_log"; rm -f "$mise_log.removed" "$mise_log.unrequested"
|
|
OMARCHY_TEST_MISE_BUILT=1 run_installer --retire-mise || fail "--retire-mise succeeds over the wrapper" "$(cat "$test_tmp/output")"
|
|
[[ ! -e $hermes ]] || fail "--retire-mise removes the wrapper"
|
|
grep -qF "mise rm -g pipx:hermes-agent[extras=all]" "$mise_log" || fail "--retire-mise removes the global mise Hermes" "$(cat "$mise_log")"
|
|
grep -qF "mise uninstall --all pipx:hermes-agent[extras=all]" "$mise_log" || fail "--retire-mise uninstalls the mise Hermes" "$(cat "$mise_log")"
|
|
: >"$mise_log"
|
|
run_installer --retire-mise || fail "--retire-mise succeeds with nothing of Omarchy's"
|
|
[[ ! -s $mise_log ]] || fail "--retire-mise asks mise about an environment nothing proves Omarchy's" "$(cat "$mise_log")"
|
|
pass "--retire-mise removes the wrapper and the environment it built, and only with proof"
|
|
|
|
# A global tool whose name merely starts the same way is not the retired one;
|
|
# read as it, it could never be removed and the migration would stay pending.
|
|
write_legacy_stub "$hermes"
|
|
: >"$mise_log"
|
|
OMARCHY_TEST_MISE_SIBLING=1 run_installer --retire-mise || fail "--retire-mise succeeds beside a tool named like the retired one" "$(cat "$test_tmp/output")"
|
|
[[ ! -e $hermes ]] || fail "the wrapper goes when only a similarly named tool is requested"
|
|
! grep -q '^mise rm' "$mise_log" || fail "a similarly named tool is taken for the retired one" "$(cat "$mise_log")"
|
|
pass "a mise tool named like the retired one does not keep the migration pending"
|
|
|
|
# --now finishing a handover, mise environment and shim included, needs the
|
|
# package fixtures and lives in hermes-desktop-install-test.sh.
|