claude-multiacc 2.0.64 → 2.0.65

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.
Files changed (226) hide show
  1. package/bin/claude-accounts +4 -4
  2. package/bin/codex +25 -7
  3. package/bin/codex-accounts +16 -3
  4. package/docs/AUTORESUME.md +10 -1
  5. package/docs/CODEX.md +39 -0
  6. package/lib/__pycache__/audit.cpython-314.pyc +0 -0
  7. package/lib/__pycache__/autoresume.cpython-314.pyc +0 -0
  8. package/lib/__pycache__/autoresume_cross.cpython-314.pyc +0 -0
  9. package/lib/__pycache__/autoresume_cross_dispatch.cpython-314.pyc +0 -0
  10. package/lib/__pycache__/autoresume_cross_receipt.cpython-314.pyc +0 -0
  11. package/lib/__pycache__/autoresume_handoff.cpython-314.pyc +0 -0
  12. package/lib/__pycache__/autoresume_probe.cpython-314.pyc +0 -0
  13. package/lib/__pycache__/autoresume_receipt.cpython-314.pyc +0 -0
  14. package/lib/__pycache__/claude_client_error.cpython-314.pyc +0 -0
  15. package/lib/__pycache__/claude_reset.cpython-314.pyc +0 -0
  16. package/lib/__pycache__/claude_token_identity.cpython-314.pyc +0 -0
  17. package/lib/__pycache__/codex_autoresume_mode.cpython-314.pyc +0 -0
  18. package/lib/__pycache__/codex_config_edit.cpython-314.pyc +0 -0
  19. package/lib/__pycache__/codex_credit_controls.cpython-314.pyc +0 -0
  20. package/lib/__pycache__/codex_credit_local_scope.cpython-314.pyc +0 -0
  21. package/lib/__pycache__/codex_credit_selection.cpython-314.pyc +0 -0
  22. package/lib/__pycache__/codex_credits.cpython-314.pyc +0 -0
  23. package/lib/__pycache__/codex_limits_lock.cpython-314.pyc +0 -0
  24. package/lib/__pycache__/codex_mcp_oauth.cpython-314.pyc +0 -0
  25. package/lib/__pycache__/codex_python.cpython-314.pyc +0 -0
  26. package/lib/__pycache__/codex_resume_cwd.cpython-314.pyc +0 -0
  27. package/lib/__pycache__/codex_speed.cpython-314.pyc +0 -0
  28. package/lib/__pycache__/codex_trust.cpython-314.pyc +0 -0
  29. package/lib/__pycache__/codex_usage_fetch.cpython-314.pyc +0 -0
  30. package/lib/__pycache__/keychain.cpython-314.pyc +0 -0
  31. package/lib/__pycache__/mcp_mirror.cpython-314.pyc +0 -0
  32. package/lib/__pycache__/mcp_oauth.cpython-314.pyc +0 -0
  33. package/lib/__pycache__/mcp_registry.cpython-314.pyc +0 -0
  34. package/lib/__pycache__/model_catalog.cpython-314.pyc +0 -0
  35. package/lib/__pycache__/model_match.cpython-314.pyc +0 -0
  36. package/lib/__pycache__/report.cpython-314.pyc +0 -0
  37. package/lib/__pycache__/selector_credit_scope.cpython-314.pyc +0 -0
  38. package/lib/__pycache__/selector_credits.cpython-314.pyc +0 -0
  39. package/lib/__pycache__/selector_funding.cpython-314.pyc +0 -0
  40. package/lib/__pycache__/selector_policy.cpython-314.pyc +0 -0
  41. package/lib/__pycache__/selector_primitives.cpython-314.pyc +0 -0
  42. package/lib/__pycache__/session_settings.cpython-314.pyc +0 -0
  43. package/lib/__pycache__/shim_path.cpython-314.pyc +0 -0
  44. package/lib/autoresume.py +28 -8
  45. package/lib/codex_speed.py +351 -0
  46. package/lib/codex_speed.sh +39 -0
  47. package/lib/common.sh +13 -3
  48. package/lib/model_match.py +4 -1
  49. package/lib/session_settings.py +25 -6
  50. package/lib/shim_path.py +6 -0
  51. package/package.json +2 -2
  52. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-314.pyc +0 -0
  53. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-314.pyc +0 -0
  54. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-314.pyc +0 -0
  55. package/tests/__pycache__/autoresume_cross_unit_support.cpython-314.pyc +0 -0
  56. package/tests/__pycache__/autoresume_tmux_support.cpython-314.pyc +0 -0
  57. package/tests/__pycache__/packaged_command_support.cpython-314.pyc +0 -0
  58. package/tests/__pycache__/test_autoresume.cpython-314.pyc +0 -0
  59. package/tests/__pycache__/test_autoresume_catalog.cpython-314.pyc +0 -0
  60. package/tests/__pycache__/test_autoresume_catalog_cli.cpython-314.pyc +0 -0
  61. package/tests/__pycache__/test_autoresume_codex_records.cpython-314.pyc +0 -0
  62. package/tests/__pycache__/test_autoresume_codex_trust.cpython-314.pyc +0 -0
  63. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-314.pyc +0 -0
  64. package/tests/__pycache__/test_autoresume_cross_guards.cpython-314.pyc +0 -0
  65. package/tests/__pycache__/test_autoresume_cross_holders.cpython-314.pyc +0 -0
  66. package/tests/__pycache__/test_autoresume_cross_origin.cpython-314.pyc +0 -0
  67. package/tests/__pycache__/test_autoresume_cross_policy.cpython-314.pyc +0 -0
  68. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-314.pyc +0 -0
  69. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-314.pyc +0 -0
  70. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-314.pyc +0 -0
  71. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-314.pyc +0 -0
  72. package/tests/__pycache__/test_autoresume_cursor.cpython-314.pyc +0 -0
  73. package/tests/__pycache__/test_autoresume_cwd.cpython-314.pyc +0 -0
  74. package/tests/__pycache__/test_autoresume_cwd_handoff.cpython-314.pyc +0 -0
  75. package/tests/__pycache__/test_autoresume_cwd_reader.cpython-314.pyc +0 -0
  76. package/tests/__pycache__/test_autoresume_cwd_tmux.cpython-314.pyc +0 -0
  77. package/tests/__pycache__/test_autoresume_fallback.cpython-314.pyc +0 -0
  78. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-314.pyc +0 -0
  79. package/tests/__pycache__/test_autoresume_handoff_history.cpython-314.pyc +0 -0
  80. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-314.pyc +0 -0
  81. package/tests/__pycache__/test_autoresume_memory.cpython-314.pyc +0 -0
  82. package/tests/__pycache__/test_autoresume_memory_tmux.cpython-314.pyc +0 -0
  83. package/tests/__pycache__/test_autoresume_mode.cpython-314.pyc +0 -0
  84. package/tests/__pycache__/test_autoresume_probe.cpython-314.pyc +0 -0
  85. package/tests/__pycache__/test_autoresume_receipt.cpython-314.pyc +0 -0
  86. package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-314.pyc +0 -0
  87. package/tests/__pycache__/test_autoresume_settings.cpython-314.pyc +0 -0
  88. package/tests/__pycache__/test_autoresume_sidechain.cpython-314.pyc +0 -0
  89. package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-314.pyc +0 -0
  90. package/tests/__pycache__/test_autoresume_tmux.cpython-314.pyc +0 -0
  91. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-314.pyc +0 -0
  92. package/tests/__pycache__/test_autoresume_trust_tmux.cpython-314.pyc +0 -0
  93. package/tests/__pycache__/test_claude_reset.cpython-314.pyc +0 -0
  94. package/tests/__pycache__/test_claude_token_identity.cpython-314.pyc +0 -0
  95. package/tests/__pycache__/test_codex_exec_stdin.cpython-314.pyc +0 -0
  96. package/tests/__pycache__/test_codex_mcp_oauth.cpython-314.pyc +0 -0
  97. package/tests/__pycache__/test_codex_reset.cpython-314.pyc +0 -0
  98. package/tests/__pycache__/test_codex_reset_credits.cpython-314.pyc +0 -0
  99. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-314.pyc +0 -0
  100. package/tests/__pycache__/test_codex_reset_credits_report.cpython-314.pyc +0 -0
  101. package/tests/__pycache__/test_codex_reset_fetch.cpython-314.pyc +0 -0
  102. package/tests/__pycache__/test_codex_reset_polling.cpython-314.pyc +0 -0
  103. package/tests/__pycache__/test_codex_reset_reporting.cpython-314.pyc +0 -0
  104. package/tests/__pycache__/test_codex_reset_windows.cpython-314.pyc +0 -0
  105. package/tests/__pycache__/test_mcp_mirror.cpython-314.pyc +0 -0
  106. package/tests/__pycache__/test_mcp_mirror_args.cpython-314.pyc +0 -0
  107. package/tests/__pycache__/test_mcp_oauth.cpython-314.pyc +0 -0
  108. package/tests/__pycache__/test_mcp_registry.cpython-314.pyc +0 -0
  109. package/tests/__pycache__/test_selector.cpython-314.pyc +0 -0
  110. package/tests/__pycache__/test_selector_cached_credits.cpython-314.pyc +0 -0
  111. package/tests/__pycache__/test_selector_credit_scope.cpython-314.pyc +0 -0
  112. package/tests/__pycache__/test_selector_credit_shim.cpython-314.pyc +0 -0
  113. package/tests/__pycache__/test_selector_credits.cpython-314.pyc +0 -0
  114. package/tests/__pycache__/test_selector_spread.cpython-314.pyc +0 -0
  115. package/tests/__pycache__/test_token_identity_ceremony.cpython-314.pyc +0 -0
  116. package/tests/__pycache__/test_token_identity_integration.cpython-314.pyc +0 -0
  117. package/tests/__pycache__/test_token_identity_retry.cpython-314.pyc +0 -0
  118. package/tests/__pycache__/test_token_identity_sync.cpython-314.pyc +0 -0
  119. package/tests/run-tests.sh +23 -7
  120. package/tests/test_codex_speed.py +236 -0
  121. package/tests/test_mutate_lock.py +107 -0
  122. package/tests/test_shim_path.py +44 -4
  123. package/lib/__pycache__/audit.cpython-312.pyc +0 -0
  124. package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
  125. package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
  126. package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
  127. package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
  128. package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
  129. package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
  130. package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
  131. package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
  132. package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
  133. package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
  134. package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
  135. package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
  136. package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
  137. package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
  138. package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
  139. package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
  140. package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
  141. package/lib/__pycache__/codex_mcp_oauth.cpython-312.pyc +0 -0
  142. package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
  143. package/lib/__pycache__/codex_resume_cwd.cpython-312.pyc +0 -0
  144. package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
  145. package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
  146. package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
  147. package/lib/__pycache__/mcp_mirror.cpython-312.pyc +0 -0
  148. package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
  149. package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
  150. package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
  151. package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
  152. package/lib/__pycache__/report.cpython-312.pyc +0 -0
  153. package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
  154. package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
  155. package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
  156. package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
  157. package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
  158. package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
  159. package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
  160. package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
  161. package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
  162. package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
  163. package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
  164. package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
  165. package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
  166. package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
  167. package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
  168. package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
  169. package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
  170. package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
  171. package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
  172. package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
  173. package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
  174. package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
  175. package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
  176. package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
  177. package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
  178. package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
  179. package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
  180. package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
  181. package/tests/__pycache__/test_autoresume_cwd.cpython-312.pyc +0 -0
  182. package/tests/__pycache__/test_autoresume_cwd_handoff.cpython-312.pyc +0 -0
  183. package/tests/__pycache__/test_autoresume_cwd_reader.cpython-312.pyc +0 -0
  184. package/tests/__pycache__/test_autoresume_cwd_tmux.cpython-312.pyc +0 -0
  185. package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
  186. package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
  187. package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
  188. package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
  189. package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
  190. package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
  191. package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
  192. package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
  193. package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
  194. package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
  195. package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
  196. package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
  197. package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
  198. package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
  199. package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
  200. package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
  201. package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
  202. package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
  203. package/tests/__pycache__/test_codex_exec_stdin.cpython-312.pyc +0 -0
  204. package/tests/__pycache__/test_codex_mcp_oauth.cpython-312.pyc +0 -0
  205. package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
  206. package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
  207. package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
  208. package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
  209. package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
  210. package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
  211. package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
  212. package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
  213. package/tests/__pycache__/test_mcp_mirror.cpython-312.pyc +0 -0
  214. package/tests/__pycache__/test_mcp_mirror_args.cpython-312.pyc +0 -0
  215. package/tests/__pycache__/test_mcp_oauth.cpython-312.pyc +0 -0
  216. package/tests/__pycache__/test_mcp_registry.cpython-312.pyc +0 -0
  217. package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
  218. package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
  219. package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
  220. package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
  221. package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
  222. package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
  223. package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
  224. package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
  225. package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
  226. package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
