ai-switch-cli 0.2.2__tar.gz → 0.3.0__tar.gz
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.
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/PKG-INFO +3 -3
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/README.md +2 -2
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/ai_switch.py +113 -37
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/ai_switch_cli.egg-info/PKG-INFO +3 -3
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/pyproject.toml +1 -1
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/ai_switch_cli.egg-info/SOURCES.txt +0 -0
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/ai_switch_cli.egg-info/dependency_links.txt +0 -0
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/ai_switch_cli.egg-info/entry_points.txt +0 -0
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/ai_switch_cli.egg-info/top_level.txt +0 -0
- {ai_switch_cli-0.2.2 → ai_switch_cli-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-switch-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: One-command provider and model switching for Claude Code and Codex, with session-history checks
|
|
5
5
|
Author: ZidongS
|
|
6
6
|
License: MIT
|
|
@@ -133,7 +133,7 @@ For every model a profile records the Codex catalogue entry and the Claude Code
|
|
|
133
133
|
* Codex: the top-level `model` (and `model_reasoning_effort`), plus a `~/.codex/models.json` catalogue that lists **every** model of the profile, which is what makes Codex's own `/model` picker show them.
|
|
134
134
|
* Claude Code: the default model in `settings.json` plus the Opus/Sonnet/Haiku mappings. The presets map the three categories to *different* provider models where they exist, and they never set `ANTHROPIC_MODEL`, because a pinned environment model overrides your selection and makes `/model` do nothing.
|
|
135
135
|
|
|
136
|
-
The `glm` and `deepseek` presets ship multi-model lists. A custom (OpenAI-compatible) profile is open-ended: it
|
|
136
|
+
The `glm` and `deepseek` presets ship multi-model lists. A custom (OpenAI-compatible) profile is open-ended as well: when you create it you can name **several models at once, separated by spaces or commas** (for example `DeepSeek-V4.1-Flash Kimi-K3 Qwen3.8-Max`), and those become the profile's catalogue — both agents can then pick them. Any other name the endpoint serves still works with `ai-switch use NAME --model <name>`, and it is added to the catalogue when you use it.
|
|
137
137
|
|
|
138
138
|
### When a provider adds or renames a model
|
|
139
139
|
|
|
@@ -180,7 +180,7 @@ ai-switch doctor --fix # quarantine damaged Codex runtime databases
|
|
|
180
180
|
export CODEX_SQLITE_HOME=/var/tmp/codex-sqlite-$USER # a local, persistent directory
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
`
|
|
183
|
+
Codex only honours the **environment variable**: a `sqlite_home` key in `config.toml` is parsed but ignored (checked with `codex doctor`). `ai-switch add` asks for a local directory when it detects a network filesystem, creates it and prints the export line to put in your shell profile; `ai-switch doctor` reports the variable's status.
|
|
184
184
|
* **A stale model catalogue.** A catalogue left behind by the previous provider hides the new provider's models from Codex and makes sessions that used them unresumable. ai-switch removes that leftover file (only if it wrote it itself, and it backs it up first), and `doctor` verifies the configured model exists in the catalogue.
|
|
185
185
|
* **Disabled history persistence** (`[history] persistence = "none"`), a Claude Code model pinned by `ANTHROPIC_MODEL`, and `claude`/`codex` processes that are running while you switch and will rewrite their configuration on exit.
|
|
186
186
|
|
|
@@ -119,7 +119,7 @@ For every model a profile records the Codex catalogue entry and the Claude Code
|
|
|
119
119
|
* Codex: the top-level `model` (and `model_reasoning_effort`), plus a `~/.codex/models.json` catalogue that lists **every** model of the profile, which is what makes Codex's own `/model` picker show them.
|
|
120
120
|
* Claude Code: the default model in `settings.json` plus the Opus/Sonnet/Haiku mappings. The presets map the three categories to *different* provider models where they exist, and they never set `ANTHROPIC_MODEL`, because a pinned environment model overrides your selection and makes `/model` do nothing.
|
|
121
121
|
|
|
122
|
-
The `glm` and `deepseek` presets ship multi-model lists. A custom (OpenAI-compatible) profile is open-ended: it
|
|
122
|
+
The `glm` and `deepseek` presets ship multi-model lists. A custom (OpenAI-compatible) profile is open-ended as well: when you create it you can name **several models at once, separated by spaces or commas** (for example `DeepSeek-V4.1-Flash Kimi-K3 Qwen3.8-Max`), and those become the profile's catalogue — both agents can then pick them. Any other name the endpoint serves still works with `ai-switch use NAME --model <name>`, and it is added to the catalogue when you use it.
|
|
123
123
|
|
|
124
124
|
### When a provider adds or renames a model
|
|
125
125
|
|
|
@@ -166,7 +166,7 @@ ai-switch doctor --fix # quarantine damaged Codex runtime databases
|
|
|
166
166
|
export CODEX_SQLITE_HOME=/var/tmp/codex-sqlite-$USER # a local, persistent directory
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
`
|
|
169
|
+
Codex only honours the **environment variable**: a `sqlite_home` key in `config.toml` is parsed but ignored (checked with `codex doctor`). `ai-switch add` asks for a local directory when it detects a network filesystem, creates it and prints the export line to put in your shell profile; `ai-switch doctor` reports the variable's status.
|
|
170
170
|
* **A stale model catalogue.** A catalogue left behind by the previous provider hides the new provider's models from Codex and makes sessions that used them unresumable. ai-switch removes that leftover file (only if it wrote it itself, and it backs it up first), and `doctor` verifies the configured model exists in the catalogue.
|
|
171
171
|
* **Disabled history persistence** (`[history] persistence = "none"`), a Claude Code model pinned by `ANTHROPIC_MODEL`, and `claude`/`codex` processes that are running while you switch and will rewrite their configuration on exit.
|
|
172
172
|
|
|
@@ -23,7 +23,7 @@ Conversation history is never part of a profile
|
|
|
23
23
|
import argparse, hashlib, json, os, re, shutil, sqlite3, sys, tempfile, time
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
|
-
VERSION = "0.
|
|
26
|
+
VERSION = "0.3.0"
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _env_path(name, default):
|
|
@@ -267,8 +267,9 @@ def build_claude_settings(live, profile_settings, entry):
|
|
|
267
267
|
env = {k: v for k, v in live_env.items() if k not in CLAUDE_PROVIDER_ENV}
|
|
268
268
|
env.update({k: v for k, v in prof_env.items() if k not in CLAUDE_MODEL_PINS})
|
|
269
269
|
if entry:
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
mapped = entry.get("claude") if isinstance(entry.get("claude"), dict) else {}
|
|
271
|
+
env.update(mapped.get("env") or {})
|
|
272
|
+
out["model"] = mapped.get("model") or entry["slug"]
|
|
272
273
|
elif "model" in profile_settings:
|
|
273
274
|
out["model"] = profile_settings["model"]
|
|
274
275
|
out["env"] = env
|
|
@@ -322,6 +323,22 @@ def claude_mapping(model, opus=None, sonnet=None, haiku=None, subagent=None):
|
|
|
322
323
|
return {"model": model, "env": env}
|
|
323
324
|
|
|
324
325
|
|
|
326
|
+
def adhoc_model(slug, index=99):
|
|
327
|
+
"""Model entry for an endpoint whose catalogue we do not know (custom provider).
|
|
328
|
+
|
|
329
|
+
Every Claude Code category points at the same model: a gateway usually serves
|
|
330
|
+
one namespace, so Claude's own alias names (``claude-sonnet-*``) would be
|
|
331
|
+
rejected by it.
|
|
332
|
+
"""
|
|
333
|
+
return {"slug": slug, "label": "custom model", "reasoning": "",
|
|
334
|
+
"codex": codex_entry(slug, "custom model", ("text",), index, 1048576, "high"),
|
|
335
|
+
"claude": claude_mapping(slug, opus=slug, sonnet=slug, haiku=slug, subagent=slug)}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def split_models(text):
|
|
339
|
+
return [part for part in re.split(r"[,\s]+", text or "") if part]
|
|
340
|
+
|
|
341
|
+
|
|
325
342
|
GLM_MODELS = [
|
|
326
343
|
# Model names are exactly what the ZAI endpoint accepts; the ``[1m]`` marker
|
|
327
344
|
# some proxies use is rejected there (glm-5.3 is 1M context by default), so
|
|
@@ -530,11 +547,17 @@ def compute_changes(d, slug):
|
|
|
530
547
|
|
|
531
548
|
text = read_text(d / "codex-config.toml")
|
|
532
549
|
codex_catalog = None
|
|
550
|
+
# A model that is used but not listed (open-ended custom endpoint, or a legacy
|
|
551
|
+
# profile without a model list) gets a generated entry so that both agents are
|
|
552
|
+
# pointed at the requested model instead of falling back to the snapshot.
|
|
553
|
+
if slug and entry is None:
|
|
554
|
+
entry = adhoc_model(slug)
|
|
555
|
+
if catalog and catalog.get("open_ended"):
|
|
556
|
+
catalog = dict(catalog, models=catalog["models"] + [entry])
|
|
533
557
|
if text is not None:
|
|
534
|
-
#
|
|
535
|
-
#
|
|
536
|
-
|
|
537
|
-
if catalog and (not catalog.get("open_ended") or (d / "codex-models.json").exists()):
|
|
558
|
+
# Every profile publishes its models, so the picker inside Codex lists
|
|
559
|
+
# them too.
|
|
560
|
+
if catalog:
|
|
538
561
|
codex_catalog = build_codex_catalog(d, catalog)
|
|
539
562
|
if codex_catalog is not None:
|
|
540
563
|
catalog_path = catalog_target(text, CODEX_MODELS)
|
|
@@ -657,9 +680,8 @@ def codex_health_warnings(check_codex=True):
|
|
|
657
680
|
if status != "ok":
|
|
658
681
|
warnings.append(f"Codex runtime DB {db.name} is unusable ({detail}) - sessions may be missing "
|
|
659
682
|
"from the resume picker; run 'ai-switch doctor --fix'")
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
warnings.append("running now: " + ", ".join(running) +
|
|
683
|
+
if running_agents():
|
|
684
|
+
warnings.append("running now: " + running_agents_summary() +
|
|
663
685
|
" - restart them after switching, a running agent rewrites its config on exit")
|
|
664
686
|
return warnings
|
|
665
687
|
|
|
@@ -912,15 +934,17 @@ def cmd_add(_):
|
|
|
912
934
|
if preset:
|
|
913
935
|
chosen = preset["models"]
|
|
914
936
|
print(f"{provider} preset offers: " + ", ".join(m["slug"] for m in chosen))
|
|
915
|
-
wanted = _ask("Models to include (comma separated, Enter=all)", "all")
|
|
937
|
+
wanted = _ask("Models to include (space or comma separated, Enter=all)", "all")
|
|
916
938
|
if wanted.lower() not in ("all", ""):
|
|
917
|
-
names =
|
|
939
|
+
names = split_models(wanted)
|
|
918
940
|
by_slug = {m["slug"]: m for m in chosen}
|
|
919
941
|
chosen = []
|
|
920
942
|
for token in names:
|
|
921
|
-
|
|
943
|
+
exact = [s for s in by_slug if s.lower() == token.lower()]
|
|
944
|
+
slug = exact or [s for s in by_slug if s.lower().startswith(token.lower())]
|
|
922
945
|
if len(slug) != 1:
|
|
923
|
-
raise ValueError(f"unknown model '{token}'"
|
|
946
|
+
raise ValueError(f"unknown or ambiguous model '{token}'"
|
|
947
|
+
f" (choose one of: {', '.join(by_slug)})")
|
|
924
948
|
chosen.append(by_slug[slug[0]])
|
|
925
949
|
default = _ask("Default model", chosen[0]["slug"])
|
|
926
950
|
default = resolve_model({"models": chosen}, default)
|
|
@@ -931,13 +955,21 @@ def cmd_add(_):
|
|
|
931
955
|
if not endpoint:
|
|
932
956
|
raise ValueError("an endpoint URL is required")
|
|
933
957
|
claude_endpoint = endpoint[:-3].rstrip("/") if endpoint.endswith("/v1") else endpoint.rstrip("/")
|
|
934
|
-
|
|
935
|
-
if not
|
|
936
|
-
raise ValueError("
|
|
937
|
-
chosen = [
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
958
|
+
names = split_models(_ask("Model name(s) (space or comma separated, e.g. 'Big-Model Small-Model')"))
|
|
959
|
+
if not names:
|
|
960
|
+
raise ValueError("at least one model name is required")
|
|
961
|
+
chosen = []
|
|
962
|
+
for index, name in enumerate(names):
|
|
963
|
+
model = adhoc_model(name, index)
|
|
964
|
+
if len(names) > 1:
|
|
965
|
+
model["label"] = f"custom model (use --model {name})"
|
|
966
|
+
chosen.append(model)
|
|
967
|
+
default = chosen[0]["slug"]
|
|
968
|
+
if len(chosen) > 1:
|
|
969
|
+
print("This endpoint is open ended: any name works with "
|
|
970
|
+
f"'ai-switch use <profile> --model <name>', and the ones listed above appear in the picker.")
|
|
971
|
+
default = resolve_model({"models": chosen}, _ask("Default model", chosen[0]["slug"]))
|
|
972
|
+
common_env = {}
|
|
941
973
|
name = _ask("Profile name")
|
|
942
974
|
if not name:
|
|
943
975
|
raise ValueError("a profile name is required")
|
|
@@ -950,7 +982,7 @@ def cmd_add(_):
|
|
|
950
982
|
suggested = f"/var/tmp/codex-sqlite-{os.environ.get('USER', 'user')}"
|
|
951
983
|
print(f"\n{CODEX_DIR} is on a network filesystem ({fstype_for(CODEX_DIR)}); SQLite runtime databases "
|
|
952
984
|
"can be corrupted there, which makes sessions disappear.")
|
|
953
|
-
sqlite_home = _ask("Local directory for Codex runtime databases (Enter=
|
|
985
|
+
sqlite_home = _ask("Local directory for Codex runtime databases (Enter=skip)", suggested)
|
|
954
986
|
d = profile(name)
|
|
955
987
|
blank_profile_dir(d)
|
|
956
988
|
catalog = {"version": 2, "provider": provider, "default": default, "models": chosen,
|
|
@@ -965,17 +997,15 @@ def cmd_add(_):
|
|
|
965
997
|
.replace("@CATALOG@", display_path(catalog_path))
|
|
966
998
|
.replace("@ENDPOINT@", preset["endpoint"]).replace("@KEY@", key))
|
|
967
999
|
else:
|
|
968
|
-
# A
|
|
969
|
-
#
|
|
1000
|
+
# A gateway often serves dozens of models; the catalogue holds the ones
|
|
1001
|
+
# you named, and ``use --model <other>`` extends it on demand.
|
|
970
1002
|
base = (f'model = "{default}"\nmodel_provider = "custom"\npreferred_auth_method = "apikey"\n'
|
|
971
|
-
f'forced_login_method = "api"\
|
|
1003
|
+
f'forced_login_method = "api"\nmodel_catalog_json = "{display_path(CODEX_MODELS)}"\n\n'
|
|
1004
|
+
f'[model_providers.custom]\nname = "custom"\n'
|
|
972
1005
|
f'base_url = "{endpoint}"\nwire_api = "responses"\n'
|
|
973
1006
|
f'requires_openai_auth = true\nexperimental_bearer_token = "{key}"\n')
|
|
974
|
-
if sqlite_home:
|
|
975
|
-
base += f'\n# Keeps Codex runtime SQLite databases off the network filesystem.\nsqlite_home = "{display_path(expand(sqlite_home))}"\n'
|
|
976
1007
|
write_atomic(d / "codex-config.toml", base)
|
|
977
|
-
|
|
978
|
-
write_atomic(d / "codex-models.json", json.dumps(build_codex_catalog(d, catalog), indent=2) + "\n")
|
|
1008
|
+
write_atomic(d / "codex-models.json", json.dumps(build_codex_catalog(d, catalog), indent=2) + "\n")
|
|
979
1009
|
write_atomic(d / "codex-auth.json", json.dumps({"OPENAI_API_KEY": key}, indent=2) + "\n")
|
|
980
1010
|
if clients in ("both", "claude"):
|
|
981
1011
|
env = {"ANTHROPIC_BASE_URL": claude_endpoint, "ANTHROPIC_AUTH_TOKEN": key}
|
|
@@ -986,6 +1016,16 @@ def cmd_add(_):
|
|
|
986
1016
|
"provider": provider, "default_model": default}, indent=2) + "\n")
|
|
987
1017
|
print(f"\nCreated profile: {name}")
|
|
988
1018
|
print(f"Activate it with: ai-switch use {name} (choose between {len(chosen)} model(s))")
|
|
1019
|
+
if sqlite_home:
|
|
1020
|
+
# Codex reads this from the environment only: a `sqlite_home` key in
|
|
1021
|
+
# config.toml is accepted but ignored (verified with `codex doctor`).
|
|
1022
|
+
target = expand(sqlite_home)
|
|
1023
|
+
try:
|
|
1024
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
1025
|
+
except OSError:
|
|
1026
|
+
pass
|
|
1027
|
+
print(f"\nKeep Codex's runtime databases off {fstype_for(CODEX_DIR)} by exporting this before "
|
|
1028
|
+
f"starting codex (add it to ~/.bashrc):\n export CODEX_SQLITE_HOME={sqlite_home}")
|
|
989
1029
|
return 0
|
|
990
1030
|
|
|
991
1031
|
|
|
@@ -1099,21 +1139,47 @@ def running_agents():
|
|
|
1099
1139
|
return names
|
|
1100
1140
|
|
|
1101
1141
|
|
|
1142
|
+
def running_agents_summary(limit=4):
|
|
1143
|
+
"""A short, readable list (this file server runs dozens of agents)."""
|
|
1144
|
+
running = running_agents()
|
|
1145
|
+
if len(running) <= limit:
|
|
1146
|
+
return ", ".join(running)
|
|
1147
|
+
return ", ".join(running[:limit]) + f" and {len(running) - limit} more"
|
|
1148
|
+
|
|
1149
|
+
|
|
1102
1150
|
def check_report():
|
|
1103
1151
|
checks = []
|
|
1104
1152
|
|
|
1105
1153
|
def add(status, title, detail, hint=""):
|
|
1106
1154
|
checks.append({"status": status, "title": title, "detail": detail, "hint": hint})
|
|
1107
1155
|
|
|
1156
|
+
sqlite_home = os.environ.get("CODEX_SQLITE_HOME", "")
|
|
1157
|
+
sqlite_home_fs = fstype_for(expand(sqlite_home)) if sqlite_home else ""
|
|
1108
1158
|
for label, path in (("CODEX_HOME", CODEX_DIR), ("Claude config", CLAUDE_DIR), ("ai-switch home", ROOT)):
|
|
1109
1159
|
fs_type = fstype_for(path) or "unknown"
|
|
1110
1160
|
if fs_type in NETWORK_FS:
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
f"
|
|
1161
|
+
moved = path == CODEX_DIR and sqlite_home and sqlite_home_fs not in NETWORK_FS
|
|
1162
|
+
detail = f"{path} ({fs_type})"
|
|
1163
|
+
if path == CODEX_DIR and sqlite_home:
|
|
1164
|
+
detail += f", runtime databases in {sqlite_home} ({sqlite_home_fs or 'unknown'})"
|
|
1165
|
+
if moved:
|
|
1166
|
+
add("warn", f"{label} is on {fs_type}, runtime databases are not", detail,
|
|
1167
|
+
"Codex keeps state/log/goal databases in CODEX_SQLITE_HOME now; only history.jsonl and "
|
|
1168
|
+
"the rollout files stay on the network share.")
|
|
1169
|
+
else:
|
|
1170
|
+
hint = ("SQLite databases (Codex runtime state, goals, memories, logs) can be corrupted on network "
|
|
1171
|
+
"filesystems, which makes sessions disappear. Codex only honours the environment variable "
|
|
1172
|
+
"(a sqlite_home config key is ignored):\n"
|
|
1173
|
+
" export CODEX_SQLITE_HOME=/var/tmp/codex-sqlite-$USER")
|
|
1174
|
+
add("warn", f"{label} is on {fs_type}", detail, hint if path == CODEX_DIR else "")
|
|
1115
1175
|
else:
|
|
1116
1176
|
add("ok", f"{label} filesystem", f"{path} ({fs_type})")
|
|
1177
|
+
if sqlite_home:
|
|
1178
|
+
if sqlite_home_fs in NETWORK_FS:
|
|
1179
|
+
add("warn", "CODEX_SQLITE_HOME is also on a network filesystem", f"{sqlite_home} ({sqlite_home_fs})",
|
|
1180
|
+
"Pick a directory on a local disk, otherwise the runtime databases keep getting corrupted.")
|
|
1181
|
+
else:
|
|
1182
|
+
add("ok", "CODEX_SQLITE_HOME", f"{sqlite_home} ({sqlite_home_fs or 'unknown'})")
|
|
1117
1183
|
|
|
1118
1184
|
text = read_text(CODEX) or ""
|
|
1119
1185
|
persisted = section_value(text, "history", "persistence")
|
|
@@ -1132,9 +1198,19 @@ def check_report():
|
|
|
1132
1198
|
add("fail", "Codex model catalogue is missing", f"{catalog_path} does not exist",
|
|
1133
1199
|
"Re-run 'ai-switch use <profile>' to rewrite it.")
|
|
1134
1200
|
elif slugs and model and model not in slugs:
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1201
|
+
open_ended = False
|
|
1202
|
+
try:
|
|
1203
|
+
open_ended = bool(json.loads((PROFILES / active_profile() / "models.json").read_text()).get("open_ended"))
|
|
1204
|
+
except (OSError, ValueError):
|
|
1205
|
+
pass
|
|
1206
|
+
detail = f"model = {model}, catalogue has {', '.join(str(s) for s in slugs)}"
|
|
1207
|
+
if open_ended:
|
|
1208
|
+
add("warn", "Codex model catalogue does not list the configured model", detail,
|
|
1209
|
+
"The active profile describes an open-ended endpoint, so any model name is allowed; "
|
|
1210
|
+
"'ai-switch use NAME -m MODEL' adds it to the catalogue.")
|
|
1211
|
+
else:
|
|
1212
|
+
add("fail", "Codex model catalogue does not contain the configured model", detail,
|
|
1213
|
+
"Codex refuses to resume sessions whose model is unknown. Re-run 'ai-switch use <profile>'.")
|
|
1138
1214
|
elif slugs:
|
|
1139
1215
|
add("ok", "Codex model catalogue", f"{len(slugs)} model(s): {', '.join(str(s) for s in slugs)}")
|
|
1140
1216
|
else:
|
|
@@ -1205,7 +1281,7 @@ def check_report():
|
|
|
1205
1281
|
|
|
1206
1282
|
running = running_agents()
|
|
1207
1283
|
if running:
|
|
1208
|
-
add("warn", "Agents are running", "
|
|
1284
|
+
add("warn", "Agents are running", running_agents_summary() + f" ({len(running)} process(es))",
|
|
1209
1285
|
"A running agent rewrites its configuration on exit and may overwrite the switch; restart it after switching.")
|
|
1210
1286
|
else:
|
|
1211
1287
|
add("ok", "No agent processes running", "config.toml / settings.json are not being rewritten")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-switch-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: One-command provider and model switching for Claude Code and Codex, with session-history checks
|
|
5
5
|
Author: ZidongS
|
|
6
6
|
License: MIT
|
|
@@ -133,7 +133,7 @@ For every model a profile records the Codex catalogue entry and the Claude Code
|
|
|
133
133
|
* Codex: the top-level `model` (and `model_reasoning_effort`), plus a `~/.codex/models.json` catalogue that lists **every** model of the profile, which is what makes Codex's own `/model` picker show them.
|
|
134
134
|
* Claude Code: the default model in `settings.json` plus the Opus/Sonnet/Haiku mappings. The presets map the three categories to *different* provider models where they exist, and they never set `ANTHROPIC_MODEL`, because a pinned environment model overrides your selection and makes `/model` do nothing.
|
|
135
135
|
|
|
136
|
-
The `glm` and `deepseek` presets ship multi-model lists. A custom (OpenAI-compatible) profile is open-ended: it
|
|
136
|
+
The `glm` and `deepseek` presets ship multi-model lists. A custom (OpenAI-compatible) profile is open-ended as well: when you create it you can name **several models at once, separated by spaces or commas** (for example `DeepSeek-V4.1-Flash Kimi-K3 Qwen3.8-Max`), and those become the profile's catalogue — both agents can then pick them. Any other name the endpoint serves still works with `ai-switch use NAME --model <name>`, and it is added to the catalogue when you use it.
|
|
137
137
|
|
|
138
138
|
### When a provider adds or renames a model
|
|
139
139
|
|
|
@@ -180,7 +180,7 @@ ai-switch doctor --fix # quarantine damaged Codex runtime databases
|
|
|
180
180
|
export CODEX_SQLITE_HOME=/var/tmp/codex-sqlite-$USER # a local, persistent directory
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
`
|
|
183
|
+
Codex only honours the **environment variable**: a `sqlite_home` key in `config.toml` is parsed but ignored (checked with `codex doctor`). `ai-switch add` asks for a local directory when it detects a network filesystem, creates it and prints the export line to put in your shell profile; `ai-switch doctor` reports the variable's status.
|
|
184
184
|
* **A stale model catalogue.** A catalogue left behind by the previous provider hides the new provider's models from Codex and makes sessions that used them unresumable. ai-switch removes that leftover file (only if it wrote it itself, and it backs it up first), and `doctor` verifies the configured model exists in the catalogue.
|
|
185
185
|
* **Disabled history persistence** (`[history] persistence = "none"`), a Claude Code model pinned by `ANTHROPIC_MODEL`, and `claude`/`codex` processes that are running while you switch and will rewrite their configuration on exit.
|
|
186
186
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ai-switch-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "One-command provider and model switching for Claude Code and Codex, with session-history checks"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|