@dst-justin/relay 2.6.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 +8 -0
- package/package.json +2 -2
- package/relay +90 -212
package/README.md
CHANGED
|
@@ -288,6 +288,14 @@ 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
|
+
|
|
296
|
+
### v2.7.0 — 2026-09-04
|
|
297
|
+
- `relay provider add` now turns on gateway model discovery by default (previously required `--discover-models`); opt out with `--no-discover-models`.
|
|
298
|
+
|
|
291
299
|
### v2.6.0 — 2026-09-04
|
|
292
300
|
- Add `relay run -c`/`--continue`/`-r`/`--resume` — replays the last account/provider `relay run` used in the current directory, so a crashed or Ctrl-C'd one-off provider session can resume without retyping the provider name.
|
|
293
301
|
|
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>] [--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 ;;
|
|
@@ -981,7 +840,10 @@ cmd_provider_add() {
|
|
|
981
840
|
return 0
|
|
982
841
|
fi
|
|
983
842
|
|
|
984
|
-
|
|
843
|
+
# Discovery defaults on: without a pinned --model there's nothing else to
|
|
844
|
+
# route with, and with one it's still useful for `relay provider list` /
|
|
845
|
+
# future switches, so opt-out (--no-discover-models) rather than opt-in.
|
|
846
|
+
local base_url="" token="" model="" subagent_model="" discover=1 agent="claude"
|
|
985
847
|
while [[ $# -gt 0 ]]; do
|
|
986
848
|
case "$1" in
|
|
987
849
|
--base-url) base_url="${2:-}"; shift 2 ;;
|
|
@@ -989,6 +851,8 @@ cmd_provider_add() {
|
|
|
989
851
|
--model) model="${2:-}"; shift 2 ;;
|
|
990
852
|
--subagent-model) subagent_model="${2:-}"; shift 2 ;;
|
|
991
853
|
--discover-models) discover=1; shift ;;
|
|
854
|
+
--no-discover-models) discover=0; shift ;;
|
|
855
|
+
--codex) agent="codex"; shift ;;
|
|
992
856
|
*) err "unknown option: $1"; exit 1 ;;
|
|
993
857
|
esac
|
|
994
858
|
done
|
|
@@ -1000,12 +864,30 @@ cmd_provider_add() {
|
|
|
1000
864
|
*) err "--base-url must start with http:// or https://"; exit 1 ;;
|
|
1001
865
|
esac
|
|
1002
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
|
+
|
|
1003
884
|
RELAY_PROVIDER_PATH="$(provider_file "${name}")" \
|
|
1004
885
|
RELAY_PROVIDER_BASE_URL="${base_url}" \
|
|
1005
886
|
RELAY_PROVIDER_TOKEN="${token}" \
|
|
1006
887
|
RELAY_PROVIDER_MODEL="${model}" \
|
|
1007
888
|
RELAY_PROVIDER_SUBAGENT_MODEL="${subagent_model}" \
|
|
1008
889
|
RELAY_PROVIDER_DISCOVER="${discover}" \
|
|
890
|
+
RELAY_PROVIDER_AGENT="${agent}" \
|
|
1009
891
|
"${PY}" -c '
|
|
1010
892
|
import json, os
|
|
1011
893
|
path = os.environ["RELAY_PROVIDER_PATH"]
|
|
@@ -1014,6 +896,7 @@ token = os.environ["RELAY_PROVIDER_TOKEN"]
|
|
|
1014
896
|
model = os.environ.get("RELAY_PROVIDER_MODEL", "")
|
|
1015
897
|
subagent_model = os.environ.get("RELAY_PROVIDER_SUBAGENT_MODEL", "")
|
|
1016
898
|
discover = os.environ.get("RELAY_PROVIDER_DISCOVER", "")
|
|
899
|
+
agent = os.environ.get("RELAY_PROVIDER_AGENT", "")
|
|
1017
900
|
d = {"base_url": base_url, "auth_token": token}
|
|
1018
901
|
if model:
|
|
1019
902
|
d["model"] = model
|
|
@@ -1021,6 +904,8 @@ if subagent_model:
|
|
|
1021
904
|
d["subagent_model"] = subagent_model
|
|
1022
905
|
if discover == "1":
|
|
1023
906
|
d["discover_models"] = True
|
|
907
|
+
if agent == "codex":
|
|
908
|
+
d["agent"] = "codex"
|
|
1024
909
|
with open(path, "w") as f:
|
|
1025
910
|
json.dump(d, f)
|
|
1026
911
|
os.chmod(path, 0o600)
|
|
@@ -1036,59 +921,21 @@ cmd_provider_list() {
|
|
|
1036
921
|
warn "No providers configured — add one with: relay provider add <name> --base-url <url> --token <token>"
|
|
1037
922
|
return 0
|
|
1038
923
|
fi
|
|
1039
|
-
local
|
|
1040
|
-
local name base_url model subagent_model info
|
|
924
|
+
local name base_url model subagent_model agent info
|
|
1041
925
|
while IFS= read -r name; do
|
|
1042
926
|
[[ -z "${name}" ]] && continue
|
|
1043
927
|
base_url=$(_provider_field "${name}" base_url)
|
|
1044
928
|
model=$(_provider_field "${name}" model)
|
|
1045
929
|
subagent_model=$(_provider_field "${name}" subagent_model)
|
|
930
|
+
agent=$(_provider_field "${name}" agent)
|
|
1046
931
|
info="${base_url}"
|
|
1047
932
|
[[ -n "${model}" ]] && info="${info} model=${model}"
|
|
1048
933
|
[[ -n "${subagent_model}" ]] && info="${info} subagent_model=${subagent_model}"
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
else
|
|
1052
|
-
printf " %-16s ${D}%s${R}\n" "${name}" "${info}"
|
|
1053
|
-
fi
|
|
934
|
+
[[ "${agent}" == "codex" ]] && info="${info} agent=codex"
|
|
935
|
+
printf " %-16s ${D}%s${R}\n" "${name}" "${info}"
|
|
1054
936
|
done <<< "${names}"
|
|
1055
937
|
}
|
|
1056
938
|
|
|
1057
|
-
cmd_provider_use() {
|
|
1058
|
-
local name="${1:-}"
|
|
1059
|
-
[[ -z "${name}" ]] && { err "usage: relay provider use <name>"; exit 1; }
|
|
1060
|
-
provider_exists "${name}" || { err "Provider '${name}' not found"; exit 1; }
|
|
1061
|
-
|
|
1062
|
-
local base_url token model subagent_model discover
|
|
1063
|
-
base_url=$(_provider_field "${name}" base_url)
|
|
1064
|
-
token=$(_provider_field "${name}" auth_token)
|
|
1065
|
-
model=$(_provider_field "${name}" model)
|
|
1066
|
-
subagent_model=$(_provider_field "${name}" subagent_model)
|
|
1067
|
-
discover=$(_provider_discover "${name}")
|
|
1068
|
-
|
|
1069
|
-
local pairs=("ANTHROPIC_BASE_URL=${base_url}" "ANTHROPIC_AUTH_TOKEN=${token}")
|
|
1070
|
-
[[ -n "${model}" ]] && pairs+=("ANTHROPIC_MODEL=${model}")
|
|
1071
|
-
[[ -n "${subagent_model}" ]] && pairs+=("CLAUDE_CODE_SUBAGENT_MODEL=${subagent_model}")
|
|
1072
|
-
[[ -n "${discover}" ]] && pairs+=("CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1")
|
|
1073
|
-
|
|
1074
|
-
_settings_env_activate "${pairs[@]}" || { err "Failed to update ${CLAUDE_SETTINGS}"; exit 1; }
|
|
1075
|
-
printf '%s' "${name}" > "${ACTIVE_PROVIDER_FILE}"
|
|
1076
|
-
|
|
1077
|
-
printf "\n ${GR}${B}⚡ provider active → %s${R} ${D}%s${R}\n" "${name}" "${base_url}"
|
|
1078
|
-
printf " ${D}Active sessions pick up the switch on next message. New session: ${CY}claude -c${R}\n\n"
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
cmd_provider_off() {
|
|
1082
|
-
local active; active=$(active_provider_name)
|
|
1083
|
-
if [[ -z "${active}" ]]; then
|
|
1084
|
-
warn "No provider is currently active"
|
|
1085
|
-
return 0
|
|
1086
|
-
fi
|
|
1087
|
-
_settings_env_restore || { err "Failed to update ${CLAUDE_SETTINGS}"; exit 1; }
|
|
1088
|
-
rm -f "${ACTIVE_PROVIDER_FILE}"
|
|
1089
|
-
ok "Provider mode off — subscription account resumes"
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
939
|
cmd_provider_remove() {
|
|
1093
940
|
local name="${1:-}"
|
|
1094
941
|
[[ -z "${name}" ]] && { err "usage: relay provider remove <name>"; exit 1; }
|
|
@@ -1096,9 +943,6 @@ cmd_provider_remove() {
|
|
|
1096
943
|
printf "\n ${YL}Delete provider '${B}${name}${R}${YL}'? (y/N) ${R}"
|
|
1097
944
|
read -r c
|
|
1098
945
|
[[ "${c}" = "y" || "${c}" = "Y" ]] || { log "cancelled"; return 0; }
|
|
1099
|
-
if [[ "$(active_provider_name)" == "${name}" ]]; then
|
|
1100
|
-
cmd_provider_off
|
|
1101
|
-
fi
|
|
1102
946
|
rm -f "$(provider_file "${name}")"
|
|
1103
947
|
ok "Deleted provider '${name}'"
|
|
1104
948
|
}
|
|
@@ -1123,9 +967,7 @@ _run_last_name_for_cwd() {
|
|
|
1123
967
|
|
|
1124
968
|
cmd_run() {
|
|
1125
969
|
local name="${1:-}"
|
|
1126
|
-
[[ -z "${name}" ]] && { err "usage: relay run <name> [-- <
|
|
1127
|
-
|
|
1128
|
-
require_claude
|
|
970
|
+
[[ -z "${name}" ]] && { err "usage: relay run <name> [--codex|--claude] [-- <args...>]"; exit 1; }
|
|
1129
971
|
|
|
1130
972
|
case "${name}" in
|
|
1131
973
|
-c|--continue|-r|--resume)
|
|
@@ -1136,15 +978,30 @@ cmd_run() {
|
|
|
1136
978
|
;;
|
|
1137
979
|
*)
|
|
1138
980
|
shift
|
|
1139
|
-
[[ "${1:-}" == "--" ]] && shift
|
|
1140
981
|
;;
|
|
1141
982
|
esac
|
|
1142
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
|
+
|
|
1143
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
|
|
1144
997
|
_run_record_last "${name}"
|
|
1145
998
|
do_switch "${name}"
|
|
1146
999
|
exec "${REAL_CLAUDE}" "$@"
|
|
1147
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
|
+
|
|
1148
1005
|
_run_record_last "${name}"
|
|
1149
1006
|
local base_url token model subagent_model discover settings_file
|
|
1150
1007
|
base_url=$(_provider_field "${name}" base_url)
|
|
@@ -1153,6 +1010,28 @@ cmd_run() {
|
|
|
1153
1010
|
subagent_model=$(_provider_field "${name}" subagent_model)
|
|
1154
1011
|
discover=$(_provider_discover "${name}")
|
|
1155
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
|
+
|
|
1156
1035
|
# Opportunistic cleanup: a prior `relay run <provider>` can't clean up
|
|
1157
1036
|
# after itself (exec replaces the process, so no trap ever fires) —
|
|
1158
1037
|
# sweep anything old enough that the session which created it has
|
|
@@ -2802,8 +2681,9 @@ cmd_help() {
|
|
|
2802
2681
|
printf " ${B}One-off runs${R}\n"
|
|
2803
2682
|
printf " %-42s %s\n" " relay run <name>" "one-off session on an account or provider, then exec claude"
|
|
2804
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)"
|
|
2805
2686
|
printf " %-42s %s\n" " relay run -c|--resume" "replay last account/provider used by 'relay run' in this dir"
|
|
2806
|
-
printf " %-42s %s\n" " (leaves global switch/provider state untouched — safe for concurrent sessions)"
|
|
2807
2687
|
echo ""
|
|
2808
2688
|
|
|
2809
2689
|
printf " ${B}LiteLLM providers${R}\n"
|
|
@@ -2813,10 +2693,10 @@ cmd_help() {
|
|
|
2813
2693
|
printf " %-42s %s\n" " --model <model>" "optional: pin ANTHROPIC_MODEL to this alias"
|
|
2814
2694
|
printf " %-42s %s\n" " --subagent-model <model>" "optional: pin CLAUDE_CODE_SUBAGENT_MODEL"
|
|
2815
2695
|
printf " %-42s %s\n" " " "(overrides every subagent's own model: frontmatter)"
|
|
2816
|
-
printf " %-42s %s\n" " --discover-models"
|
|
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)"
|
|
2817
2699
|
printf " %-42s %s\n" " relay provider list" "list configured providers + their settings"
|
|
2818
|
-
printf " %-42s %s\n" " relay provider use <name>" "route all future sessions through this provider"
|
|
2819
|
-
printf " %-42s %s\n" " relay provider off" "stop routing through a provider, resume subscription account"
|
|
2820
2700
|
printf " %-42s %s\n" " relay provider remove <name>" "delete a provider (prompts for confirmation)"
|
|
2821
2701
|
echo ""
|
|
2822
2702
|
|
|
@@ -2897,10 +2777,8 @@ case "${CMD}" in
|
|
|
2897
2777
|
case "${sub}" in
|
|
2898
2778
|
add) cmd_provider_add "$@" ;;
|
|
2899
2779
|
list|ls) cmd_provider_list ;;
|
|
2900
|
-
use) cmd_provider_use "$@" ;;
|
|
2901
|
-
off) cmd_provider_off ;;
|
|
2902
2780
|
remove|rm) cmd_provider_remove "$@" ;;
|
|
2903
|
-
*) err "usage: relay provider <add|list|
|
|
2781
|
+
*) err "usage: relay provider <add|list|remove> ..."; exit 1 ;;
|
|
2904
2782
|
esac ;;
|
|
2905
2783
|
run) cmd_run "$@" ;;
|
|
2906
2784
|
sessions|sess) cmd_sessions ;;
|