nepher-cli 0.2.5__tar.gz → 0.2.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.
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/PKG-INFO +1 -1
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/pyproject.toml +1 -1
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/account.py +73 -1
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/tournament.py +38 -9
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/tournament/api.py +20 -3
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/.gitignore +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/README.md +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/__init__.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/__main__.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/cli.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/__init__.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/envhub.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/hackathon.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/highlight.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/commands/simstore.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/config.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/core/__init__.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/core/credentials.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/core/http.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/envhub/__init__.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/envhub/cache.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/envhub/config.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/py.typed +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/tournament/__init__.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/tournament/agent_check.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/tournament/packer.py +0 -0
- {nepher_cli-0.2.5 → nepher_cli-0.2.6}/src/nepher_cli/tournament/wallet.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nepher-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: npcli — unified Nepher command-line interface for accounts, hackathons, EnvHub, tournaments, and SimStore
|
|
5
5
|
Project-URL: Homepage, https://github.com/nepher-ai/nepher-cli
|
|
6
6
|
Author: Nepher
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nepher-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "npcli — unified Nepher command-line interface for accounts, hackathons, EnvHub, tournaments, and SimStore"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -396,7 +396,7 @@ def register_coldkey(wallet: str, api_key: str, base_url: str) -> int:
|
|
|
396
396
|
|
|
397
397
|
@click.group("account")
|
|
398
398
|
def account() -> None:
|
|
399
|
-
"""Manage your Nepher account — login, API keys, and
|
|
399
|
+
"""Manage your Nepher account — login, API keys, coldkey, and platform balance."""
|
|
400
400
|
|
|
401
401
|
|
|
402
402
|
# ── Auth ────────────────────────────────────────────────────────────────────
|
|
@@ -627,6 +627,78 @@ def api_keys_revoke(key_id: str, api_key: str | None) -> None:
|
|
|
627
627
|
# ── Coldkey ──────────────────────────────────────────────────────────────────
|
|
628
628
|
|
|
629
629
|
|
|
630
|
+
@account.command("balance")
|
|
631
|
+
@click.option(
|
|
632
|
+
"--api-key", "--apikey", "api_key",
|
|
633
|
+
default=None, envvar="NEPHER_API_KEY", metavar="KEY",
|
|
634
|
+
help="Nepher API key (nepher_...). Falls back to stored credentials.",
|
|
635
|
+
)
|
|
636
|
+
def cmd_balance(api_key: str | None) -> None:
|
|
637
|
+
"""Show platform balance used for submission fees and unlocks."""
|
|
638
|
+
headers = _require_auth(api_key)
|
|
639
|
+
url = f"{ACCOUNT_BACKEND.rstrip('/')}/api/v1/billing/balance"
|
|
640
|
+
try:
|
|
641
|
+
r = httpx.get(url, headers=headers, timeout=30.0)
|
|
642
|
+
except httpx.RequestError as e:
|
|
643
|
+
console.print(f"[red]Network error[/red]: {e}")
|
|
644
|
+
raise SystemExit(1) from e
|
|
645
|
+
if r.status_code >= 400:
|
|
646
|
+
console.print(f"[red]{parse_error_body(r.text) or r.text.strip() or f'HTTP {r.status_code}'}[/red]")
|
|
647
|
+
raise SystemExit(1)
|
|
648
|
+
body = r.json()
|
|
649
|
+
console.print(f" Balance : [bold]{body.get('balance_tao', 0)} TAO[/bold]")
|
|
650
|
+
if body.get("coldkey"):
|
|
651
|
+
console.print(f" Coldkey : {body['coldkey']}")
|
|
652
|
+
else:
|
|
653
|
+
console.print(" Coldkey : [yellow]not registered[/yellow] — run npcli account register-coldkey")
|
|
654
|
+
if body.get("network"):
|
|
655
|
+
console.print(f" Network : {body['network']}")
|
|
656
|
+
console.print(
|
|
657
|
+
" Deposit : "
|
|
658
|
+
+ ("available via Account → Billing on the website" if body.get("deposit_available") else "unavailable")
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@account.command("ledger")
|
|
663
|
+
@click.option("--limit", default=20, show_default=True, help="Number of ledger rows.")
|
|
664
|
+
@click.option(
|
|
665
|
+
"--api-key", "--apikey", "api_key",
|
|
666
|
+
default=None, envvar="NEPHER_API_KEY", metavar="KEY",
|
|
667
|
+
help="Nepher API key (nepher_...). Falls back to stored credentials.",
|
|
668
|
+
)
|
|
669
|
+
def cmd_ledger(limit: int, api_key: str | None) -> None:
|
|
670
|
+
"""Show recent platform billing history (deposits, fees, unlocks)."""
|
|
671
|
+
headers = _require_auth(api_key)
|
|
672
|
+
url = f"{ACCOUNT_BACKEND.rstrip('/')}/api/v1/billing/ledger"
|
|
673
|
+
try:
|
|
674
|
+
r = httpx.get(url, headers=headers, params={"limit": limit}, timeout=30.0)
|
|
675
|
+
except httpx.RequestError as e:
|
|
676
|
+
console.print(f"[red]Network error[/red]: {e}")
|
|
677
|
+
raise SystemExit(1) from e
|
|
678
|
+
if r.status_code >= 400:
|
|
679
|
+
console.print(f"[red]{parse_error_body(r.text) or r.text.strip() or f'HTTP {r.status_code}'}[/red]")
|
|
680
|
+
raise SystemExit(1)
|
|
681
|
+
items = (r.json() or {}).get("items") or []
|
|
682
|
+
if not items:
|
|
683
|
+
console.print("[dim]No billing activity yet.[/dim]")
|
|
684
|
+
return
|
|
685
|
+
table = Table(show_header=True, header_style="bold")
|
|
686
|
+
table.add_column("When")
|
|
687
|
+
table.add_column("Type")
|
|
688
|
+
table.add_column("Amount (TAO)", justify="right")
|
|
689
|
+
table.add_column("Balance after", justify="right")
|
|
690
|
+
table.add_column("Details")
|
|
691
|
+
for row in items:
|
|
692
|
+
table.add_row(
|
|
693
|
+
str(row.get("created_at") or "—"),
|
|
694
|
+
str(row.get("entry_type") or "—"),
|
|
695
|
+
f"{float(row.get('amount_tao') or 0):.6f}",
|
|
696
|
+
f"{float(row.get('balance_after_tao') or 0):.6f}",
|
|
697
|
+
str(row.get("description") or "—"),
|
|
698
|
+
)
|
|
699
|
+
console.print(table)
|
|
700
|
+
|
|
701
|
+
|
|
630
702
|
@account.command("register-coldkey")
|
|
631
703
|
@click.option("--wallet", required=True, metavar="NAME", help="Bittensor wallet name. Must exist in your local btcli wallet.")
|
|
632
704
|
@click.option(
|
|
@@ -582,16 +582,43 @@ def tournament_submit(
|
|
|
582
582
|
resolved_tournament_id = token_data["tournament_id"]
|
|
583
583
|
upload_token = token_data["upload_token"]
|
|
584
584
|
|
|
585
|
+
fee = token_data.get("submission_fee_tao")
|
|
586
|
+
if fee is not None:
|
|
587
|
+
console.print(
|
|
588
|
+
f"Submission fee: [bold]{fee} TAO[/bold] "
|
|
589
|
+
f"(debited from platform balance on upload)"
|
|
590
|
+
)
|
|
591
|
+
fee_min = token_data.get("submission_fee_min_tao")
|
|
592
|
+
fee_max = token_data.get("submission_fee_max_tao")
|
|
593
|
+
fee_dur = token_data.get("submission_fee_duration_hours")
|
|
594
|
+
if fee_min is not None and fee_max is not None:
|
|
595
|
+
console.print(
|
|
596
|
+
f" [dim]Fee curve: {fee_max} → {fee_min} TAO"
|
|
597
|
+
+ (f" over {fee_dur}h" if fee_dur is not None else "")
|
|
598
|
+
+ "[/dim]"
|
|
599
|
+
)
|
|
600
|
+
|
|
585
601
|
console.print("Uploading agent...")
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
602
|
+
try:
|
|
603
|
+
upload_result = await tournament_api.upload_agent(
|
|
604
|
+
api_key=resolved_key,
|
|
605
|
+
api_url=resolved_url,
|
|
606
|
+
tournament_id=resolved_tournament_id,
|
|
607
|
+
upload_token=upload_token,
|
|
608
|
+
miner_hotkey=miner_hotkey,
|
|
609
|
+
content_hash=content_hash,
|
|
610
|
+
file_path=archive,
|
|
611
|
+
)
|
|
612
|
+
except tournament_api.UploadChargeError as e:
|
|
613
|
+
console.print(f"[red]Submission fee failed:[/red] {e}")
|
|
614
|
+
console.print(
|
|
615
|
+
" Check balance with [bold]npcli account balance[/bold], "
|
|
616
|
+
"then deposit TAO on Account → Billing."
|
|
617
|
+
)
|
|
618
|
+
return 1
|
|
619
|
+
|
|
620
|
+
agent_id = upload_result["agent_id"]
|
|
621
|
+
charged = upload_result.get("submission_fee_tao")
|
|
595
622
|
|
|
596
623
|
t_title = _tournament_title(token_data)
|
|
597
624
|
t_task = _tournament_task_name(token_data)
|
|
@@ -600,6 +627,8 @@ def tournament_submit(
|
|
|
600
627
|
console.print("[green]Agent submitted successfully.[/green]")
|
|
601
628
|
console.print(f" Agent ID : [bold]{agent_id}[/bold]")
|
|
602
629
|
console.print(f" Tournament ID: [bold]{resolved_tournament_id}[/bold]")
|
|
630
|
+
if charged is not None:
|
|
631
|
+
console.print(f" Fee charged : [bold]{charged} TAO[/bold]")
|
|
603
632
|
if t_title and t_title != "—":
|
|
604
633
|
console.print(f" Title : {t_title}")
|
|
605
634
|
if t_task and t_task != "—":
|
|
@@ -21,6 +21,14 @@ def _json_headers(api_key: str) -> dict[str, str]:
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class UploadChargeError(RuntimeError):
|
|
25
|
+
"""Raised when upload fails due to payment / platform balance."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, message: str, *, status_code: int = 402):
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
|
|
31
|
+
|
|
24
32
|
def _raise_for_error(response: httpx.Response) -> None:
|
|
25
33
|
if response.status_code < 400:
|
|
26
34
|
return
|
|
@@ -29,6 +37,8 @@ def _raise_for_error(response: httpx.Response) -> None:
|
|
|
29
37
|
or response.text.strip()
|
|
30
38
|
or f"HTTP {response.status_code}"
|
|
31
39
|
)
|
|
40
|
+
if response.status_code == 402:
|
|
41
|
+
raise UploadChargeError(msg, status_code=402)
|
|
32
42
|
raise RuntimeError(msg)
|
|
33
43
|
|
|
34
44
|
|
|
@@ -76,8 +86,11 @@ async def upload_agent(
|
|
|
76
86
|
miner_hotkey: str,
|
|
77
87
|
content_hash: str,
|
|
78
88
|
file_path: Path,
|
|
79
|
-
) -> str:
|
|
80
|
-
"""POST /api/v1/agents/upload/{tournament_id} (multipart) →
|
|
89
|
+
) -> dict[str, Any]:
|
|
90
|
+
"""POST /api/v1/agents/upload/{tournament_id} (multipart) → response dict.
|
|
91
|
+
|
|
92
|
+
Returns at least ``agent_id``; may include ``submission_fee_tao``.
|
|
93
|
+
"""
|
|
81
94
|
url = f"{api_url.rstrip('/')}/api/v1/agents/upload/{tournament_id}"
|
|
82
95
|
headers = {
|
|
83
96
|
"X-API-Key": api_key,
|
|
@@ -97,4 +110,8 @@ async def upload_agent(
|
|
|
97
110
|
agent_id = data.get("id") or data.get("agent_id")
|
|
98
111
|
if not agent_id:
|
|
99
112
|
raise RuntimeError(f"No agent ID in upload response: {data}")
|
|
100
|
-
return
|
|
113
|
+
return {
|
|
114
|
+
"agent_id": str(agent_id),
|
|
115
|
+
"submission_fee_tao": data.get("submission_fee_tao"),
|
|
116
|
+
"raw": data,
|
|
117
|
+
}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|