hypercli-cli 0.7.11__tar.gz → 0.8.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercli-cli
3
- Version: 0.7.11
3
+ Version: 0.8.0
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
@@ -302,23 +302,28 @@ def plans(
302
302
  OPENCLAW_CONFIG_PATH = Path.home() / ".openclaw" / "openclaw.json"
303
303
 
304
304
 
305
- def fetch_models(api_base: str = PROD_API_BASE) -> list[dict]:
306
- """Fetch available models from HyperClaw /v1/models endpoint."""
305
+ def fetch_models(api_key: str, api_base: str = PROD_API_BASE) -> list[dict]:
306
+ """Fetch available models from LiteLLM /v1/models (served by HyperClaw)."""
307
307
  import httpx
308
308
  try:
309
- resp = httpx.get(f"{api_base}/api/v1/models", timeout=10)
309
+ resp = httpx.get(
310
+ f"{api_base}/v1/models",
311
+ headers={"Authorization": f"Bearer {api_key}"},
312
+ timeout=10,
313
+ )
310
314
  resp.raise_for_status()
311
315
  data = resp.json().get("data", [])
312
316
  return [
313
317
  {
314
318
  "id": m["id"],
315
- "name": m.get("id", "").replace("-", " ").title(),
319
+ "name": m["id"].replace("-", " ").title(),
316
320
  "reasoning": False,
317
321
  "input": ["text"],
318
- "contextWindow": m.get("context_window", 200000),
319
- "maxTokens": m.get("max_tokens", 8192),
322
+ "contextWindow": 200000,
323
+ "maxTokens": 8192,
320
324
  }
321
325
  for m in data
326
+ if m.get("id")
322
327
  ]
323
328
  except Exception as e:
324
329
  console.print(f"[yellow]⚠ Could not fetch models from API: {e}[/yellow]")
@@ -366,8 +371,8 @@ def openclaw_setup(
366
371
  else:
367
372
  config = {}
368
373
 
369
- # Fetch current model list from API
370
- models = fetch_models()
374
+ # Fetch current model list from LiteLLM via API
375
+ models = fetch_models(api_key)
371
376
 
372
377
  # Patch only models.providers.hyperclaw
373
378
  config.setdefault("models", {}).setdefault("providers", {})
@@ -7,7 +7,7 @@ from rich.prompt import Prompt
7
7
  from hypercli import HyperCLI, APIError, configure
8
8
  from hypercli.config import CONFIG_FILE
9
9
 
10
- from . import billing, claw, comfyui, flow, instances, jobs, llm, renders, user, wallet
10
+ from . import billing, claw, comfyui, flow, instances, jobs, keys, llm, renders, user, wallet
11
11
 
12
12
  console = Console()
13
13
 
@@ -61,6 +61,7 @@ app.add_typer(claw.app, name="claw")
61
61
  app.add_typer(comfyui.app, name="comfyui")
62
62
  app.add_typer(flow.app, name="flow")
63
63
  app.add_typer(instances.app, name="instances")
64
+ app.add_typer(keys.app, name="keys")
64
65
  app.add_typer(jobs.app, name="jobs")
65
66
  app.add_typer(llm.app, name="llm")
66
67
  app.add_typer(renders.app, name="renders")
@@ -0,0 +1,83 @@
1
+ """API Key management CLI commands"""
2
+ from datetime import datetime, timezone
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+
7
+ app = typer.Typer(help="API key management")
8
+
9
+
10
+ def _fmt_ts(ts) -> str:
11
+ """Format a timestamp (epoch float or ISO string) for display."""
12
+ if not ts:
13
+ return ""
14
+ if isinstance(ts, (int, float)):
15
+ return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
16
+ return str(ts)[:16]
17
+ console = Console()
18
+
19
+
20
+ def _get_client():
21
+ from hypercli import HyperCLI
22
+ return HyperCLI()
23
+
24
+
25
+ @app.command("create")
26
+ def create_key(name: str = typer.Option("default", help="Key name")):
27
+ """Create a new API key"""
28
+ client = _get_client()
29
+ key = client.keys.create(name=name)
30
+ console.print(f"\n[bold green]API key created![/bold green]\n")
31
+ console.print(f" Key ID: {key.key_id}")
32
+ console.print(f" Name: {key.name}")
33
+ console.print(f" API Key: [bold]{key.api_key}[/bold]")
34
+ console.print(f"\n[yellow]⚠ Save this key now — it won't be shown again.[/yellow]\n")
35
+
36
+
37
+ @app.command("list")
38
+ def list_keys():
39
+ """List all API keys"""
40
+ client = _get_client()
41
+ keys = client.keys.list()
42
+
43
+ if not keys:
44
+ console.print("[dim]No API keys found.[/dim]")
45
+ return
46
+
47
+ table = Table(title="API Keys")
48
+ table.add_column("Key ID", style="dim")
49
+ table.add_column("Name")
50
+ table.add_column("Key Preview")
51
+ table.add_column("Active", justify="center")
52
+ table.add_column("Created")
53
+ table.add_column("Last Used")
54
+
55
+ for key in keys:
56
+ active = "✓" if key.is_active else "✗"
57
+ active_style = "green" if key.is_active else "red"
58
+ table.add_row(
59
+ key.key_id[:8] + "...",
60
+ key.name or "",
61
+ key.api_key_preview or "",
62
+ f"[{active_style}]{active}[/{active_style}]",
63
+ _fmt_ts(key.created_at),
64
+ _fmt_ts(key.last_used_at) or "never",
65
+ )
66
+
67
+ console.print(table)
68
+
69
+
70
+ @app.command("disable")
71
+ def disable_key(
72
+ key_id: str = typer.Argument(help="Key ID to disable"),
73
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
74
+ ):
75
+ """Disable an API key (irreversible)"""
76
+ if not yes:
77
+ confirm = typer.confirm(f"Disable key {key_id[:8]}...? This cannot be undone")
78
+ if not confirm:
79
+ raise typer.Abort()
80
+
81
+ client = _get_client()
82
+ result = client.keys.disable(key_id)
83
+ console.print(f"[red]Key {key_id[:8]}... disabled.[/red]")
@@ -240,6 +240,88 @@ def balance():
240
240
  console.print(f"\n[green]✓[/green] You have [bold]{balance_usdc:.2f} USDC[/bold]")
241
241
 
242
242
 
243
+ @app.command("login")
244
+ def wallet_login(
245
+ name: str = typer.Option("cli", help="Name for the generated API key"),
246
+ api_url: str = typer.Option(None, help="API URL override"),
247
+ ):
248
+ """Login with wallet signature, create an API key, and save it.
249
+
250
+ This is the recommended way to set up HyperCLI from scratch:
251
+ 1. hyper wallet create
252
+ 2. hyper wallet login
253
+ """
254
+ require_wallet_deps()
255
+ from eth_account.messages import encode_defunct
256
+ import httpx
257
+ from hypercli.config import get_api_url, configure
258
+
259
+ base_url = (api_url or get_api_url()).rstrip("/")
260
+
261
+ # Step 1: Load wallet
262
+ account = load_wallet()
263
+ console.print(f"[green]✓[/green] Wallet: {account.address}\n")
264
+
265
+ # Step 2: Get challenge
266
+ console.print("[bold]Requesting login challenge...[/bold]")
267
+ with httpx.Client(timeout=15) as client:
268
+ resp = client.post(
269
+ f"{base_url}/api/auth/wallet/challenge",
270
+ json={"wallet": account.address},
271
+ )
272
+ if resp.status_code != 200:
273
+ console.print(f"[red]❌ Challenge failed: {resp.text}[/red]")
274
+ raise typer.Exit(1)
275
+ challenge = resp.json()
276
+
277
+ # Step 3: Sign the challenge
278
+ console.print("[bold]Signing challenge...[/bold]")
279
+ message = encode_defunct(text=challenge["message"])
280
+ signed = account.sign_message(message)
281
+
282
+ # Step 4: Login
283
+ console.print("[bold]Logging in...[/bold]")
284
+ with httpx.Client(timeout=15) as client:
285
+ resp = client.post(
286
+ f"{base_url}/api/auth/wallet/login",
287
+ json={
288
+ "wallet": account.address,
289
+ "signature": signed.signature.hex(),
290
+ "timestamp": challenge["timestamp"],
291
+ },
292
+ )
293
+ if resp.status_code != 200:
294
+ console.print(f"[red]❌ Login failed: {resp.text}[/red]")
295
+ raise typer.Exit(1)
296
+ jwt_token = resp.json()["token"]
297
+
298
+ console.print("[green]✓[/green] Authenticated\n")
299
+
300
+ # Step 5: Create API key using JWT
301
+ console.print(f"[bold]Creating API key '{name}'...[/bold]")
302
+ with httpx.Client(timeout=15) as client:
303
+ resp = client.post(
304
+ f"{base_url}/api/keys",
305
+ json={"name": name},
306
+ headers={"Authorization": f"Bearer {jwt_token}"},
307
+ )
308
+ if resp.status_code != 200:
309
+ console.print(f"[red]❌ Key creation failed: {resp.text}[/red]")
310
+ raise typer.Exit(1)
311
+ key_data = resp.json()
312
+
313
+ api_key = key_data["api_key"]
314
+
315
+ # Step 6: Save to config
316
+ configure(api_key, api_url)
317
+
318
+ console.print(f"[green]✓[/green] API key created and saved!\n")
319
+ console.print(f" Name: {key_data['name']}")
320
+ console.print(f" Key: [bold]{api_key}[/bold]")
321
+ console.print(f" Saved: ~/.hypercli/config")
322
+ console.print(f"\n[green]You're all set! Try:[/green] hyper keys list\n")
323
+
324
+
243
325
  def load_wallet():
244
326
  """Load and decrypt wallet (helper function for other commands)"""
245
327
  require_wallet_deps()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hypercli-cli"
7
- version = "0.7.11"
7
+ version = "0.8.0"
8
8
  description = "CLI for HyperCLI - GPU orchestration and LLM API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes