Apply agent account selection to CLI subprocesses (#14233)
* Apply agent account selection to CLI subprocesses * Fix account dispatch edge cases and remove redundant shell wrappers * Use the current account when launching a new agent session
This commit is contained in:
1 parent
5657a91f8d
commit
f9646299bf
15 files changed
+351
-67
No files matched your search
+15
-7
@@ -136,19 +136,27 @@ pi)
|
||||
;;
|
||||
esac
|
||||
|
||||
# Claude, Codex, and Grok start as the active subscription account, the same
|
||||
# as the shell functions in default/bash/fns/agent-accounts. The home travels
|
||||
# in the command itself, since a terminal launched through uwsm doesn't
|
||||
# inherit this process's environment.
|
||||
# A new session uses the current selection rather than its parent's account
|
||||
# pin. Explicit provider homes still override the selection. Carry the new pin
|
||||
# in the command, since terminals launched through uwsm don't inherit our env.
|
||||
case "$agent" in
|
||||
claude) home_env="CLAUDE_CONFIG_DIR" ;;
|
||||
codex) home_env="CODEX_HOME" ;;
|
||||
grok) home_env="GROK_HOME" ;;
|
||||
*) home_env="" ;;
|
||||
esac
|
||||
if [[ -n $home_env && -z ${!home_env:-} ]]; then
|
||||
home=$(omarchy-agent-account-home "$agent")
|
||||
[[ -n $home ]] && command=(env "$home_env=$home" "${command[@]}")
|
||||
if [[ -n $home_env ]]; then
|
||||
account_pin="OMARCHY_AGENT_${agent^^}_HOME"
|
||||
if [[ -v $account_pin && ${!home_env:-} == "${!account_pin}" ]]; then
|
||||
unset "$home_env"
|
||||
fi
|
||||
unset "$account_pin"
|
||||
if [[ -z ${!home_env:-} ]]; then
|
||||
home=$(omarchy-agent-account-home "$agent")
|
||||
account_env=(env -u "$home_env" "$account_pin=$home")
|
||||
[[ -n $home ]] && account_env+=("$home_env=$home")
|
||||
command=("${account_env[@]}" "${command[@]}")
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $inline == "true" ]]; then
|
||||
|
||||
@@ -138,7 +138,7 @@ esac
|
||||
|
||||
# The CLI's own home, whatever this shell has pointed it at.
|
||||
login() {
|
||||
interruptible env -u CLAUDE_CONFIG_DIR -u CODEX_HOME -u GROK_HOME "${login_command[@]}"
|
||||
interruptible env -u CLAUDE_CONFIG_DIR -u CODEX_HOME -u GROK_HOME "OMARCHY_AGENT_${provider^^}_HOME=" "${login_command[@]}"
|
||||
}
|
||||
|
||||
# Signing an account that's already here in again, where it lives: the
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Run Claude, Codex, or Grok as the selected subscription account
|
||||
# omarchy:args=<claude|codex|grok> [args...]
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
claude) account_env="CLAUDE_CONFIG_DIR" ;;
|
||||
codex) account_env="CODEX_HOME" ;;
|
||||
grok) account_env="GROK_HOME" ;;
|
||||
*)
|
||||
echo "Usage: omarchy-agent-account-exec <claude|codex|grok> [args...]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
provider=$1
|
||||
shift
|
||||
|
||||
# mise removes its wrapper dispatch directories before calling us, so exec
|
||||
# finds the real CLI (or its first-run installer) rather than this wrapper.
|
||||
# Pin the resolved home for children, including an implicit Main account.
|
||||
# Explicit provider homes still take precedence.
|
||||
if [[ -z ${!account_env:-} ]]; then
|
||||
account_home=$(omarchy-agent-account-home "$provider")
|
||||
export "OMARCHY_AGENT_${provider^^}_HOME=$account_home"
|
||||
if [[ -n $account_home ]]; then
|
||||
export "$account_env=$account_home"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Resolve before restoring dispatch on PATH: mise removes the wrapper paths to
|
||||
# avoid recursion, but the agent's subprocesses need them for other providers.
|
||||
if ! agent_binary=$(type -P "$provider"); then
|
||||
# A mise dispatcher can exist before its CLI or first-run installer does.
|
||||
export MISE_MINIMUM_RELEASE_AGE=0
|
||||
mise use -g --quiet "$provider"
|
||||
agent_binary=$(mise which "$provider")
|
||||
fi
|
||||
export PATH="${MISE_DATA_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/mise}/command-wrappers/bin:$PATH"
|
||||
exec "$agent_binary" "$@"
|
||||
@@ -13,6 +13,13 @@ registry="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/agents/accounts/${1:-}.j
|
||||
if [[ ${1:-} != "claude" && ${1:-} != "codex" && ${1:-} != "grok" ]]; then
|
||||
echo "Usage: omarchy-agent-account-home <claude|codex|grok>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# A running session pins its home, including an empty value for Main. Claude's
|
||||
# implicit default also selects ~/.claude.json, so Main must stay implicit.
|
||||
account_pin="OMARCHY_AGENT_${1^^}_HOME"
|
||||
if [[ -v $account_pin ]]; then
|
||||
printf '%s' "${!account_pin}"
|
||||
elif [[ -f $registry ]]; then
|
||||
exec omarchy-agent-account-state home "$1"
|
||||
fi
|
||||
@@ -25,6 +25,27 @@ if [ "$OMARCHY_PATH" != /usr/share/omarchy ]; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# mise's command wrappers must precede real tool directories inherited from a
|
||||
# parent shell, so scripts follow account switches without Bash functions.
|
||||
# mise activate also keeps this directory ahead of its managed tool binaries.
|
||||
omarchy_wrapper_dir="${MISE_DATA_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/mise}/command-wrappers/bin"
|
||||
omarchy_wrapper_path=$omarchy_wrapper_dir
|
||||
if [ -n "${PATH:-}" ]; then
|
||||
omarchy_remaining_path=$PATH
|
||||
while :; do
|
||||
omarchy_path_entry=${omarchy_remaining_path%%:*}
|
||||
if [ "$omarchy_path_entry" != "$omarchy_wrapper_dir" ]; then
|
||||
omarchy_wrapper_path="$omarchy_wrapper_path:$omarchy_path_entry"
|
||||
fi
|
||||
case "$omarchy_remaining_path" in
|
||||
*:*) omarchy_remaining_path=${omarchy_remaining_path#*:} ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
PATH=$omarchy_wrapper_path
|
||||
unset omarchy_wrapper_dir omarchy_wrapper_path omarchy_remaining_path omarchy_path_entry
|
||||
|
||||
# User-level tool paths, appended so system binaries keep precedence. This is
|
||||
# what lets login shells (ssh, bash -lc) and the uwsm session find mise-managed
|
||||
# tools without an interactive rc. Keep these directories in sync with the PAM
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Start Claude Code, Codex, and Grok as the active subscription account, so
|
||||
# `cx`, `cy`, `tdl` and a plain `claude` all follow `omarchy agent account use`.
|
||||
# An explicitly set CLAUDE_CONFIG_DIR / CODEX_HOME / GROK_HOME still wins.
|
||||
claude() {
|
||||
local home=""
|
||||
[[ -z ${CLAUDE_CONFIG_DIR:-} ]] && home=$(omarchy-agent-account-home claude)
|
||||
|
||||
if [[ -n $home ]]; then
|
||||
CLAUDE_CONFIG_DIR="$home" command claude "$@"
|
||||
else
|
||||
command claude "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
codex() {
|
||||
local home=""
|
||||
[[ -z ${CODEX_HOME:-} ]] && home=$(omarchy-agent-account-home codex)
|
||||
|
||||
if [[ -n $home ]]; then
|
||||
CODEX_HOME="$home" command codex "$@"
|
||||
else
|
||||
command codex "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
grok() {
|
||||
local home=""
|
||||
[[ -z ${GROK_HOME:-} ]] && home=$(omarchy-agent-account-home grok)
|
||||
|
||||
if [[ -n $home ]]; then
|
||||
GROK_HOME="$home" command grok "$@"
|
||||
else
|
||||
command grok "$@"
|
||||
fi
|
||||
}
|
||||
+1
-4
@@ -172,10 +172,7 @@ Single source of truth for `OMARCHY_PATH` and dev-link-aware `PATH`. It:
|
||||
- Prepends `$OMARCHY_PATH/bin` to `PATH` **only when** `OMARCHY_PATH` is
|
||||
not `/usr/share/omarchy`. On a production install the binaries are
|
||||
already on `PATH` as `/usr/bin/omarchy-*` via the `omarchy` package.
|
||||
- Appends `~/.local/share/mise/shims` and `~/.local/bin` so login shells and
|
||||
the uwsm session find mise-managed tools — kept in sync with the PAM `PATH`
|
||||
line written by `install/config/ssh-command-path.sh`, which covers SSH
|
||||
commands that run no shell setup at all.
|
||||
- Prepends mise's `command-wrappers/bin` so subscription account dispatch runs before inherited tool binaries, then appends `~/.local/share/mise/shims` and `~/.local/bin` so login shells and the uwsm session find mise-managed tools. The existing mise tool shims also dispatch these wrappers for SSH commands that run no shell setup; the PAM path needs no additional entry. `etc/mise/conf.d/omarchy-agent-accounts.toml` declares the Claude, Codex, and Grok wrappers; `mise reshim` builds them during user setup and migration. Each invocation resolves the selected account through `omarchy-agent-account-exec`, while an explicit provider home takes precedence. Credentials and running sessions remain in their original account homes.
|
||||
|
||||
Sourced by every entry point that needs the env set:
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Resolve the active subscription at execution time, including in subprocesses.
|
||||
# User and project mise config can override these system defaults.
|
||||
[wrappers.claude]
|
||||
command = "omarchy-agent-account-exec"
|
||||
args = ["claude"]
|
||||
|
||||
[wrappers.codex]
|
||||
command = "omarchy-agent-account-exec"
|
||||
args = ["codex"]
|
||||
|
||||
[wrappers.grok]
|
||||
command = "omarchy-agent-account-exec"
|
||||
args = ["grok"]
|
||||
@@ -24,3 +24,6 @@ omarchy-mise-install github:OpenRouterLabs/ori-releases ori
|
||||
if omarchy-cmd-missing muse; then
|
||||
omarchy-mise-install "http:muse[url=https://api.meta.ai/muse-launcher.sh,bin=muse,version_list_url=https://api.meta.ai/muse-code/channels/muse-stable,version_json_path=.version]" muse
|
||||
fi
|
||||
|
||||
# Build the account dispatchers declared by /etc/mise/conf.d.
|
||||
mise reshim
|
||||
+2
-2
@@ -46,11 +46,11 @@ Left-click the bar icon for the panel, right-click to launch your default agent.
|
||||
|
||||
If you pay for more than one Claude, Codex, or Grok plan — a personal Max and a work one, say — Omarchy can keep them all signed in and move between them. Add a subscription with the + in the agents panel, which walks you through it right there, or with `omarchy agent account add claude Work` (or `codex`, or `grok`). It installs the CLI if it's missing. The first account for a provider signs in through your normal browser, right where the CLI always looks. Every one after that signs in through a private window, so the account your browser is already signed in to doesn't get picked up by mistake; sign in there as the account you're adding, and close any private window you already had open first. Never log out of the CLI to do this: logging out revokes the saved sign-in, and that account would have to be added again.
|
||||
|
||||
One account per provider is active, and every new session starts as it: `cx`, `cy`, a plain `claude`, `codex`, or `grok`, and anything launched through `omarchy agent`. Switch with `omarchy agent account use work` (and back with `omarchy agent account use main`), or in the panel by pressing an account's number and then `Enter` (or clicking its Use button). Sessions that are already running stay on the account they started with. Conversation history, settings, skills, and plugins are shared across accounts, so `claude --continue` in the same folder picks up where you left off on the new account.
|
||||
One account per provider is active, and every new session starts as it: `cx`, `cy`, a plain `claude`, `codex`, or `grok`, anything launched through `omarchy agent`, and scripts or editors that find the CLI on `PATH`. Switch with `omarchy agent account use work` (and back with `omarchy agent account use main`), or in the panel by pressing an account's number and then `Enter` (or clicking its Use button). Sessions that are already running stay on the account they started with. Conversation history, settings, skills, and plugins are shared across accounts, so `claude --continue` in the same folder picks up where you left off on the new account.
|
||||
|
||||
With more than one account, the panel shows each account's limits side by side, with the active one marked. By default Omarchy notifies you when the active account passes 95% of a limit, and clicking the notification switches to the account with the most headroom. Hover an account's line in the panel and click the Autoswitch that appears beside Use (or press `m`), or run `omarchy agent account mode auto`, to have it switch on its own instead; while it's on, Autoswitch takes Use's place, and clicking it again goes back to notifying. Add a number to change the threshold, as in `omarchy agent account mode auto 90`. If every account is over the threshold, you'll hear about it once, along with which account frees up first. While an active account is within 15 points of its threshold (80% at the default), its limits are checked every three minutes rather than every 15.
|
||||
|
||||
`omarchy agent account list` shows every account and its latest limits, `omarchy agent account remove work` forgets one, and `omarchy agent account rename primary Hey` renames one (or click an account's name in the panel). These commands work on your default agent's accounts; to reach the other provider, name it first, as in `omarchy agent account use codex side`. The login you already had in `~/.claude` or `~/.codex` is the _Main_ account and stays where it is. An account left unused for more than about eight hours shows its last known limits until a session runs in it again. MCP servers you had when an account was added come along to it, but ones you add later with `claude mcp add` only reach the account you added them in. Claude Desktop and editors that start `claude` themselves don't follow the switch.
|
||||
`omarchy agent account list` shows every account and its latest limits, `omarchy agent account remove work` forgets one, and `omarchy agent account rename primary Hey` renames one (or click an account's name in the panel). These commands work on your default agent's accounts; to reach the other provider, name it first, as in `omarchy agent account use codex side`. The login you already had in `~/.claude` or `~/.codex` is the _Main_ account and stays where it is. An account left unused for more than about eight hours shows its last known limits until a session runs in it again. MCP servers you had when an account was added come along to it, but ones you add later with `claude mcp add` only reach the account you added them in. Claude Desktop and tools configured with an absolute path to a CLI binary do not follow the switch. An explicit `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, or `GROK_HOME` takes precedence over the selected account.
|
||||
|
||||
### Crash diagnosis
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
echo "Make agent account selection apply to scripts and subprocesses"
|
||||
|
||||
# The settings package ships the mise wrapper declarations. Build the dispatch
|
||||
# links for this user without installing or changing any agent version.
|
||||
mise reshim
|
||||
Executable
+206
@@ -0,0 +1,206 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
require_command mise
|
||||
require_command python3
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
account_test_home="$test_tmp/home"
|
||||
real_bin="$test_tmp/real/bin"
|
||||
mise_data="$test_tmp/data/mise"
|
||||
registry_dir="$account_test_home/.local/state/omarchy/agents/accounts"
|
||||
mkdir -p "$account_test_home" "$real_bin" "$registry_dir"
|
||||
|
||||
# Use real mise dispatch and real account resolution, with fake agent binaries.
|
||||
# No shell functions, login startup files, credentials, or network are involved.
|
||||
for provider in claude codex grok; do
|
||||
cat >"$real_bin/$provider" <<'SH'
|
||||
#!/bin/bash
|
||||
case "${0##*/}" in
|
||||
claude) printf '%s\n' "${CLAUDE_CONFIG_DIR:-default}" ;;
|
||||
codex) printf '%s\n' "${CODEX_HOME:-default}" ;;
|
||||
grok) printf '%s\n' "${GROK_HOME:-default}" ;;
|
||||
esac
|
||||
if (($#)); then
|
||||
printf '%s\n' "$@"
|
||||
fi
|
||||
if [[ ${OMARCHY_TEST_SWITCH_ACCOUNT:-} == "yes" ]]; then
|
||||
unset OMARCHY_TEST_SWITCH_ACCOUNT
|
||||
omarchy-agent-account-state use "${0##*/}" "${OMARCHY_TEST_SWITCH_TO:-side}" >/dev/null
|
||||
if [[ ${OMARCHY_TEST_NEW_SESSION:-} == "yes" ]]; then
|
||||
omarchy-agent --inline
|
||||
else
|
||||
"${0##*/}"
|
||||
fi
|
||||
fi
|
||||
if [[ -n ${OMARCHY_TEST_AGENT_CHILD:-} ]]; then
|
||||
child=$OMARCHY_TEST_AGENT_CHILD
|
||||
unset OMARCHY_TEST_AGENT_CHILD
|
||||
"$child"
|
||||
fi
|
||||
exit "${OMARCHY_TEST_AGENT_EXIT:-0}"
|
||||
SH
|
||||
chmod +x "$real_bin/$provider"
|
||||
done
|
||||
|
||||
run_isolated() {
|
||||
env -i HOME="$account_test_home" \
|
||||
XDG_CONFIG_HOME="$test_tmp/config" XDG_DATA_HOME="$test_tmp/data" XDG_CACHE_HOME="$test_tmp/cache" \
|
||||
MISE_DATA_DIR="$mise_data" MISE_SYSTEM_CONFIG_DIR="$ROOT/etc/mise" MISE_OFFLINE=1 \
|
||||
OMARCHY_PATH="$ROOT" PATH="$mise_data/command-wrappers/bin:$ROOT/bin:$real_bin:/usr/bin" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
select_account() {
|
||||
local provider=$1
|
||||
local selected=$2
|
||||
printf '{"active":"%s","accounts":[{"id":"main","primary":true},{"id":"side","home":"%s"}]}\n' \
|
||||
"$selected" "$test_tmp/accounts/$provider side" >"$registry_dir/$provider.json"
|
||||
}
|
||||
|
||||
for helper in omarchy-agent-usage-update omarchy-notification-send; do
|
||||
printf '#!/bin/bash\nexit 0\n' >"$real_bin/$helper"
|
||||
chmod +x "$real_bin/$helper"
|
||||
done
|
||||
|
||||
cd "$test_tmp"
|
||||
run_isolated mise reshim
|
||||
|
||||
for provider in claude codex grok; do
|
||||
select_account "$provider" side
|
||||
account_dir="$test_tmp/accounts/$provider side"
|
||||
output=$(run_isolated "$provider" 'a spaced prompt' '' '--flag=$literal')
|
||||
expected=$(printf '%s\n' "$account_dir" 'a spaced prompt' '' '--flag=$literal')
|
||||
[[ $output == "$expected" ]] || fail "$provider dispatch selects the active account and preserves arguments" "$output"
|
||||
pass "$provider dispatch selects the active account and preserves arguments"
|
||||
|
||||
case "$provider" in
|
||||
claude) account_env=CLAUDE_CONFIG_DIR ;;
|
||||
codex) account_env=CODEX_HOME ;;
|
||||
grok) account_env=GROK_HOME ;;
|
||||
esac
|
||||
output=$(run_isolated env "$account_env=/explicit/account" "$provider")
|
||||
[[ $output == /explicit/account ]] || fail "$provider preserves an explicit account home" "$output"
|
||||
pass "$provider preserves an explicit account home"
|
||||
|
||||
select_account "$provider" main
|
||||
output=$(run_isolated "$provider")
|
||||
[[ $output == default ]] || fail "$provider picks up switching back to Main without rebuilding shims" "$output"
|
||||
pass "$provider picks up switching back to Main without rebuilding shims"
|
||||
|
||||
output=$(run_isolated env OMARCHY_TEST_SWITCH_ACCOUNT=yes "$provider")
|
||||
[[ $output == $'default\ndefault' ]] || fail "$provider Main sessions keep their account after a switch" "$output"
|
||||
pass "$provider Main sessions keep their account after a switch"
|
||||
|
||||
mkdir -p "$account_test_home/.config/omarchy/defaults"
|
||||
printf '%s\n' "$provider" >"$account_test_home/.config/omarchy/defaults/agent"
|
||||
select_account "$provider" main
|
||||
output=$(run_isolated env OMARCHY_TEST_SWITCH_ACCOUNT=yes OMARCHY_TEST_NEW_SESSION=yes "$provider")
|
||||
[[ $(sed -n '2p' <<<"$output") == "$account_dir" ]] || fail "$provider new sessions follow a switch from Main" "$output"
|
||||
pass "$provider new sessions follow a switch from Main"
|
||||
|
||||
output=$(run_isolated env OMARCHY_TEST_SWITCH_ACCOUNT=yes OMARCHY_TEST_SWITCH_TO=main OMARCHY_TEST_NEW_SESSION=yes "$provider")
|
||||
[[ $(sed -n '2p' <<<"$output") == default ]] || fail "$provider new sessions follow a switch to Main" "$output"
|
||||
pass "$provider new sessions follow a switch to Main"
|
||||
|
||||
select_account "$provider" side
|
||||
output=$(run_isolated env OMARCHY_TEST_SWITCH_ACCOUNT=yes OMARCHY_TEST_SWITCH_TO=main "$provider")
|
||||
[[ $output == "$(printf '%s\n' "$account_dir" "$account_dir")" ]] || fail "$provider side sessions keep their account after a switch" "$output"
|
||||
pass "$provider side sessions keep their account after a switch"
|
||||
|
||||
output=$(run_isolated env "OMARCHY_AGENT_${provider^^}_HOME=" "$account_env=/explicit/account" omarchy-agent --inline)
|
||||
[[ ${output%%$'\n'*} == /explicit/account ]] || fail "$provider new sessions preserve explicit account overrides" "$output"
|
||||
pass "$provider new sessions preserve explicit account overrides"
|
||||
|
||||
select_account "$provider" side
|
||||
output=$(run_isolated omarchy-agent-account-add --reauth :primary "$provider" </dev/null)
|
||||
grep -Fxq default <<<"$output" || fail "$provider primary reauthentication bypasses the active side account" "$output"
|
||||
pass "$provider primary reauthentication bypasses the active side account"
|
||||
|
||||
rm "$registry_dir/$provider.json"
|
||||
output=$(run_isolated "$provider")
|
||||
[[ $output == default ]] || fail "$provider works without an account registry" "$output"
|
||||
pass "$provider works without an account registry"
|
||||
done
|
||||
|
||||
select_account claude side
|
||||
output=$(run_isolated python3 -c 'import subprocess; print(subprocess.check_output(["claude", "-p", "review"], text=True), end="")')
|
||||
[[ $output == "$(printf '%s\n' "$test_tmp/accounts/claude side" -p review)" ]] ||
|
||||
fail "a Python subprocess follows the selected Claude subscription" "$output"
|
||||
pass "a Python subprocess follows the selected Claude subscription"
|
||||
|
||||
select_account codex side
|
||||
output=$(run_isolated env OMARCHY_TEST_AGENT_CHILD=codex claude)
|
||||
expected=$(printf '%s\n' "$test_tmp/accounts/claude side" "$test_tmp/accounts/codex side")
|
||||
[[ $output == "$expected" ]] || fail "an agent's subprocess still dispatches the other provider's selected account" "$output"
|
||||
pass "an agent's subprocess still dispatches the other provider's selected account"
|
||||
|
||||
# Give mise a real installed Claude tool directory, not just a system fallback.
|
||||
ln -s "$real_bin/claude" "$test_tmp/real/claude"
|
||||
run_isolated mise link claude@1.0.0 "$test_tmp/real" >/dev/null
|
||||
mkdir -p "$test_tmp/config/mise"
|
||||
printf '[tools]\nclaude = "1.0.0"\n' >"$test_tmp/config/mise/config.toml"
|
||||
run_isolated mise trust "$test_tmp/config/mise/config.toml" >/dev/null 2>&1
|
||||
|
||||
# Existing tool shims used by SSH already dispatch wrappers, including with a
|
||||
# relocated mise data directory. No additional stock-only PAM path is needed.
|
||||
run_isolated mise reshim
|
||||
output=$(run_isolated env PATH="$mise_data/shims:$ROOT/bin:/usr/bin" claude)
|
||||
[[ $output == "$test_tmp/accounts/claude side" ]] || fail "SSH-style mise tool shims route through account dispatch in a custom data directory" "$output"
|
||||
pass "SSH-style mise tool shims route through account dispatch in a custom data directory"
|
||||
|
||||
# Full PATH activation must also prefer the dispatcher over installed tools.
|
||||
output=$(run_isolated bash -c 'eval "$(mise env -s bash)"; [[ $PATH == *"/installs/claude/"* ]] || exit 99; claude')
|
||||
[[ $output == "$test_tmp/accounts/claude side" ]] || fail "mise PATH activation retains account dispatch" "$output"
|
||||
pass "mise PATH activation retains account dispatch"
|
||||
|
||||
status=0
|
||||
run_isolated env OMARCHY_TEST_AGENT_EXIT=42 claude >/dev/null || status=$?
|
||||
(( status == 42 )) || fail "account dispatch preserves the agent exit status" "$status"
|
||||
pass "account dispatch preserves the agent exit status"
|
||||
|
||||
# A dispatcher alone must not prevent the CLI from being installed on first use.
|
||||
mv "$real_bin/grok" "$test_tmp/grok-fixture"
|
||||
cat >"$real_bin/mise" <<'SH'
|
||||
#!/bin/bash
|
||||
case "$1" in
|
||||
use)
|
||||
[[ ${OMARCHY_TEST_INSTALL_FAIL:-} != "yes" ]] || exit 42
|
||||
[[ $* == "use -g --quiet grok" ]] || exit 99
|
||||
cp "$HOME/../grok-fixture" "$HOME/../real/bin/grok"
|
||||
;;
|
||||
which)
|
||||
[[ ${MISE_MINIMUM_RELEASE_AGE:-} == "0" ]] || exit 99
|
||||
printf '%s\n' "$HOME/../real/bin/grok"
|
||||
;;
|
||||
*) exit 99 ;;
|
||||
esac
|
||||
SH
|
||||
chmod +x "$real_bin/mise"
|
||||
select_account grok side
|
||||
output=$(run_isolated grok)
|
||||
[[ $output == "$test_tmp/accounts/grok side" ]] || fail "a dispatcher without a CLI installs it and uses the selected account" "$output"
|
||||
pass "a dispatcher without a CLI installs it and uses the selected account"
|
||||
|
||||
rm "$real_bin/grok"
|
||||
status=0
|
||||
run_isolated env OMARCHY_TEST_INSTALL_FAIL=yes grok >/dev/null || status=$?
|
||||
(( status == 42 )) || fail "a failed first-run install stops dispatch" "$status"
|
||||
pass "a failed first-run install stops dispatch"
|
||||
rm "$real_bin/mise"
|
||||
|
||||
# Migration only rebuilds native mise dispatch; account files stay untouched.
|
||||
registry_before=$(sha256sum "$registry_dir"/*.json)
|
||||
for attempt in 1 2; do
|
||||
run_isolated bash -euo pipefail "$ROOT/migrations/1791112479.sh" >/dev/null
|
||||
for provider in claude codex grok; do
|
||||
[[ -L $mise_data/command-wrappers/bin/$provider ]] || fail "migration builds every account dispatcher"
|
||||
done
|
||||
done
|
||||
[[ $(sha256sum "$registry_dir"/*.json) == "$registry_before" ]] || fail "migration preserves account state"
|
||||
pass "migration rebuilds dispatchers idempotently without changing account state"
|
||||
@@ -201,8 +201,7 @@ OMARCHY_TEST_LOGIN_UUID=u-grok-2 OMARCHY_TEST_LOGIN_EMAIL=side@example.com \
|
||||
grep -qx -- "--private https://auth.x.ai/oauth/authorize" "$OMARCHY_TEST_BROWSER_LOG" ||
|
||||
fail "a second Grok login opens in a private window" "$(cat "$OMARCHY_TEST_BROWSER_LOG")"
|
||||
omarchy-agent-account-use grok side >/dev/null
|
||||
source "$ROOT/default/bash/fns/agent-accounts"
|
||||
[[ $(grok --version) == "grok home=$accounts/grok/side args=--version" ]] || fail "grok starts as the active Grok account" "$(grok --version)"
|
||||
[[ $(omarchy-agent-account-exec grok --version) == "grok home=$accounts/grok/side args=--version" ]] || fail "grok starts as the active Grok account" "$(omarchy-agent-account-exec grok --version)"
|
||||
omarchy-agent-account-use grok main >/dev/null
|
||||
pass "Grok accounts are added and used like the others"
|
||||
|
||||
@@ -213,11 +212,10 @@ omarchy-agent-account-use claude work >/dev/null
|
||||
grep -q "New Claude sessions now use Work (Max 5x)" "$notifications" || fail "switching says where new sessions go"
|
||||
pass "use makes an account active and says so"
|
||||
|
||||
source "$ROOT/default/bash/fns/agent-accounts"
|
||||
[[ $(claude --version) == "claude home=$work args=--version" ]] || fail "claude at a prompt starts as the active account"
|
||||
[[ $(CLAUDE_CONFIG_DIR=/elsewhere claude) == "claude home=/elsewhere args=" ]] || fail "an explicit CLAUDE_CONFIG_DIR wins over the active account"
|
||||
[[ $(codex) == "codex home=default args=" ]] || fail "codex stays on its primary until switched"
|
||||
pass "shell launches follow the active account"
|
||||
[[ $(omarchy-agent-account-exec claude --version) == "claude home=$work args=--version" ]] || fail "claude at a prompt starts as the active account"
|
||||
[[ $(CLAUDE_CONFIG_DIR=/elsewhere omarchy-agent-account-exec claude) == "claude home=/elsewhere args=" ]] || fail "an explicit CLAUDE_CONFIG_DIR wins over the active account"
|
||||
[[ $(omarchy-agent-account-exec codex) == "codex home=default args=" ]] || fail "codex stays on its primary until switched"
|
||||
pass "account dispatch follows the active account"
|
||||
|
||||
[[ $(OMARCHY_TEST_DEFAULT_AGENT=claude omarchy-agent --inline) == "claude home=$work args=--permission-mode auto" ]] ||
|
||||
fail "omarchy-agent starts Claude as the active account"
|
||||
|
||||
@@ -781,11 +781,11 @@ assert_launch pi pi "Review this project"
|
||||
assert_launch omp omp --auto-approve -- "Review this project"
|
||||
assert_launch opencode opencode --auto --prompt "Review this project"
|
||||
assert_launch ori ori code --interactive --prompt "Review this project"
|
||||
assert_launch claude claude --permission-mode auto -- "Review this project"
|
||||
assert_launch codex codex --approve-for-me -- "Review this project"
|
||||
assert_launch claude env -u CLAUDE_CONFIG_DIR OMARCHY_AGENT_CLAUDE_HOME= claude --permission-mode auto -- "Review this project"
|
||||
assert_launch codex env -u CODEX_HOME OMARCHY_AGENT_CODEX_HOME= codex --approve-for-me -- "Review this project"
|
||||
assert_launch muse muse --approval-mode never -- "Review this project"
|
||||
assert_launch crush crush run "Review this project"
|
||||
assert_launch grok grok --permission-mode bypassPermissions -- "Review this project"
|
||||
assert_launch grok env -u GROK_HOME OMARCHY_AGENT_GROK_HOME= grok --permission-mode bypassPermissions -- "Review this project"
|
||||
assert_launch cursor-agent cursor-agent --yolo --trust agent -- "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"
|
||||
@@ -809,11 +809,11 @@ assert_bypass pi pi
|
||||
assert_bypass omp omp --auto-approve
|
||||
assert_bypass opencode opencode --auto
|
||||
assert_bypass ori ori code
|
||||
assert_bypass claude claude --permission-mode auto
|
||||
assert_bypass codex codex --approve-for-me
|
||||
assert_bypass claude env -u CLAUDE_CONFIG_DIR OMARCHY_AGENT_CLAUDE_HOME= claude --permission-mode auto
|
||||
assert_bypass codex env -u CODEX_HOME OMARCHY_AGENT_CODEX_HOME= codex --approve-for-me
|
||||
assert_bypass muse muse --approval-mode never
|
||||
assert_bypass crush crush --yolo
|
||||
assert_bypass grok grok --permission-mode bypassPermissions
|
||||
assert_bypass grok env -u GROK_HOME OMARCHY_AGENT_GROK_HOME= grok --permission-mode bypassPermissions
|
||||
assert_bypass cursor-agent cursor-agent --yolo --trust
|
||||
assert_bypass hermes hermes --yolo
|
||||
assert_bypass agy agy --dangerously-skip-permissions
|
||||
|
||||
@@ -4,6 +4,8 @@ set -euo pipefail
|
||||
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
|
||||
unset MISE_DATA_DIR XDG_DATA_HOME
|
||||
|
||||
run_bootstrap() {
|
||||
local shell_bin="$1"
|
||||
local bootstrap="$2"
|
||||
@@ -56,7 +58,7 @@ pass "env-bootstrap resolves default OMARCHY_PATH"
|
||||
assert_path_present "$default_path" "$tmpdir/unrelated/bin" "env-bootstrap preserves PATH entries in default mode"
|
||||
assert_path_present "$default_path" "$home/.local/share/mise/shims" "env-bootstrap appends mise shims"
|
||||
assert_path_present "$default_path" "$home/.local/bin" "env-bootstrap appends ~/.local/bin"
|
||||
assert_path_first "$default_path" "$tmpdir/unrelated/bin" "env-bootstrap appends user-level paths after existing entries"
|
||||
assert_path_first "$default_path" "$home/.local/share/mise/command-wrappers/bin" "env-bootstrap puts command wrappers ahead of inherited tool binaries"
|
||||
|
||||
printf 'export OMARCHY_PATH="%s"\n' "$tmpdir/active" >"$tmpdir/omarchy.conf"
|
||||
mapfile -t linked_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin")
|
||||
@@ -64,23 +66,39 @@ linked_path=${linked_result[1]}
|
||||
|
||||
[[ ${linked_result[0]} == "$tmpdir/active" ]] || fail "env-bootstrap resolves linked OMARCHY_PATH" "actual: ${linked_result[0]}"
|
||||
pass "env-bootstrap resolves linked OMARCHY_PATH"
|
||||
assert_path_first "$linked_path" "$tmpdir/active/bin" "env-bootstrap prepends active checkout bin in linked mode"
|
||||
assert_path_first "$linked_path" "$home/.local/share/mise/command-wrappers/bin" "env-bootstrap keeps command wrappers first in linked mode"
|
||||
assert_path_present "$linked_path" "$tmpdir/active/bin" "env-bootstrap includes the active checkout bin in linked mode"
|
||||
assert_path_present "$linked_path" "$tmpdir/unrelated/bin" "env-bootstrap preserves unrelated PATH entries in linked mode"
|
||||
|
||||
mapfile -t linked_duplicate_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/active/bin:/usr/bin:$home/.local/share/mise/shims:$home/.local/bin")
|
||||
linked_duplicate_path=${linked_duplicate_result[1]}
|
||||
[[ $linked_duplicate_path == "$tmpdir/active/bin:/usr/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] || fail "env-bootstrap does not duplicate PATH entries" "actual PATH: $linked_duplicate_path"
|
||||
[[ $linked_duplicate_path == "$home/.local/share/mise/command-wrappers/bin:$tmpdir/active/bin:/usr/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] || fail "env-bootstrap does not duplicate PATH entries" "actual PATH: $linked_duplicate_path"
|
||||
pass "env-bootstrap does not duplicate PATH entries"
|
||||
|
||||
# An empty PATH must not produce empty entries (a bare ":" means the cwd)
|
||||
mapfile -t empty_path_result < <(run_bootstrap bash "$bootstrap" "$home" "")
|
||||
empty_path=${empty_path_result[1]}
|
||||
[[ $empty_path == "$tmpdir/active/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] || fail "env-bootstrap builds a clean PATH from an empty one" "actual PATH: $empty_path"
|
||||
[[ $empty_path == "$home/.local/share/mise/command-wrappers/bin:$tmpdir/active/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] || fail "env-bootstrap builds a clean PATH from an empty one" "actual PATH: $empty_path"
|
||||
pass "env-bootstrap builds a clean PATH from an empty one"
|
||||
|
||||
if command -v zsh >/dev/null 2>&1; then
|
||||
mapfile -t zsh_result < <(run_bootstrap zsh "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin")
|
||||
zsh_path=${zsh_result[1]}
|
||||
assert_path_first "$zsh_path" "$tmpdir/active/bin" "env-bootstrap works when sourced by zsh"
|
||||
assert_path_first "$zsh_path" "$home/.local/share/mise/command-wrappers/bin" "env-bootstrap works when sourced by zsh"
|
||||
assert_path_present "$zsh_path" "$tmpdir/unrelated/bin" "env-bootstrap zsh mode preserves unrelated PATH entries"
|
||||
fi
|
||||
|
||||
mapfile -t repeated_result < <(run_bootstrap bash "$bootstrap" "$home" "$linked_duplicate_path")
|
||||
[[ ${repeated_result[1]} == "$linked_duplicate_path" ]] || fail "repeated bootstrap leaves the command wrapper path unchanged"
|
||||
pass "repeated bootstrap leaves the command wrapper path unchanged"
|
||||
|
||||
mapfile -t custom_data_result < <(MISE_DATA_DIR="$tmpdir/custom-mise" run_bootstrap bash "$bootstrap" "$home" "/usr/bin")
|
||||
assert_path_first "${custom_data_result[1]}" "$tmpdir/custom-mise/command-wrappers/bin" "env-bootstrap honors a custom mise data directory"
|
||||
|
||||
# A parent agent or mise activation may have put real tool bins before a wrapper
|
||||
# directory that was already present. Move the existing entry rather than skip it.
|
||||
inherited_path="$tmpdir/managed/bin:$home/.local/share/mise/command-wrappers/bin:/usr/bin"
|
||||
mapfile -t reordered_result < <(run_bootstrap bash "$bootstrap" "$home" "$inherited_path")
|
||||
[[ ${reordered_result[1]} == "$home/.local/share/mise/command-wrappers/bin:$tmpdir/active/bin:$tmpdir/managed/bin:/usr/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] ||
|
||||
fail "bootstrap moves existing wrappers ahead of inherited managed tool paths without duplication" "${reordered_result[1]}"
|
||||
pass "bootstrap moves existing wrappers ahead of inherited managed tool paths without duplication"
|
||||
Reference in new issue
Block a user