* Let the agents in the panel be put in any order Drag an agent by its mark to move its section; the header it will land on lights up, and the move happens on release. Each agent's header is now a keyboard stop with its own highlight, and Ctrl+Up/Down moves the agent the cursor is in. The order is kept in agents/order.json beside the usage records. The key catcher turns Ctrl+Up/Down into a reorder only for panels that opt in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Light only an agent's mark when the cursor or a drag is on it The mark is the handle the agent moves by, so the keyboard cursor and the drop spot while dragging box it alone rather than the header line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep a lit agent mark's box from being clipped at the panel's edge The box overhangs the content's left edge, which the scrolling area clipped. The scrolling area now reaches a little into the panel's padding with the content shifted back, so nothing moves and the box draws whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reuse a Grok session scan only on the day it was made A limits-only refresh just after midnight reused a scan from the evening before, which counted yesterday's sessions as today's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep Ctrl+Up/Down from moving an agent when the cursor is outside one The hero's buttons and the starter tiles carry indices too, and the lookup read them as accounts, so with several accounts Ctrl+Up/Down on one of them moved an unrelated agent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Report a failed mise update even after the Grok upkeep runs The Grok block ran after `mise up` and its last command set the script's status, so a failed tool update could read as a success to callers that warn about it. The update's own status is now the script's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Unpack a Grok update into ~/.grok whatever GROK_HOME says The upkeep replaces ~/.grok's link, but the npm launcher unpacks into GROK_HOME when it's set, so with a custom home the link never came back at the new release and the old one was restored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold off reordering agents while an account name is being edited Ctrl+Up/Down reached the key catcher during an inline rename, and moving the agent rebuilt its section, dropping the unfinished name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Install Grok through mise's first-party package The npm launcher keeps an old ~/.grok/bin binary because mise skips its postinstall. Use mise's grok tool, and drop the npm tool so its shim does not stay ahead of the stub. * Drop the npm Grok workarounds now that mise installs Grok itself With Grok installed through mise's first-party package, the CLI is the binary mise manages, so the update upkeep that repointed ~/.grok/bin and the shared bin directory for added Grok accounts have nothing left to do. omarchy-update-mise is back to running mise up and nothing else, and adding a first Grok account installs the same package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count Grok's tokens and prompts from its usage ledger Each Grok session keeps a usage.json, the ledger `grok usage` prints, with every finished turn's end time and tokens by model. The collector now reads it for tokens today, by day for the last week, and by model, with cached input kept apart, and counts today's prompts by the turns that ended today. Found in #12352's research. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Count cache writes in Grok's daily token totals Grok's totalTokens leaves cache writes out while the per-model buckets count them, so a turn with cache writes added less to its day than to its model. The day's total is now the sum of those same buckets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remove the npm launcher's old Grok binaries when moving to mise's Grok Omarchy's npm wrapper ran the binary the launcher unpacked into ~/.grok/bin, where x.ai's installer also puts a copy with a PATH entry ahead of mise. Once the wrapper is replaced, a binary left there would keep shadowing the mise tool, so the migration removes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Jesse Miller <jmiller@jmiller.com>
420 lines
23 KiB
Bash
420 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"
|
|
|
|
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/sessions") == "$HOME/.grok/sessions" ]] ||
|
|
fail "a Grok account shares 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"
|