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.
Files changed (51) hide show
  1. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/.gitignore +1 -0
  2. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/PKG-INFO +5 -5
  3. hypercli_cli-2026.6.16/hypercli_cli/__init__.py +1 -0
  4. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/agent.py +35 -6
  5. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/agents.py +45 -6
  6. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/cli.py +91 -2
  7. hypercli_cli-2026.6.16/hypercli_cli/memory.py +334 -0
  8. hypercli_cli-2026.6.16/hypercli_cli/memory_captions.py +417 -0
  9. hypercli_cli-2026.6.16/hypercli_cli/memory_documents.py +330 -0
  10. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/output.py +22 -4
  11. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/voice.py +22 -12
  12. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/pyproject.toml +5 -5
  13. hypercli_cli-2026.6.16/tests/test_agent_env_resolution.py +139 -0
  14. hypercli_cli-2026.6.16/tests/test_agents_budget_command.py +54 -0
  15. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_exec_shell_dryrun.py +28 -0
  16. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_keys_command.py +1 -1
  17. hypercli_cli-2026.6.16/tests/test_me_command.py +147 -0
  18. hypercli_cli-2026.6.16/tests/test_memory_captions.py +265 -0
  19. hypercli_cli-2026.6.16/tests/test_memory_command.py +14 -0
  20. hypercli_cli-2026.6.16/tests/test_memory_documents.py +190 -0
  21. hypercli_cli-2026.6.16/tests/test_status_command.py +26 -0
  22. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_voice_command.py +9 -1
  23. hypercli_cli-2026.5.5/hypercli_cli/__init__.py +0 -1
  24. hypercli_cli-2026.5.5/tests/test_agent_env_resolution.py +0 -72
  25. hypercli_cli-2026.5.5/tests/test_me_command.py +0 -36
  26. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/README.md +0 -0
  27. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/billing.py +0 -0
  28. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/comfyui.py +0 -0
  29. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/embed.py +0 -0
  30. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/files.py +0 -0
  31. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/flow.py +0 -0
  32. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/instances.py +0 -0
  33. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/jobs.py +0 -0
  34. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/keys.py +0 -0
  35. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/llm.py +0 -0
  36. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/onboard.py +0 -0
  37. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/renders.py +0 -0
  38. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/stt.py +0 -0
  39. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/tui/__init__.py +0 -0
  40. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/tui/job_monitor.py +0 -0
  41. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/user.py +0 -0
  42. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/hypercli_cli/wallet.py +0 -0
  43. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_agent_subscribe_command.py +0 -0
  44. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_config_command.py +0 -0
  45. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_flow_command.py +0 -0
  46. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_flow_visibility.py +0 -0
  47. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_jobs_list_tags.py +0 -0
  48. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_llm_command.py +0 -0
  49. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_openclaw_config.py +0 -0
  50. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_wallet_command.py +0 -0
  51. {hypercli_cli-2026.5.5 → hypercli_cli-2026.6.16}/tests/test_wallet_migration_integration.py +0 -0
@@ -26,6 +26,7 @@ coverage
26
26
 
27
27
  # Misc
28
28
  .DS_Store
29
+ .agents/
29
30
  *.pem
30
31
 
31
32
  # IDE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercli-cli
3
- Version: 2026.5.5
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.5.5
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.5.5; extra == 'all'
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.5.5; extra == 'comfyui'
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 = os.getenv("HYPER_AGENTS_API_KEY", "").strip() or os.getenv("HYPER_API_KEY", "").strip()
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.5 as the default model"),
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/openai-compatible",
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 env or saved key file."""
50
- key = os.environ.get("HYPER_AGENTS_API_KEY", "").strip()
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
- output(auth_me, fmt)
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