claude-multiacc 2.0.60 → 2.0.64
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 +12 -3
- package/bin/claude +14 -3
- package/bin/codex +42 -10
- package/bin/codex-accounts +12 -4
- package/docs/ACCOUNT_OPERATIONS.md +2 -2
- package/docs/AUTORESUME.md +6 -1
- package/docs/CODEX.md +19 -2
- package/docs/CODEX_MCP_AUTH.md +58 -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_mcp_oauth.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_resume_cwd.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_mirror.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/autoresume.py +6 -5
- package/lib/autoresume_cross.py +2 -1
- package/lib/autoresume_handoff.py +13 -5
- package/lib/codex_exec_stdin.sh +45 -0
- package/lib/codex_mcp_oauth.py +204 -0
- package/lib/codex_mcp_oauth.sh +29 -0
- package/lib/codex_mcp_oauth_locks.py +158 -0
- package/lib/codex_mcp_oauth_native.py +102 -0
- package/lib/codex_mcp_oauth_store.py +149 -0
- package/lib/codex_resume_cwd.py +57 -0
- package/lib/codex_retry_process.sh +37 -0
- package/lib/common.sh +3 -0
- package/lib/mcp_mirror.py +49 -0
- package/lib/mcp_mirror.sh +47 -0
- package/lib/mcp_registry.py +7 -2
- package/package.json +9 -4
- 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_cwd.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_handoff.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_reader.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cwd_tmux.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_exec_stdin.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_mcp_oauth.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_mcp_mirror.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_mirror_args.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_oauth.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_mcp_registry.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 +23 -1
- package/tests/run-tests.sh +3 -1
- package/tests/test_autoresume_cwd.py +71 -0
- package/tests/test_autoresume_cwd_handoff.py +92 -0
- package/tests/test_autoresume_cwd_reader.py +105 -0
- package/tests/test_autoresume_cwd_tmux.py +54 -0
- package/tests/test_codex_exec_stdin.py +178 -0
- package/tests/test_codex_mcp_oauth.py +146 -0
- package/tests/test_codex_mcp_oauth_adopt.py +87 -0
- package/tests/test_codex_mcp_oauth_migration.py +141 -0
- package/tests/test_codex_mcp_oauth_native.py +109 -0
- package/tests/test_codex_mcp_oauth_safety.py +130 -0
- package/tests/test_codex_mcp_oauth_shim.py +188 -0
- package/tests/test_codex_retry_process.py +143 -0
- package/tests/test_mcp_mirror.py +148 -0
- package/tests/test_mcp_mirror_args.py +129 -0
package/README.md
CHANGED
|
@@ -571,7 +571,12 @@ claude-accounts mcp apply # re-apply now (repair drift)
|
|
|
571
571
|
config dir outside the pool is plain passthrough. Global options in front of `mcp` are
|
|
572
572
|
looked past the way the real parser does (`alias claude='claude
|
|
573
573
|
--dangerously-skip-permissions'` puts one before every typed `claude mcp add`; until
|
|
574
|
-
2026-10-22 that kept motionsites in 2 of 11 accounts).
|
|
574
|
+
2026-10-22 that kept motionsites in 2 of 11 accounts). Mirroring is limited to the server
|
|
575
|
+
named by the command, including its project approvals: a concurrent runner update to
|
|
576
|
+
another server cannot become a synced override or tombstone. An unrecognized command
|
|
577
|
+
option leaves the native command intact and warns that its change was not mirrored.
|
|
578
|
+
Claude's desktop bulk import mirrors changes except names and retirements belonging to
|
|
579
|
+
another machine-local overlay owner; an explicit named command can still override one.
|
|
575
580
|
- **Adoption**: whatever reached an account by another route — the real binary run
|
|
576
581
|
directly, a hand edit, another tool's installer — is adopted by the next reconcile: a
|
|
577
582
|
server (user scope or a project entry) or a project's `.mcp.json` approval that no section
|
|
@@ -680,8 +685,12 @@ claude-accounts mcp auth-forget higgsfield # log it out of EVERY account on th
|
|
|
680
685
|
cannot open) stays ignored and is cleared by a later pass.
|
|
681
686
|
- **Kill switches**: `CLAUDE_MULTIACC_MCP_OAUTH=0` stops sharing (the shim's merge and the
|
|
682
687
|
limits pass's sync); `CLAUDE_MULTIACC_MCP_OAUTH_REFRESH=0` never refreshes — and so shares
|
|
683
|
-
nothing. `auth-status` and `auth-forget` always run.
|
|
684
|
-
|
|
688
|
+
nothing. `auth-status` and `auth-forget` always run. These commands describe the Claude pool.
|
|
689
|
+
|
|
690
|
+
Codex shares its native MCP credential file and refresh locks locally, so a login in
|
|
691
|
+
one pooled account works in the others. Run `codex-accounts mcp auth-sync` to repair
|
|
692
|
+
sharing now; use native `codex mcp login NAME`, `list`, and `logout NAME` for authentication.
|
|
693
|
+
See [Codex MCP authentication](docs/CODEX_MCP_AUTH.md) for storage constraints and recovery.
|
|
685
694
|
|
|
686
695
|
app-robot tasks are different: the panel launches Claude with `--strict-mcp-config` and a
|
|
687
696
|
merged MCP document (`agent_mcp.servers_for`) and Codex with the same document as `-c`
|
package/bin/claude
CHANGED
|
@@ -224,15 +224,26 @@ mcp_mutation_argv() {
|
|
|
224
224
|
i="$(cli_positional_at "$@")" || return 1
|
|
225
225
|
[ "${!i}" = "mcp" ] || return 1
|
|
226
226
|
j=$((i + 1))
|
|
227
|
-
case "${!j:-}" in add|add-json|add-from-claude-desktop|remove) return 0 ;; esac
|
|
227
|
+
case "${!j:-}" in add|add-json|add-from-claude-desktop|remove) MCP_MUTATION_AT="$j"; return 0 ;; esac
|
|
228
228
|
return 1
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
mcp_mirror_run() { # $1 acct dir, then the user's argv — exits when it handled the run
|
|
232
|
-
local d="$1" snap="" rc
|
|
232
|
+
local d="$1" snap="" rc parse_rc name="" mirror_args=()
|
|
233
233
|
shift
|
|
234
234
|
mcp_mutation_argv "$@" || return 1
|
|
235
235
|
[ "${CLAUDE_MULTIACC_MCP:-1}" != "0" ] && [ -f "$MCP_PY" ] || return 1
|
|
236
|
+
if [ "${!MCP_MUTATION_AT}" != add-from-claude-desktop ]; then
|
|
237
|
+
if name="$(. "$SELF_DIR/../lib/mcp_mirror.sh" && mcp_mutation_name claude "${@:$MCP_MUTATION_AT}")"; then
|
|
238
|
+
mirror_args=("--name=$name")
|
|
239
|
+
else
|
|
240
|
+
parse_rc=$?
|
|
241
|
+
"$REAL" "$@"; rc=$?
|
|
242
|
+
[ "$rc" -ne 0 ] || [ "$parse_rc" = 2 ] \
|
|
243
|
+
|| printf 'claude-multiacc: could not identify the MCP target; change not mirrored\n' >&2
|
|
244
|
+
exit "$rc"
|
|
245
|
+
fi
|
|
246
|
+
fi
|
|
236
247
|
mcp_ensure "$d"
|
|
237
248
|
snap="$(mktemp "${TMPDIR:-/tmp}/claude-mcp-snap.XXXXXX" 2>/dev/null)" || snap=""
|
|
238
249
|
if [ -n "$snap" ] && ! "$MCP_PYBIN" "$MCP_PY" --root "$ACC_ROOT" --provider claude snapshot \
|
|
@@ -243,7 +254,7 @@ mcp_mirror_run() { # $1 acct dir, then the user's argv — exits when it handled
|
|
|
243
254
|
rc=$?
|
|
244
255
|
if [ "$rc" -eq 0 ] && [ -n "$snap" ]; then
|
|
245
256
|
"$MCP_PYBIN" "$MCP_PY" --root "$ACC_ROOT" --provider claude learn --account-dir "$d" \
|
|
246
|
-
--before "$snap" --project "$(pwd -P 2>/dev/null || pwd)" </dev/null \
|
|
257
|
+
--before "$snap" --project "$(pwd -P 2>/dev/null || pwd)" ${mirror_args[@]+"${mirror_args[@]}"} </dev/null \
|
|
247
258
|
|| printf 'claude-multiacc: could not mirror the MCP change to the other accounts — run: claude-accounts mcp apply\n' >&2
|
|
248
259
|
elif [ "$rc" -eq 0 ]; then
|
|
249
260
|
printf 'claude-multiacc: could not mirror the MCP change to the other accounts — run: claude-accounts mcp apply\n' >&2
|
package/bin/codex
CHANGED
|
@@ -93,6 +93,11 @@ fi
|
|
|
93
93
|
# the servers themselves. Same contract as bin/claude's mcp_ensure.
|
|
94
94
|
MCP_PY="$SELF_DIR/../lib/mcp_registry.py"
|
|
95
95
|
MCP_PYBIN="${CLAUDE_MULTIACC_PYTHON:-python3}"
|
|
96
|
+
mcp_oauth_ensure() {
|
|
97
|
+
[ -f "$SELF_DIR/../lib/codex_mcp_oauth.sh" ] || return 0
|
|
98
|
+
bash "$SELF_DIR/../lib/codex_mcp_oauth.sh" "$ACC_ROOT" "$MCP_PYBIN" \
|
|
99
|
+
"$SELF_DIR/../lib/codex_mcp_oauth.py" "$1" "$REAL" >/dev/null 2>&1 </dev/null || true
|
|
100
|
+
}
|
|
96
101
|
mcp_sig() { # $1 acct dir -> "m:z,m:z,m:z" (registry, overlay, account config; - = absent)
|
|
97
102
|
local f out="" s
|
|
98
103
|
for f in "$ACC_ROOT/mcp-servers.json" "$ACC_ROOT/mcp-servers.local.json" "$1/config.toml"; do
|
|
@@ -165,15 +170,24 @@ mcp_mutation_argv() {
|
|
|
165
170
|
*) break ;;
|
|
166
171
|
esac
|
|
167
172
|
done
|
|
168
|
-
case "${!j:-}" in add|remove) return 0 ;; esac
|
|
173
|
+
case "${!j:-}" in add|remove) MCP_MUTATION_AT="$j"; return 0 ;; esac
|
|
169
174
|
return 1
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
mcp_mirror_run() { # $1 acct dir, then the user's argv — exits when it handled the run
|
|
173
|
-
local d="$1" snap="" rc
|
|
178
|
+
local d="$1" snap="" rc parse_rc name=""
|
|
174
179
|
shift
|
|
175
180
|
mcp_mutation_argv "$@" || return 1
|
|
176
181
|
[ "${CODEX_MULTIACC_MCP:-1}" != "0" ] && [ -f "$MCP_PY" ] || return 1
|
|
182
|
+
if name="$(. "$SELF_DIR/../lib/mcp_mirror.sh" && mcp_mutation_name codex "${@:$MCP_MUTATION_AT}")"; then
|
|
183
|
+
:
|
|
184
|
+
else
|
|
185
|
+
parse_rc=$?
|
|
186
|
+
"$REAL" "$@"; rc=$?
|
|
187
|
+
[ "$rc" -ne 0 ] || [ "$parse_rc" = 2 ] \
|
|
188
|
+
|| printf 'codex-multiacc: could not identify the MCP target; change not mirrored\n' >&2
|
|
189
|
+
exit "$rc"
|
|
190
|
+
fi
|
|
177
191
|
mcp_ensure "$d"
|
|
178
192
|
snap="$(mktemp "${TMPDIR:-/tmp}/codex-mcp-snap.XXXXXX" 2>/dev/null)" || snap=""
|
|
179
193
|
if [ -n "$snap" ] && ! "$MCP_PYBIN" "$MCP_PY" --root "$ACC_ROOT" --provider codex snapshot \
|
|
@@ -184,7 +198,7 @@ mcp_mirror_run() { # $1 acct dir, then the user's argv — exits when it handled
|
|
|
184
198
|
rc=$?
|
|
185
199
|
if [ "$rc" -eq 0 ] && [ -n "$snap" ]; then
|
|
186
200
|
"$MCP_PYBIN" "$MCP_PY" --root "$ACC_ROOT" --provider codex learn --account-dir "$d" \
|
|
187
|
-
--before "$snap" </dev/null \
|
|
201
|
+
--before "$snap" "--name=$name" </dev/null \
|
|
188
202
|
|| printf 'codex-multiacc: could not mirror the MCP change to the other accounts — run: codex-accounts mcp apply\n' >&2
|
|
189
203
|
elif [ "$rc" -eq 0 ]; then
|
|
190
204
|
printf 'codex-multiacc: could not mirror the MCP change to the other accounts — run: codex-accounts mcp apply\n' >&2
|
|
@@ -234,6 +248,9 @@ if [ -n "${CODEX_HOME:-}" ] \
|
|
|
234
248
|
printf 'codex-multiacc: auto-resume cannot enter account selection.\n' >&2
|
|
235
249
|
exit 2
|
|
236
250
|
fi
|
|
251
|
+
if [ "${CODEX_MULTIACC_DISABLE:-0}" != "1" ] && [ -n "${CODEX_HOME:-}" ]; then
|
|
252
|
+
mcp_oauth_ensure "$CODEX_HOME"
|
|
253
|
+
fi
|
|
237
254
|
mcp_nested_mirror "$@" || true
|
|
238
255
|
exec "$REAL" "$@"
|
|
239
256
|
fi
|
|
@@ -1249,9 +1266,11 @@ if [ -n "${CODEX_ACCOUNT:-}" ] && [ "$AR_PROBE" != "1" ]; then
|
|
|
1249
1266
|
share_state_index "$d"
|
|
1250
1267
|
export CODEX_HOME="$d"
|
|
1251
1268
|
export CODEX_SHIM_ACTIVE=1
|
|
1269
|
+
unset CODEX_MULTIACC_SELECTED_HOME
|
|
1252
1270
|
# A pinned account carries every registered MCP server too, and a pinned
|
|
1253
1271
|
# `codex mcp add|remove` is mirrored like any other (exits inside).
|
|
1254
1272
|
mcp_ensure "$d"
|
|
1273
|
+
mcp_oauth_ensure "$d"
|
|
1255
1274
|
mcp_mirror_run "$d" "$@" || true
|
|
1256
1275
|
exec "$REAL" "$@"
|
|
1257
1276
|
fi
|
|
@@ -1542,6 +1561,7 @@ if [ "$AR_TIER" = "credits" ]; then
|
|
|
1542
1561
|
fi
|
|
1543
1562
|
# The picked account carries every registered MCP server before the client starts.
|
|
1544
1563
|
mcp_ensure "$pick"
|
|
1564
|
+
mcp_oauth_ensure "$pick"
|
|
1545
1565
|
|
|
1546
1566
|
# Opportunistic limits refresh: non-blocking, throttled, backgrounded. The windows are
|
|
1547
1567
|
# deliberately wide (10m, matching the 5m scheduled pass): the usage endpoint rate-limits
|
|
@@ -1572,6 +1592,9 @@ sel_log "$acct weekly=$(fresh_field "$pick" weekly_percent || echo '?')%" \
|
|
|
1572
1592
|
"session-ok=${PICK_GATE_COUNT} pwd=$PWD${credit_funding_log}"
|
|
1573
1593
|
|
|
1574
1594
|
export CODEX_SHIM_ACTIVE=1
|
|
1595
|
+
# An isolated child job can distinguish this automatic selection from a caller pin.
|
|
1596
|
+
# Consumers must also require CODEX_HOME to match and respect explicit pins/disable.
|
|
1597
|
+
export CODEX_MULTIACC_SELECTED_HOME="$pick"
|
|
1575
1598
|
|
|
1576
1599
|
# A `codex mcp add|remove` is run as a child under the picked account and mirrored to
|
|
1577
1600
|
# every other one (exits inside); anything else falls through to exec.
|
|
@@ -1582,9 +1605,9 @@ if mcp_mutation_argv "$@"; then
|
|
|
1582
1605
|
fi
|
|
1583
1606
|
|
|
1584
1607
|
# Auto-retry applies only to `codex exec` runs with an alternative account available,
|
|
1585
|
-
# and
|
|
1586
|
-
#
|
|
1587
|
-
#
|
|
1608
|
+
# and stdin is a file/device or an explicit `exec ... -` prompt. That prompt needs
|
|
1609
|
+
# EOF in the native CLI too. Other pipes stay unbuffered so a non-consuming command
|
|
1610
|
+
# can finish even when its service leaves stdin open.
|
|
1588
1611
|
wants_retry=0
|
|
1589
1612
|
if [ "${CODEX_SHIM_RETRY:-1}" != "0" ] \
|
|
1590
1613
|
&& { [ "${#eligible[@]}" -ge 2 ] || [ "$AR_TIER" = "credits" ]; }; then
|
|
@@ -1594,19 +1617,26 @@ if [ "${CODEX_SHIM_RETRY:-1}" != "0" ] \
|
|
|
1594
1617
|
if [ "$wants_retry" = "1" ]; then
|
|
1595
1618
|
# A TTY cannot be buffered or replayed: `codex exec` with no prompt argument reads
|
|
1596
1619
|
# stdin, and the retry path would hand it /dev/null. Plain exec instead —
|
|
1597
|
-
# stdin is inherited untouched.
|
|
1598
|
-
#
|
|
1599
|
-
#
|
|
1620
|
+
# stdin is inherited untouched. Explicit stdin prompts can replay a pipe: an
|
|
1621
|
+
# AppInspire/Node child receives a complete prompt through a socket, not a file.
|
|
1622
|
+
# Keep unknown commands and pipes the CLI need not consume on direct exec.
|
|
1600
1623
|
if [ -t 0 ]; then
|
|
1601
1624
|
wants_retry=0
|
|
1602
1625
|
elif [ -f /dev/fd/0 ] || [ -c /dev/fd/0 ]; then
|
|
1603
1626
|
:
|
|
1627
|
+
elif bash "$SELF_DIR/../lib/codex_exec_stdin.sh" "$@" 2>/dev/null; then
|
|
1628
|
+
:
|
|
1604
1629
|
else
|
|
1605
1630
|
wants_retry=0
|
|
1606
1631
|
fi
|
|
1607
1632
|
fi
|
|
1608
1633
|
fi
|
|
1609
1634
|
|
|
1635
|
+
# Keep a damaged installation fail-open rather than wrapping an unowned child.
|
|
1636
|
+
if [ "$wants_retry" = 1 ] && ! . "$SELF_DIR/../lib/codex_retry_process.sh" 2>/dev/null; then
|
|
1637
|
+
wants_retry=0
|
|
1638
|
+
fi
|
|
1639
|
+
|
|
1610
1640
|
share_state_index "$pick"
|
|
1611
1641
|
if [ "$wants_retry" = "0" ]; then
|
|
1612
1642
|
export CODEX_HOME="$pick"
|
|
@@ -1678,7 +1708,8 @@ cur="$pick"
|
|
|
1678
1708
|
rc=0
|
|
1679
1709
|
while :; do
|
|
1680
1710
|
if [ -n "$stdin_file" ]; then exec 3< "$stdin_file"; else exec 3< /dev/null; fi
|
|
1681
|
-
CODEX_HOME="$cur" "$
|
|
1711
|
+
CODEX_HOME="$cur" CODEX_MULTIACC_SELECTED_HOME="$cur" \
|
|
1712
|
+
codex_retry_run "$REAL" "$@" <&3 > "$tmpd/out" 2> "$tmpd/err"
|
|
1682
1713
|
rc=$?
|
|
1683
1714
|
exec 3<&-
|
|
1684
1715
|
if [ "$rc" -ne 0 ] && [ "$attempt" -eq 1 ] \
|
|
@@ -1740,6 +1771,7 @@ while :; do
|
|
|
1740
1771
|
# away from — not the one that bounced.
|
|
1741
1772
|
remember_pick "$cur"
|
|
1742
1773
|
mcp_ensure "$cur"
|
|
1774
|
+
mcp_oauth_ensure "$cur"
|
|
1743
1775
|
attempt=2
|
|
1744
1776
|
continue
|
|
1745
1777
|
fi
|
package/bin/codex-accounts
CHANGED
|
@@ -133,6 +133,8 @@ USAGE
|
|
|
133
133
|
re-apply the registry now (repair drift)
|
|
134
134
|
codex-accounts mcp import-local --owner NAME (stdin JSON) machine-local overlay for
|
|
135
135
|
a runner daemon; applied here, never synced
|
|
136
|
+
codex-accounts mcp auth-sync share native MCP files and refresh locks locally
|
|
137
|
+
Sign in with `codex mcp login NAME`; sign out with `codex mcp logout NAME`.
|
|
136
138
|
|
|
137
139
|
ENV
|
|
138
140
|
CODEX_ACCOUNTS_ROOT pool root, overriding ~/.codex-accounts — one isolated pool
|
|
@@ -146,6 +148,7 @@ ENV
|
|
|
146
148
|
CODEX_MULTIACC_DISABLE=1 bypass the shim entirely
|
|
147
149
|
CODEX_MULTIACC_AUTO_RESET=0 disable automatic usage-reset redemption
|
|
148
150
|
CODEX_MULTIACC_MCP=0 disable the MCP registry (no reconcile at seed/launch, no mirror)
|
|
151
|
+
CODEX_MULTIACC_MCP_OAUTH=0 stop preparing shared native MCP storage and locks
|
|
149
152
|
CODEX_MULTIACC_MODEL_CATALOG=0 never refresh the codex pool's model-catalog.json
|
|
150
153
|
(MULTIACC_MODEL_CATALOG=0: neither pool's)
|
|
151
154
|
EOF
|
|
@@ -1848,6 +1851,7 @@ PYEOF
|
|
|
1848
1851
|
trap - EXIT
|
|
1849
1852
|
# Every account's MCP servers, adopting what one holds that the registry does not know.
|
|
1850
1853
|
mcp_registry_pass
|
|
1854
|
+
codex_mcp_oauth_ensure "$ACC_ROOT" "$PYBIN" "$LIB_DIR/codex_mcp_oauth.py"
|
|
1851
1855
|
# This pool's model catalog, once its copy is an hour old (lib/common.sh).
|
|
1852
1856
|
model_catalog_backstop
|
|
1853
1857
|
# The refresher fails open per account; a NON-zero status means the pass itself
|
|
@@ -2264,10 +2268,14 @@ cmd_mcp() {
|
|
|
2264
2268
|
local args=()
|
|
2265
2269
|
case "$sub" in
|
|
2266
2270
|
add|add-json|remove|list|apply|import-local) shift ;;
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
+
auth-sync)
|
|
2272
|
+
shift
|
|
2273
|
+
local real
|
|
2274
|
+
real="$(find_real_codex "$_self")" || die "real codex binary not found"
|
|
2275
|
+
"$PYBIN" "$LIB_DIR/codex_mcp_oauth.py" ensure --root "$ACC_ROOT" --codex-bin "$real" "$@"
|
|
2276
|
+
return $? ;;
|
|
2277
|
+
auth-status|auth-forget)
|
|
2278
|
+
die "use codex mcp list for native auth status, or codex mcp logout NAME to clear shared MCP authentication" ;;
|
|
2271
2279
|
"") usage; return 1 ;;
|
|
2272
2280
|
*) die "unknown mcp subcommand: $sub (add, add-json, remove, list, apply, import-local)" ;;
|
|
2273
2281
|
esac
|
|
@@ -260,8 +260,8 @@ What to expect, and how to read `auth-status`:
|
|
|
260
260
|
re-authentication in ANY one account revokes the shared login at the server, so the pool
|
|
261
261
|
retires it in every account (`login: none — retired … (cleared in acct-NN file)`); one
|
|
262
262
|
sign-in in any account shares the new one. `mcp auth-forget NAME` is the same logout from
|
|
263
|
-
the operator's side.
|
|
264
|
-
|
|
263
|
+
the operator's side. Codex uses native login/logout over shared local storage;
|
|
264
|
+
`codex-accounts mcp auth-sync` repairs it. See [Codex MCP authentication](CODEX_MCP_AUTH.md).
|
|
265
265
|
- **Rolling it out**: the first share runs at install time (the limits agent starts on
|
|
266
266
|
install), and a build older than this one reads a shared MCP-only file as a live login:
|
|
267
267
|
every setup-token account would then say "Not logged in". Install it only where no older
|
package/docs/AUTORESUME.md
CHANGED
|
@@ -164,7 +164,12 @@ shell: runs the shim again → token → marker on the old account → normal
|
|
|
164
164
|
1. It consumes the token. A token is single-use and valid for 10 minutes.
|
|
165
165
|
2. It writes the old account's marker with the pool's existing writers (see the tables
|
|
166
166
|
below).
|
|
167
|
-
3. It `cd`s to the session's directory.
|
|
167
|
+
3. It `cd`s to the session's directory. For Codex, this is the latest directory saved
|
|
168
|
+
by that session, including a change since its first turn. Starting or resuming it
|
|
169
|
+
from another shell directory therefore does not stop the automatic restart at
|
|
170
|
+
**Working directory · resume**. The selected account trusts this same directory
|
|
171
|
+
before startup. If the saved directory is unavailable, an existing initial session
|
|
172
|
+
directory or launch directory is used as a fallback.
|
|
168
173
|
4. It runs **normal selection**, with one addition: the accounts this chain of
|
|
169
174
|
relaunches left are skipped until their own reset. The selection policy is
|
|
170
175
|
otherwise untouched.
|
package/docs/CODEX.md
CHANGED
|
@@ -18,8 +18,21 @@ then the 30-point weekly-headroom band with random spread, ≥90% any-bucket
|
|
|
18
18
|
exclusion, peers rotate), same marker semantics (`.limited` cooldowns, `.expired` parks with
|
|
19
19
|
credential/policy scoping and soft expiry), same fail-open guarantees, same sync
|
|
20
20
|
safety guards. The `codex` shim engages the buffered auto-retry only for
|
|
21
|
-
`codex exec` runs with
|
|
22
|
-
|
|
21
|
+
`codex exec` runs with file/device stdin and explicit `codex exec ... -` prompts from pipes.
|
|
22
|
+
The latter require EOF in the native CLI too; the shim saves and replays the exact prompt
|
|
23
|
+
on its one account retry. Unknown commands, interactive stdin and non-consuming live pipes
|
|
24
|
+
remain unbuffered. Explicit `CODEX_HOME` and `CODEX_ACCOUNT` pins still bypass rotation.
|
|
25
|
+
Cancelling a buffered run forwards TERM/INT/HUP to its active native process and reaps it
|
|
26
|
+
before removing the temporary prompt/output files.
|
|
27
|
+
Interactive `codex` in tmux gets [auto-resume](AUTORESUME.md) instead, like `claude`.
|
|
28
|
+
|
|
29
|
+
Automatic selection exports `CODEX_MULTIACC_SELECTED_HOME` equal to the serving
|
|
30
|
+
`CODEX_HOME`, including after a retry. Selecting an explicit `CODEX_ACCOUNT` clears the marker;
|
|
31
|
+
a home passthrough never creates it. Nested invocations still pass through.
|
|
32
|
+
An isolated job launcher may request fresh pool selection only when `CODEX_SHIM_ACTIVE=1`,
|
|
33
|
+
the marker exactly matches `CODEX_HOME`, no `CODEX_ACCOUNT` is set and
|
|
34
|
+
`CODEX_MULTIACC_DISABLE` is not `1`. Omit the inherited home/guard/marker only for that
|
|
35
|
+
child. Unmarked older sessions and overridden homes retain their existing pin behavior.
|
|
23
36
|
|
|
24
37
|
Codex-specific notes:
|
|
25
38
|
|
|
@@ -215,6 +228,10 @@ claude-multiacc codex status # same status command via the npm wrapper
|
|
|
215
228
|
|
|
216
229
|
## MCP servers for every account
|
|
217
230
|
|
|
231
|
+
OAuth logins also need shared storage; see
|
|
232
|
+
[Codex MCP authentication](CODEX_MCP_AUTH.md). `codex-accounts mcp auth-sync` repairs
|
|
233
|
+
local credential and refresh-lock sharing without touching account `auth.json` files.
|
|
234
|
+
|
|
218
235
|
`codex-accounts mcp add|add-json|remove|list|apply|import-local` manages the pool's MCP
|
|
219
236
|
registry (`~/.codex-accounts/mcp-servers.json`); `add`/`remove` default to `--provider both`,
|
|
220
237
|
so the claude pool beside it gets the same change, and `mcp remove` retires the server in
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Codex MCP authentication across pooled accounts
|
|
2
|
+
|
|
3
|
+
The MCP registry shares server configuration. Codex stores OAuth logins separately:
|
|
4
|
+
the direct OS keyring is shared, but its fallback `.credentials.json` belongs to
|
|
5
|
+
`CODEX_HOME`. On 2026-10-23 MotionSites was configured in all 18 accounts, while its
|
|
6
|
+
login existed only in `acct-06`. The other 17 reported `not_logged_in`.
|
|
7
|
+
|
|
8
|
+
The addon prepares one machine-local native MCP credential file and one directory
|
|
9
|
+
of native refresh/store locks under `<pool>/.codex-mcp-oauth/`. Accounts hardlink
|
|
10
|
+
`.credentials.json` to that file and link `mcp-oauth-locks` to the shared locks.
|
|
11
|
+
Codex reads, refreshes, saves and logs out using its own implementation. The addon
|
|
12
|
+
never refreshes a token or reads the OS keyring. Account `auth.json` files remain
|
|
13
|
+
independent, and none of this MCP state is included in fleet sync.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
codex-accounts mcp auth-sync # repair storage sharing now
|
|
17
|
+
codex mcp login motionsites # authorize once on this machine
|
|
18
|
+
codex mcp list # native authentication status
|
|
19
|
+
codex mcp logout motionsites # clear the shared login
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Preparation runs before pooled launches, during seeding, and after limits passes.
|
|
23
|
+
It requires native Codex 0.163.0 or newer, whose storage and lock contract is checked
|
|
24
|
+
before preparation; older clients keep their existing stores.
|
|
25
|
+
A first login saved after preparation is picked up at the next launch or explicit
|
|
26
|
+
`auth-sync`. Restart an already running session to reload its MCP connections.
|
|
27
|
+
`CODEX_MULTIACC_MCP_OAUTH=0` stops preparation; it does not undo existing shared
|
|
28
|
+
storage. Each machine still needs its own authorization.
|
|
29
|
+
|
|
30
|
+
## Native storage constraints
|
|
31
|
+
|
|
32
|
+
Codex 0.163.0 refuses final-component credential symlinks with `O_NOFOLLOW`, so a
|
|
33
|
+
regular hardlinked file is necessary. Its file writer updates that inode in place
|
|
34
|
+
under `mcp-oauth-locks/file-store.lock`. A tokenless reserved entry keeps the native
|
|
35
|
+
document nonempty after its last real login is removed: otherwise Codex unlinks
|
|
36
|
+
only the account's filename, leaving the other hardlinks with the old login.
|
|
37
|
+
The reserved entry is not a configured server and cannot authenticate a request.
|
|
38
|
+
|
|
39
|
+
The refresh lock must be shared too: native locks normally live under each
|
|
40
|
+
`CODEX_HOME`, even when the grant is in the globally shared direct OS keyring.
|
|
41
|
+
Sharing a rotating grant without its lock lets simultaneous sessions reuse an
|
|
42
|
+
obsolete refresh token. Preparation preserves existing lock inodes, defers busy
|
|
43
|
+
stores and refuses independent credential documents rather than choosing a grant.
|
|
44
|
+
Initial migration atomically exchanges native lock directories with their shared
|
|
45
|
+
links. It retains discarded lock files until preexisting client handles close;
|
|
46
|
+
an operation racing that first migration can make preparation wait for its native
|
|
47
|
+
lock timeout. Later launches use the existing shared paths without that migration.
|
|
48
|
+
Linux excludes an inaccessible process only after proving that it has exited with
|
|
49
|
+
no living threads; inaccessible live or uncertain handle state still defers migration.
|
|
50
|
+
Only manifest accounts are eligible, including the supported `acct-NN → ~/.codex`
|
|
51
|
+
adoption. Foreign directory symlinks, duplicate homes and unsupported scoped
|
|
52
|
+
credential entries are refused. Encrypted secrets storage is not merged.
|
|
53
|
+
|
|
54
|
+
Implementation: `lib/codex_mcp_oauth.py` and its supporting modules. Native contract:
|
|
55
|
+
[OAuth storage](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/rmcp-client/src/oauth.rs)
|
|
56
|
+
and [refresh locks](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/rmcp-client/src/oauth/refresh_lock.rs).
|
|
57
|
+
The supported store settings are documented in the
|
|
58
|
+
[OpenAI configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/autoresume.py
CHANGED
|
@@ -69,6 +69,7 @@ from autoresume_cross_receipt import confirm_cross
|
|
|
69
69
|
from model_catalog import load as load_catalog, parse_id
|
|
70
70
|
from model_match import resume_settings, settings_flags
|
|
71
71
|
from session_settings import session_settings
|
|
72
|
+
from codex_resume_cwd import codex_resume_cwd
|
|
72
73
|
|
|
73
74
|
# ---- vocabulary ---------------------------------------------------------------------
|
|
74
75
|
|
|
@@ -2698,11 +2699,11 @@ class Watcher:
|
|
|
2698
2699
|
return self.busy_memo[1]
|
|
2699
2700
|
|
|
2700
2701
|
def session_cwd(self):
|
|
2701
|
-
"""
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2702
|
+
"""Restore the session's directory before the picked account trusts and resumes it.
|
|
2703
|
+
Codex's saved cwd can differ from both the launching shell and initial metadata."""
|
|
2704
|
+
order = (self.cwd, self.st["cwd"])
|
|
2705
|
+
if self.provider == "codex":
|
|
2706
|
+
order = (codex_resume_cwd(self.transcript, self.sid),) + order
|
|
2706
2707
|
for cand in order:
|
|
2707
2708
|
if cand and os.path.isdir(cand):
|
|
2708
2709
|
return cand
|
package/lib/autoresume_cross.py
CHANGED
|
@@ -110,7 +110,8 @@ def revalidate(watcher, plan):
|
|
|
110
110
|
history = plan['history']
|
|
111
111
|
current = validate_source(watcher.provider, watcher.sid, watcher.transcript,
|
|
112
112
|
watcher.session_cwd(), inode=history['inode'])
|
|
113
|
-
return (current is not None and current['offset'] >= history['offset'] and
|
|
113
|
+
return (current is not None and current['offset'] >= history['offset'] and current['cwd'] == history['cwd']
|
|
114
|
+
and not shared_source(watcher)
|
|
114
115
|
and all(enabled(watcher, provider, root) for provider, root in plan['roots'].items()))
|
|
115
116
|
|
|
116
117
|
|
|
@@ -12,6 +12,8 @@ import os
|
|
|
12
12
|
import re
|
|
13
13
|
import stat
|
|
14
14
|
|
|
15
|
+
from codex_resume_cwd import codex_resume_cwd
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
PROVIDERS = ("claude", "codex")
|
|
17
19
|
SID_RE = re.compile(r"^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$")
|
|
@@ -48,8 +50,8 @@ def _read_source(path, inode):
|
|
|
48
50
|
return None
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
def _codex_identity(data, sid, cwd):
|
|
52
|
-
"""
|
|
53
|
+
def _codex_identity(data, sid, cwd, path, identity):
|
|
54
|
+
"""Bind main-thread identity and its saved cwd at the validated snapshot boundary."""
|
|
53
55
|
try:
|
|
54
56
|
record = json.loads(data.split(b"\n", 1)[0])
|
|
55
57
|
except (ValueError, UnicodeError):
|
|
@@ -63,8 +65,14 @@ def _codex_identity(data, sid, cwd):
|
|
|
63
65
|
return False
|
|
64
66
|
if meta.get("originator") not in (None, "codex-tui", "codex_cli_rs", "codex_cli"):
|
|
65
67
|
return False
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
try:
|
|
69
|
+
directory = codex_resume_cwd(path, sid, upto=identity.st_size)
|
|
70
|
+
after = os.stat(path)
|
|
71
|
+
except OSError:
|
|
72
|
+
return False
|
|
73
|
+
return (directory is not None and os.path.realpath(directory) == cwd
|
|
74
|
+
and (after.st_dev, after.st_ino) == (identity.st_dev, identity.st_ino)
|
|
75
|
+
and after.st_size >= identity.st_size)
|
|
68
76
|
|
|
69
77
|
|
|
70
78
|
def _claude_identity(data, sid, cwd):
|
|
@@ -110,7 +118,7 @@ def validate_source(provider, sid, path, cwd, inode=None):
|
|
|
110
118
|
if provider == "codex":
|
|
111
119
|
if not name.startswith("rollout-") or not name.endswith("-" + sid + ".jsonl"):
|
|
112
120
|
return None
|
|
113
|
-
valid = _codex_identity(data, sid, directory)
|
|
121
|
+
valid = _codex_identity(data, sid, directory, resolved, identity)
|
|
114
122
|
else:
|
|
115
123
|
valid = name == sid + ".jsonl" and _claude_identity(data, sid, directory)
|
|
116
124
|
if not valid:
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Exit 0 only for a known `codex exec ... -` prompt that itself requires stdin EOF.
|
|
3
|
+
# Unknown options/subcommands and early exits fail closed into ordinary passthrough.
|
|
4
|
+
set -u
|
|
5
|
+
|
|
6
|
+
seen_exec=0
|
|
7
|
+
for arg in "$@"; do
|
|
8
|
+
case "$arg" in -h|--help|-V|--version) exit 1 ;; esac
|
|
9
|
+
done
|
|
10
|
+
while [ "$#" -gt 0 ]; do
|
|
11
|
+
arg="$1"; shift
|
|
12
|
+
case "$arg" in
|
|
13
|
+
--)
|
|
14
|
+
[ "$seen_exec" = 1 ] && [ "$#" = 1 ] && [ "$1" = - ]
|
|
15
|
+
exit $? ;;
|
|
16
|
+
-)
|
|
17
|
+
[ "$seen_exec" = 1 ] && [ "$#" = 0 ]
|
|
18
|
+
exit $? ;;
|
|
19
|
+
-c|--config|--enable|--disable|--remote|--remote-auth-token-env|-m|--model|--local-provider|\
|
|
20
|
+
-p|--profile|-s|--sandbox|-C|--cd|--add-dir|-a|--ask-for-approval|--thread-source|\
|
|
21
|
+
--cyber-access-program|--output-schema|--color|-o|--output-last-message)
|
|
22
|
+
[ "$#" -gt 0 ] || exit 1
|
|
23
|
+
case "$1" in -*) exit 1 ;; esac
|
|
24
|
+
shift ;;
|
|
25
|
+
-i|--image)
|
|
26
|
+
[ "$#" -gt 0 ] || exit 1
|
|
27
|
+
case "$1" in -*) exit 1 ;; esac
|
|
28
|
+
while [ "$#" -gt 0 ]; do
|
|
29
|
+
case "$1" in -*) break ;; esac
|
|
30
|
+
shift
|
|
31
|
+
done ;;
|
|
32
|
+
--config=*|--enable=*|--disable=*|--remote=*|--remote-auth-token-env=*|--model=*|--local-provider=*|\
|
|
33
|
+
--profile=*|--sandbox=*|--cd=*|--add-dir=*|--ask-for-approval=*|--thread-source=*|\
|
|
34
|
+
--cyber-access-program=*|--output-schema=*|--color=*|--output-last-message=*|--image=*|\
|
|
35
|
+
-c?*|-m?*|-p?*|-s?*|-C?*|-a?*|-o?*|-i?*) ;;
|
|
36
|
+
--strict-config|--oss|--approve-for-me|--dangerously-bypass-approvals-and-sandbox|\
|
|
37
|
+
--dangerously-bypass-hook-trust|--worktree|--skip-git-repo-check|--ephemeral|\
|
|
38
|
+
--ignore-user-config|--ignore-rules|--json|--no-daemon) ;;
|
|
39
|
+
exec|e)
|
|
40
|
+
[ "$seen_exec" = 0 ] || exit 1
|
|
41
|
+
seen_exec=1 ;;
|
|
42
|
+
*) exit 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
done
|
|
45
|
+
exit 1
|