hypercli-cli 2026.4.5__tar.gz → 2026.4.7__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.5 → hypercli_cli-2026.4.7}/PKG-INFO +5 -4
- hypercli_cli-2026.4.7/hypercli_cli/__init__.py +1 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/agent.py +195 -64
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/cli.py +50 -2
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/flow.py +171 -93
- hypercli_cli-2026.4.7/hypercli_cli/llm.py +155 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/stt.py +3 -3
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/voice.py +28 -7
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/wallet.py +233 -120
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/pyproject.toml +10 -4
- hypercli_cli-2026.4.7/tests/test_config_command.py +50 -0
- hypercli_cli-2026.4.7/tests/test_flow_command.py +231 -0
- hypercli_cli-2026.4.7/tests/test_flow_visibility.py +17 -0
- hypercli_cli-2026.4.7/tests/test_llm_command.py +63 -0
- hypercli_cli-2026.4.7/tests/test_openclaw_config.py +159 -0
- hypercli_cli-2026.4.7/tests/test_voice_command.py +72 -0
- hypercli_cli-2026.4.7/tests/test_wallet_command.py +144 -0
- hypercli_cli-2026.4.7/tests/test_wallet_migration_integration.py +71 -0
- hypercli_cli-2026.4.5/hypercli_cli/__init__.py +0 -1
- hypercli_cli-2026.4.5/tests/test_openclaw_config.py +0 -63
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/.gitignore +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/README.md +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/agents.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/billing.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/comfyui.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/embed.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/files.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/instances.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/jobs.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/keys.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/onboard.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/output.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/renders.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/tui/__init__.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/tui/job_monitor.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/user.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_agent_env_resolution.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_exec_shell_dryrun.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_jobs_list_tags.py +0 -0
- {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_me_command.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypercli-cli
|
|
3
|
-
Version: 2026.4.
|
|
3
|
+
Version: 2026.4.7
|
|
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,8 +9,9 @@ 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.4.
|
|
12
|
+
Requires-Dist: hypercli-sdk>=2026.4.7
|
|
13
13
|
Requires-Dist: mutagen>=1.47.0
|
|
14
|
+
Requires-Dist: openai>=2.8.1
|
|
14
15
|
Requires-Dist: pyyaml>=6.0
|
|
15
16
|
Requires-Dist: rich>=14.2.0
|
|
16
17
|
Requires-Dist: typer>=0.20.0
|
|
@@ -19,11 +20,11 @@ Provides-Extra: all
|
|
|
19
20
|
Requires-Dist: argon2-cffi>=25.0.0; extra == 'all'
|
|
20
21
|
Requires-Dist: eth-account>=0.13.0; extra == 'all'
|
|
21
22
|
Requires-Dist: faster-whisper>=1.1.0; extra == 'all'
|
|
22
|
-
Requires-Dist: hypercli-sdk[comfyui]>=2026.4.
|
|
23
|
+
Requires-Dist: hypercli-sdk[comfyui]>=2026.4.7; extra == 'all'
|
|
23
24
|
Requires-Dist: web3>=7.0.0; extra == 'all'
|
|
24
25
|
Requires-Dist: x402[evm,httpx]>=2.0.0; extra == 'all'
|
|
25
26
|
Provides-Extra: comfyui
|
|
26
|
-
Requires-Dist: hypercli-sdk[comfyui]>=2026.4.
|
|
27
|
+
Requires-Dist: hypercli-sdk[comfyui]>=2026.4.7; extra == 'comfyui'
|
|
27
28
|
Provides-Extra: dev
|
|
28
29
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
30
|
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2026.4.7"
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
from datetime import datetime, timedelta
|
|
7
9
|
import typer
|
|
8
10
|
from rich.console import Console
|
|
9
11
|
from rich.table import Table
|
|
10
12
|
|
|
13
|
+
from hypercli import HyperCLI
|
|
14
|
+
|
|
11
15
|
from .onboard import onboard as _onboard_fn
|
|
12
16
|
from .voice import app as voice_app
|
|
13
|
-
from .stt import transcribe as _stt_transcribe
|
|
14
17
|
from .embed import app as embed_app
|
|
15
18
|
|
|
16
19
|
app = typer.Typer(help="HyperAgent inference commands")
|
|
@@ -21,26 +24,6 @@ app.command("onboard")(_onboard_fn)
|
|
|
21
24
|
app.add_typer(voice_app, name="voice")
|
|
22
25
|
app.add_typer(embed_app, name="embed")
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
@app.command("transcribe")
|
|
26
|
-
def transcribe(
|
|
27
|
-
audio_file: Path = typer.Argument(..., help="Audio file to transcribe (wav, mp3, ogg, m4a, etc.)"),
|
|
28
|
-
model: str = typer.Option("turbo", "--model", "-m", help="Whisper model: tiny, base, small, medium, large-v3, turbo"),
|
|
29
|
-
language: str = typer.Option(None, "--language", "-l", help="Language code (e.g. en, de, fr). Auto-detect if omitted."),
|
|
30
|
-
device: str = typer.Option("auto", "--device", "-d", help="Device: auto, cpu, cuda"),
|
|
31
|
-
compute_type: str = typer.Option("auto", "--compute", help="Compute type: auto, int8, float16, float32"),
|
|
32
|
-
json_output: bool = typer.Option(False, "--json", help="Output as JSON with timestamps"),
|
|
33
|
-
output: Path = typer.Option(None, "--output", "-o", help="Write transcript to file"),
|
|
34
|
-
):
|
|
35
|
-
"""Transcribe audio to text using faster-whisper (runs locally).
|
|
36
|
-
|
|
37
|
-
Examples:
|
|
38
|
-
hyper agent transcribe voice.ogg
|
|
39
|
-
hyper agent transcribe meeting.mp3 --model large-v3 --language en
|
|
40
|
-
hyper agent transcribe audio.wav --json -o transcript.json
|
|
41
|
-
"""
|
|
42
|
-
_stt_transcribe(audio_file, model, language, device, compute_type, json_output, output)
|
|
43
|
-
|
|
44
27
|
# Check if wallet dependencies are available
|
|
45
28
|
try:
|
|
46
29
|
from x402 import x402Client
|
|
@@ -68,6 +51,30 @@ def require_x402_deps():
|
|
|
68
51
|
raise typer.Exit(1)
|
|
69
52
|
|
|
70
53
|
|
|
54
|
+
def _resolve_agent_query_key() -> str:
|
|
55
|
+
key = os.getenv("HYPER_AGENTS_API_KEY", "").strip() or os.getenv("HYPER_API_KEY", "").strip()
|
|
56
|
+
if key:
|
|
57
|
+
return key
|
|
58
|
+
if AGENT_KEY_PATH.exists():
|
|
59
|
+
try:
|
|
60
|
+
with open(AGENT_KEY_PATH) as f:
|
|
61
|
+
data = json.load(f)
|
|
62
|
+
key = str(data.get("key") or "").strip()
|
|
63
|
+
if key:
|
|
64
|
+
return key
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
console.print("[red]❌ No HyperClaw API key found.[/red]")
|
|
68
|
+
console.print("Set HYPER_AGENTS_API_KEY or HYPER_API_KEY, or subscribe first with [bold]hyper agent subscribe[/bold].")
|
|
69
|
+
raise typer.Exit(1)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_agent_query_client(dev: bool) -> HyperCLI:
|
|
73
|
+
key = _resolve_agent_query_key()
|
|
74
|
+
api_base = DEV_API_BASE if dev else PROD_API_BASE
|
|
75
|
+
return HyperCLI(api_key=key, agent_api_key=key, api_url=api_base, agent_dev=dev)
|
|
76
|
+
|
|
77
|
+
|
|
71
78
|
@app.command("subscribe")
|
|
72
79
|
def subscribe(
|
|
73
80
|
plan_id: str = typer.Argument("1aiu", help="Plan ID: 1aiu, 2aiu, 5aiu, 10aiu (default: 1aiu)"),
|
|
@@ -151,7 +158,7 @@ def subscribe(
|
|
|
151
158
|
console.print(f"Limits: {result['tpm_limit']:,} TPM / {result['rpm_limit']:,} RPM")
|
|
152
159
|
console.print(f"\n[green]✓[/green] Key saved to [bold]{AGENT_KEY_PATH}[/bold]")
|
|
153
160
|
console.print(f"[green]✓[/green] Key history: [bold]{keys_history_path}[/bold]")
|
|
154
|
-
console.print("\nConfigure OpenClaw with: [bold]hyper
|
|
161
|
+
console.print("\nConfigure OpenClaw with: [bold]hyper config openclaw --apply[/bold]")
|
|
155
162
|
|
|
156
163
|
|
|
157
164
|
async def _subscribe_async(account, plan_id: str, api_base: str, amount: str = None):
|
|
@@ -344,6 +351,104 @@ def plans(
|
|
|
344
351
|
console.print("Subscribe with: [bold]hyper agent subscribe <plan_id> <amount>[/bold]")
|
|
345
352
|
|
|
346
353
|
|
|
354
|
+
@app.command("current-plan")
|
|
355
|
+
def current_plan(
|
|
356
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
357
|
+
json_output: bool = typer.Option(False, "--json", help="Print raw JSON response"),
|
|
358
|
+
):
|
|
359
|
+
"""Show the effective current HyperClaw plan."""
|
|
360
|
+
client = _get_agent_query_client(dev)
|
|
361
|
+
current = client.agent.current_plan()
|
|
362
|
+
|
|
363
|
+
if json_output:
|
|
364
|
+
console.print_json(json.dumps(current.__dict__, indent=2, default=str))
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
console.print("\n[bold]Current HyperClaw Plan[/bold]\n")
|
|
368
|
+
console.print(f"Plan: [bold]{current.name}[/bold] ({current.id})")
|
|
369
|
+
console.print(f"TPM/RPM: {current.tpm_limit:,} / {current.rpm_limit:,}")
|
|
370
|
+
if current.pooled_tpd:
|
|
371
|
+
console.print(f"Pooled TPD: {current.pooled_tpd:,}")
|
|
372
|
+
if current.expires_at:
|
|
373
|
+
console.print(f"Expires: {current.expires_at.strftime('%Y-%m-%d %H:%M:%S %Z')}")
|
|
374
|
+
if current.provider:
|
|
375
|
+
console.print(f"Provider: {current.provider}")
|
|
376
|
+
console.print(f"Cancel at period end: {'yes' if current.cancel_at_period_end else 'no'}")
|
|
377
|
+
if current.slot_inventory:
|
|
378
|
+
console.print("\n[bold]Slots[/bold]")
|
|
379
|
+
for tier, inventory in current.slot_inventory.items():
|
|
380
|
+
console.print(
|
|
381
|
+
f" {tier}: {inventory.get('used', 0)}/{inventory.get('granted', 0)} "
|
|
382
|
+
f"({inventory.get('available', 0)} available)"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@app.command("subscriptions")
|
|
387
|
+
def subscriptions(
|
|
388
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
389
|
+
json_output: bool = typer.Option(False, "--json", help="Print raw JSON response"),
|
|
390
|
+
):
|
|
391
|
+
"""List your HyperClaw subscriptions/entitlements."""
|
|
392
|
+
client = _get_agent_query_client(dev)
|
|
393
|
+
items = client.agent.subscriptions()
|
|
394
|
+
|
|
395
|
+
if json_output:
|
|
396
|
+
console.print_json(json.dumps([item.__dict__ for item in items], indent=2, default=str))
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
table = Table(title="HyperClaw Subscriptions")
|
|
400
|
+
table.add_column("ID", style="cyan")
|
|
401
|
+
table.add_column("Plan", style="green")
|
|
402
|
+
table.add_column("Qty", justify="right")
|
|
403
|
+
table.add_column("Provider")
|
|
404
|
+
table.add_column("Status")
|
|
405
|
+
table.add_column("Expires")
|
|
406
|
+
for item in items:
|
|
407
|
+
expires = item.expires_at.strftime("%Y-%m-%d %H:%M:%S %Z") if item.expires_at else ""
|
|
408
|
+
table.add_row(item.id[:12], item.plan_name or item.plan_id, str(item.quantity), item.provider, item.status, expires)
|
|
409
|
+
console.print()
|
|
410
|
+
console.print(table)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@app.command("subscription-summary")
|
|
414
|
+
def subscription_summary(
|
|
415
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
416
|
+
json_output: bool = typer.Option(False, "--json", help="Print raw JSON response"),
|
|
417
|
+
):
|
|
418
|
+
"""Show your effective HyperClaw entitlement summary."""
|
|
419
|
+
client = _get_agent_query_client(dev)
|
|
420
|
+
summary = client.agent.subscription_summary()
|
|
421
|
+
|
|
422
|
+
if json_output:
|
|
423
|
+
console.print_json(json.dumps({
|
|
424
|
+
"effective_plan_id": summary.effective_plan_id,
|
|
425
|
+
"current_subscription_id": summary.current_subscription_id,
|
|
426
|
+
"pooled_tpm_limit": summary.pooled_tpm_limit,
|
|
427
|
+
"pooled_rpm_limit": summary.pooled_rpm_limit,
|
|
428
|
+
"pooled_tpd": summary.pooled_tpd,
|
|
429
|
+
"slot_inventory": summary.slot_inventory,
|
|
430
|
+
"active_subscription_count": summary.active_subscription_count,
|
|
431
|
+
"active_subscriptions": [item.__dict__ for item in summary.active_subscriptions],
|
|
432
|
+
"subscriptions": [item.__dict__ for item in summary.subscriptions],
|
|
433
|
+
"user": summary.user,
|
|
434
|
+
}, indent=2, default=str))
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
console.print("\n[bold]HyperClaw Entitlement Summary[/bold]\n")
|
|
438
|
+
console.print(f"Effective plan: [bold]{summary.effective_plan_id}[/bold]")
|
|
439
|
+
console.print(f"Current subscription: {summary.current_subscription_id or ''}")
|
|
440
|
+
console.print(
|
|
441
|
+
f"Pooled limits: {summary.pooled_tpm_limit:,} TPM / "
|
|
442
|
+
f"{summary.pooled_rpm_limit:,} RPM / {summary.pooled_tpd:,} TPD"
|
|
443
|
+
)
|
|
444
|
+
console.print("\n[bold]Slots[/bold]")
|
|
445
|
+
for tier, inventory in summary.slot_inventory.items():
|
|
446
|
+
console.print(
|
|
447
|
+
f" {tier}: {inventory.get('used', 0)}/{inventory.get('granted', 0)} "
|
|
448
|
+
f"({inventory.get('available', 0)} available)"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
347
452
|
@app.command("models")
|
|
348
453
|
def models(
|
|
349
454
|
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
@@ -538,7 +643,7 @@ def login(
|
|
|
538
643
|
console.print(f" Wallet: {wallet_addr}")
|
|
539
644
|
console.print(f"\n[green]You're all set![/green]")
|
|
540
645
|
console.print(f" Launch agent: [bold]hyper agents create[/bold]")
|
|
541
|
-
console.print(f" Configure: [bold]hyper
|
|
646
|
+
console.print(f" Configure: [bold]hyper config openclaw --apply[/bold]")
|
|
542
647
|
|
|
543
648
|
|
|
544
649
|
OPENCLAW_CONFIG_PATH = Path.home() / ".openclaw" / "openclaw.json"
|
|
@@ -734,15 +839,16 @@ def _config_openclaw(
|
|
|
734
839
|
embedding_models = [m for m in supported_models if m.get("mode") == "embedding"]
|
|
735
840
|
kimi_models = [m for m in chat_models if "kimi" in _model_suffix(m.get("id", ""))]
|
|
736
841
|
glm_models = [m for m in chat_models if _model_suffix(m.get("id", "")) == "glm-5"]
|
|
737
|
-
|
|
842
|
+
other_chat_models = [
|
|
738
843
|
m for m in chat_models
|
|
739
844
|
if m not in kimi_models and m not in glm_models
|
|
740
845
|
]
|
|
846
|
+
provider_models = kimi_models + glm_models + other_chat_models
|
|
741
847
|
embedding_model_id = embedding_models[0]["id"] if embedding_models else None
|
|
742
848
|
primary_model = (
|
|
743
|
-
f"
|
|
849
|
+
f"hyperclaw/{kimi_models[0]['id']}" if kimi_models else (
|
|
744
850
|
f"hyperclaw/{glm_models[0]['id']}" if glm_models else (
|
|
745
|
-
f"hyperclaw
|
|
851
|
+
f"hyperclaw/{other_chat_models[0]['id']}" if other_chat_models else None
|
|
746
852
|
)
|
|
747
853
|
)
|
|
748
854
|
)
|
|
@@ -756,46 +862,18 @@ def _config_openclaw(
|
|
|
756
862
|
"baseUrl": api_base,
|
|
757
863
|
"apiKey": config_api_key,
|
|
758
864
|
"api": "anthropic-messages",
|
|
759
|
-
"
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
{
|
|
763
|
-
"kimi-coding": {
|
|
764
|
-
# Use the upstream Kimi provider semantics while still
|
|
765
|
-
# routing requests through the HyperClaw Anthropic proxy.
|
|
766
|
-
"baseUrl": api_base,
|
|
767
|
-
"apiKey": config_api_key,
|
|
768
|
-
"api": "anthropic-messages",
|
|
769
|
-
"headers": {
|
|
770
|
-
"User-Agent": "claude-code/0.1.0",
|
|
771
|
-
},
|
|
772
|
-
"models": kimi_models,
|
|
773
|
-
},
|
|
774
|
-
}
|
|
775
|
-
if kimi_models
|
|
776
|
-
else {}
|
|
777
|
-
),
|
|
778
|
-
**(
|
|
779
|
-
{
|
|
780
|
-
"hyperclaw-openai": {
|
|
781
|
-
"baseUrl": f"{api_base}/v1",
|
|
782
|
-
"apiKey": config_api_key,
|
|
783
|
-
"api": "openai-completions",
|
|
784
|
-
"models": openai_chat_models,
|
|
785
|
-
},
|
|
786
|
-
}
|
|
787
|
-
if openai_chat_models
|
|
788
|
-
else {}
|
|
789
|
-
),
|
|
865
|
+
"authHeader": True,
|
|
866
|
+
"models": provider_models,
|
|
867
|
+
}
|
|
790
868
|
}
|
|
791
869
|
},
|
|
792
870
|
"agents": {
|
|
793
871
|
"defaults": {
|
|
794
872
|
**({"model": {"primary": primary_model}} if primary_model else {}),
|
|
795
873
|
"models": {
|
|
874
|
+
**{f"hyperclaw/{m['id']}": {"alias": "kimi"} for m in kimi_models},
|
|
796
875
|
**{f"hyperclaw/{m['id']}": {"alias": "glm"} for m in glm_models},
|
|
797
|
-
**{f"
|
|
798
|
-
**{f"hyperclaw-openai/{m['id']}": {"alias": m['id'].split('-')[0]} for m in openai_chat_models},
|
|
876
|
+
**{f"hyperclaw/{m['id']}": {"alias": m['id'].split('-')[0]} for m in other_chat_models},
|
|
799
877
|
},
|
|
800
878
|
**(
|
|
801
879
|
{
|
|
@@ -890,10 +968,10 @@ def config_cmd(
|
|
|
890
968
|
|
|
891
969
|
Examples:
|
|
892
970
|
hyper agent config # Show all configs
|
|
893
|
-
hyper
|
|
971
|
+
hyper config openclaw # OpenClaw snippet
|
|
894
972
|
hyper agent config opencode --key sk-... # OpenCode with explicit key
|
|
895
|
-
hyper
|
|
896
|
-
hyper
|
|
973
|
+
hyper config openclaw --base-url https://api.dev.hypercli.com
|
|
974
|
+
hyper config openclaw --apply # Write directly to openclaw.json
|
|
897
975
|
hyper agent config env # Shell export lines
|
|
898
976
|
"""
|
|
899
977
|
api_key = _resolve_api_key(key)
|
|
@@ -937,8 +1015,11 @@ def _show_snippet(name: str, path_hint: str, data: dict, apply: bool, target_pat
|
|
|
937
1015
|
if target_path.exists():
|
|
938
1016
|
with open(target_path) as f:
|
|
939
1017
|
existing = json.load(f)
|
|
940
|
-
|
|
941
|
-
|
|
1018
|
+
if name == "OpenClaw":
|
|
1019
|
+
merged = _merge_openclaw_config(existing, data)
|
|
1020
|
+
else:
|
|
1021
|
+
_deep_merge(existing, data)
|
|
1022
|
+
merged = existing
|
|
942
1023
|
else:
|
|
943
1024
|
merged = data
|
|
944
1025
|
|
|
@@ -947,6 +1028,31 @@ def _show_snippet(name: str, path_hint: str, data: dict, apply: bool, target_pat
|
|
|
947
1028
|
json.dump(merged, f, indent=2)
|
|
948
1029
|
f.write("\n")
|
|
949
1030
|
console.print(f"[green]✅ Written to {target_path}[/green]\n")
|
|
1031
|
+
if name == "OpenClaw":
|
|
1032
|
+
_refresh_openclaw_runtime()
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def _refresh_openclaw_runtime():
|
|
1036
|
+
"""Best-effort refresh of OpenClaw generated runtime state after config changes."""
|
|
1037
|
+
if shutil.which("openclaw") is None:
|
|
1038
|
+
console.print("[yellow]⚠[/yellow] OpenClaw CLI not found in PATH.")
|
|
1039
|
+
console.print("Run after install: [bold]openclaw models list[/bold]")
|
|
1040
|
+
console.print("Then restart when ready: [bold]openclaw gateway restart[/bold]\n")
|
|
1041
|
+
return
|
|
1042
|
+
|
|
1043
|
+
try:
|
|
1044
|
+
subprocess.run(
|
|
1045
|
+
["openclaw", "models", "list"],
|
|
1046
|
+
capture_output=True,
|
|
1047
|
+
text=True,
|
|
1048
|
+
timeout=30,
|
|
1049
|
+
check=True,
|
|
1050
|
+
)
|
|
1051
|
+
console.print("[green]✓[/green] Regenerated OpenClaw model cache.")
|
|
1052
|
+
except Exception:
|
|
1053
|
+
console.print("[yellow]⚠[/yellow] Could not regenerate OpenClaw model cache automatically.")
|
|
1054
|
+
console.print("Run manually: [bold]openclaw models list[/bold]")
|
|
1055
|
+
console.print("Restart when ready: [bold]openclaw gateway restart[/bold]\n")
|
|
950
1056
|
|
|
951
1057
|
|
|
952
1058
|
def _deep_merge(base: dict, overlay: dict):
|
|
@@ -956,3 +1062,28 @@ def _deep_merge(base: dict, overlay: dict):
|
|
|
956
1062
|
_deep_merge(base[k], v)
|
|
957
1063
|
else:
|
|
958
1064
|
base[k] = v
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _merge_openclaw_config(existing: dict, snippet: dict) -> dict:
|
|
1068
|
+
"""Merge OpenClaw config while replacing generated provider/model sections exactly."""
|
|
1069
|
+
merged = dict(existing)
|
|
1070
|
+
_deep_merge(merged, snippet)
|
|
1071
|
+
|
|
1072
|
+
snippet_models = ((snippet.get("models") or {}).get("providers") or {})
|
|
1073
|
+
if snippet_models:
|
|
1074
|
+
merged.setdefault("models", {})
|
|
1075
|
+
merged["models"]["providers"] = snippet_models
|
|
1076
|
+
|
|
1077
|
+
snippet_defaults = (((snippet.get("agents") or {}).get("defaults") or {}).get("models") or {})
|
|
1078
|
+
if snippet_defaults:
|
|
1079
|
+
merged.setdefault("agents", {})
|
|
1080
|
+
merged["agents"].setdefault("defaults", {})
|
|
1081
|
+
merged["agents"]["defaults"]["models"] = snippet_defaults
|
|
1082
|
+
|
|
1083
|
+
snippet_model_config = (((snippet.get("agents") or {}).get("defaults") or {}).get("model") or {})
|
|
1084
|
+
if snippet_model_config:
|
|
1085
|
+
merged.setdefault("agents", {})
|
|
1086
|
+
merged["agents"].setdefault("defaults", {})
|
|
1087
|
+
merged["agents"]["defaults"]["model"] = snippet_model_config
|
|
1088
|
+
|
|
1089
|
+
return merged
|
|
@@ -4,11 +4,12 @@ import json
|
|
|
4
4
|
import typer
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
from rich.prompt import Prompt
|
|
7
|
+
from rich.table import Table
|
|
7
8
|
|
|
8
9
|
from hypercli import HyperCLI, APIError, configure
|
|
9
10
|
from hypercli.config import CONFIG_FILE
|
|
10
11
|
|
|
11
|
-
from . import agent, agents, billing, comfyui, files, flow, instances, jobs, keys, user, wallet
|
|
12
|
+
from . import agent, agents, billing, comfyui, files, flow, instances, jobs, keys, llm, user, voice, wallet
|
|
12
13
|
from .output import output, spinner
|
|
13
14
|
|
|
14
15
|
console = Console()
|
|
@@ -56,10 +57,16 @@ app = typer.Typer(
|
|
|
56
57
|
no_args_is_help=True,
|
|
57
58
|
rich_markup_mode="rich",
|
|
58
59
|
)
|
|
60
|
+
config_app = typer.Typer(
|
|
61
|
+
help="Generate config for OpenClaw and other tools",
|
|
62
|
+
no_args_is_help=True,
|
|
63
|
+
rich_markup_mode="rich",
|
|
64
|
+
)
|
|
59
65
|
|
|
60
66
|
# Register subcommands
|
|
61
67
|
app.add_typer(agents.app, name="agents")
|
|
62
68
|
app.add_typer(agent.app, name="agent")
|
|
69
|
+
app.add_typer(config_app, name="config")
|
|
63
70
|
app.add_typer(billing.app, name="billing")
|
|
64
71
|
app.add_typer(comfyui.app, name="comfyui")
|
|
65
72
|
app.add_typer(files.app, name="files")
|
|
@@ -67,10 +74,31 @@ app.add_typer(flow.app, name="flow")
|
|
|
67
74
|
app.add_typer(instances.app, name="instances")
|
|
68
75
|
app.add_typer(keys.app, name="keys")
|
|
69
76
|
app.add_typer(jobs.app, name="jobs")
|
|
77
|
+
app.add_typer(llm.app, name="llm")
|
|
70
78
|
app.add_typer(user.app, name="user")
|
|
79
|
+
app.add_typer(voice.app, name="voice")
|
|
71
80
|
app.add_typer(wallet.app, name="wallet")
|
|
72
81
|
|
|
73
82
|
|
|
83
|
+
@config_app.command("openclaw")
|
|
84
|
+
def config_openclaw_cmd(
|
|
85
|
+
key: str = typer.Option(None, "--key", "-k", help="API key. Falls back to ~/.hypercli/agent-key.json"),
|
|
86
|
+
base_url: str = typer.Option(None, "--base-url", help="HyperClaw API base URL. Falls back to HYPER_API_BASE, then --dev/prod defaults"),
|
|
87
|
+
placeholder_env: str = typer.Option(None, "--placeholder-env", help="Write ${ENV_VAR} placeholders into generated config instead of literal API keys"),
|
|
88
|
+
apply: bool = typer.Option(False, "--apply", help="Write directly to ~/.openclaw/openclaw.json"),
|
|
89
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
90
|
+
):
|
|
91
|
+
"""Generate or apply OpenClaw config."""
|
|
92
|
+
agent.config_cmd(
|
|
93
|
+
format="openclaw",
|
|
94
|
+
key=key,
|
|
95
|
+
base_url=base_url,
|
|
96
|
+
placeholder_env=placeholder_env,
|
|
97
|
+
apply=apply,
|
|
98
|
+
dev=dev,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
74
102
|
@app.command("me")
|
|
75
103
|
def me_cmd(
|
|
76
104
|
fmt: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
|
|
@@ -79,7 +107,27 @@ def me_cmd(
|
|
|
79
107
|
client = HyperCLI()
|
|
80
108
|
with spinner("Resolving auth context..."):
|
|
81
109
|
auth_me = client.user.auth_me()
|
|
82
|
-
|
|
110
|
+
if fmt == "json":
|
|
111
|
+
output(auth_me, fmt)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
table = Table(show_header=False, box=None)
|
|
115
|
+
table.add_column("Key", style="bold cyan")
|
|
116
|
+
table.add_column("Value")
|
|
117
|
+
table.add_row("user_id", auth_me.user_id)
|
|
118
|
+
table.add_row("orchestra_user_id", str(auth_me.orchestra_user_id or ""))
|
|
119
|
+
table.add_row("team_id", auth_me.team_id)
|
|
120
|
+
table.add_row("plan_id", auth_me.plan_id)
|
|
121
|
+
table.add_row("email", str(auth_me.email or ""))
|
|
122
|
+
table.add_row("auth_type", auth_me.auth_type)
|
|
123
|
+
has_active_subscription = bool(getattr(auth_me, "has_active_subscription", False))
|
|
124
|
+
table.add_row("has_active_subscription", "yes" if has_active_subscription else "no")
|
|
125
|
+
table.add_row("key_id", str(getattr(auth_me, "key_id", None) or ""))
|
|
126
|
+
table.add_row("key_name", str(getattr(auth_me, "key_name", None) or ""))
|
|
127
|
+
raw_capabilities = list(getattr(auth_me, "capabilities", []) or [])
|
|
128
|
+
capabilities = "\n".join(raw_capabilities) if raw_capabilities else ""
|
|
129
|
+
table.add_row("capabilities", capabilities)
|
|
130
|
+
console.print(table)
|
|
83
131
|
|
|
84
132
|
|
|
85
133
|
@app.command("configure")
|