@@ -1708,7 +1708,7 @@ token_identity_receipt() { # Unknown results must never overwrite a peer's prove
1708
1708
  limits_distribute_now() {
1709
1709
  local lock="$ACC_ROOT/tmp/limits-push.lock"
1710
1710
  mkdir -p "$ACC_ROOT/tmp" 2>/dev/null || return 0
1711
- if ! mkdir "$lock" 2>/dev/null; then
1711
+ if ! atomic_mkdir "$lock" 2>/dev/null; then
1712
1712
  # A DETACHED push that is killed (logout, reboot, pkill) never runs its EXIT trap,
1713
1713
  # and mkdir can never take a lock dir nobody will remove: one stranded lock
1714
1714
  # silently stopped ALL telemetry distribution on the live pool from 2026-09-03
@@ -1721,7 +1721,7 @@ limits_distribute_now() {
1721
1721
  # exactly as before.
1722
1722
  [ $(( $(epoch_now) - $(file_mtime "$lock") )) -gt 600 ] || return 0
1723
1723
  rm -rf "$lock" 2>/dev/null
1724
- mkdir "$lock" 2>/dev/null || return 0
1724
+ atomic_mkdir "$lock" 2>/dev/null || return 0
1725
1725
  log_to sync.log "stale limits-push lock broken (older than 600s); distributing"
1726
1726
  fi
1727
1727
  trap 'rmdir "$lock" 2>/dev/null || true' EXIT
@@ -1792,7 +1792,7 @@ cmd_limits() {
1792
1792
  done
1793
1793
  local lock="$ACC_ROOT/.locks/limits"
1794
1794
  mkdir -p "$ACC_ROOT/.locks"
1795
- if ! mkdir "$lock" 2>/dev/null; then
1795
+ if ! atomic_mkdir "$lock" 2>/dev/null; then
1796
1796
  local age=$(( $(epoch_now) - $(file_mtime "$lock") ))
1797
1797
  if [ "$age" -lt 120 ]; then
1798
1798
  # A --json caller still gets the document (built from the state on disk) —
@@ -1802,7 +1802,7 @@ cmd_limits() {
1802
1802
  return 0
1803
1803
  fi
1804
1804
  rm -rf "$lock"
1805
- if ! mkdir "$lock" 2>/dev/null; then
1805
+ if ! atomic_mkdir "$lock" 2>/dev/null; then
1806
1806
  if [ "$json" = "1" ]; then emit_report_json limits; return $?; fi
1807
1807
  return 0
1808
1808
  fi
package/bin/codex CHANGED
@@ -156,6 +156,20 @@ cli_positional_at() { # argv -> prints the index of its first positional argumen
156
156
  return 1
157
157
  }
158
158
 
159
+ # Account/model capabilities are resolved only after the serving account is known.
160
+ # The helper is optional so a damaged install retains the shim's fail-open contract.
161
+ codex_native_argv() {
162
+ CODEX_SPEED_ARGV=("$@")
163
+ if [ -f "$SELF_DIR/../lib/codex_speed.sh" ]; then
164
+ . "$SELF_DIR/../lib/codex_speed.sh"
165
+ codex_speed_prepare "$@" || return $?
166
+ fi
167
+ }
168
+ codex_native_exec() {
169
+ codex_native_argv "$@" || exit $?
170
+ exec "$REAL" ${CODEX_SPEED_ARGV[@]+"${CODEX_SPEED_ARGV[@]}"}
171
+ }
172
+
159
173
  # Whether argv is `codex [global options] mcp add|remove …`.
160
174
  mcp_mutation_argv() {
161
175
  local i j
@@ -252,7 +266,7 @@ if [ -n "${CODEX_HOME:-}" ] \
252
266
  mcp_oauth_ensure "$CODEX_HOME"
253
267
  fi
254
268
  mcp_nested_mirror "$@" || true
255
- exec "$REAL" "$@"
269
+ codex_native_exec "$@"
256
270
  fi
257
271
 
258
272
  now="$(date +%s)"
@@ -1074,6 +1088,9 @@ ar_argv_ok() { # the argv codex is about to get
1074
1088
  model:*) case "$a" in ''|-*) return 1 ;; esac ;;
1075
1089
  effort:model_reasoning_effort=?*)
1076
1090
  case "${a#model_reasoning_effort=}" in *[!$AR_LOWER]*) return 1 ;; esac ;;
1091
+ effort:service_tier=*)
1092
+ case "${a#service_tier=}" in default|fast|priority|ultrafast|'"default"'|'"fast"'|'"priority"'|'"ultrafast"'|"'default'"|"'fast'"|"'priority'"|"'ultrafast'") ;; *) return 1 ;; esac ;;
1093
+ effort:features.fast_mode=true) ;;
1077
1094
  *) return 1 ;;
