hypercli-cli 2026.3.25__tar.gz → 2026.4.5__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 (31) hide show
  1. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/PKG-INFO +4 -4
  2. hypercli_cli-2026.4.5/hypercli_cli/__init__.py +1 -0
  3. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/cli.py +12 -0
  4. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/flow.py +1 -1
  5. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/jobs.py +3 -3
  6. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/keys.py +9 -2
  7. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/voice.py +60 -61
  8. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/pyproject.toml +4 -4
  9. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/tests/test_agent_env_resolution.py +33 -0
  10. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/tests/test_jobs_list_tags.py +1 -1
  11. hypercli_cli-2026.4.5/tests/test_me_command.py +36 -0
  12. hypercli_cli-2026.3.25/hypercli_cli/__init__.py +0 -1
  13. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/.gitignore +0 -0
  14. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/README.md +0 -0
  15. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/agent.py +0 -0
  16. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/agents.py +0 -0
  17. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/billing.py +0 -0
  18. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/comfyui.py +0 -0
  19. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/embed.py +0 -0
  20. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/files.py +0 -0
  21. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/instances.py +0 -0
  22. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/onboard.py +0 -0
  23. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/output.py +0 -0
  24. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/renders.py +0 -0
  25. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/stt.py +0 -0
  26. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/tui/__init__.py +0 -0
  27. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/tui/job_monitor.py +0 -0
  28. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/user.py +0 -0
  29. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/hypercli_cli/wallet.py +0 -0
  30. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/tests/test_exec_shell_dryrun.py +0 -0
  31. {hypercli_cli-2026.3.25 → hypercli_cli-2026.4.5}/tests/test_openclaw_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercli-cli
3
- Version: 2026.3.25
3
+ Version: 2026.4.5
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,7 +9,7 @@ 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.3.25
12
+ Requires-Dist: hypercli-sdk>=2026.4.5
13
13
  Requires-Dist: mutagen>=1.47.0
14
14
  Requires-Dist: pyyaml>=6.0
15
15
  Requires-Dist: rich>=14.2.0
@@ -19,11 +19,11 @@ Provides-Extra: all
19
19
  Requires-Dist: argon2-cffi>=25.0.0; extra == 'all'
20
20
  Requires-Dist: eth-account>=0.13.0; extra == 'all'
21
21
  Requires-Dist: faster-whisper>=1.1.0; extra == 'all'
22
- Requires-Dist: hypercli-sdk[comfyui]>=2026.3.25; extra == 'all'
22
+ Requires-Dist: hypercli-sdk[comfyui]>=2026.4.5; extra == 'all'
23
23
  Requires-Dist: web3>=7.0.0; extra == 'all'
24
24
  Requires-Dist: x402[evm,httpx]>=2.0.0; extra == 'all'
25
25
  Provides-Extra: comfyui
26
- Requires-Dist: hypercli-sdk[comfyui]>=2026.3.25; extra == 'comfyui'
26
+ Requires-Dist: hypercli-sdk[comfyui]>=2026.4.5; extra == 'comfyui'
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
29
29
  Requires-Dist: ruff>=0.3.0; extra == 'dev'
@@ -0,0 +1 @@
1
+ __version__ = "2026.4.5"
@@ -9,6 +9,7 @@ from hypercli import HyperCLI, APIError, configure
9
9
  from hypercli.config import CONFIG_FILE
10
10
 
11
11
  from . import agent, agents, billing, comfyui, files, flow, instances, jobs, keys, user, wallet
12
+ from .output import output, spinner
12
13
 
13
14
  console = Console()
14
15
 
@@ -70,6 +71,17 @@ app.add_typer(user.app, name="user")
70
71
  app.add_typer(wallet.app, name="wallet")
71
72
 
72
73
 
