hypercli-cli 0.7.12__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.12
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
@@ -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.12"
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