Merge pull request #7469 from omacom/hermes-agent
Add Hermes as a desktop app and a coding agent
This commit is contained in:
24 files changed
+1216
-19
No files matched your search
@@ -431,8 +431,10 @@ assert_launched() {
|
||||
fail "$agent launch $description" "expected: ${expected[*]}\nactual: ${actual[*]}"
|
||||
|
||||
for ((index = 0; index < ${#expected[@]}; index++)); do
|
||||
[[ ${actual[$index]} == ${expected[$index]} ]] ||
|
||||
fail "$agent launch $description" "expected: ${expected[*]}\nactual: ${actual[*]}"
|
||||
case ${actual[$index]} in
|
||||
"${expected[$index]}") ;;
|
||||
*) fail "$agent launch $description" "expected: ${expected[*]}\nactual: ${actual[*]}" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
@@ -462,10 +464,18 @@ assert_launch claude claude --permission-mode auto -- "Review this project"
|
||||
assert_launch codex codex --approve-for-me -- "Review this project"
|
||||
assert_launch crush crush run "Review this project"
|
||||
assert_launch grok grok --permission-mode bypassPermissions -- "Review this project"
|
||||
assert_launch hermes env -u HERMES_SESSION_SOURCE hermes chat --yolo --tui "--query=Review this project"
|
||||
assert_launch agy agy --dangerously-skip-permissions --prompt-interactive "Review this project"
|
||||
assert_launch copilot copilot --allow-all --interactive "Review this project"
|
||||
pass "agent launcher adapts initial prompts for every supported agent"
|
||||
|
||||
literal_hermes_prompt=$' --help !Crash /quit {$(touch must-not-run)}\ntrailing\\ '
|
||||
printf '%s\n' "hermes" >"$agent_file"
|
||||
omarchy-agent-prompt "$literal_hermes_prompt"
|
||||
assert_launched hermes "binds its literal initial prompt" env -u HERMES_SESSION_SOURCE \
|
||||
hermes chat --yolo --tui "--query=$literal_hermes_prompt"
|
||||
pass "Hermes receives prompted launches as one literal query argument"
|
||||
|
||||
assert_bypass pi pi
|
||||
assert_bypass omp omp --auto-approve
|
||||
assert_bypass opencode opencode --auto
|
||||
@@ -474,6 +484,7 @@ assert_bypass claude claude --permission-mode auto
|
||||
assert_bypass codex codex --approve-for-me
|
||||
assert_bypass crush crush --yolo
|
||||
assert_bypass grok grok --permission-mode bypassPermissions
|
||||
assert_bypass hermes hermes --yolo
|
||||
assert_bypass agy agy --dangerously-skip-permissions
|
||||
assert_bypass copilot copilot --allow-all
|
||||
pass "agent launcher skips permission prompts for every supported agent"
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
migration="$ROOT/migrations/1787760281.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"
|
||||
marker="# Written by omarchy-install-hermes-cli."
|
||||
mkdir -p "$mock_bin" "$test_home/.local/bin" "$test_home/.local/state/omarchy"
|
||||
|
||||
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ ${OMARCHY_TEST_DESKTOP_INSTALLED:-0} == 1 ]]
|
||||
SH
|
||||
|
||||
cat >"$mock_bin/omarchy-cmd-missing" <<'SH'
|
||||
#!/bin/bash
|
||||
! command -v "$1" >/dev/null 2>&1
|
||||
SH
|
||||
|
||||
mise_log="$test_tmp/mise-log"
|
||||
cat >"$mock_bin/mise" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\0' "$@" >>"$OMARCHY_TEST_MISE_LOG"
|
||||
[[ $1 != "where" ]]
|
||||
SH
|
||||
|
||||
chmod +x "$mock_bin"/*
|
||||
|
||||
# The real installer is on PATH so the migration writes today's stub, not a
|
||||
# copy of it.
|
||||
run_migration() {
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED="${1:-0}" \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$test_home" \
|
||||
PATH="$mock_bin:$ROOT/bin:$PATH" \
|
||||
bash -euo pipefail "$migration" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_migration || fail "the migration installs the wrapper on a plain install"
|
||||
[[ -x $hermes ]] && grep -qxF "$marker" "$hermes" || fail "the migration writes the Omarchy wrapper"
|
||||
pass "the migration installs the Hermes wrapper"
|
||||
|
||||
before=$(cat "$hermes")
|
||||
run_migration || fail "rerunning the migration succeeds"
|
||||
[[ $(cat "$hermes") == "$before" ]] || fail "rerunning the migration leaves the same wrapper"
|
||||
pass "the migration is idempotent"
|
||||
|
||||
chmod -x "$hermes"
|
||||
run_migration || fail "the migration repairs a non-executable Omarchy wrapper"
|
||||
[[ -x $hermes ]] && grep -qxF "$marker" "$hermes" ||
|
||||
fail "the migration restores a non-executable Omarchy wrapper"
|
||||
pass "the migration repairs a non-executable Omarchy wrapper"
|
||||
|
||||
rm -f "$hermes"
|
||||
touch "$test_home/.local/state/omarchy/preinstalls-removed"
|
||||
run_migration || fail "the migration succeeds for users who removed the preinstalls"
|
||||
[[ ! -e $hermes ]] || fail "the migration respects the preinstalls opt-out"
|
||||
pass "the migration skips users who removed the preinstalls"
|
||||
rm -f "$test_home/.local/state/omarchy/preinstalls-removed"
|
||||
|
||||
run_migration 1 || fail "the migration succeeds when Hermes Desktop owns Hermes"
|
||||
[[ ! -e $hermes ]] || fail "the migration writes nothing when Hermes Desktop owns Hermes"
|
||||
pass "the migration stands aside for Hermes Desktop"
|
||||
|
||||
# Standing aside is not the same as leaving a second Hermes behind: the wrapper
|
||||
# an earlier install wrote and the mise copy it points at both go when the
|
||||
# desktop app owns Hermes, even though the app has not finished setting up.
|
||||
printf '%s\n' "#!/bin/bash" "$marker" >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
: >"$mise_log"
|
||||
run_migration 1 || fail "the migration succeeds when Hermes Desktop owns Hermes and the old wrapper is present"
|
||||
[[ ! -e $hermes ]] || fail "the migration removes the Omarchy wrapper when Hermes Desktop owns Hermes"
|
||||
mise_calls=$(tr '\0' ' ' <"$mise_log")
|
||||
[[ $mise_calls == *"rm -g "* ]] || fail "the migration removes the global mise Hermes for Hermes Desktop"
|
||||
[[ $mise_calls == *"uninstall --all "* ]] || fail "the migration uninstalls the mise Hermes for Hermes Desktop"
|
||||
pass "the migration clears the old Omarchy Hermes for Hermes Desktop"
|
||||
|
||||
# ...while anyone else's hermes stays exactly where it is, and is not run.
|
||||
foreign_ran="$test_tmp/foreign-ran"
|
||||
foreign_body="#!/bin/bash
|
||||
touch $foreign_ran
|
||||
exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\""
|
||||
printf '%s\n' "$foreign_body" >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
run_migration 1 || fail "the migration succeeds over a foreign hermes when Hermes Desktop owns Hermes"
|
||||
[[ -x $hermes && $(cat "$hermes") == "$foreign_body" ]] ||
|
||||
fail "the migration leaves a foreign hermes alone when Hermes Desktop owns Hermes"
|
||||
[[ ! -e $foreign_ran ]] || fail "the migration does not run a foreign hermes"
|
||||
pass "the migration preserves a foreign hermes for Hermes Desktop"
|
||||
rm -f "$hermes"
|
||||
|
||||
official_body="#!/bin/bash
|
||||
unset PYTHONPATH
|
||||
unset PYTHONHOME
|
||||
exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\""
|
||||
printf '%s\n' "$official_body" >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
run_migration || fail "the migration succeeds over a foreign hermes command"
|
||||
[[ $(cat "$hermes") == "$official_body" ]] || fail "the migration leaves a foreign hermes command alone"
|
||||
pass "the migration preserves a foreign hermes command"
|
||||
|
||||
chmod -x "$hermes"
|
||||
run_migration || fail "the migration succeeds over a non-executable foreign hermes"
|
||||
[[ -f $hermes && ! -x $hermes && $(cat "$hermes") == "$official_body" ]] ||
|
||||
fail "the migration leaves a non-executable foreign hermes alone"
|
||||
pass "the migration preserves a non-executable foreign hermes"
|
||||
|
||||
rm -f "$hermes"
|
||||
ln -s "$test_home/nowhere/hermes" "$hermes"
|
||||
run_migration || fail "the migration succeeds over a dangling hermes link"
|
||||
[[ -L $hermes && $(readlink "$hermes") == "$test_home/nowhere/hermes" ]] ||
|
||||
fail "the migration leaves a dangling hermes link alone"
|
||||
pass "the migration preserves a dangling hermes link"
|
||||
|
||||
rm -f "$hermes"
|
||||
mkdir "$hermes"
|
||||
run_migration || fail "the migration succeeds over a directory at the hermes path"
|
||||
[[ -d $hermes ]] || fail "the migration leaves a directory at the hermes path alone"
|
||||
pass "the migration preserves a directory at the hermes path"
|
||||
|
||||
rmdir "$hermes"
|
||||
printf '%s\n' "#!/bin/bash" "# Replaces the stub omarchy-install-hermes-cli used to write." >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
run_migration || fail "the migration succeeds over a wrapper that mentions the installer"
|
||||
grep -qxF "$marker" "$hermes" && fail "the migration does not rewrite a wrapper that merely mentions the installer"
|
||||
pass "the migration preserves a wrapper that merely mentions the installer"
|
||||
Executable
+398
@@ -0,0 +1,398 @@
|
||||
#!/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"
|
||||
mise_log="$test_tmp/mise-log"
|
||||
mkdir -p "$mock_bin" "$test_home/.local/bin"
|
||||
|
||||
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ ${OMARCHY_TEST_DESKTOP_INSTALLED:-0} == 1 ]]
|
||||
SH
|
||||
|
||||
cat >"$mock_bin/omarchy-cmd-missing" <<'SH'
|
||||
#!/bin/bash
|
||||
! command -v "$1" >/dev/null 2>&1
|
||||
SH
|
||||
|
||||
# `mise where` must fail so the installer sees no Hermes behind the stub.
|
||||
cat >"$mock_bin/mise" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\0' "$@" >>"$OMARCHY_TEST_MISE_LOG"
|
||||
if [[ $1 == "where" && ${OMARCHY_TEST_MISE_WHERE_OK:-0} == 1 ]]; then
|
||||
printf '%s\n' "$OMARCHY_TEST_MISE_ROOT"
|
||||
exit 0
|
||||
fi
|
||||
[[ $1 != "where" ]]
|
||||
SH
|
||||
|
||||
chmod +x "$mock_bin"/*
|
||||
|
||||
run_installer() {
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED="$1" \
|
||||
OMARCHY_TEST_MISE_WHERE_OK="${OMARCHY_TEST_MISE_WHERE_OK:-0}" \
|
||||
OMARCHY_TEST_MISE_ROOT="$test_tmp/mise" \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$test_home" \
|
||||
PATH="$mock_bin:$PATH" \
|
||||
bash "$ROOT/bin/omarchy-install-hermes-cli" ${2:+"$2"} >/dev/null 2>&1
|
||||
}
|
||||
|
||||
stub_marker="# Written by omarchy-install-hermes-cli."
|
||||
python_pin="3.13"
|
||||
app_stub_body='#!/bin/bash
|
||||
exec /home/x/.hermes/hermes-agent/venv/bin/hermes "$@"'
|
||||
|
||||
# Writing the stub must not provision anything: user setup calls this on every
|
||||
# machine, including the ones that never run Hermes.
|
||||
: >"$mise_log"
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
run_installer 0 || fail "installer failed with no desktop installed"
|
||||
[[ -x $test_home/.local/bin/hermes ]] || fail "installer writes a hermes stub when the desktop is absent"
|
||||
grep -qxF "$stub_marker" "$test_home/.local/bin/hermes" || fail "the stub records which command wrote it"
|
||||
tr '\0' ' ' <"$mise_log" | grep -q "use -g --quiet uv" &&
|
||||
fail "writing the stub does not install uv"
|
||||
pass "writing the Hermes stub provisions nothing"
|
||||
|
||||
# The desktop app owns Hermes, so our own stub must go rather than sit there
|
||||
# answering `hermes` until the app's bootstrap replaces it.
|
||||
printf '%s\n' "#!/bin/bash" "$stub_marker" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 1 || true
|
||||
[[ ! -e $test_home/.local/bin/hermes ]] ||
|
||||
fail "the desktop taking over removes the stub this command wrote"
|
||||
pass "installing the desktop app removes the CLI stub"
|
||||
|
||||
# ...but the app's own hermes is not ours to delete.
|
||||
printf '%s\n' "$app_stub_body" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 1 || true
|
||||
[[ -x $test_home/.local/bin/hermes ]] ||
|
||||
fail "the desktop app's own hermes command survives"
|
||||
pass "the app's own hermes command is left alone"
|
||||
|
||||
# A copy mise cannot vouch for is still a second Hermes.
|
||||
printf '%s\n' "#!/bin/bash" "$stub_marker" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
: >"$mise_log"
|
||||
OMARCHY_TEST_MISE_WHERE_OK=1 run_installer 1 || true
|
||||
tr '\0' '\n' <"$mise_log" | grep -q "uninstall" ||
|
||||
fail "takeover removes a mise copy even when it is not healthy"
|
||||
pass "takeover removes an unhealthy mise copy"
|
||||
|
||||
# --check answers about Hermes being usable, not about the venv appearing. The
|
||||
# venv exists from the python-deps stage, several stages before the command.
|
||||
rm -rf "$test_home/.hermes"
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
run_installer 1 --check && fail "--check reports Hermes missing before the app installs it"
|
||||
# The venv command answers the readiness probes, as the real one does: foreign
|
||||
# wrappers below exec it, and the installer runs both before trusting them.
|
||||
mkdir -p "$test_home/.hermes/hermes-agent/venv/bin"
|
||||
cat >"$test_home/.hermes/hermes-agent/venv/bin/hermes" <<'SH'
|
||||
#!/bin/bash
|
||||
if [[ ${1:-} == "chat" && ${2:-} == "--help" ]]; then
|
||||
[[ ${OMARCHY_TEST_HERMES_CAPABLE:-1} == 1 ]] && echo "--oneshot"
|
||||
else
|
||||
echo "hermes-agent 0.0.0-test"
|
||||
fi
|
||||
SH
|
||||
chmod +x "$test_home/.hermes/hermes-agent/venv/bin/hermes"
|
||||
run_installer 1 --check && fail "--check waits for the install to finish, not just the venv"
|
||||
touch "$test_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
||||
printf '%s\n' "#!/bin/bash" "exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\"" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 1 --check || fail "--check reports Hermes present once the app has finished"
|
||||
pass "--check follows the app's completed install"
|
||||
|
||||
# An executable called hermes that belongs to something else is not this
|
||||
# install being ready.
|
||||
printf '%s\n' "#!/bin/bash" "exec /usr/local/bin/somebody-elses-hermes \"\$@\"" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 1 --check && fail "--check rejects a hermes command belonging to something else"
|
||||
pass "--check rejects a foreign hermes command"
|
||||
|
||||
# A hermes the user installed themselves -- the official installer, a wrapper of
|
||||
# their own -- is not ours to replace. --check follows whether it runs, and
|
||||
# installing steps aside so the default agent uses it.
|
||||
official_body="#!/bin/bash
|
||||
unset PYTHONPATH
|
||||
unset PYTHONHOME
|
||||
exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\""
|
||||
printf '%s\n' "$official_body" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check || fail "--check accepts a working foreign hermes command"
|
||||
run_installer 0 || fail "installing over a foreign hermes command returns success"
|
||||
run_installer 0 --now || fail "--now over a foreign hermes command returns success"
|
||||
[[ $(cat "$test_home/.local/bin/hermes") == "$official_body" ]] ||
|
||||
fail "a foreign hermes command is left untouched"
|
||||
pass "a foreign hermes command is preserved and satisfies --check"
|
||||
|
||||
OMARCHY_TEST_HERMES_CAPABLE=0 run_installer 0 --check &&
|
||||
fail "--check rejects a foreign Hermes without native prompted sessions"
|
||||
OMARCHY_TEST_HERMES_CAPABLE=0 run_installer 0 &&
|
||||
fail "installing refuses a foreign Hermes without native prompted sessions"
|
||||
[[ $(cat "$test_home/.local/bin/hermes") == "$official_body" ]] ||
|
||||
fail "an older foreign Hermes command is left untouched"
|
||||
pass "a foreign Hermes must support native prompted sessions"
|
||||
|
||||
# Broken foreign paths are still foreign. They cannot be used, so --check says
|
||||
# so and the installer refuses rather than replacing them.
|
||||
printf '%s\n' "$official_body" >"$test_home/.local/bin/hermes"
|
||||
chmod -x "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check && fail "--check rejects a non-executable foreign hermes"
|
||||
run_installer 0 && fail "the installer does not succeed over a non-executable foreign hermes"
|
||||
[[ -f $test_home/.local/bin/hermes && ! -x $test_home/.local/bin/hermes ]] ||
|
||||
fail "a non-executable foreign hermes is left untouched"
|
||||
pass "a non-executable foreign hermes is preserved"
|
||||
|
||||
# The executable bit is not enough: a wrapper whose interpreter is gone passes
|
||||
# -x and still cannot run. The probe has to run it to find out, and finding
|
||||
# out never touches the file.
|
||||
broken_interp_body="#!$test_home/nowhere/python3
|
||||
print('hermes')"
|
||||
printf '%s\n' "$broken_interp_body" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check && fail "--check rejects a foreign hermes whose interpreter is missing"
|
||||
run_installer 0 && fail "the installer does not succeed over a foreign hermes whose interpreter is missing"
|
||||
run_installer 0 --now && fail "--now does not succeed over a foreign hermes whose interpreter is missing"
|
||||
[[ -x $test_home/.local/bin/hermes && $(cat "$test_home/.local/bin/hermes") == "$broken_interp_body" ]] ||
|
||||
fail "a foreign hermes whose interpreter is missing is left untouched"
|
||||
pass "a foreign hermes with a missing interpreter is preserved and rejected"
|
||||
|
||||
# Likewise a wrapper that execs a target that is no longer there.
|
||||
broken_target_body="#!/bin/bash
|
||||
exec $test_home/nowhere/hermes \"\$@\""
|
||||
printf '%s\n' "$broken_target_body" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check && fail "--check rejects a foreign hermes whose target is missing"
|
||||
run_installer 0 && fail "the installer does not succeed over a foreign hermes whose target is missing"
|
||||
run_installer 0 --now && fail "--now does not succeed over a foreign hermes whose target is missing"
|
||||
[[ -x $test_home/.local/bin/hermes && $(cat "$test_home/.local/bin/hermes") == "$broken_target_body" ]] ||
|
||||
fail "a foreign hermes whose target is missing is left untouched"
|
||||
pass "a foreign hermes with a missing target is preserved and rejected"
|
||||
|
||||
foreign_target="$test_home/foreign/hermes"
|
||||
mkdir -p "$(dirname "$foreign_target")"
|
||||
printf '%s\n' "$official_body" >"$foreign_target"
|
||||
chmod +x "$foreign_target"
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
ln -s "$foreign_target" "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check || fail "--check accepts a foreign link to a working hermes command"
|
||||
run_installer 0 || fail "the installer succeeds over a foreign link to a working hermes command"
|
||||
run_installer 0 --now || fail "--now succeeds over a foreign link to a working hermes command"
|
||||
[[ -L $test_home/.local/bin/hermes && $(readlink "$test_home/.local/bin/hermes") == "$foreign_target" ]] ||
|
||||
fail "a foreign link to a working hermes command is left untouched"
|
||||
pass "a foreign link to a working hermes command is preserved"
|
||||
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
ln -s "$test_home/nowhere/hermes" "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check && fail "--check rejects a dangling hermes link"
|
||||
run_installer 0 && fail "the installer does not succeed over a dangling hermes link"
|
||||
[[ -L $test_home/.local/bin/hermes && $(readlink "$test_home/.local/bin/hermes") == "$test_home/nowhere/hermes" ]] ||
|
||||
fail "a dangling hermes link is left untouched"
|
||||
pass "a dangling hermes link is preserved"
|
||||
|
||||
# A directory passes -x on search permission alone. It is still not a command.
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
mkdir "$test_home/.local/bin/hermes"
|
||||
run_installer 0 --check && fail "--check rejects a directory at the hermes path"
|
||||
run_installer 0 && fail "the installer does not succeed over a directory at the hermes path"
|
||||
[[ -d $test_home/.local/bin/hermes ]] || fail "a directory at the hermes path is left untouched"
|
||||
pass "a directory at the hermes path is preserved and rejected"
|
||||
|
||||
# Mentioning the installer is not the same as being written by it.
|
||||
rmdir "$test_home/.local/bin/hermes"
|
||||
mentions_body="#!/bin/bash
|
||||
# Replaces the stub omarchy-install-hermes-cli used to write.
|
||||
exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\""
|
||||
printf '%s\n' "$mentions_body" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 0 || fail "installing over a wrapper that mentions the installer returns success"
|
||||
[[ $(cat "$test_home/.local/bin/hermes") == "$mentions_body" ]] ||
|
||||
fail "a wrapper that merely mentions the installer is left untouched"
|
||||
pass "ownership needs the exact marker line, not a mention"
|
||||
|
||||
# Our own stub is ours to rewrite, so reinstalling refreshes it to the current
|
||||
# template.
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
printf '%s\n' "#!/bin/bash" "$stub_marker" "# stale template" >"$test_home/.local/bin/hermes"
|
||||
chmod +x "$test_home/.local/bin/hermes"
|
||||
run_installer 0 || fail "reinstalling over our own stub succeeds"
|
||||
grep -qxF "$stub_marker" "$test_home/.local/bin/hermes" || fail "the refreshed stub still carries the marker"
|
||||
grep -q "stale template" "$test_home/.local/bin/hermes" && fail "reinstalling rewrites our own stub"
|
||||
grep -q "exec env -u UV_PYTHON mise x" "$test_home/.local/bin/hermes" || fail "the refreshed stub is the current template"
|
||||
pass "reinstalling refreshes the Omarchy stub"
|
||||
|
||||
mkdir -p "$test_tmp/mise/hermes-agent/lib/python$python_pin"
|
||||
: >"$mise_log"
|
||||
OMARCHY_TEST_MISE_WHERE_OK=1 run_installer 0 || fail "reinstalling replaces an older owned Hermes environment"
|
||||
tr '\0' '\n' <"$mise_log" | grep -q '^rm$' || fail "an older owned Hermes environment is removed from mise config"
|
||||
tr '\0' '\n' <"$mise_log" | grep -q '^uninstall$' || fail "an older owned Hermes environment is uninstalled"
|
||||
pass "reinstalling replaces an older owned Hermes environment"
|
||||
|
||||
rm -f "$test_home/.local/bin/hermes"
|
||||
: >"$mise_log"
|
||||
OMARCHY_TEST_MISE_WHERE_OK=1 run_installer 0 &&
|
||||
fail "installing refuses to claim an unmarked Hermes mise environment"
|
||||
tr '\0' '\n' <"$mise_log" | grep -Eq '^(rm|uninstall)$' &&
|
||||
fail "an unmarked Hermes mise environment is never removed"
|
||||
[[ ! -e $test_home/.local/bin/hermes ]] ||
|
||||
fail "an unmarked Hermes mise environment is not given an Omarchy wrapper"
|
||||
pass "a Hermes mise environment needs wrapper ownership before replacement"
|
||||
|
||||
# install/user/mise.sh is sourced by install/user/all.sh through run_logged,
|
||||
# which runs it under `bash -eE` and hands its exit code back to
|
||||
# omarchy-provision-user's `set -euo pipefail`. Everything that finalizes a user
|
||||
# -- the default browser, the mailto handler, the first-install migration
|
||||
# markers, the finalize-user marker -- runs after that source, so this leaf
|
||||
# returning non-zero costs the user all of it. The Hermes installer is the only
|
||||
# line in it that can fail, and it does exactly that whenever hermes-desktop is
|
||||
# installed but the app has not been launched yet: the case a second user on a
|
||||
# shared machine hits on their first login.
|
||||
mise_sh_home="$test_tmp/mise-sh-home"
|
||||
mkdir -p "$mise_sh_home/.local/bin"
|
||||
|
||||
cat >"$mock_bin/omarchy-mise-install" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "$mock_bin/omarchy-mise-install"
|
||||
|
||||
# Desktop installed, nothing bootstrapped: omarchy-install-hermes-cli exits 1.
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED=1 \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$mise_sh_home" \
|
||||
PATH="$mock_bin:$ROOT/bin:$PATH" \
|
||||
bash "$ROOT/bin/omarchy-install-hermes-cli" >/dev/null 2>&1 &&
|
||||
fail "the Hermes installer exits non-zero when the desktop app has not set Hermes up"
|
||||
|
||||
# Sourced exactly as run_logged does it.
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED=1 \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$mise_sh_home" \
|
||||
PATH="$mock_bin:$ROOT/bin:$PATH" \
|
||||
bash -eE -c 'source "$1"' bash "$ROOT/install/user/mise.sh" >/dev/null 2>&1 ||
|
||||
fail "user setup survives a Hermes install that cannot finish"
|
||||
pass "user setup survives a Hermes install that cannot finish"
|
||||
|
||||
# UV_PYTHON pins the interpreter Hermes is built against. Left in the
|
||||
# environment it reaches Hermes itself and every command the agent shells out
|
||||
# to, so a `uv` run in the user's own project resolves 3.13 there as well --
|
||||
# uv only warns that this contradicts the project's requires-python, then
|
||||
# builds the venv anyway. The stub drops it before handing over.
|
||||
leak_home="$test_tmp/leak-home"
|
||||
leak_bin="$test_tmp/leak-bin"
|
||||
leak_log="$test_tmp/leak-log"
|
||||
leak_prefix="$test_tmp/leak-prefix"
|
||||
mkdir -p "$leak_home/.local/bin" "$leak_bin" "$leak_prefix/hermes-agent/lib/python$python_pin"
|
||||
|
||||
# A mise whose `where` satisfies the stub's probe, so the stub goes straight to
|
||||
# handing over, and whose `x` records the UV_PYTHON it was handed.
|
||||
cat >"$leak_bin/mise" <<SH
|
||||
#!/bin/bash
|
||||
case \$1 in
|
||||
where) echo "$leak_prefix" ;;
|
||||
x) printf '%s' "\${UV_PYTHON-}" >"$leak_log" ;;
|
||||
esac
|
||||
SH
|
||||
chmod +x "$leak_bin/mise"
|
||||
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED=0 \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$leak_home" \
|
||||
PATH="$mock_bin:$PATH" \
|
||||
bash "$ROOT/bin/omarchy-install-hermes-cli" >/dev/null 2>&1 ||
|
||||
fail "the installer writes a stub for the leak check"
|
||||
|
||||
HOME="$leak_home" PATH="$leak_bin:$mock_bin:$PATH" \
|
||||
"$leak_home/.local/bin/hermes" --version >/dev/null 2>&1
|
||||
|
||||
[[ -f $leak_log ]] || fail "the stub reaches the command it wraps"
|
||||
[[ -z $(cat "$leak_log") ]] ||
|
||||
fail "the interpreter pin does not follow Hermes into the commands it runs"
|
||||
pass "the interpreter pin does not follow Hermes into the commands it runs"
|
||||
|
||||
# --owns is the one answer to whether the wrapper on PATH is this installer's.
|
||||
# Remove Preinstalls and the migration both ask it rather than carrying their
|
||||
# own copy of the marker, so a change to what ownership means reaches them.
|
||||
owns_home="$test_tmp/owns-home"
|
||||
mkdir -p "$owns_home/.local/bin"
|
||||
|
||||
run_owns() {
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED=0 \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$owns_home" \
|
||||
PATH="$mock_bin:$PATH" \
|
||||
bash "$ROOT/bin/omarchy-install-hermes-cli" --owns
|
||||
}
|
||||
|
||||
rm -f "$owns_home/.local/bin/hermes"
|
||||
run_owns && fail "--owns says no when there is no wrapper at all"
|
||||
|
||||
printf '%s\n' "#!/bin/bash" "$stub_marker" >"$owns_home/.local/bin/hermes"
|
||||
chmod +x "$owns_home/.local/bin/hermes"
|
||||
run_owns || fail "--owns recognises the stub this installer wrote"
|
||||
|
||||
printf '%s\n' "#!/bin/bash" "# Replaces the stub omarchy-install-hermes-cli used to write." \
|
||||
>"$owns_home/.local/bin/hermes"
|
||||
run_owns && fail "--owns needs the exact marker line, not a mention"
|
||||
|
||||
# Quoting the marker inside a longer line is not the same as carrying it: the
|
||||
# match is whole-line, so a wrapper describing what it replaced stays the
|
||||
# user's.
|
||||
printf '%s\n' "#!/bin/bash" "# Replaced '$stub_marker' with my own." \
|
||||
>"$owns_home/.local/bin/hermes"
|
||||
run_owns && fail "--owns needs the marker to be the whole line, not part of one"
|
||||
|
||||
rm -f "$owns_home/.local/bin/hermes"
|
||||
ln -s "$test_home/.local/bin/hermes" "$owns_home/.local/bin/hermes"
|
||||
run_owns && fail "--owns disclaims a symlink, whatever it resolves to"
|
||||
rm -f "$owns_home/.local/bin/hermes"
|
||||
pass "--owns answers for the wrapper this installer wrote and nothing else"
|
||||
|
||||
# The marker lives in exactly one place. Every other caller asks --owns, so a
|
||||
# second copy is drift waiting to happen.
|
||||
marker_copies=$(grep -rl "Written by omarchy-install-hermes-cli" \
|
||||
"$ROOT/bin" "$ROOT/install" "$ROOT/migrations" 2>/dev/null | wc -l)
|
||||
(( marker_copies == 1 )) ||
|
||||
fail "only omarchy-install-hermes-cli spells out the ownership marker"
|
||||
pass "the ownership marker is written down once"
|
||||
|
||||
# The app's marker says its install once landed, not that it is still there. A
|
||||
# wrapper whose runtime has since gone answers for nothing, so readiness runs
|
||||
# the command, exactly as it does for a hermes the user installed themselves.
|
||||
ready_home="$test_tmp/ready-home"
|
||||
mkdir -p "$ready_home/.hermes/hermes-agent/venv/bin" "$ready_home/.local/bin"
|
||||
touch "$ready_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
||||
printf '%s\n' "#!/bin/bash" "exec $ready_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\"" \
|
||||
>"$ready_home/.local/bin/hermes"
|
||||
chmod +x "$ready_home/.local/bin/hermes"
|
||||
|
||||
run_ready_check() {
|
||||
OMARCHY_TEST_DESKTOP_INSTALLED=1 \
|
||||
OMARCHY_TEST_MISE_LOG="$mise_log" \
|
||||
HOME="$ready_home" \
|
||||
PATH="$mock_bin:$PATH" \
|
||||
bash "$ROOT/bin/omarchy-install-hermes-cli" --check >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_ready_check && fail "--check rejects the app's wrapper when its runtime is gone"
|
||||
|
||||
cat >"$ready_home/.hermes/hermes-agent/venv/bin/hermes" <<'SH'
|
||||
#!/bin/bash
|
||||
if [[ ${1:-} == "chat" && ${2:-} == "--help" ]]; then
|
||||
echo "--oneshot"
|
||||
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 the app's wrapper once it runs"
|
||||
pass "readiness runs the app's command rather than trusting its marker"
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/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"
|
||||
mkdir -p "$mock_bin"
|
||||
|
||||
cat >"$mock_bin/omarchy-pkg-drop" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\0' "$@" >>"$OMARCHY_TEST_DROP_LOG"
|
||||
SH
|
||||
chmod +x "$mock_bin"/*
|
||||
|
||||
seed_install() {
|
||||
rm -rf "$test_home"
|
||||
mkdir -p "$test_home/.hermes/hermes-agent" "$test_home/.hermes/bootstrap-cache" \
|
||||
"$test_home/.hermes/bin" "$test_home/.hermes/node/bin" \
|
||||
"$test_home/.hermes/memories" "$test_home/.hermes/sessions" \
|
||||
"$test_home/.config/Hermes" "$test_home/.local/bin"
|
||||
printf 'chat\n' >"$test_home/.hermes/sessions/one.json"
|
||||
printf 'memory\n' >"$test_home/.hermes/memories/one.md"
|
||||
printf 'soul\n' >"$test_home/.hermes/SOUL.md"
|
||||
printf 'uv\n' >"$test_home/.hermes/bin/uv"
|
||||
ln -sf "$test_home/.hermes/node/bin/node" "$test_home/.local/bin/node"
|
||||
ln -sf "$test_home/.hermes/node/bin/npm" "$test_home/.local/bin/npm"
|
||||
ln -sf /usr/bin/npx "$test_home/.local/bin/npx"
|
||||
printf 'node\n' >"$test_home/.hermes/node/bin/node"
|
||||
touch "$test_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
||||
}
|
||||
|
||||
remove() {
|
||||
OMARCHY_TEST_DROP_LOG="$test_tmp/drop-log" HOME="$test_home" PATH="$mock_bin:$PATH" \
|
||||
bash "$ROOT/bin/omarchy-remove-ai-hermes" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# The app brings its own uv and its own node; both are runtime, not data.
|
||||
seed_install
|
||||
printf '%s\n' "#!/bin/bash" "exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\"" \
|
||||
>"$test_home/.local/bin/hermes"
|
||||
remove || fail "remove succeeds"
|
||||
[[ ! -d $test_home/.hermes/hermes-agent ]] || fail "the runtime checkout is removed"
|
||||
[[ ! -d $test_home/.hermes/bin ]] || fail "the uv the app installed is removed"
|
||||
[[ ! -d $test_home/.hermes/node ]] || fail "the node the app installed is removed"
|
||||
pass "removal takes the whole runtime the app installed"
|
||||
|
||||
[[ -d $test_home/.config/Hermes ]] ||
|
||||
fail "gateway connections, tokens and settings survive removal"
|
||||
pass "removal keeps the app's connections and settings"
|
||||
|
||||
# -L, not -e: a dangling symlink fails -e while very much still being there.
|
||||
[[ ! -L $test_home/.local/bin/node ]] || fail "a node symlink into ~/.hermes is removed"
|
||||
[[ ! -L $test_home/.local/bin/npm ]] || fail "an npm symlink into ~/.hermes is removed"
|
||||
[[ -L $test_home/.local/bin/npx ]] || fail "an npx symlink pointing elsewhere survives"
|
||||
pass "removal clears only the managed Node links it stranded"
|
||||
|
||||
[[ -f $test_home/.hermes/sessions/one.json ]] || fail "chats survive removal"
|
||||
[[ -f $test_home/.hermes/memories/one.md ]] || fail "memories survive removal"
|
||||
[[ -f $test_home/.hermes/SOUL.md ]] || fail "SOUL.md survives removal"
|
||||
pass "removal keeps what belongs to the user"
|
||||
|
||||
[[ ! -e $test_home/.local/bin/hermes ]] || fail "the app's own hermes command is removed"
|
||||
pass "removal takes the command the app installed"
|
||||
|
||||
# A hermes command the app did not write survives even when the app did install
|
||||
# a runtime of its own.
|
||||
seed_install
|
||||
printf '%s\n' "#!/bin/bash" "exec /usr/local/bin/my-own-hermes \"\$@\"" \
|
||||
>"$test_home/.local/bin/hermes"
|
||||
remove || fail "remove succeeds with a foreign hermes present"
|
||||
[[ -f $test_home/.local/bin/hermes ]] ||
|
||||
fail "a hermes command the app did not write survives removal"
|
||||
pass "removal leaves a hermes it does not own"
|
||||
|
||||
# Installed but never launched. The app provisions its runtime on first launch
|
||||
# and marks it complete when it lands, so without that marker everything under
|
||||
# ~/.hermes predates the app -- an official install, or one built by hand -- and
|
||||
# the paths are identical either way. Dropping the package is the whole job.
|
||||
seed_install
|
||||
rm -f "$test_home/.hermes/hermes-agent/.hermes-bootstrap-complete"
|
||||
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"
|
||||
[[ -d $test_home/.hermes/hermes-agent ]] ||
|
||||
fail "a Hermes runtime the app never installed survives removal"
|
||||
[[ -f $test_home/.hermes/hermes-agent/PATCH ]] ||
|
||||
fail "local changes to a runtime the app never installed survive removal"
|
||||
[[ -d $test_home/.hermes/bin && -d $test_home/.hermes/node ]] ||
|
||||
fail "the rest of a runtime the app never installed survives removal"
|
||||
[[ -f $test_home/.local/bin/hermes ]] ||
|
||||
fail "the command a runtime the app never installed put on PATH survives removal"
|
||||
[[ -L $test_home/.local/bin/node ]] ||
|
||||
fail "node links belonging to a runtime the app never installed survive removal"
|
||||
pass "removal leaves a Hermes the app never installed"
|
||||
|
||||
# ~/.hermes carries a dot, so a pattern rather than a plain string would also
|
||||
# claim a wrapper pointing at a sibling directory that merely looks like it.
|
||||
seed_install
|
||||
mkdir -p "$test_home/xhermes/bin"
|
||||
sibling_body="#!/bin/bash
|
||||
exec $test_home/xhermes/bin/hermes \"\$@\""
|
||||
printf '%s\n' "$sibling_body" >"$test_home/.local/bin/hermes"
|
||||
remove || fail "remove succeeds with a wrapper pointing at a sibling directory"
|
||||
[[ -f $test_home/.local/bin/hermes && $(cat "$test_home/.local/bin/hermes") == "$sibling_body" ]] ||
|
||||
fail "a wrapper pointing at ~/xhermes is not mistaken for one pointing into ~/.hermes"
|
||||
pass "removal matches the runtime path as a plain string"
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
|
||||
migration="$ROOT/migrations/1787843905.sh"
|
||||
[[ -f $migration ]] || fail "Hermes skills migration exists"
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
home="$test_dir/home"
|
||||
|
||||
run_migration() {
|
||||
HOME="$home" OMARCHY_PATH="$ROOT" bash -euo pipefail "$migration" >/dev/null ||
|
||||
fail "migration exits clean"
|
||||
}
|
||||
|
||||
assert_link() {
|
||||
local link="$1"
|
||||
local skill="$2"
|
||||
local description="$3"
|
||||
|
||||
[[ -L $link && $(readlink "$link") == "$ROOT/default/agents/skills/$skill" ]] ||
|
||||
fail "$description" "$link -> $(readlink "$link" 2>/dev/null || echo missing)"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ default home, no profiles
|
||||
|
||||
rm -rf "$home"
|
||||
mkdir -p "$home"
|
||||
run_migration
|
||||
|
||||
for skill in omarchy diagnose-crash; do
|
||||
assert_link "$home/.hermes/skills/$skill" "$skill" "migration links $skill into the default Hermes home"
|
||||
done
|
||||
[[ -e $home/.hermes/profiles ]] && fail "migration does not create Hermes profiles"
|
||||
pass "migration links the default Hermes home and does not create profiles"
|
||||
|
||||
run_migration
|
||||
for skill in omarchy diagnose-crash; do
|
||||
assert_link "$home/.hermes/skills/$skill" "$skill" "migration is idempotent on the default home for $skill"
|
||||
done
|
||||
pass "migration is idempotent on the default home"
|
||||
|
||||
# ------------------------------------------------------------------ pre-existing profile
|
||||
|
||||
rm -rf "$home"
|
||||
mkdir -p "$home/.hermes/profiles/james"
|
||||
run_migration
|
||||
|
||||
for skill in omarchy diagnose-crash; do
|
||||
assert_link "$home/.hermes/skills/$skill" "$skill" "migration links $skill into the default Hermes home when a profile exists"
|
||||
assert_link "$home/.hermes/profiles/james/skills/$skill" "$skill" "migration links $skill into a pre-existing Hermes profile"
|
||||
done
|
||||
[[ -d $home/.hermes/profiles/james ]] || fail "migration leaves the pre-existing profile in place"
|
||||
profile_count=$(find "$home/.hermes/profiles" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||
(( profile_count == 1 )) || fail "migration does not create extra profiles" "count=$profile_count"
|
||||
pass "migration links a pre-existing Hermes profile and does not create extras"
|
||||
|
||||
run_migration
|
||||
for skill in omarchy diagnose-crash; do
|
||||
assert_link "$home/.hermes/skills/$skill" "$skill" "migration is idempotent on the default home when a profile exists for $skill"
|
||||
assert_link "$home/.hermes/profiles/james/skills/$skill" "$skill" "migration is idempotent on a pre-existing profile for $skill"
|
||||
done
|
||||
pass "migration is idempotent on a pre-existing profile"
|
||||
|
||||
# ------------------------------------------------------------------ missing skill source
|
||||
|
||||
rm -rf "$home"
|
||||
mkdir -p "$home" "$test_dir/empty-omarchy"
|
||||
HOME="$home" OMARCHY_PATH="$test_dir/empty-omarchy" bash -euo pipefail "$migration" >/dev/null ||
|
||||
fail "migration exits clean when the skill source is missing"
|
||||
[[ -e $home/.hermes ]] && fail "migration no-ops when the skill source is missing"
|
||||
pass "migration no-ops when the skill source is missing"
|
||||
@@ -224,6 +224,7 @@ const expectedAgents = {
|
||||
claude: { icon: '', label: 'Claude' },
|
||||
codex: { icon: '\ue905', iconFont: 'omarchy', label: 'Codex' },
|
||||
grok: { icon: '\ue904', iconFont: 'omarchy', label: 'Grok' },
|
||||
hermes: { icon: '\ue90a', iconFont: 'omarchy', label: 'Hermes' },
|
||||
copilot: { icon: '', label: 'Copilot' },
|
||||
crush: { icon: '', label: 'Crush' },
|
||||
}
|
||||
@@ -244,7 +245,7 @@ assertDeepEqual(
|
||||
defaultItems
|
||||
.filter(item => item.parent === 'setup.default.agent')
|
||||
.map(item => item.label),
|
||||
['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Grok', 'omp', 'OpenCode', 'Ori', 'Pi'],
|
||||
['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Grok', 'Hermes', 'omp', 'OpenCode', 'Ori', 'Pi'],
|
||||
'menu sorts coding agents alphabetically'
|
||||
)
|
||||
const expectedDefaults = {
|
||||
@@ -636,5 +637,5 @@ assert(
|
||||
JS
|
||||
|
||||
font_charset=$(fc-query --format='%{charset}' "$ROOT/default/fonts/omarchy/omarchy.ttf")
|
||||
[[ $font_charset == *"e900-e909"* ]] || fail "Omarchy icon font includes every custom menu glyph"
|
||||
[[ $font_charset == *"e900-e90a"* ]] || fail "Omarchy icon font includes every custom menu glyph"
|
||||
pass "Omarchy icon font includes the official agent marks"
|
||||
@@ -36,7 +36,10 @@ SH
|
||||
|
||||
chmod +x "$mock_bin"/*
|
||||
|
||||
export PATH="$mock_bin:$PATH"
|
||||
# $ROOT/bin after the mocks: Remove Preinstalls asks omarchy-install-hermes-cli
|
||||
# whether the wrapper is Omarchy's rather than matching the marker itself, and
|
||||
# that is the real command at runtime. The mocks still shadow what they name.
|
||||
export PATH="$mock_bin:$ROOT/bin:$PATH"
|
||||
export HOME="$test_home"
|
||||
export OMARCHY_TEST_PKG_LOG="$pkg_log"
|
||||
|
||||
@@ -89,3 +92,43 @@ pass "declining Remove Preinstalls changes nothing"
|
||||
"$ROOT/bin/omarchy-remove-preinstalls" >/dev/null
|
||||
[[ -f $marker ]] || fail "Remove Preinstalls records the opt-out"
|
||||
pass "Remove Preinstalls records the opt-out"
|
||||
|
||||
# Hermes' wrapper is only a preinstall when omarchy-install-hermes-cli wrote it.
|
||||
# The desktop app's command and an official install live at the same path and
|
||||
# are the user's, whether or not any package says so.
|
||||
hermes="$test_home/.local/bin/hermes"
|
||||
mkdir -p "$(dirname "$hermes")"
|
||||
|
||||
printf '%s\n' "#!/bin/bash" "# Written by omarchy-install-hermes-cli." >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
"$ROOT/bin/omarchy-remove-preinstalls" >/dev/null
|
||||
[[ ! -e $hermes ]] || fail "Remove Preinstalls deletes the Omarchy Hermes wrapper"
|
||||
pass "Remove Preinstalls deletes the Omarchy Hermes wrapper"
|
||||
|
||||
printf '%s\n' "#!/bin/bash" "exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\"" >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
"$ROOT/bin/omarchy-remove-preinstalls" >/dev/null
|
||||
[[ -x $hermes ]] || fail "Remove Preinstalls keeps the desktop app's Hermes command"
|
||||
pass "Remove Preinstalls keeps the desktop app's Hermes command"
|
||||
|
||||
official_body="#!/bin/bash
|
||||
unset PYTHONPATH
|
||||
unset PYTHONHOME
|
||||
exec $test_home/.hermes/hermes-agent/venv/bin/hermes \"\$@\""
|
||||
printf '%s\n' "$official_body" >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
"$ROOT/bin/omarchy-remove-preinstalls" >/dev/null
|
||||
[[ -x $hermes && $(cat "$hermes") == "$official_body" ]] || fail "Remove Preinstalls keeps an official Hermes install"
|
||||
pass "Remove Preinstalls keeps an official Hermes install"
|
||||
|
||||
printf '%s\n' "#!/bin/bash" "# Replaces the stub omarchy-install-hermes-cli used to write." >"$hermes"
|
||||
chmod +x "$hermes"
|
||||
"$ROOT/bin/omarchy-remove-preinstalls" >/dev/null
|
||||
[[ -x $hermes ]] || fail "Remove Preinstalls keeps a wrapper that merely mentions the installer"
|
||||
pass "Remove Preinstalls keeps a wrapper that merely mentions the installer"
|
||||
|
||||
rm -f "$hermes"
|
||||
ln -s "$test_home/nowhere/hermes" "$hermes"
|
||||
"$ROOT/bin/omarchy-remove-preinstalls" >/dev/null
|
||||
[[ -L $hermes ]] || fail "Remove Preinstalls keeps a foreign hermes link"
|
||||
pass "Remove Preinstalls keeps a foreign hermes link"
|
||||
@@ -8,7 +8,7 @@ test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
mock_bin="$test_tmp/bin"
|
||||
mkdir -p "$mock_bin" "$test_tmp/home"
|
||||
mkdir -p "$mock_bin" "$test_tmp/home" "$test_tmp/home/.hermes/profiles/james"
|
||||
|
||||
for command in xdg-user-dirs-update xdg-settings xdg-mime; do
|
||||
printf '#!/bin/bash\nexit 0\n' >"$mock_bin/$command"
|
||||
@@ -30,6 +30,14 @@ for skill in omarchy diagnose-crash; do
|
||||
link="$test_tmp/home/.gemini/config/skills/$skill"
|
||||
[[ -L $link && $(readlink "$link") == "$ROOT/default/agents/skills/$skill" ]] ||
|
||||
fail "omarchy-provision-user provisions the $skill skill for Antigravity"
|
||||
|
||||
link="$test_tmp/home/.hermes/skills/$skill"
|
||||
[[ -L $link && $(readlink "$link") == "$ROOT/default/agents/skills/$skill" ]] ||
|
||||
fail "omarchy-provision-user provisions the $skill skill for Hermes"
|
||||
|
||||
link="$test_tmp/home/.hermes/profiles/james/skills/$skill"
|
||||
[[ -L $link && $(readlink "$link") == "$ROOT/default/agents/skills/$skill" ]] ||
|
||||
fail "omarchy-provision-user provisions the $skill skill for a Hermes profile"
|
||||
done
|
||||
|
||||
pass "omarchy-provision-user provisions Antigravity skills"
|
||||
pass "omarchy-provision-user provisions Antigravity and Hermes skills"
|
||||
Reference in new issue
Block a user