@dst-justin/relay 2.7.0 → 2.8.0

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 (3) hide show
  1. package/README.md +5 -0
  2. package/package.json +2 -2
  3. package/relay +85 -211
package/README.md CHANGED
@@ -288,6 +288,11 @@ Sessions live in `~/.claude/projects/` and are shared across all accounts — af
288
288
 
289
289
  ## Changelog
290
290
 
291
+ ### v2.8.0 — 2026-09-06
292
+ - **Removed** `relay provider use <name>` and `relay provider off` — the global "always-on" provider switch (which mutated `${CLAUDE_DIR}/settings.json`) is gone entirely. All provider usage now goes through `relay run <name>`, which never touches global state.
293
+ - Add Codex CLI as an alternate `relay run` exec target: `relay provider add <name> --codex` stores `agent=codex` (requires `--model`; `--discover-models`/`--subagent-model` are ignored with a warning), and `relay run <name> --codex`/`--claude` override the stored agent for a single invocation.
294
+ - Codex runs pass the LiteLLM `base_url`/`model` via `-c` TOML overrides; the auth token is handed to the `codex` process only through a child-process env var, never argv.
295
+
291
296
  ### v2.7.0 — 2026-09-04
292
297
  - `relay provider add` now turns on gateway model discovery by default (previously required `--discover-models`); opt out with `--no-discover-models`.
293
298
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dst-justin/relay",
3
- "version": "2.7.0",
4
- "description": "Multi-account switcher for Claude Code — instant credential swap across macOS, Linux, and Windows",
3
+ "version": "2.8.0",
4
+ "description": "Multi-account switcher for Claude Code — instant credential swap across macOS, Linux, and Windows, with one-off LiteLLM provider runs targeting Claude Code or Codex CLI",
5
5
  "bin": {
6
6
  "relay": "./relay.js"
7
7
  },
package/relay CHANGED
@@ -15,12 +15,11 @@ CURRENT_FILE="${RELAY_DIR}/current"
15
15
  ORDER_FILE="${RELAY_DIR}/order"
16
16
  UPDATE_CACHE="${RELAY_DIR}/.update_cache"
17
17
  PROVIDERS_STORE="${RELAY_DIR}/providers"
18
- ACTIVE_PROVIDER_FILE="${RELAY_DIR}/active_provider"
19
- SETTINGS_ENV_SNAPSHOT="${RELAY_DIR}/settings_env_snapshot.json"
20
18
  CLAUDE_DIR="${HOME}/.claude"
21
19
  CLAUDE_JSON="${HOME}/.claude.json"
22
20
  CLAUDE_SETTINGS="${CLAUDE_DIR}/settings.json"
23
21
  REAL_CLAUDE=$(command -v claude 2>/dev/null || echo "")
22
+ REAL_CODEX=$(command -v codex 2>/dev/null || echo "")
24
23
  # macOS: /usr/bin/python3 uses the system TLS stack (correct certs);
25
24
  # /usr/local/bin/python3 (Homebrew/standalone) often lacks bundled certs → SSL failures
26
25
  if [[ "$(uname)" == "Darwin" ]] && [[ -x "/usr/bin/python3" ]]; then
@@ -106,7 +105,6 @@ account_exists() { [[ -f "$(account_creds "$1")" ]]; }
106
105
 
107
106
  provider_file() { echo "${PROVIDERS_STORE}/$1.json"; }
108
107
  provider_exists() { [[ -f "$(provider_file "$1")" ]]; }
109
- active_provider_name() { [[ -f "${ACTIVE_PROVIDER_FILE}" ]] && cat "${ACTIVE_PROVIDER_FILE}" || echo ""; }
110
108
 
111
109
  # list providers alphabetically, one per line (bash 3.2 compatible)
