@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.
- package/README.md +5 -0
- package/package.json +2 -2
- 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.
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
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
|
-
|
|
1054
|
-
|
|
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> [-- <
|
|
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|
|
|
2781
|
+
*) err "usage: relay provider <add|list|remove> ..."; exit 1 ;;
|
|
2908
2782
|
esac ;;
|
|
2909
2783
|
run) cmd_run "$@" ;;
|
|
2910
2784
|
sessions|sess) cmd_sessions ;;
|