1078
1095
  esac
1079
1096
  want=""
@@ -1272,7 +1289,7 @@ if [ -n "${CODEX_ACCOUNT:-}" ] && [ "$AR_PROBE" != "1" ]; then
1272
1289
  mcp_ensure "$d"
1273
1290
  mcp_oauth_ensure "$d"
1274
1291
  mcp_mirror_run "$d" "$@" || true
1275
- exec "$REAL" "$@"
1292
+ codex_native_exec "$@"
1276
1293
  fi
1277
1294
  sel_log "pin-invalid account=$CODEX_ACCOUNT (no such dir; random fallback)"
1278
1295
  fi
@@ -1343,7 +1360,7 @@ if [ "${#valid[@]}" -eq 0 ]; then
1343
1360
  [ -t 2 ] && printf 'codex-multiacc: no pool account is usable (%s) — see: codex-accounts expired, then: codex-accounts relogin\n' \
1344
1361
  "${ids# }" >&2
1345
1362
  fi
1346
- exec "$REAL" "$@"
1363
+ codex_native_exec "$@"
1347
1364
  fi
1348
1365
 
1349
1366
  # ---- rotation ----------------------------------------------------------------
@@ -1657,14 +1674,14 @@ if [ "$wants_retry" = "0" ]; then
1657
1674
  fi
