claude-multiacc 2.0.58 → 2.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -4
- package/bin/claude +389 -94
- package/bin/claude-accounts +276 -42
- package/bin/codex +69 -5
- package/bin/codex-accounts +33 -4
- package/docs/ACCOUNT_OPERATIONS.md +62 -2
- package/docs/AUTORESUME-HANDOFF.md +28 -3
- package/docs/AUTORESUME.md +275 -18
- package/docs/CODEX.md +1 -0
- package/docs/INSTALLATION.md +7 -1
- package/docs/VERIFICATION.md +4 -0
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
- package/lib/__pycache__/report.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
- package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
- package/lib/audit.py +88 -22
- package/lib/autoresume.py +1197 -82
- package/lib/autoresume_cross.py +3 -1
- package/lib/autoresume_handoff.py +81 -9
- package/lib/autoresume_probe.py +2 -2
- package/lib/claude_client_error.py +9 -4
- package/lib/codex_trust.py +263 -0
- package/lib/common.sh +146 -3
- package/lib/credential.py +5 -2
- package/lib/install_actions.sh +19 -0
- package/lib/keychain.py +180 -36
- package/lib/mcp_oauth.py +1748 -0
- package/lib/mcp_registry.py +230 -8
- package/lib/model_catalog.json +404 -0
- package/lib/model_catalog.py +201 -0
- package/lib/model_match.py +104 -0
- package/lib/report.py +8 -5
- package/lib/session_settings.py +123 -0
- package/package.json +2 -2
- package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
- package/tests/fake_codex_tui.py +25 -0
- package/tests/fake_security.sh +24 -2
- package/tests/fixtures/cross_provider_cli.py +109 -0
- package/tests/run-tests.sh +1171 -23
- package/tests/test_autoresume.py +215 -8
- package/tests/test_autoresume_catalog.py +226 -0
- package/tests/test_autoresume_catalog_cli.py +185 -0
- package/tests/test_autoresume_codex_records.py +16 -0
- package/tests/test_autoresume_codex_trust.py +247 -0
- package/tests/test_autoresume_cross_guards.py +28 -0
- package/tests/test_autoresume_cross_tmux.py +27 -2
- package/tests/test_autoresume_cross_tokens.py +32 -2
- package/tests/test_autoresume_handoff_argv.py +120 -16
- package/tests/test_autoresume_handoff_origins.py +14 -0
- package/tests/test_autoresume_memory.py +1141 -0
- package/tests/test_autoresume_memory_tmux.py +94 -0
- package/tests/test_autoresume_probe.py +2 -1
- package/tests/test_autoresume_relaunch_settings.py +317 -0
- package/tests/test_autoresume_settings.py +135 -0
- package/tests/test_autoresume_sidechain.py +1518 -0
- package/tests/test_autoresume_sidechain_tmux.py +79 -0
- package/tests/test_autoresume_trust_tmux.py +63 -0
- package/tests/test_claude_client_error.py +10 -7
- package/tests/test_claude_org_block.py +1 -0
- package/tests/test_claude_pool_prepare.py +4 -1
- package/tests/test_claude_reset.py +3 -0
- package/tests/test_keychain_nologin.py +340 -0
- package/tests/test_mcp_oauth.py +1414 -0
- package/tests/test_mcp_registry.py +234 -3
- package/tests/test_packaged_commands.py +41 -0
package/bin/codex
CHANGED
|
@@ -119,11 +119,60 @@ mcp_ensure() { # $1 acct dir — silent, never changes the exit status
|
|
|
119
119
|
# snapshot taken just before). The real CLI's own parser and writer do the work; the shim
|
|
120
120
|
# only copies the outcome. Exits with the real binary's status; a mirror failure is one
|
|
121
121
|
# hint line. Returns 1 when this run is not such a command.
|
|
122
|
+
# The index of the first positional argument as the real CLI's parser (clap) finds it:
|
|
123
|
+
# global options may come first, in any number. The operator's `alias codex='codex
|
|
124
|
+
# --dangerously-bypass-approvals-and-sandbox'` puts one in front of every `codex mcp add`
|
|
125
|
+
# typed at a prompt, and the mirror once looked at $1 alone (bin/claude met the same on
|
|
126
|
+
# 2026-10-22). A value option skips its value (attached `-mX`/`--model=X` is one word); a
|
|
127
|
+
# variadic one every word up to the next option. `--`, or no positional: status 1.
|
|
128
|
+
# Options of codex-cli 0.163 (`codex --help`); an unknown one counts as a flag.
|
|
129
|
+
CLI_VALUE_OPTS=" -c --config --enable --disable --remote --remote-auth-token-env -m --model \
|
|
130
|
+
--local-provider -p --profile -s --sandbox -C --cd --add-dir -a --ask-for-approval "
|
|
131
|
+
CLI_VARIADIC_OPTS=" -i --image "
|
|
132
|
+
cli_positional_at() { # argv -> prints the index of its first positional argument
|
|
133
|
+
local i=1 a nx
|
|
134
|
+
while [ "$i" -le "$#" ]; do
|
|
135
|
+
a="${!i}"
|
|
136
|
+
case "$a" in
|
|
137
|
+
--) return 1 ;;
|
|
138
|
+
--*=*) i=$((i + 1)); continue ;;
|
|
139
|
+
-?*) ;;
|
|
140
|
+
*) printf '%s\n' "$i"; return 0 ;;
|
|
141
|
+
esac
|
|
142
|
+
i=$((i + 1))
|
|
143
|
+
case "$CLI_VALUE_OPTS" in *" $a "*) i=$((i + 1)); continue ;; esac
|
|
144
|
+
case "$CLI_VARIADIC_OPTS" in
|
|
145
|
+
*" $a "*)
|
|
146
|
+
while [ "$i" -le "$#" ]; do
|
|
147
|
+
nx="${!i}"; case "$nx" in -*) break ;; esac; i=$((i + 1))
|
|
148
|
+
done ;;
|
|
149
|
+
esac
|
|
150
|
+
done
|
|
151
|
+
return 1
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Whether argv is `codex [global options] mcp add|remove …`.
|
|
155
|
+
mcp_mutation_argv() {
|
|
156
|
+
local i j
|
|
157
|
+
i="$(cli_positional_at "$@")" || return 1
|
|
158
|
+
[ "${!i}" = "mcp" ] || return 1
|
|
159
|
+
j=$((i + 1))
|
|
160
|
+
# `mcp` takes options of its own before its subcommand: -c/--config, --enable, --disable
|
|
161
|
+
while :; do
|
|
162
|
+
case "${!j:-}" in
|
|
163
|
+
-c|--config|--enable|--disable) j=$((j + 2)) ;;
|
|
164
|
+
--*=*|-c?*) j=$((j + 1)) ;;
|
|
165
|
+
*) break ;;
|
|
166
|
+
esac
|
|
167
|
+
done
|
|
168
|
+
case "${!j:-}" in add|remove) return 0 ;; esac
|
|
169
|
+
return 1
|
|
170
|
+
}
|
|
171
|
+
|
|
122
172
|
mcp_mirror_run() { # $1 acct dir, then the user's argv — exits when it handled the run
|
|
123
173
|
local d="$1" snap="" rc
|
|
124
174
|
shift
|
|
125
|
-
|
|
126
|
-
case "${2:-}" in add|remove) ;; *) return 1 ;; esac
|
|
175
|
+
mcp_mutation_argv "$@" || return 1
|
|
127
176
|
[ "${CODEX_MULTIACC_MCP:-1}" != "0" ] && [ -f "$MCP_PY" ] || return 1
|
|
128
177
|
mcp_ensure "$d"
|
|
129
178
|
snap="$(mktemp "${TMPDIR:-/tmp}/codex-mcp-snap.XXXXXX" 2>/dev/null)" || snap=""
|
|
@@ -156,8 +205,8 @@ mcp_nested_mirror() {
|
|
|
156
205
|
[ "${CODEX_MULTIACC_DISABLE:-0}" = "1" ] && return 1
|
|
157
206
|
[ "${CODEX_MULTIACC_MCP:-1}" = "0" ] && return 1
|
|
158
207
|
[ -n "${CODEX_HOME:-}" ] || return 1
|
|
159
|
-
local
|
|
160
|
-
|
|
208
|
+
local cfg="${CODEX_HOME%/}" d=""
|
|
209
|
+
mcp_mutation_argv "$@" || return 1
|
|
161
210
|
# A DIRECT child of the pool root, by its given spelling or its physical one (an
|
|
162
211
|
# adopted account is a symlink whose target is the operator's ~/.claude).
|
|
163
212
|
case "$cfg" in
|
|
@@ -766,6 +815,7 @@ AR_AVOID_CLAUDE="" AR_AVOID_CODEX="" AR_POOL_CLAUDE="" AR_POOL_CODEX=""
|
|
|
766
815
|
AR_ARGV=()
|
|
767
816
|
AR_ORIG_ARGV=()
|
|
768
817
|
AR_NO_DAEMON=0
|
|
818
|
+
AR_TRUST=0 # only a consumed automatic relaunch trusts its cwd in the new account
|
|
769
819
|
AR_ROLLOUT_CURSOR=""
|
|
770
820
|
AR_TIER="" # which cut produced the pick: eligible | soft | hard (probe answer)
|
|
771
821
|
AR_R_SID="" # the session a relaunch file named, for the dead-token hint
|
|
@@ -987,11 +1037,14 @@ ar_relaunch() { # $1 token
|
|
|
987
1037
|
# Field 2 is the word `autoresume`, never an account id: lib/report.py and the
|
|
988
1038
|
# *-accounts tools read an acct-NN in field 2 as a pick.
|
|
989
1039
|
sel_log "autoresume relaunch chain=${AR_CHAIN:--} from=${acct:--} class=${cls:--} depth=$AR_DEPTH"
|
|
1040
|
+
AR_TRUST=1
|
|
990
1041
|
return 0
|
|
991
1042
|
}
|
|
992
1043
|
|
|
993
1044
|
# The argv shapes a relaunch can rebuild as `resume <kept opts> <sid> <prompt>` — anything
|
|
994
1045
|
# else (exec/e, -p profile, -c overrides, any subcommand, an unknown flag) gets no watcher.
|
|
1046
|
+
# The one -c a relaunch carries itself is let through: `-c model_reasoning_effort=<level>`,
|
|
1047
|
+
# beside `-m`, pins the session's own effort (lib/autoresume.py codex_argv_parse mirrors it).
|
|
995
1048
|
# A positional must contain a space to count as a prompt: a single word may be a
|
|
996
1049
|
# subcommand.
|
|
997
1050
|
ar_argv_ok() { # the argv codex is about to get
|
|
@@ -1002,6 +1055,8 @@ ar_argv_ok() { # the argv codex is about to get
|
|
|
1002
1055
|
sandbox:read-only) sandbox=1 ;;
|
|
1003
1056
|
approval:on-request) approval=1 ;;
|
|
1004
1057
|
model:*) case "$a" in ''|-*) return 1 ;; esac ;;
|
|
1058
|
+
effort:model_reasoning_effort=?*)
|
|
1059
|
+
case "${a#model_reasoning_effort=}" in *[!$AR_LOWER]*) return 1 ;; esac ;;
|
|
1005
1060
|
*) return 1 ;;
|
|
1006
1061
|
esac
|
|
1007
1062
|
want=""
|
|
@@ -1013,6 +1068,7 @@ ar_argv_ok() { # the argv codex is about to get
|
|
|
1013
1068
|
--sandbox) [ "$sandbox" = 0 ] || return 1; want=sandbox ;;
|
|
1014
1069
|
--ask-for-approval) [ "$approval" = 0 ] || return 1; want=approval ;;
|
|
1015
1070
|
-m|--model) want=model ;;
|
|
1071
|
+
-c) want=effort ;;
|
|
1016
1072
|
--model=?*) ;;
|
|
1017
1073
|
--last)
|
|
1018
1074
|
[ "$resume" = 1 ] && [ "$sid" = 0 ] || return 1
|
|
@@ -1519,7 +1575,7 @@ export CODEX_SHIM_ACTIVE=1
|
|
|
1519
1575
|
|
|
1520
1576
|
# A `codex mcp add|remove` is run as a child under the picked account and mirrored to
|
|
1521
1577
|
# every other one (exits inside); anything else falls through to exec.
|
|
1522
|
-
if
|
|
1578
|
+
if mcp_mutation_argv "$@"; then
|
|
1523
1579
|
share_state_index "$pick"
|
|
1524
1580
|
export CODEX_HOME="$pick"
|
|
1525
1581
|
mcp_mirror_run "$pick" "$@" || true
|
|
@@ -1554,6 +1610,14 @@ fi
|
|
|
1554
1610
|
share_state_index "$pick"
|
|
1555
1611
|
if [ "$wants_retry" = "0" ]; then
|
|
1556
1612
|
export CODEX_HOME="$pick"
|
|
1613
|
+
# Folder access is account-local even when the conversation and cwd are unchanged.
|
|
1614
|
+
# Persist the relaunch's trust before Codex starts, without sending keys into its TUI.
|
|
1615
|
+
if [ "$AR_TRUST" = "1" ]; then
|
|
1616
|
+
if ! ar_find_python || ! "$AR_PY" -I "$SELF_DIR/../lib/codex_trust.py" \
|
|
1617
|
+
--acct-dir "$pick" --cwd "$PWD" </dev/null >/dev/null 2>&1; then
|
|
1618
|
+
sel_log "autoresume trust-failed chain=${AR_CHAIN:--} acct=${pick##*/}"
|
|
1619
|
+
fi
|
|
1620
|
+
fi
|
|
1557
1621
|
# Auto-resume: a gated, detached watcher beside this exec — the exec itself (same pid,
|
|
1558
1622
|
# same environment minus the auto-resume variables). Supported clients own their
|
|
1559
1623
|
# task through --no-daemon, including the continuation passed on resume.
|
package/bin/codex-accounts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# codex-accounts — manage the codex-multiacc account pool (OpenAI Codex CLI).
|
|
3
3
|
# Subcommands: list status add import adopt dedupe remove login expired relogin
|
|
4
4
|
# sync verify limits post-sync health self-update
|
|
5
|
-
# export-credential import-credential
|
|
5
|
+
# export-credential import-credential model-catalog
|
|
6
6
|
set -u
|
|
7
7
|
# lib/codex_audit.py is imported by several subcommands; keep the install tree free
|
|
8
8
|
# of __pycache__ (it may be root-owned, read-only, or an npm global prefix).
|
|
@@ -98,7 +98,14 @@ USAGE
|
|
|
98
98
|
stay selectable. Skips accounts fetched in the last 45s and honors
|
|
99
99
|
429/refresh backoff; --force ignores all three.
|
|
100
100
|
codex-accounts health limits + full verify; logs to health.log
|
|
101
|
-
codex-accounts self-update update this npm/git install; logs to update.log
|
|
101
|
+
codex-accounts self-update update this npm/git install (never to an older version); logs to update.log
|
|
102
|
+
codex-accounts model-catalog [refresh|show] [--if-stale] [--quiet]
|
|
103
|
+
the model/effort catalog auto-resume relaunches a session with. refresh (the default)
|
|
104
|
+
merges the catalog this release ships with the model caches the CLIs keep in their
|
|
105
|
+
account dirs (no request, no credential) into model-catalog.json in BOTH pool roots;
|
|
106
|
+
every install and self-update runs it, and each limits pass refreshes its own pool's
|
|
107
|
+
copy once it is an hour old. --if-stale leaves a younger copy alone; --quiet prints
|
|
108
|
+
nothing. show prints the merged catalog.
|
|
102
109
|
codex-accounts post-sync (server side) seed dirs, fix perms, quick verify
|
|
103
110
|
codex-accounts mcp add <name> [--provider claude|codex|both] [--scope user]
|
|
104
111
|
[-e KEY=VAL]... [-H 'Header: v']... [--transport stdio|http]
|
|
@@ -139,6 +146,8 @@ ENV
|
|
|
139
146
|
CODEX_MULTIACC_DISABLE=1 bypass the shim entirely
|
|
140
147
|
CODEX_MULTIACC_AUTO_RESET=0 disable automatic usage-reset redemption
|
|
141
148
|
CODEX_MULTIACC_MCP=0 disable the MCP registry (no reconcile at seed/launch, no mirror)
|
|
149
|
+
CODEX_MULTIACC_MODEL_CATALOG=0 never refresh the codex pool's model-catalog.json
|
|
150
|
+
(MULTIACC_MODEL_CATALOG=0: neither pool's)
|
|
142
151
|
EOF
|
|
143
152
|
}
|
|
144
153
|
|
|
@@ -1837,6 +1846,10 @@ PYEOF
|
|
|
1837
1846
|
# lock while a consumer reads its output.
|
|
1838
1847
|
codex_limits_lock_release
|
|
1839
1848
|
trap - EXIT
|
|
1849
|
+
# Every account's MCP servers, adopting what one holds that the registry does not know.
|
|
1850
|
+
mcp_registry_pass
|
|
1851
|
+
# This pool's model catalog, once its copy is an hour old (lib/common.sh).
|
|
1852
|
+
model_catalog_backstop
|
|
1840
1853
|
# The refresher fails open per account; a NON-zero status means the pass itself
|
|
1841
1854
|
# broke (unreadable manifest, dead python). Report the state anyway — stale data
|
|
1842
1855
|
# beats silence — but hand the caller the failure, exactly as before --json existed.
|
|
@@ -2251,6 +2264,10 @@ cmd_mcp() {
|
|
|
2251
2264
|
local args=()
|
|
2252
2265
|
case "$sub" in
|
|
2253
2266
|
add|add-json|remove|list|apply|import-local) shift ;;
|
|
2267
|
+
# Sharing MCP OAuth logins across accounts is a claude-pool feature (lib/mcp_oauth.py):
|
|
2268
|
+
# Codex keeps an MCP server's OAuth state in its own per-account store.
|
|
2269
|
+
auth-status|auth-sync|auth-forget)
|
|
2270
|
+
die "mcp $sub is not supported for the codex pool — MCP OAuth logins are shared across the claude pool only (claude-accounts mcp $sub)" ;;
|
|
2254
2271
|
"") usage; return 1 ;;
|
|
2255
2272
|
*) die "unknown mcp subcommand: $sub (add, add-json, remove, list, apply, import-local)" ;;
|
|
2256
2273
|
esac
|
|
@@ -2369,7 +2386,17 @@ cmd_self_update() {
|
|
|
2369
2386
|
ulog "self-update: already latest ($cur)"
|
|
2370
2387
|
return 0
|
|
2371
2388
|
fi
|
|
2372
|
-
|
|
2389
|
+
# Only ever UP, to a version known to be newer (claude-accounts says why): the same
|
|
2390
|
+
# package, so either CLI could otherwise install `latest` over a newer hand install.
|
|
2391
|
+
case "$lat" in [0-9]*.*[0-9]) ;; *)
|
|
2392
|
+
ulog "self-update: could not read the registry's version (npm view); not updating"
|
|
2393
|
+
return 1 ;;
|
|
2394
|
+
esac
|
|
2395
|
+
if ver_newer "$cur" "$lat"; then
|
|
2396
|
+
ulog "self-update: this install ($cur) is newer than the registry's $lat; not downgrading"
|
|
2397
|
+
return 0
|
|
2398
|
+
fi
|
|
2399
|
+
ulog "self-update: npm $cur -> $lat (prefix $prefix, npm $npm_bin)"
|
|
2373
2400
|
if PATH="$npm_path" "$npm_bin" install -g --prefix "$prefix" claude-multiacc@latest \
|
|
2374
2401
|
>>"$ACC_ROOT/update.log" 2>&1; then
|
|
2375
2402
|
after="$(pkg_version_at "$REPO_DIR")"
|
|
@@ -2378,6 +2405,7 @@ cmd_self_update() {
|
|
|
2378
2405
|
return 1
|
|
2379
2406
|
fi
|
|
2380
2407
|
ulog "self-update: npm update ok ($after)"
|
|
2408
|
+
model_catalog_after_update "$quiet"
|
|
2381
2409
|
else
|
|
2382
2410
|
ulog "self-update: npm update FAILED (see update.log)"
|
|
2383
2411
|
return 1
|
|
@@ -2441,7 +2469,7 @@ PYEOF
|
|
|
2441
2469
|
# distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
|
|
2442
2470
|
# FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
|
|
2443
2471
|
# the dispatcher below implements, and a test pins that both ways.
|
|
2444
|
-
_KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync configure verify limits post-sync health self-update init-pool mcp"
|
|
2472
|
+
_KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync configure verify limits post-sync health self-update init-pool mcp model-catalog"
|
|
2445
2473
|
case " $_KNOWN_VERBS " in
|
|
2446
2474
|
*" ${1:-help} "*)
|
|
2447
2475
|
# ...but never past a literal `--`: from there on the words belong to an MCP
|
|
@@ -2473,6 +2501,7 @@ case "${1:-help}" in
|
|
|
2473
2501
|
self-update) shift; cmd_self_update "$@" ;;
|
|
2474
2502
|
init-pool) shift; cmd_init_pool "$@" ;;
|
|
2475
2503
|
mcp) shift; cmd_mcp "$@" ;;
|
|
2504
|
+
model-catalog) shift; cmd_model_catalog "$@" ;;
|
|
2476
2505
|
help|--help|-h) usage ;;
|
|
2477
2506
|
*) usage; exit 1 ;;
|
|
2478
2507
|
esac
|
|
@@ -59,7 +59,9 @@ claude-accounts adopt acct-01
|
|
|
59
59
|
`adopt` makes the account dir a **symlink to `~/.claude`** — the machine's existing
|
|
60
60
|
login joins the pool with a single credential file (no copy of the OAuth grant exists
|
|
61
61
|
anywhere else, so refreshes can never race). This is how the two current accounts run:
|
|
62
|
-
acct-01 = the Mac's login, acct-02 = the server's login (adopted server-side).
|
|
62
|
+
acct-01 = the Mac's login, acct-02 = the server's login (adopted server-side). It refuses
|
|
63
|
+
an account dir that already holds a credential of its own — any `.credentials.json`, even
|
|
64
|
+
one with only MCP server sign-ins in it, or a `server.token` — rather than delete it.
|
|
63
65
|
|
|
64
66
|
### Add a fresh account (~1 min, one browser sign-in)
|
|
65
67
|
|
|
@@ -212,6 +214,62 @@ is applied on the Mac and never synced. `--help` on `mcp` lists every option;
|
|
|
212
214
|
`CLAUDE_MULTIACC_MCP=0` / `CODEX_MULTIACC_MCP=0` stop the reconcile at seed and launch and the
|
|
213
215
|
shim's mirror (the CLI, sync and fan-out keep working).
|
|
214
216
|
|
|
217
|
+
### MCP OAuth logins (per machine, shared by every account)
|
|
218
|
+
|
|
219
|
+
The registry carries a server's CONFIG; an OAuth server's LOGIN lives in each account's
|
|
220
|
+
credential store (`mcpOAuth` in `.credentials.json`, or the account's Keychain item). The
|
|
221
|
+
claude pool shares those logins across every account on the machine through
|
|
222
|
+
`<pool>/mcp-oauth.json` (lib/mcp_oauth.py): authenticate a server once in any account
|
|
223
|
+
(`/mcp` in a session) and every account has it from its next launch. It goes into the store
|
|
224
|
+
each account already has — its `.credentials.json`, or its Keychain item — and an account that
|
|
225
|
+
runs on its setup token with no file gets one created, holding the MCP logins alone (0600):
|
|
226
|
+
in an ssh/tmux session that file is all its client reads them from, and it is no Claude login
|
|
227
|
+
to the shim, the limits pass, the audit or verify (`list` keeps showing `auth=token`). No file
|
|
228
|
+
is created beside a Keychain login, in a parked account (`.expired`) or an adopted dir.
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
claude-accounts mcp auth-status # server, expiry, source account, refresh state, per-account stores
|
|
232
|
+
claude-accounts mcp auth-sync # what the limits pass does every 15 minutes, now
|
|
233
|
+
claude-accounts mcp auth-sync --force # ...retrying a refresh that is backing off
|
|
234
|
+
claude-accounts mcp auth-forget higgsfield # deliberate logout, every account on this machine
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
What to expect, and how to read `auth-status`:
|
|
238
|
+
|
|
239
|
+
- **fresh / stale / own / stub / missing / absent** per account file or Keychain item. `stale`
|
|
240
|
+
(a provably older copy), `stub` and `missing` turn `fresh` at that account's next launch (the
|
|
241
|
+
shim merges before exec) or at the next limits pass; `own` is a login of another grant whose
|
|
242
|
+
age the expiries cannot settle — left where it is; `locked` means the item exists but this
|
|
243
|
+
session (ssh, tmux) cannot open it — a Terminal on the Mac, or the scheduled pass, reaches it.
|
|
244
|
+
An `absent` file turns `fresh` only for a setup-token account (the file is created for it);
|
|
245
|
+
an absent Keychain item is never created.
|
|
246
|
+
- **NOT shared: …** / **sharing paused: …**: one login in many accounts needs ONE refresher —
|
|
247
|
+
the pool, ahead of every client — so nothing is shared with
|
|
248
|
+
`CLAUDE_MULTIACC_MCP_OAUTH_REFRESH=0`, by a launch when no refreshing limits pass ran in the
|
|
249
|
+
last hour (`mcp auth-sync` runs one now), or for a login inside the clients' own 5-minute
|
|
250
|
+
window, refused by its server, or whose failing refresh would next try too late. Each
|
|
251
|
+
account keeps what it holds meanwhile.
|
|
252
|
+
- **NEEDS RE-AUTH**: the authorization server refused the refresh token. Sign in again in any
|
|
253
|
+
one account (`/mcp`); the new login replaces the old one everywhere. A backoff (`N
|
|
254
|
+
failure(s), last http 503`) retries by itself, 15 minutes doubling to 6 hours; `refresh: due
|
|
255
|
+
… — deferred: a copy sits in acct-NN keychain` waits for a pass that can open that item,
|
|
256
|
+
until 20 minutes before expiry.
|
|
257
|
+
- **Never synced**: each Mac keeps its own grants. A runner Mac, a replica and the source
|
|
258
|
+
each sign in once; `sync` and the limits fan-out push explicit file lists without it.
|
|
259
|
+
- **Logging out**: a `/mcp` "Clear authentication", `claude mcp login <server>` or a
|
|
260
|
+
re-authentication in ANY one account revokes the shared login at the server, so the pool
|
|
261
|
+
retires it in every account (`login: none — retired … (cleared in acct-NN file)`); one
|
|
262
|
+
sign-in in any account shares the new one. `mcp auth-forget NAME` is the same logout from
|
|
263
|
+
the operator's side. `codex-accounts mcp auth-*` is not supported (Codex keeps MCP OAuth in
|
|
264
|
+
its own store).
|
|
265
|
+
- **Rolling it out**: the first share runs at install time (the limits agent starts on
|
|
266
|
+
install), and a build older than this one reads a shared MCP-only file as a live login:
|
|
267
|
+
every setup-token account would then say "Not logged in". Install it only where no older
|
|
268
|
+
build can come back: from the registry once it is published, or by hand at or above the
|
|
269
|
+
registry's version (npm `self-update` never installs an older one). Until then, hold the
|
|
270
|
+
sharing with `CLAUDE_MULTIACC_MCP_OAUTH=0` (`launchctl setenv` before the install for the
|
|
271
|
+
scheduled agents).
|
|
272
|
+
|
|
215
273
|
### Pointing sync somewhere else (or nowhere)
|
|
216
274
|
|
|
217
275
|
The target is no longer baked in. In precedence order:
|
|
@@ -332,7 +390,7 @@ emitted for diagnostics.
|
|
|
332
390
|
| Credential | Where it lives | Class | Can it be copied to another machine? |
|
|
333
391
|
| --- | --- | --- | --- |
|
|
334
392
|
| claude setup-token | `<acct>/server.token` | **portable** | **Yes**; the fleet-distributed credential |
|
|
335
|
-
| claude OAuth login | `<acct>/.credentials.json`, or a macOS Keychain item | machine-local | **No**; refresh tokens rotate |
|
|
393
|
+
| claude OAuth login | `<acct>/.credentials.json`, or a macOS Keychain item, carrying `claudeAiOauth` (MCP OAuth state alone is no login) | machine-local | **No**; refresh tokens rotate |
|
|
336
394
|
| codex ChatGPT login | `<acct>/auth.json` | machine-local | **No**; Codex has no portable credential |
|
|
337
395
|
|
|
338
396
|
Non-portable accounts are not a dead end: they show up as `credential_class:
|
|
@@ -422,6 +480,8 @@ derived from the pool root. Uninstalling an instance removes only that instance'
|
|
|
422
480
|
| `CLAUDE_MULTIACC_CLIENT_LIMIT_CONFIRM_DELAY=<s>` | five-hour recovery grace; default 300s (weekly never clears early) |
|
|
423
481
|
| `CLAUDE_SHIM_RETRY=0` | disable the `-p` auto-retry |
|
|
424
482
|
| `CLAUDE_MULTIACC_AUTORESUME=0` | no [auto-resume](AUTORESUME.md) watcher for launches from this environment. `touch <pool>/autoresume.off` also stops running watchers |
|
|
483
|
+
| `CLAUDE_MULTIACC_MCP_OAUTH=0` | stop sharing MCP OAuth logins across accounts (no merge before exec, no sync in the limits pass); `auth-status` / `auth-forget` still run |
|
|
484
|
+
| `CLAUDE_MULTIACC_MCP_OAUTH_REFRESH=0` | never refresh MCP OAuth logins — and so share none: a login in many accounts needs ONE refresher, or their clients refresh one rotating grant at once (each account keeps what it holds) |
|
|
425
485
|
| `CLAUDE_ACCOUNTS_ROOT=...` | relocate the pool; legacy spelling is `CLAUDE_ACCOUNTS_DIR` |
|
|
426
486
|
| `CLAUDE_MULTIACC_SYNC_TARGET=...` | sync target, overriding the manifest; `none` = local-only |
|
|
427
487
|
| `CLAUDE_MULTIACC_SYNC_ROOT=... / _SYNC_REPO=...` | remote pool root / addon repo that goes with it |
|
|
@@ -42,9 +42,34 @@ Account avoidance is separate for each provider, so Claude's `acct-01` and Codex
|
|
|
42
42
|
|
|
43
43
|
## Permissions and arguments
|
|
44
44
|
|
|
45
|
-
The working directory is preserved.
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
The working directory is preserved. Session selectors, the original launch prompt and the
|
|
46
|
+
source's own model and effort flags are not replayed as destination CLI arguments. The
|
|
47
|
+
destination starts on the model and effort of the **same level** instead. Both are read from
|
|
48
|
+
the validated source history, up to its snapshot boundary, and matched through the
|
|
49
|
+
[model catalog](../README.md#auto-resume):
|
|
50
|
+
|
|
51
|
+
- **Models match by level** (operator's map, 2026-10-14):
|
|
52
|
+
|
|
53
|
+
| Codex source | Claude destination | | Claude source | Codex destination |
|
|
54
|
+
|---|---|---|---|---|
|
|
55
|
+
| astra | opus | | fable, mythos | astra |
|
|
56
|
+
| sol | sonnet | | opus | astra |
|
|
57
|
+
| terra | haiku | | sonnet | sol |
|
|
58
|
+
| luna | haiku | | haiku | terra |
|
|
59
|
+
|
|
60
|
+
The catalog encodes it as levels: Claude haiku 2, sonnet 3, opus 4, fable 5 (mythos counts
|
|
61
|
+
as fable); Codex luna 1, terra 2, sol 3, astra 4. Equal levels match; a level with no
|
|
62
|
+
family on the other side (luna, fable) takes the nearest one, the higher on a tie. The
|
|
63
|
+
match is that family's newest listed model, whatever its version (launches are staggered,
|
|
64
|
+
so a family a release behind is still its own level). A retiring model counts as the model
|
|
65
|
+
the catalog upgrades it to.
|
|
66
|
+
- **Efforts match by name.** low, medium, high, xhigh and max carry over unchanged. Codex's
|
|
67
|
+
`ultra` becomes `max`, and `minimal` or `none` becomes `low`. The result is clamped to the
|
|
68
|
+
destination model: the nearest supported level below, else its lowest.
|
|
69
|
+
- **Unknown means the destination's default.** A model or effort the history does not
|
|
70
|
+
record, or the catalog cannot place, is left out, and the destination CLI applies its own
|
|
71
|
+
default. Claude takes either flag alone. Codex takes both or neither, with the matched
|
|
72
|
+
model's default effort when only the model is known.
|
|
48
73
|
|
|
49
74
|
An explicit permission bypass maps to the equivalent destination bypass. Merely allowing
|
|
50
75
|
a bypass flag is not treated as enabling it. Ordinary launches use explicit conservative
|