tokensaver-cli 0.3.2__tar.gz → 0.3.3__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.
- {tokensaver_cli-0.3.2/src/tokensaver_cli.egg-info → tokensaver_cli-0.3.3}/PKG-INFO +1 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/__init__.py +1 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/agents.py +31 -2
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/profiles.py +9 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3/src/tokensaver_cli.egg-info}/PKG-INFO +1 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/tests/test_route_cli.py +34 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/LICENSE +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/README.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/pyproject.toml +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/setup.cfg +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/api_client.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/approve_cmd.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/auth_cmd.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/banner.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/.claude-plugin/marketplace.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/.claude-plugin/plugin.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/.mcp.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/approve.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/model.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/pipeline.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/setup.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/status.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/unroute.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/hooks/hooks.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/config.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/credentials.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/doctor.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/json_merge.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/keys_cmd.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/logo.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/main.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/plugin_install.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/route_frame.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/state.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/status.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli.egg-info/SOURCES.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli.egg-info/dependency_links.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli.egg-info/entry_points.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli.egg-info/requires.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli.egg-info/top_level.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/tests/test_approve_cli.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/tests/test_auth_cli.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/tests/test_banner.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/tests/test_deploy_mode.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/tests/test_main_args.py +0 -0
|
@@ -255,6 +255,18 @@ def _restore_routed_files(files: list[RoutedFile], *, strip_if_no_backup: bool =
|
|
|
255
255
|
path.unlink(missing_ok=True)
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
def _sticky_claude_model() -> str | None:
|
|
259
|
+
"""Last model chosen via ``--model`` / ``--profile`` (survives bare ``route --launch``)."""
|
|
260
|
+
rec = load_state().get("claude")
|
|
261
|
+
if not rec or not isinstance(rec.meta, dict):
|
|
262
|
+
return None
|
|
263
|
+
for key in ("sticky_model", "model"):
|
|
264
|
+
value = rec.meta.get(key)
|
|
265
|
+
if isinstance(value, str) and value.strip():
|
|
266
|
+
return value.strip()
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
|
|
258
270
|
def route_claude(
|
|
259
271
|
cfg: RouteConfig,
|
|
260
272
|
*,
|
|
@@ -267,12 +279,25 @@ def route_claude(
|
|
|
267
279
|
install_plugin: bool = True,
|
|
268
280
|
) -> int:
|
|
269
281
|
plan_slug = resolve_plan_slug()
|
|
282
|
+
explicit_model = bool(model and model.strip())
|
|
283
|
+
explicit_profile = bool(profile and profile.strip())
|
|
284
|
+
sticky = None if (explicit_model or explicit_profile) else _sticky_claude_model()
|
|
270
285
|
try:
|
|
271
|
-
model_id, profile_name = resolve_model(
|
|
286
|
+
model_id, profile_name = resolve_model(
|
|
287
|
+
model=model,
|
|
288
|
+
profile=profile,
|
|
289
|
+
plan_slug=plan_slug,
|
|
290
|
+
sticky_model=sticky,
|
|
291
|
+
)
|
|
272
292
|
except ValueError as exc:
|
|
273
293
|
print(str(exc), file=sys.stderr)
|
|
274
294
|
return 2
|
|
275
|
-
if
|
|
295
|
+
if sticky and model_id == sticky and not explicit_model and not explicit_profile:
|
|
296
|
+
print(
|
|
297
|
+
f"Keeping previous model {model_id} (set with --model / --profile to change)",
|
|
298
|
+
file=sys.stderr,
|
|
299
|
+
)
|
|
300
|
+
elif is_free_plan(plan_slug) and not explicit_model and not sticky:
|
|
276
301
|
print(
|
|
277
302
|
f"Free plan → default model {model_id} (override with --model / --profile)",
|
|
278
303
|
file=sys.stderr,
|
|
@@ -357,15 +382,19 @@ def route_claude(
|
|
|
357
382
|
file=sys.stderr,
|
|
358
383
|
)
|
|
359
384
|
|
|
385
|
+
prev_sticky = _sticky_claude_model()
|
|
386
|
+
sticky_model = model_id if (explicit_model or explicit_profile) else (prev_sticky or model_id)
|
|
360
387
|
meta = {
|
|
361
388
|
"scope": scope,
|
|
362
389
|
"model": model_id,
|
|
390
|
+
"sticky_model": sticky_model,
|
|
363
391
|
"profile": profile_name,
|
|
364
392
|
"with_fs": fs_ok,
|
|
365
393
|
"console_url": cfg.console_url,
|
|
366
394
|
"flows_url": cfg.flows_url(),
|
|
367
395
|
"plugin_dir": str(plugin_dir) if plugin_dir else None,
|
|
368
396
|
"deploy_mode": cfg.deploy_mode,
|
|
397
|
+
"plan_slug": plan_slug,
|
|
369
398
|
}
|
|
370
399
|
record_route("claude", routed, meta=meta)
|
|
371
400
|
|
|
@@ -64,8 +64,12 @@ def resolve_model(
|
|
|
64
64
|
model: str | None,
|
|
65
65
|
profile: str | None,
|
|
66
66
|
plan_slug: str | None = None,
|
|
67
|
+
sticky_model: str | None = None,
|
|
67
68
|
) -> tuple[str | None, str | None]:
|
|
68
|
-
"""Return ``(model_id, profile_name)``.
|
|
69
|
+
"""Return ``(model_id, profile_name)``.
|
|
70
|
+
|
|
71
|
+
Precedence: ``--model`` > ``--profile`` > sticky (last explicit choice) > plan default.
|
|
72
|
+
"""
|
|
69
73
|
profiles = load_profiles(plan_slug=plan_slug)
|
|
70
74
|
if model and model.strip():
|
|
71
75
|
mid = model.strip()
|
|
@@ -77,6 +81,10 @@ def resolve_model(
|
|
|
77
81
|
known = ", ".join(sorted(profiles))
|
|
78
82
|
raise ValueError(f"Unknown profile {profile!r}. Known: {known}")
|
|
79
83
|
return profiles[name], name
|
|
84
|
+
if sticky_model and sticky_model.strip():
|
|
85
|
+
mid = sticky_model.strip()
|
|
86
|
+
matched = next((name for name, value in profiles.items() if value == mid), None)
|
|
87
|
+
return mid, matched
|
|
80
88
|
return profiles.get("default"), "default"
|
|
81
89
|
|
|
82
90
|
|
|
@@ -129,6 +129,40 @@ def test_route_claude_free_plan_default_model(
|
|
|
129
129
|
assert settings["env"]["ANTHROPIC_MODEL"] == FREE_PROFILES["default"]
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
def test_route_claude_keeps_sticky_model_on_relaunch(
|
|
133
|
+
route_home: Path,
|
|
134
|
+
cfg: RouteConfig,
|
|
135
|
+
tmp_path: Path,
|
|
136
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Bare ``route --launch`` must not reset an explicit --model to plan default."""
|
|
139
|
+
monkeypatch.delenv("TOKENSAVER_PLAN", raising=False)
|
|
140
|
+
project = tmp_path / "project"
|
|
141
|
+
project.mkdir()
|
|
142
|
+
glm = "openrouter/z-ai/glm-4.7-flash"
|
|
143
|
+
old_cwd = os.getcwd()
|
|
144
|
+
try:
|
|
145
|
+
os.chdir(project)
|
|
146
|
+
assert (
|
|
147
|
+
route_claude(
|
|
148
|
+
cfg,
|
|
149
|
+
launch=False,
|
|
150
|
+
extra_args=[],
|
|
151
|
+
model=glm,
|
|
152
|
+
install_plugin=False,
|
|
153
|
+
)
|
|
154
|
+
== 0
|
|
155
|
+
)
|
|
156
|
+
assert (
|
|
157
|
+
route_claude(cfg, launch=False, extra_args=[], install_plugin=False) == 0
|
|
158
|
+
)
|
|
159
|
+
finally:
|
|
160
|
+
os.chdir(old_cwd)
|
|
161
|
+
settings = json.loads((route_home / ".claude" / "settings.json").read_text())
|
|
162
|
+
assert settings["env"]["ANTHROPIC_MODEL"] == glm
|
|
163
|
+
assert load_state()["claude"].meta.get("sticky_model") == glm
|
|
164
|
+
|
|
165
|
+
|
|
132
166
|
def test_route_claude_project_scope(route_home: Path, cfg: RouteConfig, tmp_path: Path) -> None:
|
|
133
167
|
project = tmp_path / "project"
|
|
134
168
|
project.mkdir()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/approve.md
RENAMED
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/model.md
RENAMED
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/pipeline.md
RENAMED
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/setup.md
RENAMED
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/status.md
RENAMED
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/commands/unroute.md
RENAMED
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli/claude_plugin/hooks/hooks.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tokensaver_cli-0.3.2 → tokensaver_cli-0.3.3}/src/tokensaver_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|