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.
Files changed (40) hide show
  1. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/PKG-INFO +5 -4
  2. hypercli_cli-2026.4.7/hypercli_cli/__init__.py +1 -0
  3. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/agent.py +195 -64
  4. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/cli.py +50 -2
  5. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/flow.py +171 -93
  6. hypercli_cli-2026.4.7/hypercli_cli/llm.py +155 -0
  7. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/stt.py +3 -3
  8. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/voice.py +28 -7
  9. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/wallet.py +233 -120
  10. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/pyproject.toml +10 -4
  11. hypercli_cli-2026.4.7/tests/test_config_command.py +50 -0
  12. hypercli_cli-2026.4.7/tests/test_flow_command.py +231 -0
  13. hypercli_cli-2026.4.7/tests/test_flow_visibility.py +17 -0
  14. hypercli_cli-2026.4.7/tests/test_llm_command.py +63 -0
  15. hypercli_cli-2026.4.7/tests/test_openclaw_config.py +159 -0
  16. hypercli_cli-2026.4.7/tests/test_voice_command.py +72 -0
  17. hypercli_cli-2026.4.7/tests/test_wallet_command.py +144 -0
  18. hypercli_cli-2026.4.7/tests/test_wallet_migration_integration.py +71 -0
  19. hypercli_cli-2026.4.5/hypercli_cli/__init__.py +0 -1
  20. hypercli_cli-2026.4.5/tests/test_openclaw_config.py +0 -63
  21. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/.gitignore +0 -0
  22. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/README.md +0 -0
  23. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/agents.py +0 -0
  24. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/billing.py +0 -0
  25. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/comfyui.py +0 -0
  26. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/embed.py +0 -0
  27. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/files.py +0 -0
  28. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/instances.py +0 -0
  29. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/jobs.py +0 -0
  30. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/keys.py +0 -0
  31. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/onboard.py +0 -0
  32. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/output.py +0 -0
  33. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/renders.py +0 -0
  34. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/tui/__init__.py +0 -0
  35. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/tui/job_monitor.py +0 -0
  36. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/hypercli_cli/user.py +0 -0
  37. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_agent_env_resolution.py +0 -0
  38. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_exec_shell_dryrun.py +0 -0
  39. {hypercli_cli-2026.4.5 → hypercli_cli-2026.4.7}/tests/test_jobs_list_tags.py +0 -0
  40. {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.5
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.5
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.5; extra == 'all'
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.5; extra == 'comfyui'
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 agent openclaw-setup[/bold]")
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 agent config openclaw --apply[/bold]")
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
- openai_chat_models = [
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"kimi-coding/{kimi_models[0]['id']}" if kimi_models else (
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-openai/{openai_chat_models[0]['id']}" if openai_chat_models else None
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
- "models": glm_models,
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"kimi-coding/{m['id']}": {"alias": "kimi"} for m in kimi_models},
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 agent config openclaw # OpenClaw snippet
971
+ hyper config openclaw # OpenClaw snippet
894
972
  hyper agent config opencode --key sk-... # OpenCode with explicit key
895
- hyper agent config openclaw --base-url https://api.dev.hypercli.com
896
- hyper agent config openclaw --apply # Write directly to openclaw.json
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
- _deep_merge(existing, data)
941
- merged = existing
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
- output(auth_me, fmt)
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")