hypercli-cli 0.8.5__tar.gz → 0.8.6__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.8.5 → hypercli_cli-0.8.6}/PKG-INFO +4 -4
- hypercli_cli-0.8.6/hypercli_cli/__init__.py +1 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/claw.py +51 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/instances.py +102 -27
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/jobs.py +41 -6
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/renders.py +48 -3
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/tui/job_monitor.py +5 -2
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/wallet.py +17 -12
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/pyproject.toml +4 -4
- hypercli_cli-0.8.5/hypercli_cli/__init__.py +0 -1
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/.gitignore +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/README.md +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/billing.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/cli.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/comfyui.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/flow.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/keys.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/llm.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/onboard.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/output.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/tui/__init__.py +0 -0
- {hypercli_cli-0.8.5 → hypercli_cli-0.8.6}/hypercli_cli/user.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypercli-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6
|
|
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,7 +9,7 @@ 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>=0.
|
|
12
|
+
Requires-Dist: hypercli-sdk>=0.8.3
|
|
13
13
|
Requires-Dist: mutagen>=1.47.0
|
|
14
14
|
Requires-Dist: openai>=2.8.1
|
|
15
15
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -19,11 +19,11 @@ Requires-Dist: websocket-client>=1.6.0
|
|
|
19
19
|
Provides-Extra: all
|
|
20
20
|
Requires-Dist: argon2-cffi>=25.0.0; extra == 'all'
|
|
21
21
|
Requires-Dist: eth-account>=0.13.0; extra == 'all'
|
|
22
|
-
Requires-Dist: hypercli-sdk[comfyui]>=0.
|
|
22
|
+
Requires-Dist: hypercli-sdk[comfyui]>=0.8.3; extra == 'all'
|
|
23
23
|
Requires-Dist: web3>=7.0.0; extra == 'all'
|
|
24
24
|
Requires-Dist: x402[evm,httpx]>=2.0.0; extra == 'all'
|
|
25
25
|
Provides-Extra: comfyui
|
|
26
|
-
Requires-Dist: hypercli-sdk[comfyui]>=0.
|
|
26
|
+
Requires-Dist: hypercli-sdk[comfyui]>=0.8.3; extra == 'comfyui'
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
29
|
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.6"
|
|
@@ -304,6 +304,57 @@ def plans(
|
|
|
304
304
|
console.print("Subscribe with: [bold]hyper claw subscribe <plan_id> <amount>[/bold]")
|
|
305
305
|
|
|
306
306
|
|
|
307
|
+
@app.command("models")
|
|
308
|
+
def models(
|
|
309
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
310
|
+
json_output: bool = typer.Option(False, "--json", help="Print raw JSON response"),
|
|
311
|
+
):
|
|
312
|
+
"""List available HyperClaw models from the public /models endpoint."""
|
|
313
|
+
import httpx
|
|
314
|
+
|
|
315
|
+
api_base = DEV_API_BASE if dev else PROD_API_BASE
|
|
316
|
+
url = f"{api_base}/models"
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
response = httpx.get(url, timeout=15)
|
|
320
|
+
response.raise_for_status()
|
|
321
|
+
payload = response.json()
|
|
322
|
+
except Exception as e:
|
|
323
|
+
console.print(f"[red]❌ Failed to fetch models from {url}: {e}[/red]")
|
|
324
|
+
raise typer.Exit(1)
|
|
325
|
+
|
|
326
|
+
models_data = payload.get("models")
|
|
327
|
+
if not isinstance(models_data, list):
|
|
328
|
+
console.print("[red]❌ Unexpected /models response shape (expected top-level models list)[/red]")
|
|
329
|
+
if json_output:
|
|
330
|
+
console.print_json(json.dumps(payload))
|
|
331
|
+
raise typer.Exit(1)
|
|
332
|
+
|
|
333
|
+
if json_output:
|
|
334
|
+
console.print_json(json.dumps(payload))
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
table = Table(title="HyperClaw Models")
|
|
338
|
+
table.add_column("Model ID", style="cyan")
|
|
339
|
+
table.add_column("Context", style="blue")
|
|
340
|
+
table.add_column("Vision", style="green")
|
|
341
|
+
table.add_column("Tools", style="green")
|
|
342
|
+
table.add_column("Reasoning", style="magenta")
|
|
343
|
+
|
|
344
|
+
for model in models_data:
|
|
345
|
+
model_id = str(model.get("id", ""))
|
|
346
|
+
context_length = model.get("context_length", "")
|
|
347
|
+
vision = "yes" if model.get("supports_vision") else "no"
|
|
348
|
+
tools = "yes" if model.get("supports_tools") else "no"
|
|
349
|
+
reasoning = "yes" if model.get("supports_reasoning") else "no"
|
|
350
|
+
table.add_row(model_id, str(context_length), vision, tools, reasoning)
|
|
351
|
+
|
|
352
|
+
console.print()
|
|
353
|
+
console.print(table)
|
|
354
|
+
console.print()
|
|
355
|
+
console.print(f"Source: {url}")
|
|
356
|
+
|
|
357
|
+
|
|
307
358
|
OPENCLAW_CONFIG_PATH = Path.home() / ".openclaw" / "openclaw.json"
|
|
308
359
|
|
|
309
360
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""hyper instances commands"""
|
|
2
2
|
import typer
|
|
3
3
|
from typing import Optional
|
|
4
|
-
from hypercli import HyperCLI
|
|
4
|
+
from hypercli import HyperCLI, X402Client
|
|
5
5
|
from .output import output, console, success, spinner
|
|
6
6
|
|
|
7
7
|
app = typer.Typer(help="GPU instances - browse and launch")
|
|
@@ -193,12 +193,20 @@ def launch(
|
|
|
193
193
|
port: Optional[list[str]] = typer.Option(None, "--port", "-p", help="Ports (name:port)"),
|
|
194
194
|
registry_user: Optional[str] = typer.Option(None, "--registry-user", help="Private registry username"),
|
|
195
195
|
registry_password: Optional[str] = typer.Option(None, "--registry-password", help="Private registry password"),
|
|
196
|
+
x402: bool = typer.Option(False, "--x402", help="Pay per-use via embedded x402 wallet"),
|
|
197
|
+
amount: Optional[float] = typer.Option(None, "--amount", help="USDC amount to spend with --x402"),
|
|
196
198
|
follow: bool = typer.Option(False, "--follow", "-f", help="Follow logs after creation"),
|
|
197
199
|
cancel_on_exit: bool = typer.Option(False, "--cancel-on-exit", help="Cancel job when exiting with Ctrl+C"),
|
|
198
200
|
fmt: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
|
|
199
201
|
):
|
|
200
|
-
"""Launch a new GPU instance
|
|
201
|
-
|
|
202
|
+
"""Launch a new GPU instance
|
|
203
|
+
|
|
204
|
+
Examples:
|
|
205
|
+
hyper instances launch nvidia/cuda:12.6.3-base-ubuntu22.04 -c 'nvidia-smi && sleep 60'
|
|
206
|
+
hyper instances launch nvidia/cuda:12.6.3-base-ubuntu22.04 -g l4 -r kr -c 'nvidia-smi' -t 120
|
|
207
|
+
hyper instances launch my-registry.com/my-image:latest -g h100 -n 8 -c 'python train.py'
|
|
208
|
+
hyper instances launch nvidia/cuda:12.6.3-base-ubuntu22.04 --x402 --amount 2.5
|
|
209
|
+
"""
|
|
202
210
|
|
|
203
211
|
# Parse env vars
|
|
204
212
|
env_dict = None
|
|
@@ -223,32 +231,99 @@ def launch(
|
|
|
223
231
|
if registry_user and registry_password:
|
|
224
232
|
registry_auth = {"username": registry_user, "password": registry_password}
|
|
225
233
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
# Auto-wrap command in sh -c if it contains shell operators
|
|
235
|
+
if command and any(op in command for op in ["&&", "||", "|", ";", ">", "<", "$"]):
|
|
236
|
+
command = f'sh -c "{command}"'
|
|
237
|
+
|
|
238
|
+
follow_api_key = None
|
|
239
|
+
|
|
240
|
+
if x402:
|
|
241
|
+
if amount is None:
|
|
242
|
+
raise typer.BadParameter("--amount is required when using --x402")
|
|
243
|
+
if amount <= 0:
|
|
244
|
+
raise typer.BadParameter("--amount must be greater than 0")
|
|
245
|
+
if runtime is not None:
|
|
246
|
+
console.print("[yellow]Ignoring --runtime with --x402 (runtime is computed from payment amount).[/yellow]")
|
|
247
|
+
|
|
248
|
+
from .wallet import require_wallet_deps, load_wallet
|
|
249
|
+
|
|
250
|
+
require_wallet_deps()
|
|
251
|
+
account = load_wallet()
|
|
252
|
+
x402_client = X402Client()
|
|
253
|
+
|
|
254
|
+
with spinner("Launching instance with x402..."):
|
|
255
|
+
x402_job = x402_client.create_job(
|
|
256
|
+
amount=amount,
|
|
257
|
+
account=account,
|
|
258
|
+
image=image,
|
|
259
|
+
command=command,
|
|
260
|
+
gpu_type=gpu,
|
|
261
|
+
gpu_count=count,
|
|
262
|
+
region=region,
|
|
263
|
+
interruptible=interruptible,
|
|
264
|
+
env=env_dict,
|
|
265
|
+
ports=ports_dict,
|
|
266
|
+
registry_auth=registry_auth,
|
|
267
|
+
)
|
|
268
|
+
job = x402_job.job
|
|
269
|
+
follow_api_key = x402_job.access_key
|
|
270
|
+
|
|
271
|
+
if fmt == "json":
|
|
272
|
+
output(
|
|
273
|
+
{
|
|
274
|
+
"job": job.__dict__,
|
|
275
|
+
"access_key": x402_job.access_key,
|
|
276
|
+
"status_url": x402_job.status_url,
|
|
277
|
+
"logs_url": x402_job.logs_url,
|
|
278
|
+
"cancel_url": x402_job.cancel_url,
|
|
279
|
+
},
|
|
280
|
+
"json",
|
|
281
|
+
)
|
|
282
|
+
else:
|
|
283
|
+
success(f"Instance launched: {job.job_id}")
|
|
284
|
+
console.print(f" State: {job.state}")
|
|
285
|
+
console.print(f" GPU: {job.gpu_type} x{job.gpu_count}")
|
|
286
|
+
console.print(f" Region: {job.region}")
|
|
287
|
+
console.print(f" Price: ${job.price_per_hour:.2f}/hr")
|
|
288
|
+
if job.hostname:
|
|
289
|
+
console.print(f" Hostname: {job.hostname}")
|
|
290
|
+
console.print(f" Access Key: {x402_job.access_key}")
|
|
291
|
+
console.print(f" Status URL: {x402_job.status_url}")
|
|
292
|
+
console.print(f" Logs URL: {x402_job.logs_url}")
|
|
293
|
+
console.print(f" Cancel URL: {x402_job.cancel_url}")
|
|
242
294
|
else:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
295
|
+
client = get_client()
|
|
296
|
+
|
|
297
|
+
with spinner("Launching instance..."):
|
|
298
|
+
job = client.jobs.create(
|
|
299
|
+
image=image,
|
|
300
|
+
command=command,
|
|
301
|
+
gpu_type=gpu,
|
|
302
|
+
gpu_count=count,
|
|
303
|
+
region=region,
|
|
304
|
+
runtime=runtime,
|
|
305
|
+
interruptible=interruptible,
|
|
306
|
+
env=env_dict,
|
|
307
|
+
ports=ports_dict,
|
|
308
|
+
registry_auth=registry_auth,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if fmt == "json":
|
|
312
|
+
output(job, "json")
|
|
313
|
+
else:
|
|
314
|
+
success(f"Instance launched: {job.job_id}")
|
|
315
|
+
console.print(f" State: {job.state}")
|
|
316
|
+
console.print(f" GPU: {job.gpu_type} x{job.gpu_count}")
|
|
317
|
+
console.print(f" Region: {job.region}")
|
|
318
|
+
console.print(f" Price: ${job.price_per_hour:.2f}/hr")
|
|
319
|
+
if job.hostname:
|
|
320
|
+
console.print(f" Hostname: {job.hostname}")
|
|
250
321
|
|
|
251
322
|
if follow:
|
|
252
323
|
console.print()
|
|
253
324
|
from .tui.job_monitor import run_job_monitor
|
|
254
|
-
|
|
325
|
+
|
|
326
|
+
if follow_api_key:
|
|
327
|
+
run_job_monitor(job.job_id, cancel_on_exit=cancel_on_exit, api_key=follow_api_key)
|
|
328
|
+
else:
|
|
329
|
+
run_job_monitor(job.job_id, cancel_on_exit=cancel_on_exit)
|
|
@@ -45,18 +45,28 @@ def get_job(
|
|
|
45
45
|
@app.command("logs")
|
|
46
46
|
def logs(
|
|
47
47
|
job_id: str = typer.Argument(..., help="Job ID"),
|
|
48
|
-
follow: bool = typer.Option(False, "--follow", "-f", help="Stream logs"),
|
|
49
|
-
|
|
48
|
+
follow: bool = typer.Option(False, "--follow", "-f", help="Stream logs via WebSocket"),
|
|
49
|
+
tail: int = typer.Option(None, "--tail", "-n", help="Show last N lines"),
|
|
50
|
+
tui: bool = typer.Option(False, "--tui", help="Interactive TUI with metrics"),
|
|
51
|
+
cancel_on_exit: bool = typer.Option(False, "--cancel-on-exit", help="Cancel job when exiting with Ctrl+C (with --tui)"),
|
|
50
52
|
):
|
|
51
53
|
"""Get job logs"""
|
|
52
54
|
client = get_client()
|
|
53
55
|
|
|
54
|
-
if
|
|
56
|
+
if tui:
|
|
55
57
|
_follow_job(job_id, cancel_on_exit=cancel_on_exit)
|
|
58
|
+
elif follow:
|
|
59
|
+
_stream_logs(job_id)
|
|
56
60
|
else:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
logs_str = client.jobs.logs(job_id)
|
|
62
|
+
if not logs_str:
|
|
63
|
+
print("(no logs)")
|
|
64
|
+
return
|
|
65
|
+
lines = logs_str.rstrip("\n").split("\n")
|
|
66
|
+
if tail:
|
|
67
|
+
lines = lines[-tail:]
|
|
68
|
+
for line in lines:
|
|
69
|
+
print(line)
|
|
60
70
|
|
|
61
71
|
|
|
62
72
|
@app.command("metrics")
|
|
@@ -168,6 +178,31 @@ def _make_bar(value: float, max_val: float, warn: float = None, crit: float = No
|
|
|
168
178
|
return f"[{color}]{bar}[/{color}]"
|
|
169
179
|
|
|
170
180
|
|
|
181
|
+
def _stream_logs(job_id: str):
|
|
182
|
+
"""Stream logs via WebSocket (like tail -f)"""
|
|
183
|
+
import asyncio
|
|
184
|
+
from hypercli import stream_logs
|
|
185
|
+
|
|
186
|
+
client = get_client()
|
|
187
|
+
|
|
188
|
+
async def _run():
|
|
189
|
+
try:
|
|
190
|
+
await stream_logs(
|
|
191
|
+
client,
|
|
192
|
+
job_id,
|
|
193
|
+
on_line=lambda line: print(line),
|
|
194
|
+
fetch_initial=True,
|
|
195
|
+
fetch_final=True,
|
|
196
|
+
)
|
|
197
|
+
except KeyboardInterrupt:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
asyncio.run(_run())
|
|
202
|
+
except KeyboardInterrupt:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
|
|
171
206
|
def _follow_job(job_id: str, cancel_on_exit: bool = False):
|
|
172
207
|
"""Follow job with TUI"""
|
|
173
208
|
from .tui.job_monitor import run_job_monitor
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import time
|
|
3
3
|
import typer
|
|
4
4
|
from typing import Optional
|
|
5
|
-
from hypercli import HyperCLI
|
|
5
|
+
from hypercli import HyperCLI, X402Client
|
|
6
6
|
from .output import output, console, success, spinner
|
|
7
7
|
|
|
8
8
|
app = typer.Typer(help="Manage renders")
|
|
@@ -52,12 +52,13 @@ def create_render(
|
|
|
52
52
|
gpu: str = typer.Option("L40S", "--gpu", "-g", help="GPU type"),
|
|
53
53
|
region: Optional[str] = typer.Option(None, "--region", "-r", help="Region"),
|
|
54
54
|
render_type: str = typer.Option("comfyui", "--type", "-t", help="Render type"),
|
|
55
|
+
x402: bool = typer.Option(False, "--x402", help="Pay per-use via embedded x402 wallet"),
|
|
56
|
+
amount: Optional[float] = typer.Option(None, "--amount", help="USDC amount to spend with --x402"),
|
|
55
57
|
wait: bool = typer.Option(False, "--wait", "-w", help="Wait for completion"),
|
|
56
58
|
notify_url: Optional[str] = typer.Option(None, "--notify", help="Webhook URL for completion"),
|
|
57
59
|
fmt: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
|
|
58
60
|
):
|
|
59
61
|
"""Create a new render"""
|
|
60
|
-
client = get_client()
|
|
61
62
|
|
|
62
63
|
params = {
|
|
63
64
|
"template": template,
|
|
@@ -67,6 +68,51 @@ def create_render(
|
|
|
67
68
|
if region:
|
|
68
69
|
params["region"] = region
|
|
69
70
|
|
|
71
|
+
if x402:
|
|
72
|
+
if amount is None:
|
|
73
|
+
raise typer.BadParameter("--amount is required when using --x402")
|
|
74
|
+
if amount <= 0:
|
|
75
|
+
raise typer.BadParameter("--amount must be greater than 0")
|
|
76
|
+
|
|
77
|
+
from .wallet import require_wallet_deps, load_wallet
|
|
78
|
+
|
|
79
|
+
require_wallet_deps()
|
|
80
|
+
account = load_wallet()
|
|
81
|
+
x402_client = X402Client()
|
|
82
|
+
|
|
83
|
+
with spinner("Creating render with x402..."):
|
|
84
|
+
x402_render = x402_client.create_render(
|
|
85
|
+
amount=amount,
|
|
86
|
+
account=account,
|
|
87
|
+
params=params,
|
|
88
|
+
render_type=render_type,
|
|
89
|
+
notify_url=notify_url,
|
|
90
|
+
)
|
|
91
|
+
render = x402_render.render
|
|
92
|
+
wait_client = HyperCLI(api_key=x402_render.access_key)
|
|
93
|
+
|
|
94
|
+
if fmt == "json" and not wait:
|
|
95
|
+
output(
|
|
96
|
+
{
|
|
97
|
+
"render": render.__dict__,
|
|
98
|
+
"access_key": x402_render.access_key,
|
|
99
|
+
"status_url": x402_render.status_url,
|
|
100
|
+
"cancel_url": x402_render.cancel_url,
|
|
101
|
+
},
|
|
102
|
+
"json",
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
console.print(f"[bold green]✓[/bold green] Render created: [cyan]{render.render_id}[/cyan]")
|
|
106
|
+
console.print(f" State: {render.state}")
|
|
107
|
+
console.print(f" Access Key: {x402_render.access_key}")
|
|
108
|
+
console.print(f" Status URL: {x402_render.status_url}")
|
|
109
|
+
console.print(f" Cancel URL: {x402_render.cancel_url}")
|
|
110
|
+
|
|
111
|
+
if wait:
|
|
112
|
+
_wait_for_render(wait_client, render.render_id, fmt)
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
client = get_client()
|
|
70
116
|
with spinner("Creating render..."):
|
|
71
117
|
render = client.renders.create(params=params, render_type=render_type, notify_url=notify_url)
|
|
72
118
|
|
|
@@ -79,7 +125,6 @@ def create_render(
|
|
|
79
125
|
if wait:
|
|
80
126
|
_wait_for_render(client, render.render_id, fmt)
|
|
81
127
|
|
|
82
|
-
|
|
83
128
|
@app.command("status")
|
|
84
129
|
def status(
|
|
85
130
|
render_id: str = typer.Argument(..., help="Render ID"),
|
|
@@ -163,9 +163,10 @@ async def _run_job_monitor_async(
|
|
|
163
163
|
status_q: Queue = None,
|
|
164
164
|
stop_on_status_complete: bool = False,
|
|
165
165
|
cancel_on_exit: bool = False,
|
|
166
|
+
api_key: str | None = None,
|
|
166
167
|
):
|
|
167
168
|
"""Async job monitor - uses SDK LogStream for logs"""
|
|
168
|
-
client = HyperCLI()
|
|
169
|
+
client = HyperCLI(api_key=api_key) if api_key else HyperCLI()
|
|
169
170
|
should_cancel = False
|
|
170
171
|
logs: deque[str] = deque(maxlen=MAX_LOG_LINES)
|
|
171
172
|
log_stream: Optional[LogStream] = None
|
|
@@ -340,6 +341,7 @@ def run_job_monitor(
|
|
|
340
341
|
status_q: Queue = None,
|
|
341
342
|
stop_on_status_complete: bool = False,
|
|
342
343
|
cancel_on_exit: bool = False,
|
|
344
|
+
api_key: str | None = None,
|
|
343
345
|
):
|
|
344
346
|
"""Run the job monitor TUI (sync wrapper).
|
|
345
347
|
|
|
@@ -348,5 +350,6 @@ def run_job_monitor(
|
|
|
348
350
|
status_q: Optional queue receiving JobStatus updates for status pane
|
|
349
351
|
stop_on_status_complete: If True, exit when JobStatus.complete is True
|
|
350
352
|
cancel_on_exit: If True, cancel the job when exiting via Ctrl+C
|
|
353
|
+
api_key: Optional bearer token (API key or x402 access key)
|
|
351
354
|
"""
|
|
352
|
-
asyncio.run(_run_job_monitor_async(job_id, status_q, stop_on_status_complete, cancel_on_exit))
|
|
355
|
+
asyncio.run(_run_job_monitor_async(job_id, status_q, stop_on_status_complete, cancel_on_exit, api_key))
|
|
@@ -20,6 +20,7 @@ except ImportError:
|
|
|
20
20
|
|
|
21
21
|
WALLET_DIR = Path.home() / ".hypercli"
|
|
22
22
|
WALLET_PATH = WALLET_DIR / "wallet.json"
|
|
23
|
+
WALLET_PASSPHRASE_PATH = WALLET_DIR / "wallet.passphrase"
|
|
23
24
|
BASE_RPC = "https://mainnet.base.org"
|
|
24
25
|
USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
25
26
|
|
|
@@ -33,6 +34,20 @@ def require_wallet_deps():
|
|
|
33
34
|
raise typer.Exit(1)
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
|
|
38
|
+
def _get_wallet_passphrase(prompt: str = "Unlock keystore passphrase: ") -> str:
|
|
39
|
+
passphrase_env = os.getenv("HYPERCLI_WALLET_PASSPHRASE")
|
|
40
|
+
if passphrase_env:
|
|
41
|
+
return passphrase_env
|
|
42
|
+
|
|
43
|
+
if WALLET_PASSPHRASE_PATH.exists():
|
|
44
|
+
passphrase_file = WALLET_PASSPHRASE_PATH.read_text().strip()
|
|
45
|
+
if passphrase_file:
|
|
46
|
+
console.print("[dim]Using passphrase from ~/.hypercli/wallet.passphrase[/dim]")
|
|
47
|
+
return passphrase_file
|
|
48
|
+
|
|
49
|
+
return getpass.getpass(prompt)
|
|
50
|
+
|
|
36
51
|
@app.command("create")
|
|
37
52
|
def create():
|
|
38
53
|
"""Create a new wallet with encrypted keystore"""
|
|
@@ -195,12 +210,7 @@ def balance():
|
|
|
195
210
|
with open(WALLET_PATH) as f:
|
|
196
211
|
keystore = json.load(f)
|
|
197
212
|
|
|
198
|
-
|
|
199
|
-
passphrase_env = os.getenv("HYPERCLI_WALLET_PASSPHRASE")
|
|
200
|
-
if passphrase_env:
|
|
201
|
-
passphrase = passphrase_env
|
|
202
|
-
else:
|
|
203
|
-
passphrase = getpass.getpass("Unlock keystore passphrase: ")
|
|
213
|
+
passphrase = _get_wallet_passphrase()
|
|
204
214
|
|
|
205
215
|
try:
|
|
206
216
|
private_key = Account.decrypt(keystore, passphrase)
|
|
@@ -503,12 +513,7 @@ def load_wallet():
|
|
|
503
513
|
with open(WALLET_PATH) as f:
|
|
504
514
|
keystore = json.load(f)
|
|
505
515
|
|
|
506
|
-
|
|
507
|
-
passphrase_env = os.getenv("HYPERCLI_WALLET_PASSPHRASE")
|
|
508
|
-
if passphrase_env:
|
|
509
|
-
passphrase = passphrase_env
|
|
510
|
-
else:
|
|
511
|
-
passphrase = getpass.getpass("Unlock keystore passphrase: ")
|
|
516
|
+
passphrase = _get_wallet_passphrase()
|
|
512
517
|
|
|
513
518
|
try:
|
|
514
519
|
private_key = Account.decrypt(keystore, passphrase)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hypercli-cli"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.6"
|
|
8
8
|
description = "CLI for HyperCLI - GPU orchestration and LLM API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -13,7 +13,7 @@ authors = [
|
|
|
13
13
|
{ name = "HyperCLI", email = "support@hypercli.com" }
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"hypercli-sdk>=0.
|
|
16
|
+
"hypercli-sdk>=0.8.3",
|
|
17
17
|
"openai>=2.8.1",
|
|
18
18
|
"typer>=0.20.0",
|
|
19
19
|
"rich>=14.2.0",
|
|
@@ -25,7 +25,7 @@ dependencies = [
|
|
|
25
25
|
|
|
26
26
|
[project.optional-dependencies]
|
|
27
27
|
comfyui = [
|
|
28
|
-
"hypercli-sdk[comfyui]>=0.
|
|
28
|
+
"hypercli-sdk[comfyui]>=0.8.3",
|
|
29
29
|
]
|
|
30
30
|
wallet = [
|
|
31
31
|
"x402[httpx,evm]>=2.0.0",
|
|
@@ -35,7 +35,7 @@ wallet = [
|
|
|
35
35
|
"qrcode[pil]>=7.4.0",
|
|
36
36
|
]
|
|
37
37
|
all = [
|
|
38
|
-
"hypercli-sdk[comfyui]>=0.
|
|
38
|
+
"hypercli-sdk[comfyui]>=0.8.3",
|
|
39
39
|
"x402[httpx,evm]>=2.0.0",
|
|
40
40
|
"eth-account>=0.13.0",
|
|
41
41
|
"web3>=7.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.3"
|
|
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
|