1658
1675
  ar_scrub_env
1659
1676
  [ "$AR_NO_DAEMON" != 1 ] || set -- --no-daemon "$@"
1660
- exec "$REAL" "$@"
1677
+ codex_native_exec "$@"
1661
1678
  fi
1662
1679
 
1663
1680
  # Retry path: buffer stdio so a retried call never double-emits partial output.
1664
1681
  mkdir -p "$ACC_ROOT/tmp" 2>/dev/null || true
1665
1682
  tmpd="$(mktemp -d "$ACC_ROOT/tmp/shim.XXXXXX" 2>/dev/null)" || {
1666
1683
  export CODEX_HOME="$pick"
1667
- exec "$REAL" "$@"
1684
+ codex_native_exec "$@"
1668
1685
  }
1669
1686
  trap 'rm -rf "$tmpd"' EXIT
1670
1687
 
@@ -1673,7 +1690,7 @@ trap 'rm -rf "$tmpd"' EXIT
1673
1690
  # nonzero — a hard failure. Verify now, fall back to plain exec if we cannot.
1674
1691
  if ! : > "$tmpd/out" 2>/dev/null || ! : > "$tmpd/err" 2>/dev/null; then
1675
1692
  export CODEX_HOME="$pick"
1676
- exec "$REAL" "$@"
1693
+ codex_native_exec "$@"
1677
1694
  fi
