hypercli-cli 2026.5.5__tar.gz → 2026.6.16__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.
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/.gitignore +1 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/PKG-INFO +5 -5
- hypercli_cli-2026.6.16/hypercli_cli/__init__.py +1 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/agent.py +35 -6
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/agents.py +45 -6
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/cli.py +91 -2
- hypercli_cli-2026.6.16/hypercli_cli/memory.py +334 -0
- hypercli_cli-2026.6.16/hypercli_cli/memory_captions.py +417 -0
- hypercli_cli-2026.6.16/hypercli_cli/memory_documents.py +330 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/output.py +22 -4
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/voice.py +22 -12
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/pyproject.toml +5 -5
- hypercli_cli-2026.6.16/tests/test_agent_env_resolution.py +139 -0
- hypercli_cli-2026.6.16/tests/test_agents_budget_command.py +54 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_exec_shell_dryrun.py +28 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_keys_command.py +1 -1
- hypercli_cli-2026.6.16/tests/test_me_command.py +147 -0
- hypercli_cli-2026.6.16/tests/test_memory_captions.py +265 -0
- hypercli_cli-2026.6.16/tests/test_memory_command.py +14 -0
- hypercli_cli-2026.6.16/tests/test_memory_documents.py +190 -0
- hypercli_cli-2026.6.16/tests/test_status_command.py +26 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_voice_command.py +9 -1
- hypercli_cli-2026.5.5/hypercli_cli/__init__.py +0 -1
- hypercli_cli-2026.5.5/tests/test_agent_env_resolution.py +0 -72
- hypercli_cli-2026.5.5/tests/test_me_command.py +0 -36
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/README.md +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/billing.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/comfyui.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/embed.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/files.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/flow.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/instances.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/jobs.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/keys.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/llm.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/onboard.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/renders.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/stt.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/tui/__init__.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/tui/job_monitor.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/user.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/wallet.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_agent_subscribe_command.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_config_command.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_flow_command.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_flow_visibility.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_jobs_list_tags.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_llm_command.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_openclaw_config.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_wallet_command.py +0 -0
- {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_wallet_migration_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypercli-cli
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.6.16
|
|
4
4
|
Summary: CLI for HyperCLI - GPU orchestration and LLM API
|
|
5
5
|
Project-URL: Homepage, https://hypercli.com
|
|
6
6
|
Project-URL: Documentation, https://docs.hypercli.com
|
|
@@ -9,22 +9,22 @@ Author-email: HyperCLI <support@hypercli.com>
|
|
|
9
9
|
License: MIT
|
|
10
10
|
Requires-Python: >=3.10
|
|
11
11
|
Requires-Dist: httpx>=0.27.0
|
|
12
|
-
Requires-Dist: hypercli-sdk>=2026.
|
|
12
|
+
Requires-Dist: hypercli-sdk>=2026.6.16
|
|
13
13
|
Requires-Dist: mutagen>=1.47.0
|
|
14
14
|
Requires-Dist: openai>=2.8.1
|
|
15
|
+
Requires-Dist: pysubs2>=1.8.0
|
|
15
16
|
Requires-Dist: pyyaml>=6.0
|
|
16
17
|
Requires-Dist: rich>=14.2.0
|
|
17
18
|
Requires-Dist: typer>=0.20.0
|
|
18
|
-
Requires-Dist: websocket-client>=1.6.0
|
|
19
19
|
Provides-Extra: all
|
|
20
20
|
Requires-Dist: argon2-cffi>=25.0.0; extra == 'all'
|
|
21
21
|
Requires-Dist: eth-account>=0.13.0; extra == 'all'
|
|
22
22
|
Requires-Dist: faster-whisper>=1.1.0; extra == 'all'
|
|
23
|
-
Requires-Dist: hypercli-sdk[comfyui]>=2026.
|
|
23
|
+
Requires-Dist: hypercli-sdk[comfyui]>=2026.6.16; extra == 'all'
|
|
24
24
|
Requires-Dist: web3>=7.0.0; extra == 'all'
|
|
25
25
|
Requires-Dist: x402[evm,httpx]>=2.0.0; extra == 'all'
|
|
26
26
|
Provides-Extra: comfyui
|
|
27
|
-
Requires-Dist: hypercli-sdk[comfyui]>=2026.
|
|
27
|
+
Requires-Dist: hypercli-sdk[comfyui]>=2026.6.16; extra == 'comfyui'
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
30
|
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2026.6.16"
|
|
@@ -12,7 +12,7 @@ from rich.console import Console
|
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
from hypercli import HyperCLI
|
|
15
|
-
from hypercli.config import get_agents_api_base_url_from_product_base
|
|
15
|
+
from hypercli.config import get_agent_api_key, get_agents_api_base_url_from_product_base
|
|
16
16
|
|
|
17
17
|
from .onboard import onboard as _onboard_fn
|
|
18
18
|
from .voice import app as voice_app
|
|
@@ -54,7 +54,7 @@ def require_x402_deps():
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def _resolve_agent_query_key() -> str:
|
|
57
|
-
key =
|
|
57
|
+
key = (get_agent_api_key() or "").strip()
|
|
58
58
|
if key:
|
|
59
59
|
return key
|
|
60
60
|
if AGENT_KEY_PATH.exists():
|
|
@@ -749,7 +749,21 @@ def fetch_models(api_key: str, api_base: str = PROD_INFERENCE_API_BASE) -> list[
|
|
|
749
749
|
normalized = (model_id or "").strip().lower()
|
|
750
750
|
aliases = {
|
|
751
751
|
"kimi-k2.5": {"name": "Kimi K2.5", "reasoning": True, "contextWindow": 262144},
|
|
752
|
+
"kimi-k2.5-anthropic": {"name": "Kimi K2.5 Anthropic", "reasoning": True, "contextWindow": 262144},
|
|
753
|
+
"kimi-k2.6": {"name": "Kimi K2.6", "reasoning": True, "contextWindow": 262144},
|
|
754
|
+
"kimi-k2.6-anthropic": {"name": "Kimi K2.6 Anthropic", "reasoning": True, "contextWindow": 262144},
|
|
752
755
|
"moonshotai/kimi-k2.5": {"name": "Kimi K2.5", "reasoning": True, "contextWindow": 262144},
|
|
756
|
+
"moonshotai/kimi-k2.5-anthropic": {
|
|
757
|
+
"name": "Kimi K2.5 Anthropic",
|
|
758
|
+
"reasoning": True,
|
|
759
|
+
"contextWindow": 262144,
|
|
760
|
+
},
|
|
761
|
+
"moonshotai/kimi-k2.6": {"name": "Kimi K2.6", "reasoning": True, "contextWindow": 262144},
|
|
762
|
+
"moonshotai/kimi-k2.6-anthropic": {
|
|
763
|
+
"name": "Kimi K2.6 Anthropic",
|
|
764
|
+
"reasoning": True,
|
|
765
|
+
"contextWindow": 262144,
|
|
766
|
+
},
|
|
753
767
|
"glm-5": {"name": "GLM-5", "reasoning": True, "contextWindow": 202752},
|
|
754
768
|
"zai-org/glm-5": {"name": "GLM-5", "reasoning": True, "contextWindow": 202752},
|
|
755
769
|
"qwen3-embedding-4b": {
|
|
@@ -814,9 +828,21 @@ def fetch_models(api_key: str, api_base: str = PROD_INFERENCE_API_BASE) -> list[
|
|
|
814
828
|
]
|
|
815
829
|
|
|
816
830
|
|
|
831
|
+
def _preferred_agent_models(models: list[dict]) -> list[dict]:
|
|
832
|
+
"""Return the recommended agent models in priority order."""
|
|
833
|
+
preferred = ["kimi-k2.6-anthropic", "kimi-k2.5-anthropic"]
|
|
834
|
+
picked = [model for model_id in preferred for model in models if model["id"] == model_id]
|
|
835
|
+
if picked:
|
|
836
|
+
return picked
|
|
837
|
+
anthropic = [model for model in models if model["id"].endswith("-anthropic")]
|
|
838
|
+
if anthropic:
|
|
839
|
+
return anthropic
|
|
840
|
+
return models[:1]
|
|
841
|
+
|
|
842
|
+
|
|
817
843
|
@app.command("openclaw-setup")
|
|
818
844
|
def openclaw_setup(
|
|
819
|
-
default: bool = typer.Option(False, "--default", help="Set hyperclaw/kimi-k2.
|
|
845
|
+
default: bool = typer.Option(False, "--default", help="Set hyperclaw/kimi-k2.6-anthropic as the default model"),
|
|
820
846
|
):
|
|
821
847
|
"""Patch OpenClaw config with your HyperClaw API key.
|
|
822
848
|
|
|
@@ -912,8 +938,8 @@ def _config_openclaw(
|
|
|
912
938
|
|
|
913
939
|
api_base = api_base.rstrip("/")
|
|
914
940
|
supported_models = [m for m in models if _is_supported_openclaw_model(m)]
|
|
915
|
-
chat_models = [m for m in supported_models if m.get("mode") != "embedding"]
|
|
916
941
|
embedding_models = [m for m in supported_models if m.get("mode") == "embedding"]
|
|
942
|
+
chat_models = _preferred_agent_models([m for m in supported_models if m.get("mode") != "embedding"])
|
|
917
943
|
kimi_models = [m for m in chat_models if "kimi" in _model_suffix(m.get("id", ""))]
|
|
918
944
|
glm_models = [m for m in chat_models if _model_suffix(m.get("id", "")) == "glm-5"]
|
|
919
945
|
other_chat_models = [
|
|
@@ -974,6 +1000,7 @@ def _config_openclaw(
|
|
|
974
1000
|
def _config_opencode(api_key: str, models: list[dict], api_base: str = PROD_INFERENCE_API_BASE) -> dict:
|
|
975
1001
|
"""OpenCode opencode.json provider snippet."""
|
|
976
1002
|
api_base = api_base.rstrip("/")
|
|
1003
|
+
models = _preferred_agent_models(models)
|
|
977
1004
|
model_entries = {}
|
|
978
1005
|
for m in models:
|
|
979
1006
|
model_entries[m["id"]] = {"name": m["id"]}
|
|
@@ -981,7 +1008,7 @@ def _config_opencode(api_key: str, models: list[dict], api_base: str = PROD_INFE
|
|
|
981
1008
|
"$schema": "https://opencode.ai/config.json",
|
|
982
1009
|
"provider": {
|
|
983
1010
|
"hypercli": {
|
|
984
|
-
"npm": "@ai-sdk/
|
|
1011
|
+
"npm": "@ai-sdk/anthropic",
|
|
985
1012
|
"name": "HyperCLI",
|
|
986
1013
|
"options": {
|
|
987
1014
|
"baseURL": f"{api_base}/v1",
|
|
@@ -989,13 +1016,15 @@ def _config_opencode(api_key: str, models: list[dict], api_base: str = PROD_INFE
|
|
|
989
1016
|
},
|
|
990
1017
|
"models": model_entries,
|
|
991
1018
|
}
|
|
992
|
-
}
|
|
1019
|
+
},
|
|
1020
|
+
"model": f"hypercli/{models[0]['id']}",
|
|
993
1021
|
}
|
|
994
1022
|
|
|
995
1023
|
|
|
996
1024
|
def _config_env(api_key: str, models: list[dict], api_base: str = PROD_INFERENCE_API_BASE) -> str:
|
|
997
1025
|
"""Shell env vars for generic OpenAI-compatible tools."""
|
|
998
1026
|
api_base = api_base.rstrip("/")
|
|
1027
|
+
models = _preferred_agent_models(models)
|
|
999
1028
|
lines = [
|
|
1000
1029
|
f'export OPENAI_API_KEY="{api_key}"',
|
|
1001
1030
|
f'export OPENAI_BASE_URL="{api_base}/v1"',
|
|
@@ -12,7 +12,8 @@ import typer
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
|
-
from hypercli.agents import Agent, Deployments, OpenClawAgent, DEFAULT_OPENCLAW_IMAGE
|
|
15
|
+
from hypercli.agents import AGENT_FILE_MAX_BYTES, Agent, Deployments, OpenClawAgent, DEFAULT_OPENCLAW_IMAGE
|
|
16
|
+
from hypercli.config import get_agent_api_key as get_config_agent_api_key
|
|
16
17
|
|
|
17
18
|
app = typer.Typer(help="Manage OpenClaw agent pods")
|
|
18
19
|
console = Console()
|
|
@@ -46,11 +47,8 @@ def agents_root(
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def _get_agent_api_key() -> str:
|
|
49
|
-
"""Resolve HyperClaw API key from
|
|
50
|
-
key =
|
|
51
|
-
if key:
|
|
52
|
-
return key
|
|
53
|
-
key = os.environ.get("HYPER_API_KEY", "").strip()
|
|
50
|
+
"""Resolve HyperClaw API key from canonical config before legacy key file."""
|
|
51
|
+
key = (get_config_agent_api_key() or "").strip()
|
|
54
52
|
if key:
|
|
55
53
|
return key
|
|
56
54
|
if AGENT_KEY_PATH.exists():
|
|
@@ -286,6 +284,8 @@ def create(
|
|
|
286
284
|
registry_url: str = typer.Option(None, "--registry-url", help="Container registry URL for private image pulls"),
|
|
287
285
|
registry_username: str = typer.Option(None, "--registry-username", help="Registry username"),
|
|
288
286
|
registry_password: str = typer.Option(None, "--registry-password", help="Registry password"),
|
|
287
|
+
sync_uid: int = typer.Option(None, "--sync-uid", help="UID for restored synced files; defaults to Lagoon's configured value"),
|
|
288
|
+
sync_gid: int = typer.Option(None, "--sync-gid", help="GID for restored synced files; defaults to Lagoon's configured value"),
|
|
289
289
|
gateway_token: str = typer.Option(None, "--gateway-token", help="OpenClaw gateway token override"),
|
|
290
290
|
dry_run: bool = typer.Option(False, "--dry-run", help="Validate launch configuration without creating the agent or pod"),
|
|
291
291
|
no_start: bool = typer.Option(False, "--no-start", help="Create without starting"),
|
|
@@ -312,6 +312,8 @@ def create(
|
|
|
312
312
|
image=_default_openclaw_image(image),
|
|
313
313
|
registry_url=registry_url,
|
|
314
314
|
registry_auth=registry_auth,
|
|
315
|
+
sync_uid=sync_uid,
|
|
316
|
+
sync_gid=sync_gid,
|
|
315
317
|
gateway_token=gateway_token,
|
|
316
318
|
dry_run=dry_run,
|
|
317
319
|
start=not no_start,
|
|
@@ -438,6 +440,36 @@ def list_agents(
|
|
|
438
440
|
|
|
439
441
|
console.print()
|
|
440
442
|
console.print(table)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@app.command("web-search")
|
|
446
|
+
def web_search_cmd(
|
|
447
|
+
query: str = typer.Argument(..., help="Search query"),
|
|
448
|
+
count: int = typer.Option(5, "--count", "-n", min=1, max=20, help="Number of Brave results to request"),
|
|
449
|
+
json_output: bool = typer.Option(False, "--json", help="Print raw JSON response"),
|
|
450
|
+
):
|
|
451
|
+
"""Search the web through the HyperClaw Brave proxy."""
|
|
452
|
+
agents = _get_deployments_client()
|
|
453
|
+
try:
|
|
454
|
+
payload = agents.web_search(query, count=count)
|
|
455
|
+
except Exception as e:
|
|
456
|
+
console.print(f"[red]❌ Web search failed: {e}[/red]")
|
|
457
|
+
raise typer.Exit(1)
|
|
458
|
+
|
|
459
|
+
if json_output:
|
|
460
|
+
console.print(json.dumps(payload, indent=2))
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
web = payload.get("web") if isinstance(payload, dict) else None
|
|
464
|
+
results = web.get("results") if isinstance(web, dict) else []
|
|
465
|
+
table = Table(title="Web Search")
|
|
466
|
+
table.add_column("Title")
|
|
467
|
+
table.add_column("URL")
|
|
468
|
+
for item in results or []:
|
|
469
|
+
if not isinstance(item, dict):
|
|
470
|
+
continue
|
|
471
|
+
table.add_row(str(item.get("title") or ""), str(item.get("url") or ""))
|
|
472
|
+
console.print(table)
|
|
441
473
|
console.print()
|
|
442
474
|
|
|
443
475
|
|
|
@@ -495,6 +527,8 @@ def start(
|
|
|
495
527
|
registry_url: str = typer.Option(None, "--registry-url", help="Container registry URL for private image pulls"),
|
|
496
528
|
registry_username: str = typer.Option(None, "--registry-username", help="Registry username"),
|
|
497
529
|
registry_password: str = typer.Option(None, "--registry-password", help="Registry password"),
|
|
530
|
+
sync_uid: int = typer.Option(None, "--sync-uid", help="UID for restored synced files; defaults to Lagoon's configured value"),
|
|
531
|
+
sync_gid: int = typer.Option(None, "--sync-gid", help="GID for restored synced files; defaults to Lagoon's configured value"),
|
|
498
532
|
gateway_token: str = typer.Option(None, "--gateway-token", help="OpenClaw gateway token override"),
|
|
499
533
|
dry_run: bool = typer.Option(False, "--dry-run", help="Validate launch configuration without starting the agent"),
|
|
500
534
|
):
|
|
@@ -523,6 +557,8 @@ def start(
|
|
|
523
557
|
image=effective_image,
|
|
524
558
|
registry_url=registry_url,
|
|
525
559
|
registry_auth=registry_auth,
|
|
560
|
+
sync_uid=sync_uid,
|
|
561
|
+
sync_gid=sync_gid,
|
|
526
562
|
gateway_token=effective_gateway_token,
|
|
527
563
|
dry_run=dry_run,
|
|
528
564
|
)
|
|
@@ -641,6 +677,9 @@ def cp(
|
|
|
641
677
|
|
|
642
678
|
try:
|
|
643
679
|
if dst_agent_id:
|
|
680
|
+
local_size = Path(src_path).stat().st_size
|
|
681
|
+
if local_size > AGENT_FILE_MAX_BYTES:
|
|
682
|
+
raise ValueError(f"Agent file writes are limited to {AGENT_FILE_MAX_BYTES // 1024 // 1024} MiB")
|
|
644
683
|
pod = _get_pod_with_token(dst_agent_id)
|
|
645
684
|
agents.cp_to(pod, src_path, dst_path)
|
|
646
685
|
console.print(f"[green]✓[/green] Copied [bold]{src_path}[/bold] to [bold]{dst_agent_id[:12]}:{dst_path}[/bold]")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""HyperCLI - Main entry point"""
|
|
2
2
|
import sys
|
|
3
3
|
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
4
5
|
import typer
|
|
5
6
|
from rich.console import Console
|
|
6
7
|
from rich.prompt import Prompt
|
|
@@ -9,12 +10,30 @@ from rich.table import Table
|
|
|
9
10
|
from hypercli import HyperCLI, APIError, configure
|
|
10
11
|
from hypercli.config import CONFIG_FILE
|
|
11
12
|
|
|
12
|
-
from . import agent, agents, billing, comfyui, files, flow, instances, jobs, keys, llm, user, voice, wallet
|
|
13
|
+
from . import agent, agents, billing, comfyui, files, flow, instances, jobs, keys, llm, memory, user, voice, wallet
|
|
13
14
|
from .output import output, spinner
|
|
14
15
|
|
|
15
16
|
console = Console()
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def _format_time_left(expires_at: datetime | None) -> str:
|
|
20
|
+
if not expires_at:
|
|
21
|
+
return ""
|
|
22
|
+
if expires_at.tzinfo is None:
|
|
23
|
+
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
|
24
|
+
remaining = expires_at - datetime.now(timezone.utc)
|
|
25
|
+
if remaining.total_seconds() <= 0:
|
|
26
|
+
return "expired"
|
|
27
|
+
days = remaining.days
|
|
28
|
+
hours = remaining.seconds // 3600
|
|
29
|
+
minutes = (remaining.seconds % 3600) // 60
|
|
30
|
+
if days:
|
|
31
|
+
return f"{days}d {hours}h"
|
|
32
|
+
if hours:
|
|
33
|
+
return f"{hours}h {minutes}m"
|
|
34
|
+
return f"{minutes}m"
|
|
35
|
+
|
|
36
|
+
|
|
18
37
|
def fuzzy_match(input_str: str, options: list[str], threshold: float = 0.5) -> list[str]:
|
|
19
38
|
"""Find similar strings using multiple heuristics"""
|
|
20
39
|
def similarity(a: str, b: str) -> float:
|
|
@@ -75,6 +94,7 @@ app.add_typer(instances.app, name="instances")
|
|
|
75
94
|
app.add_typer(keys.app, name="keys")
|
|
76
95
|
app.add_typer(jobs.app, name="jobs")
|
|
77
96
|
app.add_typer(llm.app, name="llm")
|
|
97
|
+
app.add_typer(memory.app, name="memory")
|
|
78
98
|
app.add_typer(user.app, name="user")
|
|
79
99
|
app.add_typer(voice.app, name="voice")
|
|
80
100
|
app.add_typer(wallet.app, name="wallet")
|
|
@@ -107,8 +127,32 @@ def me_cmd(
|
|
|
107
127
|
client = HyperCLI()
|
|
108
128
|
with spinner("Resolving auth context..."):
|
|
109
129
|
auth_me = client.user.auth_me()
|
|
130
|
+
entitlement_summary = None
|
|
131
|
+
entitlement_error = None
|
|
132
|
+
try:
|
|
133
|
+
entitlement_summary = client.agent.subscription_summary()
|
|
134
|
+
except APIError as exc:
|
|
135
|
+
entitlement_error = f"{exc.status_code}: {exc.detail}"
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
entitlement_error = str(exc)
|
|
110
138
|
if fmt == "json":
|
|
111
|
-
|
|
139
|
+
payload = dict(getattr(auth_me, "__dict__", {}))
|
|
140
|
+
if entitlement_summary is not None:
|
|
141
|
+
payload["agents_entitlements"] = {
|
|
142
|
+
"effective_plan_id": entitlement_summary.effective_plan_id,
|
|
143
|
+
"current_subscription_id": entitlement_summary.current_subscription_id,
|
|
144
|
+
"current_entitlement_id": entitlement_summary.current_entitlement_id,
|
|
145
|
+
"active_subscription_count": entitlement_summary.active_subscription_count,
|
|
146
|
+
"active_entitlement_count": entitlement_summary.active_entitlement_count,
|
|
147
|
+
"pooled_tpm_limit": entitlement_summary.pooled_tpm_limit,
|
|
148
|
+
"pooled_rpm_limit": entitlement_summary.pooled_rpm_limit,
|
|
149
|
+
"pooled_tpd": entitlement_summary.pooled_tpd,
|
|
150
|
+
"billing_reset_at": entitlement_summary.billing_reset_at,
|
|
151
|
+
"entitlement_items": [item.__dict__ for item in entitlement_summary.entitlement_items],
|
|
152
|
+
}
|
|
153
|
+
elif entitlement_error:
|
|
154
|
+
payload["agents_entitlements_error"] = entitlement_error
|
|
155
|
+
output(payload, fmt)
|
|
112
156
|
return
|
|
113
157
|
|
|
114
158
|
table = Table(show_header=False, box=None)
|
|
@@ -127,6 +171,51 @@ def me_cmd(
|
|
|
127
171
|
raw_capabilities = list(getattr(auth_me, "capabilities", []) or [])
|
|
128
172
|
capabilities = "\n".join(raw_capabilities) if raw_capabilities else ""
|
|
129
173
|
table.add_row("capabilities", capabilities)
|
|
174
|
+
if entitlement_summary is not None:
|
|
175
|
+
table.add_row("agents_effective_plan", entitlement_summary.effective_plan_id)
|
|
176
|
+
table.add_row("agents_current_entitlement", str(entitlement_summary.current_entitlement_id or ""))
|
|
177
|
+
table.add_row("agents_active_entitlements", str(entitlement_summary.active_entitlement_count))
|
|
178
|
+
active_items = [item for item in entitlement_summary.entitlement_items if str(item.status).upper() == "ACTIVE"]
|
|
179
|
+
expires_at = max((item.expires_at for item in active_items if item.expires_at), default=None)
|
|
180
|
+
if expires_at:
|
|
181
|
+
table.add_row("agents_expires_at", expires_at.isoformat())
|
|
182
|
+
table.add_row("agents_time_left", _format_time_left(expires_at))
|
|
183
|
+
table.add_row(
|
|
184
|
+
"agents_limits",
|
|
185
|
+
(
|
|
186
|
+
f"{entitlement_summary.pooled_tpm_limit:,} TPM / "
|
|
187
|
+
f"{entitlement_summary.pooled_rpm_limit:,} RPM / "
|
|
188
|
+
f"{entitlement_summary.pooled_tpd:,} TPD"
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
elif entitlement_error:
|
|
192
|
+
table.add_row("agents_entitlements", f"unavailable ({entitlement_error})")
|
|
193
|
+
console.print(table)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@app.command("status")
|
|
197
|
+
def status_cmd(
|
|
198
|
+
fmt: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
|
|
199
|
+
):
|
|
200
|
+
"""Show compact platform status."""
|
|
201
|
+
client = HyperCLI()
|
|
202
|
+
with spinner("Checking platform status..."):
|
|
203
|
+
payload = client.status()
|
|
204
|
+
if fmt == "json":
|
|
205
|
+
output(payload, fmt)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
table = Table(show_header=True, header_style="bold cyan")
|
|
209
|
+
table.add_column("Type")
|
|
210
|
+
table.add_column("Name")
|
|
211
|
+
table.add_column("Healthy")
|
|
212
|
+
|
|
213
|
+
for name, healthy in (payload.get("models") or {}).items():
|
|
214
|
+
table.add_row("model", str(name), "true" if healthy else "false")
|
|
215
|
+
for name, healthy in (payload.get("clusters") or {}).items():
|
|
216
|
+
table.add_row("cluster", str(name), "true" if healthy else "false")
|
|
217
|
+
|
|
218
|
+
console.print(f"ok: {'true' if payload.get('ok') else 'false'}")
|
|
130
219
|
console.print(table)
|
|
131
220
|
|
|
132
221
|
|