* Let Grok updates through mise take effect Grok's npm launcher runs ~/.grok/bin/grok whenever it exists, and the install script that repoints it at a new release doesn't run under mise, so every machine kept running the release it first unpacked. Updating mise tools now drops a link to an older release and the old binary, and lets the launcher unpack the installed one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep several Grok accounts, the way Claude and Codex do Grok honors GROK_HOME, so an added Grok account gets its own home holding only its login and settings cache, with the CLI binary, sessions, skills, plugins, memory, and config linked back to ~/.grok. The account commands, the add flow (a second account signs in through a private window), the launcher, and a grok shell function all take it like the others, and its identity and plan come from the files Grok writes at login. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Show Grok's plan and credits in the agents panel A grok collector reads each account's plan from the settings Grok caches and its credits from the endpoint behind Grok's own /usage view, so Grok gets a section, per-account limits, and autoswitch like Claude and Codex. A signed-in agent with a plan now shows before its first numbers, so a lapsed sign-in has somewhere to say so. Grok's mark joins the assets, and with every agent able to take another account the add screen no longer needs to dim one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document Grok accounts and limits alongside Claude and Codex Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Grok's credits the way xAI actually answers The answer nests under config, names a weekly period with its end, and as protobuf JSON leaves the usage percentage out while it's zero. The collector now reads that shape, so a fresh week shows as 0% until it resets rather than as nothing known. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count Grok's prompts and sessions from its session summaries Grok keeps a summary beside each session with when it was last active and how many prompts it had, so its section and the hero's summary get today's prompts and sessions and its active days. It records no token counts there, so none are claimed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give each Grok limits cache write its own temporary file Two overlapping collector runs wrote the same cache through one .tmp path, so one rename could leave the other's failing and its record not updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read a single Grok account from GROK_HOME when it's set The launcher and the CLI honor GROK_HOME, but the collector always read ~/.grok, so a Grok signed in only in a custom home showed nothing. With one account, the collector now reads the home the CLI would; with several registered, the primary stays ~/.grok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop guesses the real Grok credits answer made unnecessary The collector carried fallbacks for fields and shapes guessed from the binary before xAI's actual answer was seen, a numeric timestamp branch nothing writes, the panel's default for prompt stats, and guards that an empty sign-in already covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the working Grok release until its update is in place The update dropped the link and older binaries before the new release was unpacked and ignored a failed unpack, which could leave every Grok account without a CLI. The old release now stays until the new one is linked, and its link comes back if it never is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count only Grok sessions for today, not prompts A session's summary holds all its prompts and only when it was last active, so a resumed session put its whole history on today. Today now counts the sessions active in it; prompts stay an all-time total. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reuse the Grok session scan on a limits-only refresh Opening the panel and near-limit checks only need fresh limits, so they reuse a scan up to 15 minutes old, as the Codex collector does; --force still rescans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
421 lines
23 KiB
Bash
421 lines
23 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
require_command jq
|
|
require_command python3
|
|
|
|
test_tmp=$(mktemp -d)
|
|
trap 'rm -rf "$test_tmp"' EXIT
|
|
|
|
mock_bin="$test_tmp/bin"
|
|
notifications="$test_tmp/notifications"
|
|
mkdir -p "$mock_bin" "$test_tmp/home/.claude" "$test_tmp/home/.codex"
|
|
|
|
cat >"$mock_bin/omarchy-notification-send" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >>"$OMARCHY_TEST_NOTIFICATIONS"
|
|
SH
|
|
|
|
# The CLIs report which home they were started in, and a login writes the
|
|
# identity a real one would into whichever home it was given.
|
|
cat >"$mock_bin/claude" <<'SH'
|
|
#!/bin/bash
|
|
if [[ ${1:-} == "auth" && ${2:-} == "login" ]]; then
|
|
"${BROWSER:-omarchy-test-default-browser}" "https://claude.com/oauth/authorize"
|
|
if [[ -n ${OMARCHY_TEST_LOGIN_HANGS:-} ]]; then
|
|
echo $$ >"$OMARCHY_TEST_LOGIN_HANGS"
|
|
exec sleep 30
|
|
fi
|
|
[[ -n ${OMARCHY_TEST_LOGIN_UUID:-} ]] || exit 1
|
|
home=${CLAUDE_CONFIG_DIR:-$HOME/.claude}
|
|
account_file="$home/.claude.json"
|
|
[[ -z ${CLAUDE_CONFIG_DIR:-} ]] && account_file="$HOME/.claude.json"
|
|
printf '{"oauthAccount":{"accountUuid":"%s","emailAddress":"%s","organizationName":"Work"}}\n' \
|
|
"$OMARCHY_TEST_LOGIN_UUID" "$OMARCHY_TEST_LOGIN_EMAIL" >"$account_file"
|
|
echo '{"claudeAiOauth":{"rateLimitTier":"default_claude_max_5x","subscriptionType":"max"}}' >"$home/.credentials.json"
|
|
exit 0
|
|
fi
|
|
echo "claude home=${CLAUDE_CONFIG_DIR:-default} args=$*"
|
|
SH
|
|
|
|
cat >"$mock_bin/codex" <<'SH'
|
|
#!/bin/bash
|
|
if [[ ${1:-} == "login" ]]; then
|
|
"${BROWSER:-omarchy-test-default-browser}" "https://auth.openai.com/oauth/authorize"
|
|
claims=$(printf '{"email":"%s","https://api.openai.com/auth":{"chatgpt_plan_type":"pro"}}' "$OMARCHY_TEST_LOGIN_EMAIL" | base64 -w0 | tr '+/' '-_' | tr -d '=')
|
|
printf '{"auth_mode":"chatgpt","tokens":{"account_id":"%s","id_token":"h.%s.s"}}\n' "$OMARCHY_TEST_LOGIN_UUID" "$claims" >"${CODEX_HOME:-$HOME/.codex}/auth.json"
|
|
exit 0
|
|
fi
|
|
echo "codex home=${CODEX_HOME:-default} args=$*"
|
|
SH
|
|
|
|
# Grok keys its login by issuer and keeps the plan in the settings it caches,
|
|
# as a JSON string inside that file.
|
|
cat >"$mock_bin/grok" <<'SH'
|
|
#!/bin/bash
|
|
if [[ ${1:-} == "login" ]]; then
|
|
"${BROWSER:-omarchy-test-default-browser}" "https://auth.x.ai/oauth/authorize"
|
|
home=${GROK_HOME:-$HOME/.grok}
|
|
mkdir -p "$home"
|
|
printf '{"https://auth.x.ai::client":{"key":"t","user_id":"%s","email":"%s"}}\n' \
|
|
"${OMARCHY_TEST_LOGIN_UUID:-u-grok}" "${OMARCHY_TEST_LOGIN_EMAIL:-me@example.com}" >"$home/auth.json"
|
|
printf '{"payload":"{\\"settings\\":{\\"subscription_tier_display\\":\\"SuperGrok\\"}}"}\n' >"$home/settings_cache.json"
|
|
exit 0
|
|
fi
|
|
echo "grok home=${GROK_HOME:-default} args=$*"
|
|
SH
|
|
|
|
cat >"$mock_bin/omarchy-test-default-browser" <<'SH'
|
|
#!/bin/bash
|
|
printf 'default %s\n' "$*" >>"$OMARCHY_TEST_BROWSER_LOG"
|
|
SH
|
|
|
|
cat >"$mock_bin/omarchy-agent-usage-update" <<'SH'
|
|
#!/bin/bash
|
|
SH
|
|
|
|
cat >"$mock_bin/omarchy-default-agent" <<'SH'
|
|
#!/bin/bash
|
|
echo "${OMARCHY_TEST_DEFAULT_AGENT-claude}"
|
|
SH
|
|
|
|
cat >"$mock_bin/omarchy-launch-browser" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >>"$OMARCHY_TEST_BROWSER_LOG"
|
|
SH
|
|
|
|
chmod +x "$mock_bin"/*
|
|
|
|
export HOME="$test_tmp/home"
|
|
export XDG_STATE_HOME="$test_tmp/state"
|
|
export PATH="$mock_bin:$ROOT/bin:$PATH"
|
|
export OMARCHY_PATH="$ROOT"
|
|
export OMARCHY_TEST_NOTIFICATIONS="$notifications"
|
|
export OMARCHY_TEST_BROWSER_LOG="$test_tmp/browser"
|
|
unset CLAUDE_CONFIG_DIR CODEX_HOME GROK_HOME BROWSER
|
|
|
|
accounts="$XDG_STATE_HOME/omarchy/agents/accounts"
|
|
|
|
echo '{"oauthAccount":{"accountUuid":"u-main","emailAddress":"me@example.com","organizationName":"Me"},"mcpServers":{"docs":{"command":"docs-mcp"}}}' >"$HOME/.claude.json"
|
|
echo '{"claudeAiOauth":{"rateLimitTier":"default_claude_max_20x","subscriptionType":"max"}}' >"$HOME/.claude/.credentials.json"
|
|
echo '{"theme":"custom:omarchy"}' >"$HOME/.claude/settings.json"
|
|
|
|
# ---------------------------------------------------------------- single account
|
|
|
|
[[ -z $(omarchy-agent-account-home claude) ]] || fail "a machine with one account routes nowhere"
|
|
[[ ! -e $accounts/claude.json ]] || fail "reading the active home never creates a registry"
|
|
pass "a machine with one account launches exactly as before"
|
|
|
|
list=$(omarchy-agent-account-list claude --json)
|
|
[[ $(jq -c '.[0].accounts | map({id, label, plan, email, active, primary})' <<<"$list") == '[{"id":"main","label":"Main","plan":"Max 20x","email":"me@example.com","active":true,"primary":true}]' ]] ||
|
|
fail "the existing login is listed as the primary account" "$list"
|
|
pass "the existing login is listed as the primary account"
|
|
|
|
# ------------------------------------------------------------------------- add
|
|
|
|
OMARCHY_TEST_LOGIN_UUID=u-work OMARCHY_TEST_LOGIN_EMAIL=work@example.com \
|
|
omarchy-agent-account-add claude Work </dev/null >"$test_tmp/add-output"
|
|
grep -q "Added Work (work@example.com)" "$test_tmp/add-output" || fail "adding an account reports who signed in" "$(cat "$test_tmp/add-output")"
|
|
grep -q "private window that opens" "$test_tmp/add-output" || fail "adding an account says to sign in within the private window"
|
|
[[ $(head -1 "$OMARCHY_TEST_BROWSER_LOG") == "--private https://claude.com/oauth/authorize" ]] ||
|
|
fail "a Claude login opens in a private window" "$(cat "$OMARCHY_TEST_BROWSER_LOG")"
|
|
pass "adding an account signs in through the CLI's own login"
|
|
|
|
work="$accounts/claude/work"
|
|
[[ -f $work/.credentials.json && ! -L $work/.credentials.json ]] || fail "an added account keeps its own credentials"
|
|
[[ $(readlink "$work/projects") == "$HOME/.claude/projects" ]] || fail "an added account shares conversation history with the primary"
|
|
[[ $(readlink "$work/settings.json") == "$HOME/.claude/settings.json" ]] || fail "an added account shares settings with the primary"
|
|
[[ $(readlink "$work/CLAUDE.md") == "$HOME/.claude/CLAUDE.md" && ! -e $HOME/.claude/CLAUDE.md ]] ||
|
|
fail "a shared file the primary doesn't have yet is linked, so it's written there when it is"
|
|
[[ $(jq -c .mcpServers "$work/.claude.json") == '{"docs":{"command":"docs-mcp"}}' ]] || fail "an added account carries the primary's MCP servers"
|
|
[[ $(stat -c %a "$work") == 700 && $(stat -c %a "$accounts/claude.json") == 600 ]] || fail "account homes and the registry are private"
|
|
pass "an added account shares everything but its login with the primary"
|
|
|
|
if OMARCHY_TEST_LOGIN_UUID=u-work OMARCHY_TEST_LOGIN_EMAIL=work@example.com \
|
|
omarchy-agent-account-add claude Again </dev/null >"$test_tmp/dup-output" 2>&1; then
|
|
fail "adding the same account twice fails"
|
|
fi
|
|
grep -q "That's Work" "$test_tmp/dup-output" || fail "a duplicate login names the account it already is" "$(cat "$test_tmp/dup-output")"
|
|
[[ -z $(ls -A "$accounts/claude/.pending") ]] || fail "a duplicate login leaves no scratch home behind"
|
|
pass "adding an account that's already there is refused"
|
|
|
|
# Main was signed in to another account by hand since the registry was made;
|
|
# adding that login again is still the same subscription.
|
|
jq '.oauthAccount.accountUuid = "u-relogged"' "$HOME/.claude.json" >"$test_tmp/relogged.json"
|
|
cp "$HOME/.claude.json" "$test_tmp/original.json"
|
|
mv "$test_tmp/relogged.json" "$HOME/.claude.json"
|
|
if OMARCHY_TEST_LOGIN_UUID=u-relogged OMARCHY_TEST_LOGIN_EMAIL=me@example.com \
|
|
omarchy-agent-account-add claude Twin </dev/null >"$test_tmp/twin-output" 2>&1; then
|
|
fail "a login matching Main's current sign-in is refused"
|
|
fi
|
|
grep -q "That's Main" "$test_tmp/twin-output" || fail "a login matching Main's current sign-in names Main" "$(cat "$test_tmp/twin-output")"
|
|
mv "$test_tmp/original.json" "$HOME/.claude.json"
|
|
pass "duplicates are judged by who each home is signed in as now"
|
|
|
|
if OMARCHY_TEST_LOGIN_UUID="" omarchy-agent-account-add claude Nope </dev/null >/dev/null 2>&1; then
|
|
fail "an abandoned login adds nothing"
|
|
fi
|
|
[[ $(omarchy-agent-account-list claude --json | jq '.[0].accounts | length') == 2 ]] || fail "an abandoned login adds nothing"
|
|
pass "an abandoned login adds nothing"
|
|
|
|
OMARCHY_TEST_LOGIN_UUID=u-next OMARCHY_TEST_LOGIN_EMAIL=next@example.com \
|
|
omarchy-agent-account-add claude Next </dev/null >/dev/null
|
|
[[ $(omarchy-agent-account-list claude --json | jq -r '.[0].accounts[] | select(.label == "Next") | .id') == "next-2" ]] ||
|
|
fail "an account label can't take an id that routing already answers to"
|
|
omarchy-agent-account-remove claude next-2 </dev/null >/dev/null
|
|
pass "account ids stay clear of routing keywords"
|
|
|
|
OMARCHY_TEST_LOGIN_UUID=acct-1 OMARCHY_TEST_LOGIN_EMAIL=me@example.com \
|
|
omarchy-agent-account-add codex </dev/null >"$test_tmp/first-codex"
|
|
[[ -f $HOME/.codex/auth.json && ! -d $accounts/codex ]] || fail "the first Codex account signs in to ~/.codex itself"
|
|
grep -qx "default https://auth.openai.com/oauth/authorize" "$OMARCHY_TEST_BROWSER_LOG" ||
|
|
fail "the first Codex account signs in through the normal browser" "$(cat "$OMARCHY_TEST_BROWSER_LOG")"
|
|
[[ ! -e $HOME/.config/omarchy/defaults/agent ]] || fail "a first sign-in leaves an existing default agent alone"
|
|
pass "the first account of a provider signs in to its own home in the normal browser"
|
|
|
|
OMARCHY_TEST_LOGIN_UUID=acct-2 OMARCHY_TEST_LOGIN_EMAIL=side@example.com \
|
|
omarchy-agent-account-add codex Side </dev/null >/dev/null
|
|
[[ $(omarchy-agent-account-list codex --json | jq -c '.[0].accounts[1] | {id, email, plan}') == '{"id":"side","email":"side@example.com","plan":"Pro"}' ]] ||
|
|
fail "a Codex account reads its identity from the login's token claims"
|
|
[[ $(readlink "$accounts/codex/side/sessions") == "$HOME/.codex/sessions" ]] || fail "a Codex account shares sessions with the primary"
|
|
grep -qx -- "--private https://auth.openai.com/oauth/authorize" "$OMARCHY_TEST_BROWSER_LOG" ||
|
|
fail "a Codex login opens in a private window" "$(cat "$OMARCHY_TEST_BROWSER_LOG")"
|
|
pass "Codex accounts are added the same way"
|
|
|
|
OMARCHY_TEST_DEFAULT_AGENT="" omarchy-agent-account-add grok </dev/null >/dev/null
|
|
[[ $(cat "$HOME/.config/omarchy/defaults/agent") == "grok" ]] ||
|
|
fail "the first agent signed in on a machine with no default becomes the default"
|
|
[[ -s $HOME/.grok/auth.json ]] && grep -qx "default https://auth.x.ai/oauth/authorize" "$OMARCHY_TEST_BROWSER_LOG" ||
|
|
fail "a first Grok sign-in lands in ~/.grok through the normal browser"
|
|
pass "Grok signs in its first account"
|
|
|
|
mkdir -p "$HOME/.grok/bin" && touch "$HOME/.grok/bin/grok"
|
|
OMARCHY_TEST_LOGIN_UUID=u-grok-2 OMARCHY_TEST_LOGIN_EMAIL=side@example.com \
|
|
omarchy-agent-account-add grok Side </dev/null >/dev/null
|
|
[[ $(omarchy-agent-account-list grok --json | jq -c '.[0].accounts[1] | {id, email, plan}') == '{"id":"side","email":"side@example.com","plan":"SuperGrok"}' ]] ||
|
|
fail "a Grok account reads its identity and plan from its own home" "$(omarchy-agent-account-list grok --json)"
|
|
[[ $(readlink "$accounts/grok/side/bin") == "$HOME/.grok/bin" && $(readlink "$accounts/grok/side/sessions") == "$HOME/.grok/sessions" ]] ||
|
|
fail "a Grok account shares the CLI and sessions with the primary"
|
|
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-use grok main >/dev/null
|
|
pass "Grok accounts are added and used like the others"
|
|
|
|
# ---------------------------------------------------------------------- routing
|
|
|
|
omarchy-agent-account-use claude work >/dev/null
|
|
[[ $(omarchy-agent-account-home claude) == "$work" ]] || fail "the active account's home is what launches use"
|
|
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_TEST_DEFAULT_AGENT=claude omarchy-agent --inline) == "claude home=$work args=--permission-mode auto" ]] ||
|
|
fail "omarchy-agent starts Claude as the active account"
|
|
omarchy-agent-account-use codex side >/dev/null
|
|
[[ $(OMARCHY_TEST_DEFAULT_AGENT=codex omarchy-agent --inline) == "codex home=$accounts/codex/side args=--approve-for-me" ]] ||
|
|
fail "omarchy-agent starts Codex as the active account"
|
|
pass "omarchy-agent follows the active account"
|
|
|
|
omarchy-agent-account-use claude next >/dev/null
|
|
[[ -z $(omarchy-agent-account-home claude) ]] || fail "next cycles back to the primary"
|
|
pass "next cycles through accounts"
|
|
|
|
# With Claude as the default agent, the provider can be left out.
|
|
OMARCHY_TEST_DEFAULT_AGENT=claude omarchy-agent-account-use work >/dev/null
|
|
[[ $(omarchy-agent-account-home claude) == "$work" ]] || fail "use without a provider picks the default agent's account"
|
|
OMARCHY_TEST_DEFAULT_AGENT=claude omarchy-agent-account-use main >/dev/null
|
|
[[ -z $(omarchy-agent-account-home claude) ]] || fail "use without a provider switches back to Main"
|
|
OMARCHY_TEST_DEFAULT_AGENT=codex omarchy-agent-account-use main >/dev/null
|
|
[[ -z $(omarchy-agent-account-home codex) ]] || fail "the default agent decides which provider a short use means"
|
|
if OMARCHY_TEST_DEFAULT_AGENT=pi omarchy-agent-account-use work >/dev/null 2>&1; then
|
|
fail "a default agent with no accounts still needs the provider named"
|
|
fi
|
|
pass "the provider defaults to your default agent"
|
|
|
|
# ----------------------------------------------------------------------- rename
|
|
|
|
omarchy-agent-account-use claude work >/dev/null
|
|
omarchy-agent-account-rename claude work Day job >/dev/null
|
|
[[ $(omarchy-agent-account-list claude --json | jq -c '.[0] | {active, renamed: (.accounts[] | select(.id == "day-job") | .label)}') == '{"active":"day-job","renamed":"Day job"}' ]] ||
|
|
fail "renaming changes the label and the id it answers to, and the active account follows"
|
|
[[ $(omarchy-agent-account-home claude) == "$work" ]] || fail "renaming leaves the account's home where running sessions expect it"
|
|
OMARCHY_TEST_DEFAULT_AGENT=claude omarchy-agent-account-rename day-job Work >/dev/null
|
|
[[ $(omarchy-agent-account-home claude) == "$work" ]] || fail "a renamed account can be renamed back"
|
|
omarchy-agent-account-rename claude main Personal >/dev/null
|
|
[[ $(omarchy-agent-account-list claude --json | jq -r '.[0].accounts[] | select(.primary) | "\(.id) \(.home)"') == "personal $HOME/.claude" ]] ||
|
|
fail "the primary account can be renamed and keeps ~/.claude"
|
|
omarchy-agent-account-rename claude personal Main >/dev/null
|
|
# `primary` reaches the first login whatever it's called, as the manual says.
|
|
omarchy-agent-account-rename claude primary Hey >/dev/null ||
|
|
fail "primary names the primary account"
|
|
omarchy-agent-account-rename claude primary Main >/dev/null
|
|
[[ $(omarchy-agent-account-list claude --json | jq -r '.[0].accounts[] | select(.primary) | .id') == "main" ]] ||
|
|
fail "primary still reaches the primary account after a rename"
|
|
if omarchy-agent-account-rename claude work "" >/dev/null 2>&1; then
|
|
fail "an account can't be renamed to nothing"
|
|
fi
|
|
pass "rename relabels an account without moving it"
|
|
|
|
# ------------------------------------------------------------ mode and remove
|
|
|
|
OMARCHY_TEST_DEFAULT_AGENT=claude omarchy-agent-account-mode auto 90 >/dev/null
|
|
[[ $(jq -c '{switch, threshold}' "$accounts/claude.json") == '{"switch":"auto","threshold":90}' ]] || fail "mode sets switching and threshold"
|
|
if omarchy-agent-account-mode claude sometimes >/dev/null 2>&1; then
|
|
fail "mode refuses an unknown switch mode"
|
|
fi
|
|
pass "mode sets how switching happens"
|
|
|
|
if omarchy-agent-account-remove claude main </dev/null >/dev/null 2>&1; then
|
|
fail "the primary account can't be removed"
|
|
fi
|
|
omarchy-agent-account-use claude work >/dev/null
|
|
# A session still running in the account keeps its login until it quits.
|
|
CLAUDE_CONFIG_DIR="$work" sleep 30 &
|
|
session=$!
|
|
if omarchy-agent-account-remove claude work </dev/null >/dev/null 2>"$test_tmp/in-use"; then
|
|
fail "an account a running session uses can't be removed"
|
|
fi
|
|
[[ -d $work ]] && grep -q "Quit it first" "$test_tmp/in-use" || fail "removing an account in use leaves it and says why" "$(cat "$test_tmp/in-use")"
|
|
kill "$session"; wait "$session" 2>/dev/null || true
|
|
omarchy-agent-account-remove claude work </dev/null >/dev/null
|
|
[[ ! -e $work && -d $HOME/.claude/projects && -f $HOME/.claude/settings.json ]] || fail "removing an account deletes its home and nothing it links to"
|
|
[[ -z $(omarchy-agent-account-home claude) ]] || fail "removing the active account falls back to the primary"
|
|
pass "remove forgets an added account without touching shared files"
|
|
|
|
# A registry that can't be saved leaves the new login pending, where the add
|
|
# command cleans it up, rather than in a home nothing can manage.
|
|
STATE="$ROOT/bin/omarchy-agent-account-state" python3 - <<'PY' || fail "a failed registration rolls the new home back to pending"
|
|
import importlib.machinery, importlib.util, json, os, sys
|
|
loader = importlib.machinery.SourceFileLoader("state", os.environ["STATE"])
|
|
spec = importlib.util.spec_from_loader(loader.name, loader)
|
|
state = importlib.util.module_from_spec(spec)
|
|
loader.exec_module(state)
|
|
|
|
pending = state.begin("claude")
|
|
(pending / ".claude.json").write_text(json.dumps({"oauthAccount": {"accountUuid": "u-stranded", "emailAddress": "stranded@example.com"}}))
|
|
|
|
def broken_save(provider, registry):
|
|
raise OSError("disk full")
|
|
state.save = broken_save
|
|
|
|
try:
|
|
state.register("claude", "Stranded", pending)
|
|
except OSError:
|
|
pass
|
|
else:
|
|
sys.exit("register should fail when the registry can't be saved")
|
|
assert pending.is_dir(), "the login goes back to pending"
|
|
assert not (state.accounts_root() / "claude" / "stranded").exists(), "no home is left outside the registry"
|
|
# The add command would remove it on exit; this test stands in for it.
|
|
import shutil
|
|
shutil.rmtree(pending)
|
|
PY
|
|
pass "a failed registration rolls the new home back to pending"
|
|
|
|
# ------------------------------------------------------------ panel add flow
|
|
|
|
[[ $(omarchy-agent-account-add --check) == $'claude additional\ncodex additional\ngrok additional' ]] ||
|
|
fail "--check says what adding would mean for each provider" "$(omarchy-agent-account-add --check)"
|
|
pass "--check says what adding would mean for each provider"
|
|
|
|
: >"$notifications"
|
|
OMARCHY_TEST_LOGIN_UUID=u-events OMARCHY_TEST_LOGIN_EMAIL=events@example.com \
|
|
omarchy-agent-account-add --events claude Events </dev/null >"$test_tmp/events-output"
|
|
grep -qx "@@omarchy status Sign in as the account you're adding in the private window that opens." "$test_tmp/events-output" ||
|
|
fail "--events reports progress as tagged lines" "$(cat "$test_tmp/events-output")"
|
|
grep -qx "@@omarchy done Added Events (events@example.com)." "$test_tmp/events-output" ||
|
|
fail "--events reports the result as a tagged line" "$(cat "$test_tmp/events-output")"
|
|
grep -q "Added Events (events@example.com)." "$notifications" || fail "--events also notifies, in case the panel closed"
|
|
if OMARCHY_TEST_LOGIN_UUID=u-events OMARCHY_TEST_LOGIN_EMAIL=events@example.com \
|
|
omarchy-agent-account-add --events claude Again </dev/null >"$test_tmp/events-dup" 2>&1; then
|
|
fail "--events fails a duplicate"
|
|
fi
|
|
grep -q "^@@omarchy error That's Events" "$test_tmp/events-dup" || fail "--events reports a failure as a tagged line" "$(cat "$test_tmp/events-dup")"
|
|
pass "--events reports progress and results for the panel"
|
|
|
|
# ------------------------------------------------------------------- reauth
|
|
|
|
: >"$OMARCHY_TEST_BROWSER_LOG"
|
|
before=$(omarchy-agent-account-list claude --json | jq '.[0].accounts | length')
|
|
rm -f "$accounts/claude/events/.credentials.json"
|
|
OMARCHY_TEST_LOGIN_UUID=u-events OMARCHY_TEST_LOGIN_EMAIL=events@example.com \
|
|
omarchy-agent-account-add --events --reauth events claude </dev/null >"$test_tmp/reauth-output"
|
|
grep -qx "@@omarchy done Signed in to Claude again." "$test_tmp/reauth-output" || fail "--reauth reports the sign-in" "$(cat "$test_tmp/reauth-output")"
|
|
[[ -s $accounts/claude/events/.credentials.json ]] || fail "--reauth signs in to the account's own home"
|
|
[[ $(omarchy-agent-account-list claude --json | jq '.[0].accounts | length') == "$before" ]] || fail "--reauth adds no account"
|
|
grep -qx -- "--private https://claude.com/oauth/authorize" "$OMARCHY_TEST_BROWSER_LOG" || fail "--reauth of an added account uses a private window"
|
|
|
|
: >"$OMARCHY_TEST_BROWSER_LOG"
|
|
OMARCHY_TEST_LOGIN_UUID=u-main OMARCHY_TEST_LOGIN_EMAIL=me@example.com \
|
|
omarchy-agent-account-add --reauth main claude </dev/null >/dev/null
|
|
grep -qx "default https://claude.com/oauth/authorize" "$OMARCHY_TEST_BROWSER_LOG" || fail "--reauth of the primary uses the normal browser"
|
|
|
|
if omarchy-agent-account-add --reauth nobody claude </dev/null >/dev/null 2>&1; then
|
|
fail "--reauth of an unknown account fails"
|
|
fi
|
|
pass "--reauth signs an existing account in again where it lives"
|
|
|
|
# ------------------------------------------------------------------- cancel
|
|
|
|
# Cancelling from the panel stops a login that's waiting on the browser, right
|
|
# away, and leaves no half-made account behind.
|
|
OMARCHY_TEST_LOGIN_HANGS="$test_tmp/login.pid" omarchy-agent-account-add --events claude Slow </dev/null >/dev/null 2>&1 &
|
|
adding=$!
|
|
sleep 1
|
|
started=$(date +%s)
|
|
kill -TERM "$adding"
|
|
wait "$adding" || true
|
|
(( $(date +%s) - started < 3 )) || fail "cancelling stops a waiting login at once"
|
|
login_pid=$(cat "$test_tmp/login.pid")
|
|
for _ in {1..20}; do
|
|
kill -0 "$login_pid" 2>/dev/null || break
|
|
sleep 0.1
|
|
done
|
|
if kill -0 "$login_pid" 2>/dev/null; then
|
|
kill "$login_pid"
|
|
fail "cancelling stops the login itself"
|
|
fi
|
|
[[ -z $(ls -A "$accounts/claude/.pending") ]] || fail "cancelling leaves no half-made account"
|
|
pass "cancelling a sign-in stops the login and cleans up"
|
|
|
|
# ------------------------------------------------------------------ one account
|
|
|
|
# With one account, the usage record keeps its limits at the top level, and
|
|
# the list still shows them.
|
|
solo="$test_tmp/solo"
|
|
mkdir -p "$solo/omarchy/agents/usage"
|
|
echo '{"id":"claude","limits":[{"label":"Session (5-hour)","percent":0.4,"resetsAt":""}]}' >"$solo/omarchy/agents/usage/claude.json"
|
|
[[ $(XDG_STATE_HOME="$solo" omarchy-agent-account-list claude --json | jq -c '.[0].accounts[0].limits[0].percent') == "0.4" ]] ||
|
|
fail "a single account's limits are listed from the record's top level"
|
|
pass "a single account's limits are listed"
|
|
|
|
# ------------------------------------------------------------- signed out
|
|
|
|
# A login that's gone is gone, whatever the registry remembered about it.
|
|
rm -f "$HOME/.codex/auth.json"
|
|
[[ $(omarchy-agent-account-add --check | grep '^codex ') == "codex first" ]] ||
|
|
fail "a signed-out primary counts as a first sign-in again" "$(omarchy-agent-account-add --check)"
|
|
pass "a signed-out account no longer counts as signed in"
|
|
|
|
# --------------------------------------------------------------- refresh
|
|
|
|
# A home signed in to someone else since it was added is saved as who it is
|
|
# now, which is what the usage records name it by.
|
|
jq '.oauthAccount.emailAddress = "switched@example.com"' "$accounts/claude/events/.claude.json" >"$test_tmp/switched.json"
|
|
mv "$test_tmp/switched.json" "$accounts/claude/events/.claude.json"
|
|
omarchy-agent-account-state refresh claude
|
|
[[ $(jq -r '.accounts[] | select(.id == "events") | .email' "$accounts/claude.json") == "switched@example.com" ]] ||
|
|
fail "refresh saves who each home is signed in as now"
|
|
pass "refresh saves who each home is signed in as now"
|