1678
1695
 
1679
1696
  stdin_file=""
@@ -1708,8 +1725,9 @@ cur="$pick"
1708
1725
  rc=0
1709
1726
  while :; do
1710
1727
  if [ -n "$stdin_file" ]; then exec 3< "$stdin_file"; else exec 3< /dev/null; fi
1728
+ CODEX_HOME="$cur" codex_native_argv "$@" || exit $?
1711
1729
  CODEX_HOME="$cur" CODEX_MULTIACC_SELECTED_HOME="$cur" \
1712
- codex_retry_run "$REAL" "$@" <&3 > "$tmpd/out" 2> "$tmpd/err"
1730
+ codex_retry_run "$REAL" ${CODEX_SPEED_ARGV[@]+"${CODEX_SPEED_ARGV[@]}"} <&3 > "$tmpd/out" 2> "$tmpd/err"
1713
1731
  rc=$?
1714
1732
  exec 3<&-
1715
1733
  if [ "$rc" -ne 0 ] && [ "$attempt" -eq 1 ] \
@@ -106,6 +106,10 @@ USAGE
106
106
  every install and self-update runs it, and each limits pass refreshes its own pool's
107
107
  copy once it is an hour old. --if-stale leaves a younger copy alone; --quiet prints
108
108
  nothing. show prints the merged catalog.
109
+ codex-accounts speed --account-dir DIR --model MODEL --speed normal|fast|ultrafast [--json]
110
+ resolve speed from this account's native model catalog. Ultrafast verifies
111
+ account access through native model/list without inference; unknown or
112
+ unavailable access falls back to Fast. Never changes account configuration.
109
113
  codex-accounts post-sync (server side) seed dirs, fix perms, quick verify
