ai-switch-cli 0.3.0__tar.gz → 0.3.2__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.3.0 → ai_switch_cli-0.3.2}/PKG-INFO +21 -5
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/README.md +20 -4
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/ai_switch.py +61 -25
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/ai_switch_cli.egg-info/PKG-INFO +21 -5
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/pyproject.toml +1 -1
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/ai_switch_cli.egg-info/SOURCES.txt +0 -0
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/ai_switch_cli.egg-info/dependency_links.txt +0 -0
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/ai_switch_cli.egg-info/entry_points.txt +0 -0
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/ai_switch_cli.egg-info/top_level.txt +0 -0
- {ai_switch_cli-0.3.0 → ai_switch_cli-0.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-switch-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -78,13 +78,26 @@ Select `glm` or `deepseek`, enter the API key, and activate the generated config
|
|
|
78
78
|
ai-switch use glm
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
`use`
|
|
81
|
+
`use` activates the profile and does **not** ask anything: the profile's whole model list is published, so you
|
|
82
|
+
pick the model inside the agent with `/model` (Codex) or its Opus/Sonnet/Haiku categories (Claude Code). The
|
|
83
|
+
default for new sessions is the one you last used, or the profile default. Add `--choose` if you want to be
|
|
84
|
+
asked, or `-m/--model` to set the default directly.
|
|
82
85
|
|
|
83
86
|
```text
|
|
84
|
-
|
|
87
|
+
Active profile: glm
|
|
88
|
+
Default model: glm-5.3 (new sessions)
|
|
89
|
+
Published models: 3 - switch any time with /model inside codex (Claude Code uses its
|
|
90
|
+
Opus/Sonnet/Haiku mappings), or change the default with -m/--model.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With `--choose` you get the picker instead:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
This profile provides 3 models:
|
|
85
97
|
1) glm-5.3 GLM-5.3 flagship (1M context) [default]
|
|
86
|
-
2) glm-5.3-flash GLM-5.3
|
|
87
|
-
|
|
98
|
+
2) glm-5.3-flash GLM-5.3 Flash (fast, text+image, 1M context)
|
|
99
|
+
3) glm-5-turbo GLM-5 Turbo (agent-optimized, 200K context)
|
|
100
|
+
Select model [1-3, Enter=glm-5.3, q=cancel]:
|
|
88
101
|
```
|
|
89
102
|
|
|
90
103
|
For scripts and servers, choose without prompting (name, unique prefix, or index):
|
|
@@ -93,8 +106,11 @@ For scripts and servers, choose without prompting (name, unique prefix, or index
|
|
|
93
106
|
ai-switch use glm --model glm-5.3-flash
|
|
94
107
|
ai-switch use glm -m 2 -y
|
|
95
108
|
ai-switch use glm --dry-run # show what would change
|
|
109
|
+
ai-switch use glm -m glm-5.3 --pin # publish only this model (see below)
|
|
96
110
|
```
|
|
97
111
|
|
|
112
|
+
Selecting a model sets the **default for new sessions** in both agents, and the profile's other models stay in the catalogue so you can still switch inside the agent. That means an in-app `/model` choice is a per-session override — and Codex writes its own choice back to `~/.codex/config.toml` when it exits, so the two agents can drift apart without anything else noticing; `ai-switch doctor` reports exactly that. If you want an activation to be exclusive instead, add `--pin`: the published catalogue then holds only the selected model, so Codex's own picker has nothing else to switch to (the next `ai-switch use` without `--pin` restores the full menu).
|
|
113
|
+
|
|
98
114
|
Edit `~/.codex/config.toml` and `~/.claude/settings.json` for another provider, then save that configuration as a second profile:
|
|
99
115
|
|
|
100
116
|
```bash
|
|
@@ -64,13 +64,26 @@ Select `glm` or `deepseek`, enter the API key, and activate the generated config
|
|
|
64
64
|
ai-switch use glm
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
`use`
|
|
67
|
+
`use` activates the profile and does **not** ask anything: the profile's whole model list is published, so you
|
|
68
|
+
pick the model inside the agent with `/model` (Codex) or its Opus/Sonnet/Haiku categories (Claude Code). The
|
|
69
|
+
default for new sessions is the one you last used, or the profile default. Add `--choose` if you want to be
|
|
70
|
+
asked, or `-m/--model` to set the default directly.
|
|
68
71
|
|
|
69
72
|
```text
|
|
70
|
-
|
|
73
|
+
Active profile: glm
|
|
74
|
+
Default model: glm-5.3 (new sessions)
|
|
75
|
+
Published models: 3 - switch any time with /model inside codex (Claude Code uses its
|
|
76
|
+
Opus/Sonnet/Haiku mappings), or change the default with -m/--model.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
With `--choose` you get the picker instead:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
This profile provides 3 models:
|
|
71
83
|
1) glm-5.3 GLM-5.3 flagship (1M context) [default]
|
|
72
|
-
2) glm-5.3-flash GLM-5.3
|
|
73
|
-
|
|
84
|
+
2) glm-5.3-flash GLM-5.3 Flash (fast, text+image, 1M context)
|
|
85
|
+
3) glm-5-turbo GLM-5 Turbo (agent-optimized, 200K context)
|
|
86
|
+
Select model [1-3, Enter=glm-5.3, q=cancel]:
|
|
74
87
|
```
|
|
75
88
|
|
|
76
89
|
For scripts and servers, choose without prompting (name, unique prefix, or index):
|
|
@@ -79,8 +92,11 @@ For scripts and servers, choose without prompting (name, unique prefix, or index
|
|
|
79
92
|
ai-switch use glm --model glm-5.3-flash
|
|
80
93
|
ai-switch use glm -m 2 -y
|
|
81
94
|
ai-switch use glm --dry-run # show what would change
|
|
95
|
+
ai-switch use glm -m glm-5.3 --pin # publish only this model (see below)
|
|
82
96
|
```
|
|
83
97
|
|
|
98
|
+
Selecting a model sets the **default for new sessions** in both agents, and the profile's other models stay in the catalogue so you can still switch inside the agent. That means an in-app `/model` choice is a per-session override — and Codex writes its own choice back to `~/.codex/config.toml` when it exits, so the two agents can drift apart without anything else noticing; `ai-switch doctor` reports exactly that. If you want an activation to be exclusive instead, add `--pin`: the published catalogue then holds only the selected model, so Codex's own picker has nothing else to switch to (the next `ai-switch use` without `--pin` restores the full menu).
|
|
99
|
+
|
|
84
100
|
Edit `~/.codex/config.toml` and `~/.claude/settings.json` for another provider, then save that configuration as a second profile:
|
|
85
101
|
|
|
86
102
|
```bash
|
|
@@ -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.3.
|
|
26
|
+
VERSION = "0.3.2"
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _env_path(name, default):
|
|
@@ -476,7 +476,7 @@ def resolve_model(catalog, token):
|
|
|
476
476
|
raise ValueError(f"unknown model '{token}'; this profile offers: {available}")
|
|
477
477
|
|
|
478
478
|
|
|
479
|
-
def pick_model(catalog, requested, fallback, assume_yes=False):
|
|
479
|
+
def pick_model(catalog, requested, fallback, assume_yes=False, force=False):
|
|
480
480
|
"""Choose the model to activate (no prompting when requested/non-interactive)."""
|
|
481
481
|
if not catalog:
|
|
482
482
|
return requested
|
|
@@ -486,7 +486,7 @@ def pick_model(catalog, requested, fallback, assume_yes=False):
|
|
|
486
486
|
return resolve_model(catalog, requested)
|
|
487
487
|
if fallback not in slugs:
|
|
488
488
|
fallback = catalog["default"] if catalog["default"] in slugs else slugs[0]
|
|
489
|
-
if assume_yes or not sys.stdin.isatty() or len(entries) == 1:
|
|
489
|
+
if assume_yes or (not force and (not sys.stdin.isatty() or len(entries) == 1)):
|
|
490
490
|
return fallback
|
|
491
491
|
print(f"This profile provides {len(entries)} models:")
|
|
492
492
|
for index, entry in enumerate(entries, 1):
|
|
@@ -517,16 +517,17 @@ def catalog_target(text, catalog_path):
|
|
|
517
517
|
return catalog_path
|
|
518
518
|
|
|
519
519
|
|
|
520
|
-
def build_codex_catalog(d, catalog):
|
|
520
|
+
def build_codex_catalog(d, catalog, include_profile_entries=True):
|
|
521
521
|
"""Union of the profile's catalog file and every selectable model entry."""
|
|
522
522
|
entries = {}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if isinstance(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
523
|
+
if include_profile_entries:
|
|
524
|
+
try:
|
|
525
|
+
raw = json.loads((d / "codex-models.json").read_text())
|
|
526
|
+
for entry in (raw.get("models") if isinstance(raw, dict) else raw) or []:
|
|
527
|
+
if isinstance(entry, dict) and entry.get("slug"):
|
|
528
|
+
entries[entry["slug"]] = entry
|
|
529
|
+
except (OSError, ValueError, AttributeError):
|
|
530
|
+
pass
|
|
530
531
|
for entry in catalog["models"]:
|
|
531
532
|
if isinstance(entry.get("codex"), dict):
|
|
532
533
|
entries[entry["slug"]] = entry["codex"]
|
|
@@ -537,7 +538,7 @@ def build_codex_catalog(d, catalog):
|
|
|
537
538
|
return {"models": ordered}
|
|
538
539
|
|
|
539
540
|
|
|
540
|
-
def compute_changes(d, slug):
|
|
541
|
+
def compute_changes(d, slug, pin=False):
|
|
541
542
|
"""Return {path: text-or-None}: None means "remove this stale file"."""
|
|
542
543
|
changes, notes = {}, []
|
|
543
544
|
catalog = load_catalog(d)
|
|
@@ -556,9 +557,14 @@ def compute_changes(d, slug):
|
|
|
556
557
|
catalog = dict(catalog, models=catalog["models"] + [entry])
|
|
557
558
|
if text is not None:
|
|
558
559
|
# Every profile publishes its models, so the picker inside Codex lists
|
|
559
|
-
# them too.
|
|
560
|
+
# them too. --pin publishes only the activated one, which removes the
|
|
561
|
+
# other entries from Codex's own picker.
|
|
560
562
|
if catalog:
|
|
561
|
-
|
|
563
|
+
if pin and entry:
|
|
564
|
+
codex_catalog = build_codex_catalog(d, dict(catalog, models=[entry]),
|
|
565
|
+
include_profile_entries=False)
|
|
566
|
+
else:
|
|
567
|
+
codex_catalog = build_codex_catalog(d, catalog)
|
|
562
568
|
if codex_catalog is not None:
|
|
563
569
|
catalog_path = catalog_target(text, CODEX_MODELS)
|
|
564
570
|
changes[catalog_path] = json.dumps(codex_catalog, indent=2) + "\n"
|
|
@@ -694,23 +700,34 @@ def cmd_use(args):
|
|
|
694
700
|
if not any((d / profile_file).exists() for profile_file in PROFILE_FILES):
|
|
695
701
|
raise ValueError(f"profile '{name}' has no Codex or Claude configuration to activate")
|
|
696
702
|
catalog = load_catalog(d)
|
|
697
|
-
fallback =
|
|
698
|
-
|
|
703
|
+
fallback = last_model(name) or (catalog or {}).get("default")
|
|
704
|
+
slug = args.model or fallback
|
|
705
|
+
if args.model and catalog and catalog.get("open_ended"):
|
|
699
706
|
try:
|
|
700
707
|
slug = resolve_model(catalog, args.model)
|
|
701
708
|
except ValueError:
|
|
702
709
|
slug = args.model
|
|
703
710
|
print(f"Note: '{args.model}' is not in the profile's model list; using it as-is.", file=sys.stderr)
|
|
704
|
-
|
|
705
|
-
slug =
|
|
711
|
+
elif args.model and catalog:
|
|
712
|
+
slug = resolve_model(catalog, args.model)
|
|
713
|
+
elif catalog and args.choose:
|
|
714
|
+
# Picking a model here only sets the default for new sessions; every model
|
|
715
|
+
# of the profile is published, so it can also be changed inside the agent.
|
|
716
|
+
slug = pick_model(catalog, None, fallback, force=True)
|
|
706
717
|
if slug and catalog is None:
|
|
707
718
|
print(f"Note: profile '{name}' has no model list, using '{slug}' as-is.", file=sys.stderr)
|
|
708
|
-
changes, notes = compute_changes(d, slug)
|
|
719
|
+
changes, notes = compute_changes(d, slug, pin=args.pin)
|
|
709
720
|
for note in notes:
|
|
710
721
|
print(f"Note: {note}", file=sys.stderr)
|
|
711
722
|
backup = apply_changes(changes, name, slug, dry_run=args.dry_run)
|
|
712
723
|
if not args.dry_run:
|
|
713
|
-
print(f"Active profile: {name}" + (f"\
|
|
724
|
+
print(f"Active profile: {name}" + (f"\nDefault model: {slug} (new sessions)" if slug else ""))
|
|
725
|
+
if args.pin and slug:
|
|
726
|
+
print(f"Pinned: only '{slug}' is published, so Codex cannot switch to another model.")
|
|
727
|
+
elif catalog:
|
|
728
|
+
print(f"Published models: {len(catalog['models'])} - switch any time with /model inside "
|
|
729
|
+
"codex (Claude Code uses its Opus/Sonnet/Haiku mappings), or change the default "
|
|
730
|
+
"with -m/--model.")
|
|
714
731
|
print(f"Backup: {backup}" if backup else "Backup: nothing to back up (no files existed yet)")
|
|
715
732
|
print("Restart claude/codex so they reload their configuration.")
|
|
716
733
|
if not args.no_check:
|
|
@@ -1273,9 +1290,24 @@ def check_report():
|
|
|
1273
1290
|
f"{len(transcripts)} transcript(s) under {display_path(projects)}")
|
|
1274
1291
|
|
|
1275
1292
|
state = load_state()
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1293
|
+
active = active_profile()
|
|
1294
|
+
recorded = (state.get("models") or {}).get(active) if isinstance(state.get("models"), dict) else None
|
|
1295
|
+
add("ok", "Active profile", f"{active or '(none)'}" + (f", model {recorded}" if recorded else ""))
|
|
1296
|
+
if recorded:
|
|
1297
|
+
# A running agent rewrites its own config on exit, so the live files can
|
|
1298
|
+
# silently diverge from what 'ai-switch use' activated.
|
|
1299
|
+
if model and model != recorded:
|
|
1300
|
+
add("warn", "Codex is not using the model ai-switch activated",
|
|
1301
|
+
f"config.toml has model = {model}, ai-switch activated {recorded}",
|
|
1302
|
+
"A running codex session rewrites config.toml when it exits, so a /model choice made "
|
|
1303
|
+
f"inside codex wins. Close codex and re-run 'ai-switch use {active} -m {recorded}', or use "
|
|
1304
|
+
"--pin so the in-app picker cannot switch away.")
|
|
1305
|
+
claude_model = str(settings.get("model") or "")
|
|
1306
|
+
stem = lambda text: re.split(r"[\[/]", text)[0]
|
|
1307
|
+
if claude_model and stem(claude_model) != stem(str(recorded)):
|
|
1308
|
+
add("warn", "Claude Code is not using the model ai-switch activated",
|
|
1309
|
+
f"settings.json has model = {claude_model}, ai-switch activated {recorded}",
|
|
1310
|
+
f"Re-run 'ai-switch use {active} -m {recorded}' to bring both agents back in sync.")
|
|
1279
1311
|
profiles = [p.name for p in sorted(PROFILES.iterdir()) if p.is_dir()] if PROFILES.exists() else []
|
|
1280
1312
|
add("ok" if profiles else "note", "Profiles", ", ".join(profiles) or "none yet")
|
|
1281
1313
|
|
|
@@ -1365,8 +1397,12 @@ files inside a profile are switched. Restart claude/codex after switching."""
|
|
|
1365
1397
|
|
|
1366
1398
|
p = sub.add_parser("use", help="activate a profile (optionally choosing a model)")
|
|
1367
1399
|
p.add_argument("name")
|
|
1368
|
-
p.add_argument("-m", "--model", help="model
|
|
1369
|
-
p.add_argument("
|
|
1400
|
+
p.add_argument("-m", "--model", help="set the default model for new sessions (name, unique prefix, or index)")
|
|
1401
|
+
p.add_argument("--choose", action="store_true",
|
|
1402
|
+
help="ask which model the new sessions should start with (the others stay selectable in /model)")
|
|
1403
|
+
p.add_argument("--pin", action="store_true",
|
|
1404
|
+
help="publish only this model, so Codex's own picker cannot switch to another one")
|
|
1405
|
+
p.add_argument("-y", "--yes", action="store_true", help="accepted for compatibility; use never prompts by default")
|
|
1370
1406
|
p.add_argument("-n", "--dry-run", action="store_true", help="show what would change")
|
|
1371
1407
|
p.add_argument("--no-check", action="store_true", help="skip the session-history health check")
|
|
1372
1408
|
p.set_defaults(fn=cmd_use)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-switch-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -78,13 +78,26 @@ Select `glm` or `deepseek`, enter the API key, and activate the generated config
|
|
|
78
78
|
ai-switch use glm
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
`use`
|
|
81
|
+
`use` activates the profile and does **not** ask anything: the profile's whole model list is published, so you
|
|
82
|
+
pick the model inside the agent with `/model` (Codex) or its Opus/Sonnet/Haiku categories (Claude Code). The
|
|
83
|
+
default for new sessions is the one you last used, or the profile default. Add `--choose` if you want to be
|
|
84
|
+
asked, or `-m/--model` to set the default directly.
|
|
82
85
|
|
|
83
86
|
```text
|
|
84
|
-
|
|
87
|
+
Active profile: glm
|
|
88
|
+
Default model: glm-5.3 (new sessions)
|
|
89
|
+
Published models: 3 - switch any time with /model inside codex (Claude Code uses its
|
|
90
|
+
Opus/Sonnet/Haiku mappings), or change the default with -m/--model.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With `--choose` you get the picker instead:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
This profile provides 3 models:
|
|
85
97
|
1) glm-5.3 GLM-5.3 flagship (1M context) [default]
|
|
86
|
-
2) glm-5.3-flash GLM-5.3
|
|
87
|
-
|
|
98
|
+
2) glm-5.3-flash GLM-5.3 Flash (fast, text+image, 1M context)
|
|
99
|
+
3) glm-5-turbo GLM-5 Turbo (agent-optimized, 200K context)
|
|
100
|
+
Select model [1-3, Enter=glm-5.3, q=cancel]:
|
|
88
101
|
```
|
|
89
102
|
|
|
90
103
|
For scripts and servers, choose without prompting (name, unique prefix, or index):
|
|
@@ -93,8 +106,11 @@ For scripts and servers, choose without prompting (name, unique prefix, or index
|
|
|
93
106
|
ai-switch use glm --model glm-5.3-flash
|
|
94
107
|
ai-switch use glm -m 2 -y
|
|
95
108
|
ai-switch use glm --dry-run # show what would change
|
|
109
|
+
ai-switch use glm -m glm-5.3 --pin # publish only this model (see below)
|
|
96
110
|
```
|
|
97
111
|
|
|
112
|
+
Selecting a model sets the **default for new sessions** in both agents, and the profile's other models stay in the catalogue so you can still switch inside the agent. That means an in-app `/model` choice is a per-session override — and Codex writes its own choice back to `~/.codex/config.toml` when it exits, so the two agents can drift apart without anything else noticing; `ai-switch doctor` reports exactly that. If you want an activation to be exclusive instead, add `--pin`: the published catalogue then holds only the selected model, so Codex's own picker has nothing else to switch to (the next `ai-switch use` without `--pin` restores the full menu).
|
|
113
|
+
|
|
98
114
|
Edit `~/.codex/config.toml` and `~/.claude/settings.json` for another provider, then save that configuration as a second profile:
|
|
99
115
|
|
|
100
116
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ai-switch-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
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
|