hypercli-cli 2026.4.24__tar.gz → 2026.5.21__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.4.24 → hypercli_cli-2026.5.21}/.gitignore +1 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/PKG-INFO +5 -5
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/README.md +1 -0
- hypercli_cli-2026.5.21/hypercli_cli/__init__.py +1 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/agent.py +63 -6
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/agents.py +3 -5
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/cli.py +63 -1
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/voice.py +22 -12
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/pyproject.toml +4 -5
- hypercli_cli-2026.5.21/tests/test_agent_env_resolution.py +139 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_agent_subscribe_command.py +29 -0
- hypercli_cli-2026.5.21/tests/test_me_command.py +91 -0
- hypercli_cli-2026.4.24/hypercli_cli/__init__.py +0 -1
- hypercli_cli-2026.4.24/tests/test_agent_env_resolution.py +0 -72
- hypercli_cli-2026.4.24/tests/test_me_command.py +0 -36
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/billing.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/comfyui.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/embed.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/files.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/flow.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/instances.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/jobs.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/keys.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/llm.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/onboard.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/output.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/renders.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/stt.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/tui/__init__.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/tui/job_monitor.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/user.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/hypercli_cli/wallet.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_config_command.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_exec_shell_dryrun.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_flow_command.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_flow_visibility.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_jobs_list_tags.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_keys_command.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_llm_command.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_openclaw_config.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_voice_command.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_wallet_command.py +0 -0
- {hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/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.5.21
|
|
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,21 @@ 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.5.21
|
|
13
13
|
Requires-Dist: mutagen>=1.47.0
|
|
14
14
|
Requires-Dist: openai>=2.8.1
|
|
15
15
|
Requires-Dist: pyyaml>=6.0
|
|
16
16
|
Requires-Dist: rich>=14.2.0
|
|
17
17
|
Requires-Dist: typer>=0.20.0
|
|
18
|
-
Requires-Dist: websocket-client>=1.6.0
|
|
19
18
|
Provides-Extra: all
|
|
20
19
|
Requires-Dist: argon2-cffi>=25.0.0; extra == 'all'
|
|
21
20
|
Requires-Dist: eth-account>=0.13.0; extra == 'all'
|
|
22
21
|
Requires-Dist: faster-whisper>=1.1.0; extra == 'all'
|
|
23
|
-
Requires-Dist: hypercli-sdk[comfyui]>=2026.
|
|
22
|
+
Requires-Dist: hypercli-sdk[comfyui]>=2026.5.21; extra == 'all'
|
|
24
23
|
Requires-Dist: web3>=7.0.0; extra == 'all'
|
|
25
24
|
Requires-Dist: x402[evm,httpx]>=2.0.0; extra == 'all'
|
|
26
25
|
Provides-Extra: comfyui
|
|
27
|
-
Requires-Dist: hypercli-sdk[comfyui]>=2026.
|
|
26
|
+
Requires-Dist: hypercli-sdk[comfyui]>=2026.5.21; extra == 'comfyui'
|
|
28
27
|
Provides-Extra: dev
|
|
29
28
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
29
|
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
@@ -81,6 +80,7 @@ hyper flow text-to-image "a cinematic portrait" --x402
|
|
|
81
80
|
# HyperClaw checkout/config
|
|
82
81
|
hyper agent plans
|
|
83
82
|
hyper agent subscribe basic
|
|
83
|
+
hyper agent activate-code PROMO123
|
|
84
84
|
hyper agent config env
|
|
85
85
|
hyper agent exec <agent_id> "ls -la"
|
|
86
86
|
hyper agent shell <agent_id>
|
|
@@ -41,6 +41,7 @@ hyper flow text-to-image "a cinematic portrait" --x402
|
|
|
41
41
|
# HyperClaw checkout/config
|
|
42
42
|
hyper agent plans
|
|
43
43
|
hyper agent subscribe basic
|
|
44
|
+
hyper agent activate-code PROMO123
|
|
44
45
|
hyper agent config env
|
|
45
46
|
hyper agent exec <agent_id> "ls -la"
|
|
46
47
|
hyper agent shell <agent_id>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2026.5.21"
|
|
@@ -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():
|
|
@@ -498,6 +498,34 @@ def subscription_summary(
|
|
|
498
498
|
)
|
|
499
499
|
|
|
500
500
|
|
|
501
|
+
@app.command("activate-code")
|
|
502
|
+
def activate_code(
|
|
503
|
+
code: str = typer.Argument(..., help="Activation or promo code to redeem"),
|
|
504
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
505
|
+
json_output: bool = typer.Option(False, "--json", help="Print raw JSON response"),
|
|
506
|
+
):
|
|
507
|
+
"""Redeem a HyperClaw activation code for the current account."""
|
|
508
|
+
client = _get_agent_query_client(dev)
|
|
509
|
+
result = client.agent.redeem_grant_code(code)
|
|
510
|
+
|
|
511
|
+
if json_output:
|
|
512
|
+
console.print_json(json.dumps(result, indent=2, default=str))
|
|
513
|
+
return
|
|
514
|
+
|
|
515
|
+
grant = result.get("grant") or {}
|
|
516
|
+
entitlement = result.get("entitlement") or {}
|
|
517
|
+
console.print("\n[bold]HyperClaw Code Activated[/bold]\n")
|
|
518
|
+
console.print(f"Code: [bold]{grant.get('code') or code}[/bold]")
|
|
519
|
+
console.print(f"Plan: [bold]{entitlement.get('plan_name') or entitlement.get('plan_id') or grant.get('plan_id') or ''}[/bold]")
|
|
520
|
+
if entitlement.get("starts_at"):
|
|
521
|
+
console.print(f"Starts: {entitlement.get('starts_at')}")
|
|
522
|
+
if entitlement.get("expires_at"):
|
|
523
|
+
console.print(f"Expires: {entitlement.get('expires_at')}")
|
|
524
|
+
tags = entitlement.get("tags") or grant.get("tags") or []
|
|
525
|
+
if tags:
|
|
526
|
+
console.print(f"Tags: {', '.join(str(tag) for tag in tags)}")
|
|
527
|
+
|
|
528
|
+
|
|
501
529
|
@app.command("models")
|
|
502
530
|
def models(
|
|
503
531
|
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
@@ -721,7 +749,21 @@ def fetch_models(api_key: str, api_base: str = PROD_INFERENCE_API_BASE) -> list[
|
|
|
721
749
|
normalized = (model_id or "").strip().lower()
|
|
722
750
|
aliases = {
|
|
723
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},
|
|
724
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
|
+
},
|
|
725
767
|
"glm-5": {"name": "GLM-5", "reasoning": True, "contextWindow": 202752},
|
|
726
768
|
"zai-org/glm-5": {"name": "GLM-5", "reasoning": True, "contextWindow": 202752},
|
|
727
769
|
"qwen3-embedding-4b": {
|
|
@@ -786,9 +828,21 @@ def fetch_models(api_key: str, api_base: str = PROD_INFERENCE_API_BASE) -> list[
|
|
|
786
828
|
]
|
|
787
829
|
|
|
788
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
|
+
|
|
789
843
|
@app.command("openclaw-setup")
|
|
790
844
|
def openclaw_setup(
|
|
791
|
-
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"),
|
|
792
846
|
):
|
|
793
847
|
"""Patch OpenClaw config with your HyperClaw API key.
|
|
794
848
|
|
|
@@ -884,8 +938,8 @@ def _config_openclaw(
|
|
|
884
938
|
|
|
885
939
|
api_base = api_base.rstrip("/")
|
|
886
940
|
supported_models = [m for m in models if _is_supported_openclaw_model(m)]
|
|
887
|
-
chat_models = [m for m in supported_models if m.get("mode") != "embedding"]
|
|
888
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"])
|
|
889
943
|
kimi_models = [m for m in chat_models if "kimi" in _model_suffix(m.get("id", ""))]
|
|
890
944
|
glm_models = [m for m in chat_models if _model_suffix(m.get("id", "")) == "glm-5"]
|
|
891
945
|
other_chat_models = [
|
|
@@ -946,6 +1000,7 @@ def _config_openclaw(
|
|
|
946
1000
|
def _config_opencode(api_key: str, models: list[dict], api_base: str = PROD_INFERENCE_API_BASE) -> dict:
|
|
947
1001
|
"""OpenCode opencode.json provider snippet."""
|
|
948
1002
|
api_base = api_base.rstrip("/")
|
|
1003
|
+
models = _preferred_agent_models(models)
|
|
949
1004
|
model_entries = {}
|
|
950
1005
|
for m in models:
|
|
951
1006
|
model_entries[m["id"]] = {"name": m["id"]}
|
|
@@ -953,7 +1008,7 @@ def _config_opencode(api_key: str, models: list[dict], api_base: str = PROD_INFE
|
|
|
953
1008
|
"$schema": "https://opencode.ai/config.json",
|
|
954
1009
|
"provider": {
|
|
955
1010
|
"hypercli": {
|
|
956
|
-
"npm": "@ai-sdk/
|
|
1011
|
+
"npm": "@ai-sdk/anthropic",
|
|
957
1012
|
"name": "HyperCLI",
|
|
958
1013
|
"options": {
|
|
959
1014
|
"baseURL": f"{api_base}/v1",
|
|
@@ -961,13 +1016,15 @@ def _config_opencode(api_key: str, models: list[dict], api_base: str = PROD_INFE
|
|
|
961
1016
|
},
|
|
962
1017
|
"models": model_entries,
|
|
963
1018
|
}
|
|
964
|
-
}
|
|
1019
|
+
},
|
|
1020
|
+
"model": f"hypercli/{models[0]['id']}",
|
|
965
1021
|
}
|
|
966
1022
|
|
|
967
1023
|
|
|
968
1024
|
def _config_env(api_key: str, models: list[dict], api_base: str = PROD_INFERENCE_API_BASE) -> str:
|
|
969
1025
|
"""Shell env vars for generic OpenAI-compatible tools."""
|
|
970
1026
|
api_base = api_base.rstrip("/")
|
|
1027
|
+
models = _preferred_agent_models(models)
|
|
971
1028
|
lines = [
|
|
972
1029
|
f'export OPENAI_API_KEY="{api_key}"',
|
|
973
1030
|
f'export OPENAI_BASE_URL="{api_base}/v1"',
|
|
@@ -13,6 +13,7 @@ from rich.console import Console
|
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
15
|
from hypercli.agents import 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():
|
|
@@ -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
|
|
@@ -15,6 +16,24 @@ from .output import output, spinner
|
|
|
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:
|
|
@@ -107,8 +126,32 @@ def me_cmd(
|
|
|
107
126
|
client = HyperCLI()
|
|
108
127
|
with spinner("Resolving auth context..."):
|
|
109
128
|
auth_me = client.user.auth_me()
|
|
129
|
+
entitlement_summary = None
|
|
130
|
+
entitlement_error = None
|
|
131
|
+
try:
|
|
132
|
+
entitlement_summary = client.agent.subscription_summary()
|
|
133
|
+
except APIError as exc:
|
|
134
|
+
entitlement_error = f"{exc.status_code}: {exc.detail}"
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
entitlement_error = str(exc)
|
|
110
137
|
if fmt == "json":
|
|
111
|
-
|
|
138
|
+
payload = dict(getattr(auth_me, "__dict__", {}))
|
|
139
|
+
if entitlement_summary is not None:
|
|
140
|
+
payload["agents_entitlements"] = {
|
|
141
|
+
"effective_plan_id": entitlement_summary.effective_plan_id,
|
|
142
|
+
"current_subscription_id": entitlement_summary.current_subscription_id,
|
|
143
|
+
"current_entitlement_id": entitlement_summary.current_entitlement_id,
|
|
144
|
+
"active_subscription_count": entitlement_summary.active_subscription_count,
|
|
145
|
+
"active_entitlement_count": entitlement_summary.active_entitlement_count,
|
|
146
|
+
"pooled_tpm_limit": entitlement_summary.pooled_tpm_limit,
|
|
147
|
+
"pooled_rpm_limit": entitlement_summary.pooled_rpm_limit,
|
|
148
|
+
"pooled_tpd": entitlement_summary.pooled_tpd,
|
|
149
|
+
"billing_reset_at": entitlement_summary.billing_reset_at,
|
|
150
|
+
"entitlement_items": [item.__dict__ for item in entitlement_summary.entitlement_items],
|
|
151
|
+
}
|
|
152
|
+
elif entitlement_error:
|
|
153
|
+
payload["agents_entitlements_error"] = entitlement_error
|
|
154
|
+
output(payload, fmt)
|
|
112
155
|
return
|
|
113
156
|
|
|
114
157
|
table = Table(show_header=False, box=None)
|
|
@@ -127,6 +170,25 @@ def me_cmd(
|
|
|
127
170
|
raw_capabilities = list(getattr(auth_me, "capabilities", []) or [])
|
|
128
171
|
capabilities = "\n".join(raw_capabilities) if raw_capabilities else ""
|
|
129
172
|
table.add_row("capabilities", capabilities)
|
|
173
|
+
if entitlement_summary is not None:
|
|
174
|
+
table.add_row("agents_effective_plan", entitlement_summary.effective_plan_id)
|
|
175
|
+
table.add_row("agents_current_entitlement", str(entitlement_summary.current_entitlement_id or ""))
|
|
176
|
+
table.add_row("agents_active_entitlements", str(entitlement_summary.active_entitlement_count))
|
|
177
|
+
active_items = [item for item in entitlement_summary.entitlement_items if str(item.status).upper() == "ACTIVE"]
|
|
178
|
+
expires_at = max((item.expires_at for item in active_items if item.expires_at), default=None)
|
|
179
|
+
if expires_at:
|
|
180
|
+
table.add_row("agents_expires_at", expires_at.isoformat())
|
|
181
|
+
table.add_row("agents_time_left", _format_time_left(expires_at))
|
|
182
|
+
table.add_row(
|
|
183
|
+
"agents_limits",
|
|
184
|
+
(
|
|
185
|
+
f"{entitlement_summary.pooled_tpm_limit:,} TPM / "
|
|
186
|
+
f"{entitlement_summary.pooled_rpm_limit:,} RPM / "
|
|
187
|
+
f"{entitlement_summary.pooled_tpd:,} TPD"
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
elif entitlement_error:
|
|
191
|
+
table.add_row("agents_entitlements", f"unavailable ({entitlement_error})")
|
|
130
192
|
console.print(table)
|
|
131
193
|
|
|
132
194
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""HyperClaw Voice API commands — TTS, clone, design"""
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
from datetime import datetime, timezone
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import typer
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from hypercli import HyperCLI, APIError
|
|
10
|
+
from hypercli.config import get_agent_api_key, get_api_key
|
|
9
11
|
from .stt import transcribe as _stt_transcribe
|
|
10
12
|
|
|
11
13
|
app = typer.Typer(help="Voice commands — text-to-speech, voice cloning, voice design, and local transcription")
|
|
@@ -17,22 +19,30 @@ DEFAULT_API_BASE = "https://api.hypercli.com"
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def _get_api_key(key: str | None) -> str:
|
|
20
|
-
"""Resolve API key
|
|
22
|
+
"""Resolve API key from canonical config before legacy agent-key storage."""
|
|
21
23
|
if key:
|
|
22
24
|
return key
|
|
23
|
-
|
|
24
|
-
if
|
|
25
|
-
return
|
|
26
|
-
env_key = os.environ.get("HYPER_AGENTS_API_KEY", "").strip()
|
|
27
|
-
if env_key:
|
|
28
|
-
return env_key
|
|
25
|
+
configured = (get_api_key() or get_agent_api_key() or "").strip()
|
|
26
|
+
if configured:
|
|
27
|
+
return configured
|
|
29
28
|
if AGENT_KEY_PATH.exists():
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
try:
|
|
30
|
+
with open(AGENT_KEY_PATH) as f:
|
|
31
|
+
saved = json.load(f)
|
|
32
|
+
expires_at = saved.get("expires_at")
|
|
33
|
+
if expires_at:
|
|
34
|
+
expires = datetime.fromisoformat(str(expires_at).replace("Z", "+00:00"))
|
|
35
|
+
if expires.tzinfo is None:
|
|
36
|
+
expires = expires.replace(tzinfo=timezone.utc)
|
|
37
|
+
if expires <= datetime.now(timezone.utc):
|
|
38
|
+
saved = {}
|
|
39
|
+
k = str(saved.get("key", "")).strip()
|
|
40
|
+
if k:
|
|
41
|
+
return k
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
34
44
|
console.print("[red]❌ No API key found.[/red]")
|
|
35
|
-
console.print("Pass [bold]--key
|
|
45
|
+
console.print("Pass [bold]--key[/bold], set [bold]HYPER_API_KEY[/bold], or run [bold]hyper configure[/bold].")
|
|
36
46
|
raise typer.Exit(1)
|
|
37
47
|
|
|
38
48
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hypercli-cli"
|
|
7
|
-
version = "2026.
|
|
7
|
+
version = "2026.5.21"
|
|
8
8
|
description = "CLI for HyperCLI - GPU orchestration and LLM API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -13,11 +13,10 @@ authors = [
|
|
|
13
13
|
{ name = "HyperCLI", email = "support@hypercli.com" }
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"hypercli-sdk>=2026.
|
|
16
|
+
"hypercli-sdk>=2026.5.21",
|
|
17
17
|
"openai>=2.8.1",
|
|
18
18
|
"typer>=0.20.0",
|
|
19
19
|
"rich>=14.2.0",
|
|
20
|
-
"websocket-client>=1.6.0",
|
|
21
20
|
"mutagen>=1.47.0",
|
|
22
21
|
"httpx>=0.27.0",
|
|
23
22
|
"pyyaml>=6.0",
|
|
@@ -25,7 +24,7 @@ dependencies = [
|
|
|
25
24
|
|
|
26
25
|
[project.optional-dependencies]
|
|
27
26
|
comfyui = [
|
|
28
|
-
"hypercli-sdk[comfyui]>=2026.
|
|
27
|
+
"hypercli-sdk[comfyui]>=2026.5.21",
|
|
29
28
|
]
|
|
30
29
|
wallet = [
|
|
31
30
|
"x402[httpx,evm]>=2.0.0",
|
|
@@ -38,7 +37,7 @@ stt = [
|
|
|
38
37
|
"faster-whisper>=1.1.0",
|
|
39
38
|
]
|
|
40
39
|
all = [
|
|
41
|
-
"hypercli-sdk[comfyui]>=2026.
|
|
40
|
+
"hypercli-sdk[comfyui]>=2026.5.21",
|
|
42
41
|
"x402[httpx,evm]>=2.0.0",
|
|
43
42
|
"eth-account>=0.13.0",
|
|
44
43
|
"web3>=7.0.0",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_agents_cli_prefers_agent_key_env(monkeypatch):
|
|
7
|
+
monkeypatch.setenv("HYPER_API_KEY", "sk-product")
|
|
8
|
+
monkeypatch.setenv("HYPER_AGENTS_API_KEY", "sk-agent")
|
|
9
|
+
|
|
10
|
+
import hypercli_cli.agents as agents
|
|
11
|
+
|
|
12
|
+
importlib.reload(agents)
|
|
13
|
+
|
|
14
|
+
assert agents._get_agent_api_key() == "sk-agent"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_agents_cli_uses_config_without_legacy_agent_key(monkeypatch, tmp_path):
|
|
18
|
+
monkeypatch.delenv("HYPER_API_KEY", raising=False)
|
|
19
|
+
monkeypatch.delenv("HYPERCLI_API_KEY", raising=False)
|
|
20
|
+
monkeypatch.delenv("HYPER_AGENTS_API_KEY", raising=False)
|
|
21
|
+
|
|
22
|
+
config_path = tmp_path / "config"
|
|
23
|
+
config_path.write_text("HYPER_API_KEY=hyper_api_config\n")
|
|
24
|
+
|
|
25
|
+
import hypercli.config as config
|
|
26
|
+
import hypercli_cli.agents as agents
|
|
27
|
+
|
|
28
|
+
importlib.reload(config)
|
|
29
|
+
importlib.reload(agents)
|
|
30
|
+
monkeypatch.setattr(config, "CONFIG_FILE", config_path)
|
|
31
|
+
monkeypatch.setattr(agents, "AGENT_KEY_PATH", tmp_path / "missing-agent-key.json")
|
|
32
|
+
|
|
33
|
+
assert agents._get_agent_api_key() == "hyper_api_config"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_agent_activate_uses_config_without_legacy_agent_key(monkeypatch, tmp_path):
|
|
37
|
+
monkeypatch.delenv("HYPER_API_KEY", raising=False)
|
|
38
|
+
monkeypatch.delenv("HYPERCLI_API_KEY", raising=False)
|
|
39
|
+
monkeypatch.delenv("HYPER_AGENTS_API_KEY", raising=False)
|
|
40
|
+
|
|
41
|
+
config_path = tmp_path / "config"
|
|
42
|
+
config_path.write_text("HYPER_API_KEY=hyper_api_config\n")
|
|
43
|
+
|
|
44
|
+
import hypercli.config as config
|
|
45
|
+
import hypercli_cli.agent as agent
|
|
46
|
+
|
|
47
|
+
importlib.reload(config)
|
|
48
|
+
importlib.reload(agent)
|
|
49
|
+
monkeypatch.setattr(config, "CONFIG_FILE", config_path)
|
|
50
|
+
monkeypatch.setattr(agent, "AGENT_KEY_PATH", tmp_path / "missing-agent-key.json")
|
|
51
|
+
|
|
52
|
+
assert agent._resolve_agent_query_key() == "hyper_api_config"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_agents_cli_prefers_agent_base_env(monkeypatch):
|
|
56
|
+
monkeypatch.setenv("AGENTS_API_BASE_URL", "https://api.agents.dev.hypercli.com")
|
|
57
|
+
monkeypatch.setenv("AGENTS_WS_URL", "wss://api.agents.dev.hypercli.com/ws")
|
|
58
|
+
monkeypatch.setenv("HYPER_AGENTS_API_KEY", "sk-agent")
|
|
59
|
+
|
|
60
|
+
import hypercli_cli.agents as agents
|
|
61
|
+
|
|
62
|
+
importlib.reload(agents)
|
|
63
|
+
|
|
64
|
+
client = agents._get_deployments_client()
|
|
65
|
+
assert client._api_base == "https://api.dev.hypercli.com/agents"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_voice_cli_prefers_product_envs(monkeypatch):
|
|
69
|
+
monkeypatch.setenv("HYPER_API_KEY", "sk-product")
|
|
70
|
+
monkeypatch.setenv("HYPER_AGENTS_API_KEY", "sk-agent")
|
|
71
|
+
monkeypatch.setenv("HYPER_API_BASE", "https://api.hypercli.com")
|
|
72
|
+
monkeypatch.setenv("HYPERCLI_API_URL", "https://api.dev.hypercli.com")
|
|
73
|
+
|
|
74
|
+
import hypercli_cli.voice as voice
|
|
75
|
+
|
|
76
|
+
importlib.reload(voice)
|
|
77
|
+
|
|
78
|
+
assert voice._get_api_key(None) == "sk-product"
|
|
79
|
+
assert voice._resolve_api_base(None) == "https://api.hypercli.com"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_voice_cli_uses_config_before_expired_agent_key(monkeypatch, tmp_path):
|
|
83
|
+
monkeypatch.delenv("HYPER_API_KEY", raising=False)
|
|
84
|
+
monkeypatch.delenv("HYPERCLI_API_KEY", raising=False)
|
|
85
|
+
monkeypatch.delenv("HYPER_AGENTS_API_KEY", raising=False)
|
|
86
|
+
|
|
87
|
+
config_path = tmp_path / "config"
|
|
88
|
+
config_path.write_text("HYPER_API_KEY=hyper_api_config\n")
|
|
89
|
+
agent_key_path = tmp_path / "agent-key.json"
|
|
90
|
+
agent_key_path.write_text(
|
|
91
|
+
json.dumps(
|
|
92
|
+
{
|
|
93
|
+
"key": "hyper_api_expired",
|
|
94
|
+
"expires_at": "2026-01-01T00:00:00+00:00",
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
import hypercli.config as config
|
|
100
|
+
import hypercli_cli.voice as voice
|
|
101
|
+
|
|
102
|
+
importlib.reload(config)
|
|
103
|
+
importlib.reload(voice)
|
|
104
|
+
monkeypatch.setattr(config, "CONFIG_FILE", config_path)
|
|
105
|
+
monkeypatch.setattr(voice, "AGENT_KEY_PATH", agent_key_path)
|
|
106
|
+
|
|
107
|
+
assert voice._get_api_key(None) == "hyper_api_config"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_voice_cli_posts_to_agents_voice_prefix(monkeypatch, tmp_path):
|
|
111
|
+
monkeypatch.setenv("HYPER_API_KEY", "sk-product")
|
|
112
|
+
monkeypatch.setenv("HYPER_API_BASE", "https://api.dev.hypercli.com")
|
|
113
|
+
|
|
114
|
+
import hypercli_cli.voice as voice
|
|
115
|
+
|
|
116
|
+
importlib.reload(voice)
|
|
117
|
+
|
|
118
|
+
called = {}
|
|
119
|
+
|
|
120
|
+
class _FakeVoice:
|
|
121
|
+
def tts(self, **kwargs):
|
|
122
|
+
called["kwargs"] = kwargs
|
|
123
|
+
return b"audio"
|
|
124
|
+
|
|
125
|
+
class _FakeHyperCLI:
|
|
126
|
+
def __init__(self, *, api_key, api_url):
|
|
127
|
+
called["api_key"] = api_key
|
|
128
|
+
called["api_url"] = api_url
|
|
129
|
+
self.voice = _FakeVoice()
|
|
130
|
+
|
|
131
|
+
monkeypatch.setattr(voice, "HyperCLI", _FakeHyperCLI)
|
|
132
|
+
|
|
133
|
+
out = tmp_path / "voice.wav"
|
|
134
|
+
voice._post_voice("tts", "sk-product", out, text="hello", voice="Chelsie")
|
|
135
|
+
|
|
136
|
+
assert called["api_url"] == "https://api.dev.hypercli.com"
|
|
137
|
+
assert called["api_key"] == "sk-product"
|
|
138
|
+
assert called["kwargs"] == {"text": "hello", "voice": "Chelsie"}
|
|
139
|
+
assert out.read_bytes() == b"audio"
|
|
@@ -108,3 +108,32 @@ def test_extract_plan_purchase_url_from_discovery_ignores_nonmatching_resources(
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
assert agent_mod._extract_plan_purchase_url_from_discovery(discovery, "basic") is None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_agent_activate_code_redeems_via_sdk(monkeypatch):
|
|
114
|
+
class _FakeAgent:
|
|
115
|
+
def redeem_grant_code(self, code: str):
|
|
116
|
+
assert code == "promo-123"
|
|
117
|
+
return {
|
|
118
|
+
"grant": {"id": "grant-1", "type": "ACTIVATION_CODE", "code": "promo-123", "plan_id": "basic"},
|
|
119
|
+
"entitlement": {
|
|
120
|
+
"id": "ent-1",
|
|
121
|
+
"plan_id": "basic",
|
|
122
|
+
"plan_name": "Basic",
|
|
123
|
+
"starts_at": "2026-04-27T00:00:00Z",
|
|
124
|
+
"expires_at": "2026-05-27T00:00:00Z",
|
|
125
|
+
"tags": ["customer=acme"],
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class _FakeClient:
|
|
130
|
+
agent = _FakeAgent()
|
|
131
|
+
|
|
132
|
+
monkeypatch.setattr(agent_mod, "_get_agent_query_client", lambda dev: _FakeClient())
|
|
133
|
+
|
|
134
|
+
result = runner.invoke(app, ["agent", "activate-code", "promo-123"])
|
|
135
|
+
|
|
136
|
+
assert result.exit_code == 0
|
|
137
|
+
assert "HyperClaw Code Activated" in result.output
|
|
138
|
+
assert "promo-123" in result.output
|
|
139
|
+
assert "Basic" in result.output
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
|
|
4
|
+
from typer.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from hypercli_cli.cli import app
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
runner = CliRunner()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_me_command_outputs_capabilities(monkeypatch):
|
|
13
|
+
class FakeUserAPI:
|
|
14
|
+
def auth_me(self):
|
|
15
|
+
return SimpleNamespace(
|
|
16
|
+
user_id="user-123",
|
|
17
|
+
orchestra_user_id="orch-123",
|
|
18
|
+
team_id="team-123",
|
|
19
|
+
plan_id="pro",
|
|
20
|
+
email="user@example.com",
|
|
21
|
+
auth_type="orchestra_key",
|
|
22
|
+
capabilities=["models:*", "voice:*"],
|
|
23
|
+
key_id="key-123",
|
|
24
|
+
key_name="runtime-key",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
class FakeClient:
|
|
28
|
+
user = FakeUserAPI()
|
|
29
|
+
|
|
30
|
+
monkeypatch.setattr("hypercli_cli.cli.HyperCLI", lambda: FakeClient())
|
|
31
|
+
|
|
32
|
+
result = runner.invoke(app, ["me"])
|
|
33
|
+
|
|
34
|
+
assert result.exit_code == 0
|
|
35
|
+
assert "models:*" in result.stdout
|
|
36
|
+
assert "voice:*" in result.stdout
|
|
37
|
+
assert "runtime-key" in result.stdout
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_me_command_outputs_agents_entitlement_summary(monkeypatch):
|
|
41
|
+
class FakeUserAPI:
|
|
42
|
+
def auth_me(self):
|
|
43
|
+
return SimpleNamespace(
|
|
44
|
+
user_id="user-123",
|
|
45
|
+
orchestra_user_id=None,
|
|
46
|
+
team_id="",
|
|
47
|
+
plan_id="",
|
|
48
|
+
email=None,
|
|
49
|
+
auth_type="api_key",
|
|
50
|
+
capabilities=["*:*"],
|
|
51
|
+
has_active_subscription=False,
|
|
52
|
+
key_id="key-123",
|
|
53
|
+
key_name="gpu-operator-prod",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
class FakeAgentAPI:
|
|
57
|
+
def subscription_summary(self):
|
|
58
|
+
return SimpleNamespace(
|
|
59
|
+
effective_plan_id="pro",
|
|
60
|
+
current_subscription_id=None,
|
|
61
|
+
current_entitlement_id="ent-123",
|
|
62
|
+
active_subscription_count=0,
|
|
63
|
+
active_entitlement_count=1,
|
|
64
|
+
pooled_tpm_limit=17_361_100,
|
|
65
|
+
pooled_rpm_limit=1_736,
|
|
66
|
+
pooled_tpd=500_000_000,
|
|
67
|
+
billing_reset_at=None,
|
|
68
|
+
entitlement_items=[
|
|
69
|
+
SimpleNamespace(
|
|
70
|
+
plan_id="pro",
|
|
71
|
+
status="ACTIVE",
|
|
72
|
+
expires_at=datetime(2036, 5, 16, 11, 36, 49, tzinfo=timezone.utc),
|
|
73
|
+
)
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
class FakeClient:
|
|
78
|
+
user = FakeUserAPI()
|
|
79
|
+
agent = FakeAgentAPI()
|
|
80
|
+
|
|
81
|
+
monkeypatch.setattr("hypercli_cli.cli.HyperCLI", lambda: FakeClient())
|
|
82
|
+
|
|
83
|
+
result = runner.invoke(app, ["me"])
|
|
84
|
+
|
|
85
|
+
assert result.exit_code == 0
|
|
86
|
+
assert "has_active_subscription" in result.stdout
|
|
87
|
+
assert "no" in result.stdout
|
|
88
|
+
assert "agents_effective_plan" in result.stdout
|
|
89
|
+
assert "pro" in result.stdout
|
|
90
|
+
assert "agents_time_left" in result.stdout
|
|
91
|
+
assert "17,361,100 TPM / 1,736 RPM / 500,000,000 TPD" in result.stdout
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2026.4.24"
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import importlib
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def test_agents_cli_prefers_agent_key_env(monkeypatch):
|
|
6
|
-
monkeypatch.setenv("HYPER_API_KEY", "sk-product")
|
|
7
|
-
monkeypatch.setenv("HYPER_AGENTS_API_KEY", "sk-agent")
|
|
8
|
-
|
|
9
|
-
import hypercli_cli.agents as agents
|
|
10
|
-
|
|
11
|
-
importlib.reload(agents)
|
|
12
|
-
|
|
13
|
-
assert agents._get_agent_api_key() == "sk-agent"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_agents_cli_prefers_agent_base_env(monkeypatch):
|
|
17
|
-
monkeypatch.setenv("AGENTS_API_BASE_URL", "https://api.agents.dev.hypercli.com")
|
|
18
|
-
monkeypatch.setenv("AGENTS_WS_URL", "wss://api.agents.dev.hypercli.com/ws")
|
|
19
|
-
monkeypatch.setenv("HYPER_AGENTS_API_KEY", "sk-agent")
|
|
20
|
-
|
|
21
|
-
import hypercli_cli.agents as agents
|
|
22
|
-
|
|
23
|
-
importlib.reload(agents)
|
|
24
|
-
|
|
25
|
-
client = agents._get_deployments_client()
|
|
26
|
-
assert client._api_base == "https://api.dev.hypercli.com/agents"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_voice_cli_prefers_product_envs(monkeypatch):
|
|
30
|
-
monkeypatch.setenv("HYPER_API_KEY", "sk-product")
|
|
31
|
-
monkeypatch.setenv("HYPER_AGENTS_API_KEY", "sk-agent")
|
|
32
|
-
monkeypatch.setenv("HYPER_API_BASE", "https://api.hypercli.com")
|
|
33
|
-
monkeypatch.setenv("HYPERCLI_API_URL", "https://api.dev.hypercli.com")
|
|
34
|
-
|
|
35
|
-
import hypercli_cli.voice as voice
|
|
36
|
-
|
|
37
|
-
importlib.reload(voice)
|
|
38
|
-
|
|
39
|
-
assert voice._get_api_key(None) == "sk-product"
|
|
40
|
-
assert voice._resolve_api_base(None) == "https://api.hypercli.com"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_voice_cli_posts_to_agents_voice_prefix(monkeypatch, tmp_path):
|
|
44
|
-
monkeypatch.setenv("HYPER_API_KEY", "sk-product")
|
|
45
|
-
monkeypatch.setenv("HYPER_API_BASE", "https://api.dev.hypercli.com")
|
|
46
|
-
|
|
47
|
-
import hypercli_cli.voice as voice
|
|
48
|
-
|
|
49
|
-
importlib.reload(voice)
|
|
50
|
-
|
|
51
|
-
called = {}
|
|
52
|
-
|
|
53
|
-
class _FakeVoice:
|
|
54
|
-
def tts(self, **kwargs):
|
|
55
|
-
called["kwargs"] = kwargs
|
|
56
|
-
return b"audio"
|
|
57
|
-
|
|
58
|
-
class _FakeHyperCLI:
|
|
59
|
-
def __init__(self, *, api_key, api_url):
|
|
60
|
-
called["api_key"] = api_key
|
|
61
|
-
called["api_url"] = api_url
|
|
62
|
-
self.voice = _FakeVoice()
|
|
63
|
-
|
|
64
|
-
monkeypatch.setattr(voice, "HyperCLI", _FakeHyperCLI)
|
|
65
|
-
|
|
66
|
-
out = tmp_path / "voice.wav"
|
|
67
|
-
voice._post_voice("tts", "sk-product", out, text="hello", voice="Chelsie")
|
|
68
|
-
|
|
69
|
-
assert called["api_url"] == "https://api.dev.hypercli.com"
|
|
70
|
-
assert called["api_key"] == "sk-product"
|
|
71
|
-
assert called["kwargs"] == {"text": "hello", "voice": "Chelsie"}
|
|
72
|
-
assert out.read_bytes() == b"audio"
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from types import SimpleNamespace
|
|
2
|
-
|
|
3
|
-
from typer.testing import CliRunner
|
|
4
|
-
|
|
5
|
-
from hypercli_cli.cli import app
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
runner = CliRunner()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_me_command_outputs_capabilities(monkeypatch):
|
|
12
|
-
class FakeUserAPI:
|
|
13
|
-
def auth_me(self):
|
|
14
|
-
return SimpleNamespace(
|
|
15
|
-
user_id="user-123",
|
|
16
|
-
orchestra_user_id="orch-123",
|
|
17
|
-
team_id="team-123",
|
|
18
|
-
plan_id="pro",
|
|
19
|
-
email="user@example.com",
|
|
20
|
-
auth_type="orchestra_key",
|
|
21
|
-
capabilities=["models:*", "voice:*"],
|
|
22
|
-
key_id="key-123",
|
|
23
|
-
key_name="runtime-key",
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
class FakeClient:
|
|
27
|
-
user = FakeUserAPI()
|
|
28
|
-
|
|
29
|
-
monkeypatch.setattr("hypercli_cli.cli.HyperCLI", lambda: FakeClient())
|
|
30
|
-
|
|
31
|
-
result = runner.invoke(app, ["me"])
|
|
32
|
-
|
|
33
|
-
assert result.exit_code == 0
|
|
34
|
-
assert "models:*" in result.stdout
|
|
35
|
-
assert "voice:*" in result.stdout
|
|
36
|
-
assert "runtime-key" in result.stdout
|
|
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
|
|
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
|
|
File without changes
|
{hypercli_cli-2026.4.24 → hypercli_cli-2026.5.21}/tests/test_wallet_migration_integration.py
RENAMED
|
File without changes
|