110
114
  codex-accounts mcp add <name> [--provider claude|codex|both] [--scope user]
111
115
  [-e KEY=VAL]... [-H 'Header: v']... [--transport stdio|http]
@@ -144,6 +148,8 @@ ENV
144
148
  'none' = local-only, nothing is pushed anywhere
145
149
  CODEX_MULTIACC_SYNC_ROOT / _SYNC_REPO remote pool root / addon repo for it
146
150
  CODEX_ACCOUNT pin the shim to one account
151
+ CODEX_SPEED normal|fast|ultrafast for this launch; unavailable Ultrafast uses Fast
152
+ CODEX_SPEED_MODEL model hint for app-server launches that choose their model over RPC
147
153
  CODEX_SHIM_RETRY=0 disable the `codex exec` auto-retry
148
154
  CODEX_MULTIACC_DISABLE=1 bypass the shim entirely
149
155
  CODEX_MULTIACC_AUTO_RESET=0 disable automatic usage-reset redemption
@@ -156,6 +162,12 @@ EOF
156
162
 
157
163
  require_manifest() { [ -f "$MANIFEST" ] || die "no manifest at $MANIFEST — run claude-multiacc install first"; }
158
164
 
165
+ cmd_speed() {
166
+ local native
167
+ native="$(find_real_codex "$_self" 2>/dev/null)" || native=""
168
+ "$PYBIN" "$LIB_DIR/codex_speed.py" --native-cli "$native" "$@"
169
+ }
170
+
159
171
  cmd_configure() {
160
172
  require_manifest
161
173
  local apply=0 check=0 arg rc
@@ -257,11 +269,11 @@ PYEOF
257
269
  # later contender legitimately re-took.
258
270
  login_lock_acquire() { # $1 = lock dir; nonzero when someone else holds it
259
271
  local L="$1"
260
- if ! mkdir "$L" 2>/dev/null; then
272
+ if ! atomic_mkdir "$L" 2>/dev/null; then
261
273
  [ $(( $(epoch_now) - $(file_mtime "$L") )) -gt 1800 ] || return 1
262
274
  mv "$L" "$L.stale.$$" 2>/dev/null || return 1
263
275
  rm -rf "$L.stale.$$" 2>/dev/null
264
- mkdir "$L" 2>/dev/null || return 1
276
+ atomic_mkdir "$L" 2>/dev/null || return 1
265
277
  fi
266
278
  : > "$L/owner.$$" 2>/dev/null || true
267
279
  return 0
@@ -2477,7 +2489,7 @@ PYEOF
2477
2489
  # distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
2478
2490
  # FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
2479
2491
  # the dispatcher below implements, and a test pins that both ways.
2480
- _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"
2492
+ _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 speed"
2481
2493
  case " $_KNOWN_VERBS " in
2482
2494
  *" ${1:-help} "*)
2483
2495
  # ...but never past a literal `--`: from there on the words belong to an MCP
@@ -2510,6 +2522,7 @@ case "${1:-help}" in
2510
2522
  init-pool) shift; cmd_init_pool "$@" ;;
2511
2523
  mcp) shift; cmd_mcp "$@" ;;
2512
2524
  model-catalog) shift; cmd_model_catalog "$@" ;;
2525
+ speed) shift; cmd_speed "$@" ;;
2513
2526
  help|--help|-h) usage ;;
2514
2527
  *) usage; exit 1 ;;
2515
2528
  esac
@@ -225,7 +225,16 @@ The prompt is submitted automatically:
225
225
  - **To replace the whole text**, set `CLAUDE_MULTIACC_AUTORESUME_PROMPT` or
226
226
  `CODEX_MULTIACC_AUTORESUME_PROMPT`.
227
227
 