112
110
  list_provider_names() {
@@ -127,6 +125,14 @@ _provider_discover() {
127
125
  "${PY}" -c 'import json,sys; print("1" if json.load(open(sys.argv[1])).get("discover_models") else "")' "$(provider_file "$1")"
128
126
  }
129
127
 
128
+ # escape a value for embedding as a TOML basic string in a `codex -c key="value"` arg
129
+ _toml_str() {
130
+ local s="$1"
131
+ s="${s//\\/\\\\}"
132
+ s="${s//\"/\\\"}"
133
+ printf '"%s"' "${s}"
134
+ }
135
+
130
136
  # list accounts in canonical add-order, one per line (bash 3.2 compatible).
131
137
  # Self-healing: drops names whose credential file is gone, appends any
132
138
  # credential file not yet tracked (alphabetically), and rewrites ORDER_FILE.
@@ -293,144 +299,11 @@ require_claude() {
293
299
  exit 1
294
300
  }
295
301
 
296
- # Fully replace the 4 managed keys with exactly the given KEY=VALUE pairs.
297
- # Snapshots the pre-relay original values/absences the first time this is
298
- # called since the last full restore (SETTINGS_ENV_SNAPSHOT doesn't exist).
299
- # Assumes the caller already holds the credential lock.
300
- _settings_env_activate_locked() {
301
- RELAY_SETTINGS_PATH="${CLAUDE_SETTINGS}" \
302
- RELAY_SNAPSHOT_PATH="${SETTINGS_ENV_SNAPSHOT}" \
303
- RELAY_ENV_PAIRS="$(printf '%s\n' "$@")" \
304
- "${PY}" <<'EOF'
305
- import json, os, sys, tempfile
306
-
307
- MANAGED = ["ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_MODEL", "CLAUDE_CODE_SUBAGENT_MODEL", "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"]
308
-
309
- def load_json(p, default):
310
- if not os.path.exists(p):
311
- return default
312
- raw = open(p).read().strip()
313
- return json.loads(raw) if raw else default
314
-
315
- def atomic_write(p, data, mode=0o600):
316
- d = os.path.dirname(p) or "."
317
- fd, tmp = tempfile.mkstemp(dir=d, prefix=".relay-tmp-")
318
- try:
319
- with os.fdopen(fd, "w") as f:
320
- json.dump(data, f, indent=2)
321
- f.write("\n")
322
- os.chmod(tmp, mode)
323
- os.replace(tmp, p)
324
- except Exception:
325
- try:
326
- os.unlink(tmp)
327
- except OSError:
328
- pass
329
- raise
330
-
331
- path = os.environ["RELAY_SETTINGS_PATH"]
332
- snapshot_path = os.environ["RELAY_SNAPSHOT_PATH"]
333
- pairs = [p for p in os.environ.get("RELAY_ENV_PAIRS", "").split("\n") if p]
334
-
335
- try:
336
- data = load_json(path, {})
337
- except json.JSONDecodeError as e:
338
- print(f"settings.json is not valid JSON: {e}", file=sys.stderr)
339
- sys.exit(1)
340
- if not isinstance(data, dict):
341
- print("settings.json root is not a JSON object", file=sys.stderr)
342
- sys.exit(1)
343
-
344
- env = data.get("env")
345
- if not isinstance(env, dict):
346
- env = {}
347
-
348
- if not os.path.exists(snapshot_path):
349
- snapshot = {k: env.get(k) for k in MANAGED}
350
- atomic_write(snapshot_path, snapshot, 0o600)
351
-
352
- for k in MANAGED:
353
- env.pop(k, None)
354
- for pair in pairs:
355
- k, _, v = pair.partition("=")
356
- env[k] = v
357
- data["env"] = env
358
- atomic_write(path, data, 0o600)
359
- EOF
360
- }
361
- _settings_env_activate() { with_credential_lock _settings_env_activate_locked "$@"; }
362
-
363
- # Restore the snapshotted values/absences for the 4 managed keys (or clear
364
- # them if no snapshot exists, as a defensive fallback), then delete the
365
- # snapshot. Assumes the caller already holds the credential lock.
366
- _settings_env_restore_locked() {
367
- "${PY}" - "${CLAUDE_SETTINGS}" "${SETTINGS_ENV_SNAPSHOT}" <<'EOF'
368
- import json, os, sys, tempfile
369
-
370
- MANAGED = ["ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_MODEL", "CLAUDE_CODE_SUBAGENT_MODEL", "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"]
371
-
372
- def load_json(p, default):
373
- if not os.path.exists(p):
374
- return default
375
- raw = open(p).read().strip()
376
- return json.loads(raw) if raw else default
377
-
378
- def atomic_write(p, data, mode=0o600):
379
- d = os.path.dirname(p) or "."
380
- fd, tmp = tempfile.mkstemp(dir=d, prefix=".relay-tmp-")
381
- try:
382
- with os.fdopen(fd, "w") as f:
383
- json.dump(data, f, indent=2)
384
- f.write("\n")
385
- os.chmod(tmp, mode)
386
- os.replace(tmp, p)
387
- except Exception:
388
- try:
389
- os.unlink(tmp)
390
- except OSError:
391
- pass
392
- raise
393
-
394
- path, snapshot_path = sys.argv[1], sys.argv[2]
395
-
396
- try:
397
- data = load_json(path, {})
398
- except json.JSONDecodeError as e:
399
- print(f"settings.json is not valid JSON: {e}", file=sys.stderr)
400
- sys.exit(1)
401
- if not isinstance(data, dict):
402
- print("settings.json root is not a JSON object", file=sys.stderr)
403
- sys.exit(1)
404
-
405
- env = data.get("env")
406
- if not isinstance(env, dict):
407
- env = {}
408
-
409
- changed = False
410
- if os.path.exists(snapshot_path):
411
- snapshot = load_json(snapshot_path, {})
412
- for k in MANAGED:
413
- v = snapshot.get(k)
414
- if v is None:
415
- changed = env.pop(k, None) is not None or changed
416
- else:
417
- changed = env.get(k) != v or changed
418
- env[k] = v
419
- else:
420
- for k in MANAGED:
421
- if k in env:
422
- del env[k]
423
- changed = True
424
-
425
- if changed:
426
- data["env"] = env
427
- atomic_write(path, data, 0o600)
428
-
429
- if os.path.exists(snapshot_path):
430
- os.unlink(snapshot_path)
431
- EOF
302
+ require_codex() {
303
+ [[ -n "${REAL_CODEX}" ]] && return 0
304
+ err "codex not found install it with: npm i -g @openai/codex"
305
+ exit 1
432
306
  }
433
- _settings_env_restore() { with_credential_lock _settings_env_restore_locked; }
434
307
 
435
308
  # ══════════════════════════════════════════════════════════════════
436
309
  # Python core: parallel usage fetch + table rendering
@@ -687,11 +560,6 @@ _do_switch_locked() {
687
560
  local name="$1"
688
561
  local current; current=$(current_name)
689
562
 
690
- if [[ -n "$(active_provider_name)" ]]; then
691
- _settings_env_restore_locked
692
- rm -f "${ACTIVE_PROVIDER_FILE}"
693
- fi
694
-
695
563
  if [[ "${current}" == "${name}" ]]; then
696
564
  ok "Already on account '${B}${name}${R}'"
697
565
  return 0
@@ -731,16 +599,8 @@ _sync_current_creds() {
731
599
  [[ -n "${live}" ]] && printf '%s' "${live}" > "$(account_creds "${cur}")"
732
600
  }
733
601
 
734
- _provider_banner() {
735
- local active; active=$(active_provider_name)
736
- [[ -z "${active}" ]] && return 0
737
- local base_url; base_url=$(_provider_field "${active}" base_url)
738
- printf "\n ${YL}⚡ litellm:${R} ${B}%s${R} ${D}(%s)${R}\n" "${active}" "${base_url}"
739
- }
740
-
741
602
  cmd_quick() {
742
603
  _check_update_bg
743
- _provider_banner
744
604
  _sync_current_creds
745
605
  render_table quick "${CREDS_STORE}" "${META_STORE}" "$(current_name)" "$@"
746
606
  _show_update_notice
@@ -786,7 +646,6 @@ cmd_list() {
786
646
  }
787
647
 
788
648
  _cmd_status_once() {
789
- _provider_banner
790
649
  _sync_current_creds
791
650
  local current; current=$(current_name)
792
651
  hdr "Current Status"
@@ -966,7 +825,7 @@ PYEOF
966
825
 
967
826
  cmd_provider_add() {
968
827
  local name="${1:-}"
969
- [[ -z "${name}" ]] && { err "usage: relay provider add <name> --base-url <url> --token <token> [--model <model>] [--subagent-model <model>] [--no-discover-models]"; exit 1; }
828
+ [[ -z "${name}" ]] && { err "usage: relay provider add <name> --base-url <url> --token <token> [--model <model>] [--subagent-model <model>] [--no-discover-models] [--codex]"; exit 1; }
970
829
  shift
971
830
  case "${name}" in
972
831
  *[!a-zA-Z0-9_-]*) err "name must contain only letters, numbers, underscores, or hyphens"; exit 1 ;;
@@ -984,7 +843,7 @@ cmd_provider_add() {
984
843
  # Discovery defaults on: without a pinned --model there's nothing else to
985
844
  # route with, and with one it's still useful for `relay provider list` /
986
845
  # future switches, so opt-out (--no-discover-models) rather than opt-in.
987
- local base_url="" token="" model="" subagent_model="" discover=1
846
+ local base_url="" token="" model="" subagent_model="" discover=1 agent="claude"
988
847
  while [[ $# -gt 0 ]]; do
989
848
  case "$1" in
990
849
  --base-url) base_url="${2:-}"; shift 2 ;;
@@ -993,6 +852,7 @@ cmd_provider_add() {
993
852
  --subagent-model) subagent_model="${2:-}"; shift 2 ;;
994
853
  --discover-models) discover=1; shift ;;
995
854
  --no-discover-models) discover=0; shift ;;
855
+ --codex) agent="codex"; shift ;;
996
856
  *) err "unknown option: $1"; exit 1 ;;
997
857
  esac
998
858
  done
@@ -1004,12 +864,30 @@ cmd_provider_add() {
1004
864
  *) err "--base-url must start with http:// or https://"; exit 1 ;;
1005
865
  esac
1006
866
 
867
+ # Codex CLI has no gateway-model-discovery equivalent — it always needs an
868
+ # explicit model, and CLAUDE_CODE_SUBAGENT_MODEL is a Claude-Code-only concept.
869
+ if [[ "${agent}" == "codex" ]]; then
870
+ if [[ -z "${model}" ]]; then
871
+ err "--codex requires --model <model> — Codex CLI has no model-discovery fallback"
872
+ exit 1
873
+ fi
874
+ if [[ "${discover}" == "1" ]]; then
875
+ warn "--discover-models has no effect with --codex (no gateway model discovery in Codex) — ignoring"
876
+ discover=0
877
+ fi
878
+ if [[ -n "${subagent_model}" ]]; then
879
+ warn "--subagent-model has no effect with --codex (Claude-Code-only) — ignoring"
880
+ subagent_model=""
881
+ fi
882
+ fi
883
+
1007
884
  RELAY_PROVIDER_PATH="$(provider_file "${name}")" \
1008
885
  RELAY_PROVIDER_BASE_URL="${base_url}" \
1009
886
  RELAY_PROVIDER_TOKEN="${token}" \
1010
887
  RELAY_PROVIDER_MODEL="${model}" \
1011
888
  RELAY_PROVIDER_SUBAGENT_MODEL="${subagent_model}" \
1012
889
  RELAY_PROVIDER_DISCOVER="${discover}" \
890
+ RELAY_PROVIDER_AGENT="${agent}" \
1013
891
  "${PY}" -c '
1014
892
  import json, os
1015
893
  path = os.environ["RELAY_PROVIDER_PATH"]
@@ -1018,6 +896,7 @@ token = os.environ["RELAY_PROVIDER_TOKEN"]
1018
896
  model = os.environ.get("RELAY_PROVIDER_MODEL", "")
1019
897
  subagent_model = os.environ.get("RELAY_PROVIDER_SUBAGENT_MODEL", "")
1020
898
  discover = os.environ.get("RELAY_PROVIDER_DISCOVER", "")
899
+ agent = os.environ.get("RELAY_PROVIDER_AGENT", "")
1021
900
  d = {"base_url": base_url, "auth_token": token}
1022
901
  if model:
1023
902
  d["model"] = model
@@ -1025,6 +904,8 @@ if subagent_model:
1025
904
  d["subagent_model"] = subagent_model
1026
905
  if discover == "1":
1027
906
  d["discover_models"] = True
907
+ if agent == "codex":
908
+ d["agent"] = "codex"
1028
909
  with open(path, "w") as f:
1029
910
  json.dump(d, f)
1030
911
  os.chmod(path, 0o600)
@@ -1040,59 +921,21 @@ cmd_provider_list() {
1040
921
  warn "No providers configured — add one with: relay provider add <name> --base-url <url> --token <token>"
1041
922
  return 0
1042
923
  fi
1043
- local active; active=$(active_provider_name)
1044
- local name base_url model subagent_model info
924
+ local name base_url model subagent_model agent info
1045
925
  while IFS= read -r name; do
1046
926
  [[ -z "${name}" ]] && continue
1047
927
  base_url=$(_provider_field "${name}" base_url)
1048
928
  model=$(_provider_field "${name}" model)
1049
929
  subagent_model=$(_provider_field "${name}" subagent_model)
930
+ agent=$(_provider_field "${name}" agent)
1050
931
  info="${base_url}"
1051
932
  [[ -n "${model}" ]] && info="${info} model=${model}"
1052
933
  [[ -n "${subagent_model}" ]] && info="${info} subagent_model=${subagent_model}"
1053
- if [[ "${name}" == "${active}" ]]; then
1054
- printf " ${GR}${B}✓ %-16s${R} ${D}%s${R}\n" "${name}" "${info}"
1055
- else
1056
- printf " %-16s ${D}%s${R}\n" "${name}" "${info}"
1057
- fi
934
+ [[ "${agent}" == "codex" ]] && info="${info} agent=codex"
935
+ printf " %-16s ${D}%s${R}\n" "${name}" "${info}"
1058
936
  done <<< "${names}"
1059
937
  }
1060
938
 
1061
- cmd_provider_use() {
1062
- local name="${1:-}"
1063
- [[ -z "${name}" ]] && { err "usage: relay provider use <name>"; exit 1; }
1064
- provider_exists "${name}" || { err "Provider '${name}' not found"; exit 1; }
1065
-
1066
- local base_url token model subagent_model discover
1067
- base_url=$(_provider_field "${name}" base_url)
1068
- token=$(_provider_field "${name}" auth_token)
1069
- model=$(_provider_field "${name}" model)
1070
- subagent_model=$(_provider_field "${name}" subagent_model)
1071
- discover=$(_provider_discover "${name}")
1072
-
1073
- local pairs=("ANTHROPIC_BASE_URL=${base_url}" "ANTHROPIC_AUTH_TOKEN=${token}")
1074
- [[ -n "${model}" ]] && pairs+=("ANTHROPIC_MODEL=${model}")
1075
- [[ -n "${subagent_model}" ]] && pairs+=("CLAUDE_CODE_SUBAGENT_MODEL=${subagent_model}")
1076
- [[ -n "${discover}" ]] && pairs+=("CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1")
1077
-
1078
- _settings_env_activate "${pairs[@]}" || { err "Failed to update ${CLAUDE_SETTINGS}"; exit 1; }
1079
- printf '%s' "${name}" > "${ACTIVE_PROVIDER_FILE}"
1080
-
1081
- printf "\n ${GR}${B}⚡ provider active → %s${R} ${D}%s${R}\n" "${name}" "${base_url}"
1082
- printf " ${D}Active sessions pick up the switch on next message. New session: ${CY}claude -c${R}\n\n"
1083
- }
1084
-
1085
- cmd_provider_off() {
1086
- local active; active=$(active_provider_name)
1087
- if [[ -z "${active}" ]]; then
1088
- warn "No provider is currently active"
1089
- return 0
1090
- fi
1091
- _settings_env_restore || { err "Failed to update ${CLAUDE_SETTINGS}"; exit 1; }
1092
- rm -f "${ACTIVE_PROVIDER_FILE}"
1093
- ok "Provider mode off — subscription account resumes"
1094
- }
1095
-
1096
939
  cmd_provider_remove() {
1097
940
  local name="${1:-}"
1098
941
  [[ -z "${name}" ]] && { err "usage: relay provider remove <name>"; exit 1; }
@@ -1100,9 +943,6 @@ cmd_provider_remove() {
1100
943
  printf "\n ${YL}Delete provider '${B}${name}${R}${YL}'? (y/N) ${R}"
1101
944
  read -r c
1102
945
  [[ "${c}" = "y" || "${c}" = "Y" ]] || { log "cancelled"; return 0; }
1103
- if [[ "$(active_provider_name)" == "${name}" ]]; then
1104
- cmd_provider_off
1105
- fi
1106
946
  rm -f "$(provider_file "${name}")"
1107
947
  ok "Deleted provider '${name}'"
1108
948
  }
@@ -1127,9 +967,7 @@ _run_last_name_for_cwd() {
1127
967
 
1128
968
  cmd_run() {
1129
969
  local name="${1:-}"
1130
- [[ -z "${name}" ]] && { err "usage: relay run <name> [-- <claude args...>]"; exit 1; }
1131
-
1132
- require_claude
970
+ [[ -z "${name}" ]] && { err "usage: relay run <name> [--codex|--claude] [-- <args...>]"; exit 1; }
1133
971
 
1134
972
  case "${name}" in
1135
973
  -c|--continue|-r|--resume)
@@ -1140,15 +978,30 @@ cmd_run() {
1140
978
  ;;
1141
979
  *)
1142
980
  shift
1143
- [[ "${1:-}" == "--" ]] && shift
1144
981
  ;;
1145
982
  esac
1146
983
 
984
+ local agent_override=""
985
+ case "${1:-}" in
986
+ --codex) agent_override="codex"; shift ;;
987
+ --claude) agent_override="claude"; shift ;;
988
+ esac
989
+ [[ "${1:-}" == "--" ]] && shift
990
+
1147
991
  if account_exists "${name}"; then
992
+ if [[ "${agent_override}" == "codex" ]]; then
993
+ err "'${name}' is an account (Claude OAuth credential) — --codex is not valid for accounts"
994
+ exit 1
995
+ fi
996
+ require_claude
1148
997
  _run_record_last "${name}"
1149
998
  do_switch "${name}"
1150
999
  exec "${REAL_CLAUDE}" "$@"
1151
1000
  elif provider_exists "${name}"; then
1001
+ local agent="${agent_override}"
1002
+ [[ -z "${agent}" ]] && agent=$(_provider_field "${name}" agent)
1003
+ [[ -z "${agent}" ]] && agent="claude"
1004
+
1152
1005
  _run_record_last "${name}"
1153
1006
  local base_url token model subagent_model discover settings_file
1154
1007
  base_url=$(_provider_field "${name}" base_url)
@@ -1157,6 +1010,28 @@ cmd_run() {
1157
1010
  subagent_model=$(_provider_field "${name}" subagent_model)
1158
1011
  discover=$(_provider_discover "${name}")
1159
1012
 
1013
+ if [[ "${agent}" == "codex" ]]; then
1014
+ require_codex
1015
+ [[ -z "${model}" ]] && { err "Provider '${name}' has no --model set — Codex requires an explicit model (no discovery fallback)"; exit 1; }
1016
+
1017
+ local base_url_toml model_toml
1018
+ base_url_toml=$(_toml_str "${base_url}")
1019
+ model_toml=$(_toml_str "${model}")
1020
+
1021
+ local codex_args=(
1022
+ -c model_provider="relay"
1023
+ -c model_providers.relay.name="relay"
1024
+ -c "model_providers.relay.base_url=${base_url_toml}"
1025
+ -c model_providers.relay.env_key="RELAY_CODEX_API_KEY"
1026
+ -c model_providers.relay.wire_api="responses"
1027
+ -c model_providers.relay.stream_idle_timeout_ms=600000
1028
+ -c "model=${model_toml}"
1029
+ )
1030
+ RELAY_CODEX_API_KEY="${token}" exec "${REAL_CODEX}" "${codex_args[@]}" "$@"
1031
+ fi
1032
+
1033
+ require_claude
1034
+
1160
1035
  # Opportunistic cleanup: a prior `relay run <provider>` can't clean up
1161
1036
  # after itself (exec replaces the process, so no trap ever fires) —
1162
1037
  # sweep anything old enough that the session which created it has
@@ -2806,8 +2681,9 @@ cmd_help() {
2806
2681
  printf " ${B}One-off runs${R}\n"
2807
2682
  printf " %-42s %s\n" " relay run <name>" "one-off session on an account or provider, then exec claude"
2808
2683
  printf " %-42s %s\n" " relay run <name> -- <args>" "forward extra args to claude itself, e.g. -- --resume"
2684
+ printf " %-42s %s\n" " relay run <name> --codex" "force Codex CLI for this run, regardless of the provider's stored agent"
2685
+ printf " %-42s %s\n" " relay run <name> --claude" "force Claude Code for this run (override a provider's stored --codex)"
2809
2686
  printf " %-42s %s\n" " relay run -c|--resume" "replay last account/provider used by 'relay run' in this dir"
2810
- printf " %-42s %s\n" " (leaves global switch/provider state untouched — safe for concurrent sessions)"
2811
2687
  echo ""
2812
2688
 
2813
2689
  printf " ${B}LiteLLM providers${R}\n"
@@ -2818,9 +2694,9 @@ cmd_help() {
2818
2694
  printf " %-42s %s\n" " --subagent-model <model>" "optional: pin CLAUDE_CODE_SUBAGENT_MODEL"
2819
2695
  printf " %-42s %s\n" " " "(overrides every subagent's own model: frontmatter)"
2820
2696
  printf " %-42s %s\n" " --no-discover-models" "opt out: gateway model discovery is on by default"
2697
+ printf " %-42s %s\n" " --codex" "target OpenAI's Codex CLI instead of Claude Code for this provider"
2698
+ printf " %-42s %s\n" " " "(requires --model; --discover-models / --subagent-model don't apply)"
2821
2699
  printf " %-42s %s\n" " relay provider list" "list configured providers + their settings"
2822
- printf " %-42s %s\n" " relay provider use <name>" "route all future sessions through this provider"
2823
- printf " %-42s %s\n" " relay provider off" "stop routing through a provider, resume subscription account"
2824
2700
  printf " %-42s %s\n" " relay provider remove <name>" "delete a provider (prompts for confirmation)"
2825
2701
  echo ""
2826
2702
 
@@ -2901,10 +2777,8 @@ case "${CMD}" in
2901
2777
  case "${sub}" in
2902
2778
  add) cmd_provider_add "$@" ;;
2903
2779
  list|ls) cmd_provider_list ;;
2904
- use) cmd_provider_use "$@" ;;
2905
- off) cmd_provider_off ;;
2906
2780
  remove|rm) cmd_provider_remove "$@" ;;
2907
- *) err "usage: relay provider <add|list|use|off|remove> ..."; exit 1 ;;
2781
+ *) err "usage: relay provider <add|list|remove> ..."; exit 1 ;;
2908
2782
  esac ;;
2909
2783
  run) cmd_run "$@" ;;
2910
2784
  sessions|sess) cmd_sessions ;;