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.
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/PKG-INFO +1 -1
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/cli.py +2 -1
- hypercli_cli-0.8.0/hypercli_cli/keys.py +83 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/wallet.py +82 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/pyproject.toml +1 -1
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/.gitignore +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/README.md +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/__init__.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/billing.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/claw.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/comfyui.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/flow.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/instances.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/jobs.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/llm.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/output.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/renders.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/tui/__init__.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/tui/job_monitor.py +0 -0
- {hypercli_cli-0.7.12 → hypercli_cli-0.8.0}/hypercli_cli/user.py +0 -0
|
@@ -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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|