228
- ### Model and effort
228
+ ### Model, effort, and Codex speed
229
+
230
+ Codex relaunches also carry the most recently saved `service_tier` from the
231
+ thread's own settings or turn context. Normal, Fast, and Ultrafast remain the same
232
+ across account rotation; Ultrafast becomes Fast when the selected destination
233
+ account/model does not advertise it. Claude receives no speed flag. The shim
234
+ updates the named thread's tier through native `thread/resume` before launching
235
+ the CLI, because native resume restores a saved tier ahead of `-c` configuration.
236
+ See [Session speed](CODEX.md#session-speed) for capability discovery and the
237
+ `CODEX_SPEED` launch override.
229
238
 
230
239
  The watcher reads the model and effort from the session's own history when it decides to
231
240
  relaunch, so a `/model` or `/effort` switch made after the launch is honoured:
package/docs/CODEX.md CHANGED
@@ -92,6 +92,45 @@ Codex-specific notes:
92
92
  - **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
93
93
  addon's no-API-keys rule.
94
94
 
95
+ ## Session speed
96
+
97
+ Set `CODEX_SPEED=normal`, `fast`, or `ultrafast` for a Codex launch. Normal explicitly
98
+ uses the standard tier, even when an account's configuration defaults to Fast.
99
+ The setting also works with `CODEX_ACCOUNT` and `CODEX_HOME` pins. Explicit native
100
+ `-c service_tier=...` overrides take precedence. Claude has no corresponding option.
101
+
102
+ ```bash
103
+ CODEX_SPEED=fast codex
104
+ CODEX_SPEED=ultrafast codex exec -m gpt-6.1-sol 'Review the changed code'
105
+ codex-accounts speed --account-dir ~/.codex-accounts/acct-18 --model gpt-6.1-sol --speed ultrafast --json
106
+ ```
107
+
108
+ The shim reads **the selected account's** native model catalog, never an e-mail or
109
+ a plan-name guess. Ultrafast falls back to Fast when that account/model does not
110
+ advertise it. Every Ultrafast launch verifies the native account's `model/list`
111
+ with no inference, so even a copied cache cannot grant another account access;
112
+ an inconclusive read falls back to Fast.
113
+ Catalog probes use a temporary SQLite index, preserving the account's normal
114
+ session-index work. The inspection command above uses the same native check for
115
+ Ultrafast.
116
+
117
+ Quota retries resolve the speed again for the next account. Automatic quota
118
+ resumes carry the speed recorded in the session history. For those resumes and
119
+ explicit speed requests on a named session UUID, the shim applies the resolved
120
+ tier through native `thread/resume` before launching the resumed CLI: Codex
121
+ currently restores its saved tier ahead of process configuration. That
122
+ preparation sends no turn and preserves the conversation. A failed preparation
123
+ stops the launch instead of silently selecting another speed.
124
+
125
+ App-server callers may supply `CODEX_SPEED_MODEL` when their model is chosen over
126
+ RPC. Read the resolved `config.service_tier` with native `config/read` and pass it
127
+ as `serviceTier` on `thread/start`, `thread/resume`, and `turn/start`; otherwise
128
+ saved thread settings can override the process configuration.
129
+
130
+ The [official speed documentation](https://learn.chatgpt.com/docs/agent-configuration/speed)
131
+ describes Fast's higher usage and Ultrafast access for Pro $500 and eligible
132
+ Enterprise/Edu plans. Actual account/model capabilities remain authoritative.
133
+
95
134
  ## Spendable Codex credits
96
135
 
97
136
  The existing authenticated usage GET also supplies the account's spendable Codex
package/lib/autoresume.py CHANGED
@@ -1214,6 +1214,14 @@ def codex_effort_ok(val):
1214
1214
  and EFFORT_VALUE_RE.match(val[len(CODEX_EFFORT_KEY):]) is not None
1215
1215
 
1216
1216
 
1217
+ def codex_speed_ok(val):
1218
+ """Only known speed choices, never arbitrary config, may cross a relaunch."""
1219
+ if val == "features.fast_mode=true":
1220
+ return True
1221
+ return isinstance(val, str) and re.fullmatch(
1222
+ r'''service_tier=(?:default|fast|priority|ultrafast|"(?:default|fast|priority|ultrafast)"|'(?:default|fast|priority|ultrafast)')''', val) is not None
1223
+
1224
+
1217
1225
  def codex_argv_parse(argv):
1218
1226
  """The argv shapes a codex relaunch can rebuild (mirrors bin/codex's ar_argv_ok) ->
1219
1227
  (kept options, resume target or None), or None for anything else."""
@@ -1231,7 +1239,7 @@ def codex_argv_parse(argv):
1231
1239
  kept += [arg, expected]
1232
1240
  i += 1
1233
1241
  elif arg in ("-m", "--model", "-c"):
1234
- value_ok = codex_effort_ok if arg == "-c" else _value_ok
1242
+ value_ok = (lambda value: codex_effort_ok(value) or codex_speed_ok(value)) if arg == "-c" else _value_ok
1235
1243
  if i + 1 >= len(argv) or not value_ok(argv[i + 1]):
1236
1244
  return None
1237
1245
  kept += [arg, argv[i + 1]]
@@ -1315,7 +1323,11 @@ def settings_argv(provider, settings):
1315
1323
  (anything else counts as unknown). [] when nothing is known."""
1316
1324
  effort = settings.get("effort") if isinstance(settings, dict) else None
1317
1325
  effort = effort if isinstance(effort, str) and EFFORT_VALUE_RE.match(effort) else None
1318
- return settings_flags(provider, history_model(settings), effort)
1326
+ flags = settings_flags(provider, history_model(settings), effort)
1327
+ if provider == "codex" and isinstance(settings, dict) and settings.get("speed") in ("normal", "fast", "ultrafast"):
1328
+ flags += ["-c", "service_tier=" + {"normal": "default", "fast": "fast",
1329
+ "ultrafast": "ultrafast"}[settings["speed"]]]
1330
+ return flags
1319
1331
 
1320
1332
 
1321
1333
  def argv_settings(provider, argv):
@@ -1336,6 +1348,10 @@ def argv_settings(provider, argv):
1336
1348
  elif provider == "codex" and arg == "-c" and codex_effort_ok(val):
1337
1349
  found["effort"] = val[len(CODEX_EFFORT_KEY):]
1338
1350
  i += 1
1351
+ elif provider == "codex" and arg == "-c" and val.startswith("service_tier=") and codex_speed_ok(val):
1352
+ from codex_speed import speed
1353
+ found["speed"] = speed(val.split("=", 1)[1].strip("\"'"))
1354
+ i += 1
1339
1355
  i += 1
1340
1356
  return found
1341
1357
 
@@ -1355,14 +1371,16 @@ def claude_model_left(launch, served):
1355
1371
  return family is not None and family != launch
1356
1372
 
1357
1373
 
1358
- def without_settings(kept, model, effort):
1374
+ def without_settings(kept, model, effort, speed=False):
1359
1375
  """``kept`` without its model options (-m V, --model V, --model=V) when ``model`` and its
1360
1376
  effort options (--effort V, codex's -c model_reasoning_effort=V) when ``effort``. Both
1361
1377
  parsers refuse a value that starts with "-", so no value is ever taken for an option."""
1362
1378
  out, i = [], 0
1363
1379
  while i < len(kept):
1364
1380
  arg = kept[i]
1365
- if (model and arg in ("-m", "--model")) or (effort and arg in ("--effort", "-c")):
1381
+ val = kept[i + 1] if i + 1 < len(kept) else ""
1382
+ if ((model and arg in ("-m", "--model")) or (effort and arg == "--effort")
1383
+ or (arg == "-c" and ((effort and codex_effort_ok(val)) or (speed and codex_speed_ok(val))))):
1366
1384
  i += 2
1367
1385
  continue
1368
1386
  if not (model and arg.startswith("--model=")):
@@ -1407,10 +1425,12 @@ def relaunch_argv(provider, argv, sid, cls, prompt=None, settings=None, detail=N
1407
1425
  left = claude_model_left(argv_settings("claude", kept).get("model"), settings.get("model"))
1408
1426
  settings = {"effort": settings.get("effort")}
1409
1427
  flags = settings_argv(provider, settings)
1410
- model, effort = left or "--model" in flags or "-m" in flags, "--effort" in flags or "-c" in flags
1411
- if provider == "codex" and not flags and history_model(settings):
1428
+ model = left or "--model" in flags or "-m" in flags
1429
+ effort = "--effort" in flags or any(codex_effort_ok(value) for value in flags)
1430
+ speed = provider == "codex" and settings.get("speed") in ("normal", "fast", "ultrafast")
1431
+ if provider == "codex" and not model and history_model(settings):
1412
1432
  model = effort = True # no launch -m or -c alone: neither, and the thread restores both
1413
- return head + without_settings(kept, model, effort) + flags + tail
1433
+ return head + without_settings(kept, model, effort, speed) + flags + tail
1414
1434
 
1415
1435
 
1416
1436
  # ---- relaunch / state files ---------------------------------------------------------
@@ -2721,7 +2741,7 @@ class Watcher:
2721
2741
  try:
2722
2742
  found = session_settings(self.provider, self.transcript)
2723
2743
  self.served_model = history_model(found)
2724
- if not found.get("model") and not found.get("effort"):
2744
+ if not found.get("model") and not found.get("effort") and not found.get("speed"):
2725
2745
  return None
2726
2746
  # The catalog only clamps an effort to the model and supplies Codex's default
2727
2747
  # one: this pool's copy and caches, the other pool's only when the chain named it.