74
+ @app.command("me")
75
+ def me_cmd(
76
+ fmt: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
77
+ ):
78
+ """Resolve the current auth context and show key capabilities."""
79
+ client = HyperCLI()
80
+ with spinner("Resolving auth context..."):
81
+ auth_me = client.user.auth_me()
82
+ output(auth_me, fmt)
83
+
84
+
73
85
  @app.command("configure")
74
86
  def configure_cmd():
75
87
  """Configure HyperCLI with your API key and API URL"""
@@ -117,7 +117,7 @@ def _create_flow_render(
117
117
  return x402_result.render, x402_result
118
118
 
119
119
  with spinner("Creating render..."):
120
- render = get_client().renders._flow(f"/api/flow/{flow_type}", **clean_payload)
120
+ render = get_client().renders.create_flow(flow_type, **clean_payload)
121
121
  return render, None
122
122
 
123
123
 
@@ -11,14 +11,14 @@ def get_client() -> HyperCLI:
11
11
  return HyperCLI()
12
12
 
13
13
 
14
- def _parse_tags(tag_args: list[str]) -> dict[str, str]:
15
- tags: dict[str, str] = {}
14
+ def _parse_tags(tag_args: list[str]) -> list[str]:
15
+ tags: list[str] = []
16
16
  for tag in tag_args:
17
17
  key, sep, value = tag.partition("=")
18
18
  if not sep or not key or not value:
19
19
  console.print(f"[red]Error:[/red] Invalid tag '{tag}'. Expected KEY=VALUE.")
20
20
  raise typer.Exit(1)
21
- tags[key] = value
21
+ tags.append(tag)
22
22
  return tags
23
23
 
24
24
 
@@ -23,13 +23,18 @@ def _get_client():
23
23
 
24
24
 
25
25
  @app.command("create")
26
- def create_key(name: str = typer.Option("default", help="Key name")):
26
+ def create_key(
27
+ name: str = typer.Option("default", help="Key name"),
28
+ tag: list[str] = typer.Option(None, "--tag", help="Repeat as --tag team=dev"),
29
+ ):
27
30
  """Create a new API key"""
28
31
  client = _get_client()
29
- key = client.keys.create(name=name)
32
+ key = client.keys.create(name=name, tags=tag or None)
30
33
  console.print(f"\n[bold green]API key created![/bold green]\n")
31
34
  console.print(f" Key ID: {key.key_id}")
32
35
  console.print(f" Name: {key.name}")
36
+ if key.tags:
37
+ console.print(f" Tags: {', '.join(key.tags)}")
33
38
  console.print(f" API Key: [bold]{key.api_key}[/bold]")
34
39
  console.print(f"\n[yellow]⚠ Save this key now — it won't be shown again.[/yellow]\n")
35
40
 
@@ -47,6 +52,7 @@ def list_keys():
47
52
  table = Table(title="API Keys")
48
53
  table.add_column("Key ID", style="dim")
49
54
  table.add_column("Name")
55
+ table.add_column("Tags")
50
56
  table.add_column("Key Preview")
51
57
  table.add_column("Active", justify="center")
52
58
  table.add_column("Created")
@@ -58,6 +64,7 @@ def list_keys():
58
64
  table.add_row(
59
65
  key.key_id[:8] + "...",
60
66
  key.name or "",
67
+ ", ".join(key.tags or []),
61
68
  key.api_key_preview or "",
62
69
  f"[{active_style}]{active}[/{active_style}]",
63
70
  _fmt_ts(key.created_at),
@@ -1,13 +1,11 @@
1
1
  """HyperClaw Voice API commands — TTS, clone, design"""
2
- import base64
3
2
  import json
4
3
  import os
5
- import sys
6
4
  from pathlib import Path
7
5
 
8
- import httpx
9
6
  import typer
10
7
  from rich.console import Console
8
+ from hypercli import HyperCLI, APIError
11
9
 
12
10
  app = typer.Typer(help="Voice API — text-to-speech, voice cloning, voice design")
13
11
  console = Console()
@@ -50,43 +48,39 @@ def _resolve_api_base(base_url: str | None) -> str:
50
48
  return DEFAULT_API_BASE
51
49
 
52
50
 
53
- def _post_voice(
54
- endpoint: str,
55
- payload: dict,
56
- api_key: str,
57
- output: Path,
58
- base_url: str | None = None,
59
- ):
60
- """POST to voice endpoint and save audio output."""
51
+ def _voice_client(api_key: str, base_url: str | None = None) -> HyperCLI:
61
52
  api_base = _resolve_api_base(base_url)
62
- url = f"{api_base}/voice/{endpoint}"
53
+ return HyperCLI(api_key=api_key, api_url=api_base)
54
+
55
+
56
+ def _save_voice_output(output: Path, audio: bytes) -> None:
57
+ output.parent.mkdir(parents=True, exist_ok=True)
58
+ output.write_bytes(audio)
59
+ size_kb = len(audio) / 1024
60
+ console.print(f"[green]✅ Saved {output} ({size_kb:.1f} KB)[/green]")
61
+
62
+
63
+ def _handle_voice_error(error: APIError) -> None:
64
+ detail = error.detail if isinstance(error.detail, str) else json.dumps(error.detail)
65
+ console.print(f"[red]❌ {error.status_code}: {detail[:500]}[/red]")
66
+ raise typer.Exit(1)
67
+
63
68
 
69
+ def _post_voice(endpoint: str, api_key: str, output: Path, base_url: str | None = None, **kwargs) -> None:
70
+ """POST to voice endpoint through the SDK and save audio output."""
71
+ api_base = _resolve_api_base(base_url)
72
+ url = f"{api_base}/agents/voice/{endpoint}"
64
73
  console.print(f"[dim]→ POST {url}[/dim]")
65
74
 
66
75
  try:
67
- with httpx.Client(timeout=600.0) as client:
68
- resp = client.post(
69
- url,
70
- json=payload,
71
- headers={
72
- "Authorization": f"Bearer {api_key}",
73
- "Content-Type": "application/json",
74
- },
75
- )
76
-
77
- if resp.status_code != 200:
78
- console.print(f"[red]❌ {resp.status_code}: {resp.text[:500]}[/red]")
79
- raise typer.Exit(1)
80
-
81
- output.parent.mkdir(parents=True, exist_ok=True)
82
- with open(output, "wb") as f:
83
- f.write(resp.content)
84
-
85
- size_kb = len(resp.content) / 1024
86
- console.print(f"[green]✅ Saved {output} ({size_kb:.1f} KB)[/green]")
87
-
88
- except httpx.HTTPError as e:
89
- console.print(f"[red]❌ Request failed: {e}[/red]")
76
+ client = _voice_client(api_key, base_url)
77
+ method = getattr(client.voice, endpoint)
78
+ audio = method(**kwargs)
79
+ _save_voice_output(output, audio)
80
+ except APIError as error:
81
+ _handle_voice_error(error)
82
+ except OSError as e:
83
+ console.print(f"[red]❌ File error: {e}[/red]")
90
84
  raise typer.Exit(1)
91
85
 
92
86
 
@@ -109,13 +103,16 @@ def tts(
109
103
  api_key = _get_api_key(key)
110
104
  if output is None:
111
105
  output = Path(f"output.{format}")
112
- payload = {
113
- "text": text,
114
- "voice": voice,
115
- "language": language,
116
- "response_format": format,
117
- }
118
- _post_voice("tts", payload, api_key, output, base_url)
106
+ _post_voice(
107
+ "tts",
108
+ api_key,
109
+ output,
110
+ base_url,
111
+ text=text,
112
+ voice=voice,
113
+ language=language,
114
+ response_format=format,
115
+ )
119
116
 
120
117
 
121
118
  @app.command("clone")
@@ -143,19 +140,18 @@ def clone(
143
140
  console.print(f"[red]❌ Reference audio not found: {ref_audio}[/red]")
144
141
  raise typer.Exit(1)
145
142
 
146
- with open(ref_audio, "rb") as f:
147
- ref_b64 = base64.b64encode(f.read()).decode()
148
-
149
143
  console.print(f"[dim]Reference: {ref_audio} ({ref_audio.stat().st_size / 1024:.1f} KB)[/dim]")
150
-
151
- payload = {
152
- "text": text,
153
- "ref_audio_base64": ref_b64,
154
- "language": language,
155
- "x_vector_only": x_vector_only,
156
- "response_format": format,
157
- }
158
- _post_voice("clone", payload, api_key, output, base_url)
144
+ _post_voice(
145
+ "clone",
146
+ api_key,
147
+ output,
148
+ base_url,
149
+ text=text,
150
+ ref_audio=ref_audio,
151
+ language=language,
152
+ x_vector_only=x_vector_only,
153
+ response_format=format,
154
+ )
159
155
 
160
156
 
161
157
  @app.command("design")
@@ -177,10 +173,13 @@ def design(
177
173
  api_key = _get_api_key(key)
178
174
  if output is None:
179
175
  output = Path(f"output.{format}")
180
- payload = {
181
- "text": text,
182
- "instruct": description,
183
- "language": language,
184
- "response_format": format,
185
- }
186
- _post_voice("design", payload, api_key, output, base_url)
176
+ _post_voice(
177
+ "design",
178
+ api_key,
179
+ output,
180
+ base_url,
181
+ text=text,
182
+ description=description,
183
+ language=language,
184
+ response_format=format,
185
+ )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hypercli-cli"
7
- version = "2026.3.25"
7
+ version = "2026.4.5"
8
8
  description = "CLI for HyperCLI - GPU orchestration and LLM API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -13,7 +13,7 @@ authors = [
13
13
  { name = "HyperCLI", email = "support@hypercli.com" }
14
14
  ]
15
15
  dependencies = [
16
- "hypercli-sdk>=2026.3.25",
16
+ "hypercli-sdk>=2026.4.5",
17
17
  "typer>=0.20.0",
18
18
  "rich>=14.2.0",
19
19
  "websocket-client>=1.6.0",
@@ -24,7 +24,7 @@ dependencies = [
24
24
 
25
25
  [project.optional-dependencies]
26
26
  comfyui = [
27
- "hypercli-sdk[comfyui]>=2026.3.25",
27
+ "hypercli-sdk[comfyui]>=2026.4.5",
28
28
  ]
29
29
  wallet = [
30
30
  "x402[httpx,evm]>=2.0.0",
@@ -37,7 +37,7 @@ stt = [
37
37
  "faster-whisper>=1.1.0",
38
38
  ]
39
39
  all = [
40
- "hypercli-sdk[comfyui]>=2026.3.25",
40
+ "hypercli-sdk[comfyui]>=2026.4.5",
41
41
  "x402[httpx,evm]>=2.0.0",
42
42
  "eth-account>=0.13.0",
43
43
  "web3>=7.0.0",
@@ -1,4 +1,5 @@
1
1
  import importlib
2
+ from pathlib import Path
2
3
 
3
4
 
4
5
  def test_agents_cli_prefers_agent_key_env(monkeypatch):
@@ -37,3 +38,35 @@ def test_voice_cli_prefers_product_envs(monkeypatch):
37
38
 
38
39
  assert voice._get_api_key(None) == "sk-product"
39
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"
@@ -30,7 +30,7 @@ def test_jobs_list_passes_tags(monkeypatch):
30
30
  assert result.exit_code == 0
31
31
  assert captured == {
32
32
  "state": "running",
33
- "tags": {"team": "ml", "env": "prod"},
33
+ "tags": ["team=ml", "env=prod"],
34
34
  "page": 1,
35
35
  "page_size": 50,
36
36
  }
@@ -0,0 +1,36 @@
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
@@ -1 +0,0 @@
1
- __version__ = "2026.3.22"