tokensaver-cli 0.3.2__tar.gz → 0.3.4__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.4}/PKG-INFO +6 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/README.md +5 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/__init__.py +1 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/agents.py +31 -2
- tokensaver_cli-0.3.4/src/tokensaver_cli/catalog_cmd.py +113 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/.claude-plugin/plugin.json +3 -3
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/cache.md +19 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/catalog.md +26 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/doctor.md +11 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/flows.md +19 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/help.md +27 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/keys.md +19 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/login.md +26 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/model.md +22 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/models.md +24 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/quota.md +17 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/use.md +28 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/claude_plugin/commands/whoami.md +14 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/flows_cmd.py +90 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/main.py +57 -0
- tokensaver_cli-0.3.4/src/tokensaver_cli/models_cmd.py +81 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/profiles.py +9 -1
- tokensaver_cli-0.3.4/src/tokensaver_cli/use_cmd.py +74 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4/src/tokensaver_cli.egg-info}/PKG-INFO +6 -1
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli.egg-info/SOURCES.txt +16 -0
- tokensaver_cli-0.3.4/tests/test_models_use_cli.py +105 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/tests/test_route_cli.py +34 -0
- tokensaver_cli-0.3.2/src/tokensaver_cli/claude_plugin/commands/model.md +0 -29
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/LICENSE +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/pyproject.toml +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/setup.cfg +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/api_client.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/approve_cmd.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/auth_cmd.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/banner.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/.claude-plugin/marketplace.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/.mcp.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/commands/approve.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/commands/pipeline.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/commands/setup.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/commands/status.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/commands/unroute.md +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/claude_plugin/hooks/hooks.json +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/config.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/credentials.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/doctor.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/json_merge.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/keys_cmd.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/logo.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/plugin_install.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/route_frame.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/state.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli/status.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli.egg-info/dependency_links.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli.egg-info/entry_points.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli.egg-info/requires.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/src/tokensaver_cli.egg-info/top_level.txt +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/tests/test_approve_cli.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/tests/test_auth_cli.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/tests/test_banner.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/tests/test_deploy_mode.py +0 -0
- {tokensaver_cli-0.3.2 → tokensaver_cli-0.3.4}/tests/test_main_args.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tokensaver-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: TokenSaver CLI — route AI agents (Claude Code, Cursor, Codex) through the control plane
|
|
5
5
|
Author: TokenSaver
|
|
6
6
|
License-Expression: MIT
|
|
@@ -69,6 +69,10 @@ pip install -e ".[dev]"
|
|
|
69
69
|
| `tokensaver logout` | Remove local credentials |
|
|
70
70
|
| `tokensaver whoami` | Email, plan, key prefix, Flux IA link |
|
|
71
71
|
| `tokensaver approve [model]` | Approve model in Agent Registry (zero-trust) |
|
|
72
|
+
| `tokensaver models` | List profiles + catalogue models |
|
|
73
|
+
| `tokensaver use <profile\|model>` | Set Claude model (sticky) + approve |
|
|
74
|
+
| `tokensaver flows [--open]` | Flux IA URL + recent runs |
|
|
75
|
+
| `tokensaver catalog …` | List / approve Agent Registry |
|
|
72
76
|
| `tokensaver keys list` | List API keys |
|
|
73
77
|
| `tokensaver keys create --name CLI --use` | Create key and set as default |
|
|
74
78
|
| `tokensaver keys revoke <id>` | Delete a key |
|
|
@@ -84,6 +88,7 @@ pip install -e ".[dev]"
|
|
|
84
88
|
| `tokensaver doctor [--claude] [--local]` | Health checks |
|
|
85
89
|
| `tokensaver status [--line\|--hook]` | Route status + Flux IA |
|
|
86
90
|
| `tokensaver profiles` | cheap / default / strong (plan-aware) |
|
|
91
|
+
| Claude plugin slash | `/tokensaver-router:models\|use\|flows\|quota\|cache\|catalog\|…` |
|
|
87
92
|
|
|
88
93
|
### Claude Code options
|
|
89
94
|
|
|
@@ -42,6 +42,10 @@ pip install -e ".[dev]"
|
|
|
42
42
|
| `tokensaver logout` | Remove local credentials |
|
|
43
43
|
| `tokensaver whoami` | Email, plan, key prefix, Flux IA link |
|
|
44
44
|
| `tokensaver approve [model]` | Approve model in Agent Registry (zero-trust) |
|
|
45
|
+
| `tokensaver models` | List profiles + catalogue models |
|
|
46
|
+
| `tokensaver use <profile\|model>` | Set Claude model (sticky) + approve |
|
|
47
|
+
| `tokensaver flows [--open]` | Flux IA URL + recent runs |
|
|
48
|
+
| `tokensaver catalog …` | List / approve Agent Registry |
|
|
45
49
|
| `tokensaver keys list` | List API keys |
|
|
46
50
|
| `tokensaver keys create --name CLI --use` | Create key and set as default |
|
|
47
51
|
| `tokensaver keys revoke <id>` | Delete a key |
|
|
@@ -57,6 +61,7 @@ pip install -e ".[dev]"
|
|
|
57
61
|
| `tokensaver doctor [--claude] [--local]` | Health checks |
|
|
58
62
|
| `tokensaver status [--line\|--hook]` | Route status + Flux IA |
|
|
59
63
|
| `tokensaver profiles` | cheap / default / strong (plan-aware) |
|
|
64
|
+
| Claude plugin slash | `/tokensaver-router:models\|use\|flows\|quota\|cache\|catalog\|…` |
|
|
60
65
|
|
|
61
66
|
### Claude Code options
|
|
62
67
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Agent Registry helpers from the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import quote
|
|
8
|
+
|
|
9
|
+
from tokensaver_cli.api_client import ApiError, request_json
|
|
10
|
+
from tokensaver_cli.approve_cmd import _api_base, _auth_headers, approve_catalog_ref
|
|
11
|
+
from tokensaver_cli.credentials import resolve_access_token
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _list_assets(
|
|
15
|
+
api_v1: str,
|
|
16
|
+
*,
|
|
17
|
+
status: str | None = None,
|
|
18
|
+
asset_type: str = "model",
|
|
19
|
+
q: str | None = None,
|
|
20
|
+
limit: int = 50,
|
|
21
|
+
) -> list[dict[str, Any]]:
|
|
22
|
+
params = [f"type={quote(asset_type)}", f"limit={limit}"]
|
|
23
|
+
if status:
|
|
24
|
+
params.append(f"status={quote(status)}")
|
|
25
|
+
if q:
|
|
26
|
+
params.append(f"q={quote(q)}")
|
|
27
|
+
qs = "&".join(params)
|
|
28
|
+
headers = _auth_headers()
|
|
29
|
+
try:
|
|
30
|
+
data = request_json("GET", f"{api_v1}/sdk/catalog/assets?{qs}", headers=headers)
|
|
31
|
+
except ApiError:
|
|
32
|
+
token = resolve_access_token()
|
|
33
|
+
if not token:
|
|
34
|
+
raise
|
|
35
|
+
data = request_json(
|
|
36
|
+
"GET",
|
|
37
|
+
f"{api_v1}/catalog/assets?{qs}",
|
|
38
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
39
|
+
)
|
|
40
|
+
items = data.get("items") if isinstance(data, dict) else None
|
|
41
|
+
if not isinstance(items, list):
|
|
42
|
+
return []
|
|
43
|
+
return [i for i in items if isinstance(i, dict)]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_catalog(
|
|
47
|
+
action: str | None = None,
|
|
48
|
+
ref: str | None = None,
|
|
49
|
+
*,
|
|
50
|
+
force_local: bool = False,
|
|
51
|
+
status: str | None = None,
|
|
52
|
+
) -> int:
|
|
53
|
+
act = (action or "list").strip().lower()
|
|
54
|
+
api_v1 = _api_base(force_local=force_local)
|
|
55
|
+
|
|
56
|
+
if act in ("approve", "allow"):
|
|
57
|
+
target = (ref or "").strip()
|
|
58
|
+
if not target:
|
|
59
|
+
print("Usage: tokensaver catalog approve <provider/model>", file=sys.stderr)
|
|
60
|
+
return 2
|
|
61
|
+
try:
|
|
62
|
+
asset = approve_catalog_ref(target, force_local=force_local)
|
|
63
|
+
except ApiError as exc:
|
|
64
|
+
print(f"FAIL {exc}", file=sys.stderr)
|
|
65
|
+
return 1
|
|
66
|
+
print(f"OK approved {asset.get('ref')} status={asset.get('status')}")
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
if act not in ("list", "pending", "ls"):
|
|
70
|
+
print("Usage: tokensaver catalog [list|pending|approve <ref>]", file=sys.stderr)
|
|
71
|
+
return 2
|
|
72
|
+
|
|
73
|
+
filter_status = status
|
|
74
|
+
if act == "pending" and not filter_status:
|
|
75
|
+
# show both pending buckets
|
|
76
|
+
rows: list[dict[str, Any]] = []
|
|
77
|
+
for st in ("quarantined", "discovered"):
|
|
78
|
+
try:
|
|
79
|
+
rows.extend(_list_assets(api_v1, status=st, asset_type="model"))
|
|
80
|
+
except ApiError as exc:
|
|
81
|
+
print(f"FAIL list catalog — {exc}", file=sys.stderr)
|
|
82
|
+
return 1
|
|
83
|
+
# de-dupe by id
|
|
84
|
+
seen: set[str] = set()
|
|
85
|
+
uniq: list[dict[str, Any]] = []
|
|
86
|
+
for row in rows:
|
|
87
|
+
aid = str(row.get("id") or "")
|
|
88
|
+
if aid and aid in seen:
|
|
89
|
+
continue
|
|
90
|
+
if aid:
|
|
91
|
+
seen.add(aid)
|
|
92
|
+
uniq.append(row)
|
|
93
|
+
rows = uniq
|
|
94
|
+
else:
|
|
95
|
+
try:
|
|
96
|
+
rows = _list_assets(api_v1, status=filter_status, asset_type="model")
|
|
97
|
+
except ApiError as exc:
|
|
98
|
+
print(f"FAIL list catalog — {exc}", file=sys.stderr)
|
|
99
|
+
return 1
|
|
100
|
+
|
|
101
|
+
print("Agent Registry (models)")
|
|
102
|
+
if not rows:
|
|
103
|
+
print(" (empty)")
|
|
104
|
+
return 0
|
|
105
|
+
print(f" {'STATUS':12} REF")
|
|
106
|
+
for row in rows:
|
|
107
|
+
st = str(row.get("status") or "?")[:12]
|
|
108
|
+
ref_s = row.get("ref") or "?"
|
|
109
|
+
print(f" {st:12} {ref_s}")
|
|
110
|
+
print()
|
|
111
|
+
print("Approve: tokensaver catalog approve <ref>")
|
|
112
|
+
print(" or: tokensaver approve <ref>")
|
|
113
|
+
return 0
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokensaver-router",
|
|
3
3
|
"displayName": "TokenSaver Router",
|
|
4
|
-
"version": "0.3.
|
|
5
|
-
"description": "Route Claude Code through TokenSaver —
|
|
4
|
+
"version": "0.3.4",
|
|
5
|
+
"description": "Route Claude Code through TokenSaver — models, use, approve, Flux IA, quotas, cache, catalog, login/keys.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "TokenSaver",
|
|
8
8
|
"url": "https://tokensaver.fr"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"homepage": "https://tokensaver.fr/sdk-api",
|
|
11
11
|
"repository": "https://github.com/tokensaver/tokensaver-platform",
|
|
12
12
|
"license": "MIT",
|
|
13
|
-
"keywords": ["tokensaver", "router", "mcp", "governance", "cost"],
|
|
13
|
+
"keywords": ["tokensaver", "router", "mcp", "governance", "cost", "models"],
|
|
14
14
|
"commands": ["./commands"],
|
|
15
15
|
"hooks": "./hooks/hooks.json",
|
|
16
16
|
"mcpServers": "./.mcp.json"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show TokenSaver cache stats (and optional purge guidance)
|
|
3
|
+
argument-hint: "[stats|purge]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use MCP tools:
|
|
7
|
+
|
|
8
|
+
1. Default / `stats`: call `tokensaver_cache_stats` and paste the result.
|
|
9
|
+
2. Optional detail: `tokensaver_list_cache_entries` (small limit).
|
|
10
|
+
3. Only if the user explicitly asks to purge: confirm, then `tokensaver_cache_purge`.
|
|
11
|
+
|
|
12
|
+
Never purge without clear user consent.
|
|
13
|
+
|
|
14
|
+
CLI fallback:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
tokensaver status
|
|
18
|
+
tokensaver flows
|
|
19
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List or approve Agent Registry (catalog) models — fix zero-trust 403
|
|
3
|
+
argument-hint: "[pending|approve <ref>]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Agent Registry governs which models may egress (zero-trust).
|
|
7
|
+
|
|
8
|
+
If `$ARGUMENTS` starts with `approve`, run:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
tokensaver catalog approve <ref>
|
|
12
|
+
# or: tokensaver approve <ref>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If `$ARGUMENTS` is `pending` or empty, list pending models:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
tokensaver catalog pending
|
|
19
|
+
tokensaver catalog list
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
MCP alternatives:
|
|
23
|
+
- `tokensaver_list_catalog_assets` with `status=quarantined` or `type=model`
|
|
24
|
+
- `tokensaver_approve_catalog_asset` with `ref=…` (creates+approves if missing)
|
|
25
|
+
|
|
26
|
+
After approve, tell the user to retry their message. Related: `/tokensaver-router:use` and `/tokensaver-router:approve`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run TokenSaver doctor checks for Claude routing
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
tokensaver doctor --claude
|
|
7
|
+
# local stack:
|
|
8
|
+
tokensaver doctor --claude --local
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Report which checks failed (API key, Anthropic base URL, /v1/models, MCP). Suggest `tokensaver login`, `tokensaver route claude --local`, or `tokensaver approve --current` as appropriate.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Open Flux IA — recent governed runs / pipeline traces
|
|
3
|
+
argument-hint: "[--open]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Show TokenSaver Flux IA links and recent pipeline runs:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
tokensaver flows
|
|
10
|
+
# open browser:
|
|
11
|
+
tokensaver flows --open
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If MCP tools are available, also call:
|
|
15
|
+
- `tokensaver_list_runs` (limit 5)
|
|
16
|
+
- `tokensaver_get_last_run_detail` when the user wants step-by-step
|
|
17
|
+
- `tokensaver_open_run_console` to deep-link a run
|
|
18
|
+
|
|
19
|
+
Explain that each Claude message through TokenSaver can appear as a governed flow (cache, compression, PII, LLM, MCP). Do not invent token savings — only report what the tools/console show.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List TokenSaver Router slash commands and CLI equivalents
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Print this catalogue for the user:
|
|
6
|
+
|
|
7
|
+
| Slash | Purpose | CLI |
|
|
8
|
+
|-------|---------|-----|
|
|
9
|
+
| `/tokensaver-router:models` | List profiles + catalogue | `tokensaver models` |
|
|
10
|
+
| `/tokensaver-router:use` | Set model (sticky) + approve | `tokensaver use …` |
|
|
11
|
+
| `/tokensaver-router:model` | Alias of use | `tokensaver use …` |
|
|
12
|
+
| `/tokensaver-router:approve` | Agent Registry approve | `tokensaver approve …` |
|
|
13
|
+
| `/tokensaver-router:catalog` | List/approve registry | `tokensaver catalog …` |
|
|
14
|
+
| `/tokensaver-router:status` | Route statusline | `tokensaver status` |
|
|
15
|
+
| `/tokensaver-router:whoami` | Identity + plan | `tokensaver whoami` |
|
|
16
|
+
| `/tokensaver-router:flows` | Flux IA / recent runs | `tokensaver flows` |
|
|
17
|
+
| `/tokensaver-router:pipeline` | Flux IA pipeline hint | `tokensaver status` |
|
|
18
|
+
| `/tokensaver-router:quota` | Quotas / key overview | MCP `tokensaver_get_api_key_overview` |
|
|
19
|
+
| `/tokensaver-router:cache` | Cache stats | MCP `tokensaver_cache_stats` |
|
|
20
|
+
| `/tokensaver-router:login` | Login / signup | `tokensaver login` |
|
|
21
|
+
| `/tokensaver-router:keys` | API keys | `tokensaver keys …` |
|
|
22
|
+
| `/tokensaver-router:setup` | Full route setup | `tokensaver route claude` |
|
|
23
|
+
| `/tokensaver-router:doctor` | Health checks | `tokensaver doctor --claude` |
|
|
24
|
+
| `/tokensaver-router:unroute` | Restore Claude settings | `tokensaver unroute claude` |
|
|
25
|
+
| `/tokensaver-router:help` | This list | — |
|
|
26
|
+
|
|
27
|
+
Tip: if the current model is blocked (credits / 403), run the **CLI** commands in a terminal — slash prompts need a working model.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List / create / revoke TokenSaver API keys
|
|
3
|
+
argument-hint: "[list|create|revoke <id>]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Requires a login JWT (`tokensaver login`), not key-only import.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
tokensaver keys list
|
|
10
|
+
tokensaver keys create --name Claude --use
|
|
11
|
+
tokensaver keys revoke <key-id>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If `$ARGUMENTS` is `create`, run create with `--use`.
|
|
15
|
+
If it starts with `revoke`, revoke that id.
|
|
16
|
+
Otherwise list keys.
|
|
17
|
+
|
|
18
|
+
Warn: the plaintext key is shown once at create time — store it securely.
|
|
19
|
+
Related: `/tokensaver-router:whoami`, `/tokensaver-router:login`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Log in / signup to TokenSaver (saves credentials + API key)
|
|
3
|
+
argument-hint: "[--local|--key ts_…]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
CLI-first auth (no dashboard required):
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
tokensaver login
|
|
10
|
+
# local API:
|
|
11
|
+
tokensaver login --local
|
|
12
|
+
# import existing key:
|
|
13
|
+
tokensaver login --key ts_…
|
|
14
|
+
# non-interactive signup example:
|
|
15
|
+
tokensaver login --email you@acme.com --signup -y
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then route Claude:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
tokensaver route claude --launch
|
|
22
|
+
# or local:
|
|
23
|
+
tokensaver route claude --local --launch
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Verify with `tokensaver whoami` and `tokensaver doctor --claude`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Switch TokenSaver routing model or profile (alias of use)
|
|
3
|
+
argument-hint: "<cheap|default|strong|provider/model>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Alias of `/tokensaver-router:use`. Prefer that command.
|
|
7
|
+
|
|
8
|
+
If `$ARGUMENTS` is set, run:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
tokensaver use $ARGUMENTS
|
|
12
|
+
tokensaver status --line
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If empty, list options first:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
tokensaver models
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then ask which profile or catalogue id to apply via `tokensaver use …`.
|
|
22
|
+
After switch, approve is handled by `use`; fallback: `tokensaver approve --current`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List TokenSaver profiles and catalogue models available on your plan
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run (shell — works even if the chat model is blocked):
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
tokensaver models
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Also show sticky Claude model via:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
tokensaver status --line
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Optional MCP (richer JSON): call `tokensaver_list_models` and paste the ids.
|
|
18
|
+
|
|
19
|
+
Summarize for the user:
|
|
20
|
+
1. Profiles (`cheap` / `default` / `strong`) for their plan
|
|
21
|
+
2. Catalogue ids they can pass to `/tokensaver-router:use`
|
|
22
|
+
3. Current sticky model if any
|
|
23
|
+
|
|
24
|
+
Next step hint: `/tokensaver-router:use <profile|provider/model>`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show TokenSaver API key quotas and usage
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Prefer MCP tools (paste ASCII dashboards as-is, do not rewrite into prose):
|
|
6
|
+
|
|
7
|
+
1. `tokensaver_get_api_key_overview` — full key dashboard (primary)
|
|
8
|
+
2. Or `tokensaver_get_quota_usage` if the user only asks for quotas
|
|
9
|
+
|
|
10
|
+
If MCP is unavailable, fall back to:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
tokensaver whoami
|
|
14
|
+
tokensaver doctor
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
and the Flux IA / console usage pages from `tokensaver status`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Set Claude routing model or profile (sticky) and approve in Agent Registry
|
|
3
|
+
argument-hint: "<cheap|default|strong|provider/model>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Select a TokenSaver model for Claude Code. Prefer the CLI (updates sticky model + Agent Registry).
|
|
7
|
+
|
|
8
|
+
If `$ARGUMENTS` is empty, run `tokensaver models` and ask the user which id/profile to use.
|
|
9
|
+
|
|
10
|
+
Otherwise run:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Prefer --local when status shows local
|
|
14
|
+
tokensaver use $ARGUMENTS
|
|
15
|
+
# equivalent:
|
|
16
|
+
# tokensaver use cheap
|
|
17
|
+
# tokensaver use openrouter/z-ai/glm-4.7-flash
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then verify:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
tokensaver status --line
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Tell the user to send a **new message** (or restart Claude) so the model takes effect. Statusline must show the chosen model.
|
|
27
|
+
|
|
28
|
+
If approve fails (403), run `/tokensaver-router:approve $ARGUMENTS`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show TokenSaver identity, plan, API key, and Flux IA link
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
tokensaver whoami
|
|
9
|
+
# local stack:
|
|
10
|
+
tokensaver whoami --local
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Report email, plan (Free / Pro / Enterprise), API host, key prefix, and Flux IA URL.
|
|
14
|
+
Explain that Free plan profiles default to allowlisted models (e.g. GLM 4.7 Flash) while Enterprise defaults to Sonnet/Opus unless a sticky `--model` was set.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Flux IA deep links and recent pipeline runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import webbrowser
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from tokensaver_cli.api_client import ApiError, request_json
|
|
10
|
+
from tokensaver_cli.config import RouteConfigError, resolve_route_config
|
|
11
|
+
from tokensaver_cli.status import fetch_recent_flow_id
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _list_recent_runs(cfg, *, limit: int = 5) -> list[dict[str, Any]]:
|
|
15
|
+
url = f"{cfg.api_v1_base}/monitoring/pipelines?days=7"
|
|
16
|
+
try:
|
|
17
|
+
data = request_json(
|
|
18
|
+
"GET",
|
|
19
|
+
url,
|
|
20
|
+
headers={"Authorization": f"Bearer {cfg.api_key}"},
|
|
21
|
+
)
|
|
22
|
+
except ApiError:
|
|
23
|
+
return []
|
|
24
|
+
if not isinstance(data, dict):
|
|
25
|
+
return []
|
|
26
|
+
out: list[dict[str, Any]] = []
|
|
27
|
+
for key in ("recent_runs", "recent_run_ids"):
|
|
28
|
+
items = data.get(key)
|
|
29
|
+
if not isinstance(items, list):
|
|
30
|
+
continue
|
|
31
|
+
for item in items:
|
|
32
|
+
if isinstance(item, str) and item.strip():
|
|
33
|
+
out.append({"id": item.strip()})
|
|
34
|
+
elif isinstance(item, dict):
|
|
35
|
+
out.append(item)
|
|
36
|
+
if len(out) >= limit:
|
|
37
|
+
return out
|
|
38
|
+
# Fallback: dig pipelines buckets
|
|
39
|
+
pipelines = data.get("pipelines") or data.get("by_pipeline") or []
|
|
40
|
+
if isinstance(pipelines, dict):
|
|
41
|
+
pipelines = list(pipelines.values())
|
|
42
|
+
if isinstance(pipelines, list):
|
|
43
|
+
for entry in pipelines:
|
|
44
|
+
if not isinstance(entry, dict):
|
|
45
|
+
continue
|
|
46
|
+
for rid in entry.get("recent_run_ids") or []:
|
|
47
|
+
if isinstance(rid, str) and rid.strip():
|
|
48
|
+
out.append({"id": rid.strip(), "pipeline": entry.get("name") or entry.get("id")})
|
|
49
|
+
if len(out) >= limit:
|
|
50
|
+
return out
|
|
51
|
+
return out[:limit]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_flows(
|
|
55
|
+
*,
|
|
56
|
+
force_local: bool = False,
|
|
57
|
+
open_browser: bool = False,
|
|
58
|
+
limit: int = 5,
|
|
59
|
+
) -> int:
|
|
60
|
+
try:
|
|
61
|
+
cfg = resolve_route_config(force_local=force_local)
|
|
62
|
+
except RouteConfigError as exc:
|
|
63
|
+
print(str(exc), file=sys.stderr)
|
|
64
|
+
return 1
|
|
65
|
+
|
|
66
|
+
flow_id = fetch_recent_flow_id(cfg)
|
|
67
|
+
url = cfg.flows_url(flow_id=flow_id)
|
|
68
|
+
print("TokenSaver Flux IA")
|
|
69
|
+
print(f" Console: {url}")
|
|
70
|
+
if flow_id:
|
|
71
|
+
print(f" Latest: {flow_id}")
|
|
72
|
+
print()
|
|
73
|
+
runs = _list_recent_runs(cfg, limit=limit)
|
|
74
|
+
if runs:
|
|
75
|
+
print(f"Recent runs (up to {limit}):")
|
|
76
|
+
for row in runs:
|
|
77
|
+
rid = row.get("id") or row.get("run_id") or "?"
|
|
78
|
+
extra = row.get("pipeline") or row.get("status") or ""
|
|
79
|
+
suffix = f" {extra}" if extra else ""
|
|
80
|
+
print(f" {rid}{suffix}")
|
|
81
|
+
print()
|
|
82
|
+
else:
|
|
83
|
+
print("No recent runs found (send a Claude message first).")
|
|
84
|
+
print()
|
|
85
|
+
|
|
86
|
+
print("MCP: tokensaver_list_runs · tokensaver_get_last_run_detail · tokensaver_open_run_console")
|
|
87
|
+
if open_browser:
|
|
88
|
+
webbrowser.open(url)
|
|
89
|
+
print("Opened browser.")
|
|
90
|
+
return 0
|