tmp-nepher-cli 0.2.5__py3-none-any.whl

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.
@@ -0,0 +1,100 @@
1
+ """Tournament submission API calls — httpx only, no external dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from nepher_cli.core.http import parse_error_body
11
+
12
+ _DEFAULT_TIMEOUT = 30.0
13
+ _UPLOAD_TIMEOUT = 600.0 # 10 min — large agent ZIPs can be slow
14
+
15
+
16
+ def _json_headers(api_key: str) -> dict[str, str]:
17
+ return {
18
+ "X-API-Key": api_key,
19
+ "Content-Type": "application/json",
20
+ "Accept": "application/json",
21
+ }
22
+
23
+
24
+ def _raise_for_error(response: httpx.Response) -> None:
25
+ if response.status_code < 400:
26
+ return
27
+ msg = (
28
+ parse_error_body(response.text)
29
+ or response.text.strip()
30
+ or f"HTTP {response.status_code}"
31
+ )
32
+ raise RuntimeError(msg)
33
+
34
+
35
+ async def request_upload_token(
36
+ *,
37
+ api_key: str,
38
+ api_url: str,
39
+ miner_hotkey: str,
40
+ public_key: str,
41
+ file_info: str,
42
+ signature: str,
43
+ file_size: int,
44
+ tournament_id: str | None = None,
45
+ ) -> dict[str, Any]:
46
+ """POST /api/v1/agents/upload/verify → parsed JSON token dict.
47
+
48
+ The returned dict contains at least ``upload_token`` and ``tournament_id``.
49
+ """
50
+ body: dict[str, Any] = {
51
+ "miner_hotkey": miner_hotkey,
52
+ "public_key": public_key,
53
+ "file_info": file_info,
54
+ "signature": signature,
55
+ "file_size": file_size,
56
+ }
57
+ if tournament_id is not None:
58
+ body["tournament_id"] = tournament_id
59
+
60
+ url = f"{api_url.rstrip('/')}/api/v1/agents/upload/verify"
61
+ async with httpx.AsyncClient(
62
+ timeout=_DEFAULT_TIMEOUT,
63
+ headers=_json_headers(api_key),
64
+ ) as client:
65
+ response = await client.post(url, json=body)
66
+ _raise_for_error(response)
67
+ return response.json()
68
+
69
+
70
+ async def upload_agent(
71
+ *,
72
+ api_key: str,
73
+ api_url: str,
74
+ tournament_id: str,
75
+ upload_token: str,
76
+ miner_hotkey: str,
77
+ content_hash: str,
78
+ file_path: Path,
79
+ ) -> str:
80
+ """POST /api/v1/agents/upload/{tournament_id} (multipart) → agent_id string."""
81
+ url = f"{api_url.rstrip('/')}/api/v1/agents/upload/{tournament_id}"
82
+ headers = {
83
+ "X-API-Key": api_key,
84
+ "Accept": "application/json",
85
+ "X-Upload-Token": upload_token,
86
+ }
87
+ async with httpx.AsyncClient(timeout=httpx.Timeout(_UPLOAD_TIMEOUT)) as client:
88
+ with open(file_path, "rb") as fh:
89
+ response = await client.post(
90
+ url,
91
+ files={"file": (file_path.name, fh, "application/zip")},
92
+ data={"miner_hotkey": miner_hotkey, "content_hash": content_hash},
93
+ headers=headers,
94
+ )
95
+ _raise_for_error(response)
96
+ data = response.json()
97
+ agent_id = data.get("id") or data.get("agent_id")
98
+ if not agent_id:
99
+ raise RuntimeError(f"No agent ID in upload response: {data}")
100
+ return str(agent_id)
@@ -0,0 +1,50 @@
1
+ """Archive and checksum helpers — pure Python stdlib, no external dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import zipfile
7
+ from pathlib import Path
8
+
9
+ # Names and suffixes excluded from submission archives
10
+ _EXCLUDED_NAMES: frozenset[str] = frozenset({
11
+ "__pycache__", ".git", ".gitignore",
12
+ "logs", "outputs", ".env", "venv", ".venv", "node_modules",
13
+ })
14
+ _EXCLUDED_SUFFIXES: frozenset[str] = frozenset({".pyc", ".pyo"})
15
+
16
+
17
+ def _is_excluded(path_part: str) -> bool:
18
+ p = Path(path_part)
19
+ return p.name in _EXCLUDED_NAMES or p.suffix in _EXCLUDED_SUFFIXES
20
+
21
+
22
+ def zip_directory(source_dir: Path, output_path: Path) -> None:
23
+ """Create a ZIP archive of *source_dir* at *output_path*.
24
+
25
+ Files are added in sorted order for reproducibility. Common
26
+ build artefacts and VCS directories are excluded automatically.
27
+ """
28
+ output_path.parent.mkdir(parents=True, exist_ok=True)
29
+ with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
30
+ for file_path in sorted(source_dir.rglob("*")):
31
+ if not file_path.is_file():
32
+ continue
33
+ rel = file_path.relative_to(source_dir)
34
+ if any(_is_excluded(part) for part in rel.parts):
35
+ continue
36
+ zf.write(file_path, rel)
37
+
38
+
39
+ def compute_checksum(file_path: Path) -> str:
40
+ """Return the SHA-256 hex digest of *file_path*."""
41
+ h = hashlib.sha256()
42
+ with open(file_path, "rb") as f:
43
+ for chunk in iter(lambda: f.read(8192), b""):
44
+ h.update(chunk)
45
+ return h.hexdigest()
46
+
47
+
48
+ def get_file_size(file_path: Path) -> int:
49
+ """Return the size of *file_path* in bytes."""
50
+ return file_path.stat().st_size
@@ -0,0 +1,89 @@
1
+ """Bittensor wallet operations — lazy import so bittensor is an optional dependency."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+
7
+ _BITTENSOR_HINT = (
8
+ "Agent submission requires [bold]bittensor-wallet[/bold] for wallet signing.\n\n"
9
+ "Install it with:\n"
10
+ " [bold]pip install bittensor-wallet[/bold]\n\n"
11
+ "Or if you installed nepher-cli via pip:\n"
12
+ " [bold]pip install \"nepher-cli[bittensor]\"[/bold]"
13
+ )
14
+
15
+
16
+ def _require_wallet_class():
17
+ """Return the ``bittensor_wallet.Wallet`` class or exit with a clear error.
18
+
19
+ ``bittensor_wallet`` is a standalone package that is installed automatically
20
+ as a dependency of ``bittensor``. Importing it directly keeps the check
21
+ lightweight — we do not need the full bittensor package at import time.
22
+ """
23
+ try:
24
+ from bittensor_wallet import Wallet # type: ignore[import]
25
+ return Wallet
26
+ except ImportError:
27
+ from rich.console import Console
28
+ Console(stderr=True).print(
29
+ f"[red]bittensor-wallet not installed.[/red]\n\n{_BITTENSOR_HINT}"
30
+ )
31
+ raise SystemExit(1)
32
+
33
+
34
+ def load_wallet(wallet_name: str, wallet_hotkey: str):
35
+ """Load a Bittensor wallet by name and hotkey name.
36
+
37
+ Exits with a user-friendly message if bittensor is not installed or the
38
+ hotkey file does not exist on this machine.
39
+ """
40
+ Wallet = _require_wallet_class()
41
+ wallet = Wallet(name=wallet_name, hotkey=wallet_hotkey)
42
+ if not wallet.hotkey_file.exists_on_device():
43
+ from rich.console import Console
44
+ Console(stderr=True).print(
45
+ f"[red]Hotkey '[bold]{wallet_hotkey}[/bold]' not found "
46
+ f"for wallet '[bold]{wallet_name}[/bold]'.[/red]\n"
47
+ "Check the wallet name and hotkey with [bold]btcli wallet list[/bold]."
48
+ )
49
+ raise SystemExit(1)
50
+ return wallet
51
+
52
+
53
+ def get_hotkey(wallet) -> str:
54
+ """Return the SS58 address of the wallet's hotkey."""
55
+ return wallet.hotkey.ss58_address
56
+
57
+
58
+ def get_public_key(wallet) -> str:
59
+ """Return the hex-encoded public key of the wallet's hotkey."""
60
+ return wallet.hotkey.public_key.hex()
61
+
62
+
63
+ def sign_message(wallet, message: str) -> str:
64
+ """Sign *message* with the wallet's hotkey and return a hex-encoded signature."""
65
+ return wallet.hotkey.sign(message.encode()).hex()
66
+
67
+
68
+ def create_file_info(miner_hotkey: str, content_hash: str, timestamp: int) -> str:
69
+ """Build the canonical file_info string: ``'hotkey:content_hash:timestamp'``."""
70
+ return f"{miner_hotkey}:{content_hash}:{timestamp}"
71
+
72
+
73
+ def prepare_submission_credentials(
74
+ wallet_name: str,
75
+ wallet_hotkey: str,
76
+ content_hash: str,
77
+ ) -> tuple[str, str, str, str]:
78
+ """Load wallet and produce all signing artefacts needed for a submission.
79
+
80
+ Returns:
81
+ ``(miner_hotkey, public_key, file_info, signature)``
82
+ """
83
+ wallet = load_wallet(wallet_name, wallet_hotkey)
84
+ miner_hotkey = get_hotkey(wallet)
85
+ public_key = get_public_key(wallet)
86
+ timestamp = int(time.time())
87
+ file_info = create_file_info(miner_hotkey, content_hash, timestamp)
88
+ signature = sign_message(wallet, file_info)
89
+ return miner_hotkey, public_key, file_info, signature
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.4
2
+ Name: tmp-nepher-cli
3
+ Version: 0.2.5
4
+ Summary: npcli — unified Nepher command-line interface for accounts, hackathons, EnvHub, tournaments, and SimStore
5
+ Project-URL: Homepage, https://github.com/nepher-ai/nepher-cli
6
+ Author: Nepher
7
+ License: MIT
8
+ Keywords: bittensor,cli,envhub,hackathon,nepher,subnet,tournament
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: click>=8.1.0
18
+ Requires-Dist: httpx>=0.27.0
19
+ Requires-Dist: keyring>=25.0.0
20
+ Requires-Dist: platformdirs>=4.0.0
21
+ Requires-Dist: rich>=13.7.0
22
+ Provides-Extra: bittensor
23
+ Requires-Dist: bittensor-wallet>=4.1.0; extra == 'bittensor'
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # nepher-cli
30
+
31
+ The unified CLI for [Nepher Robotics](https://nepher.ai) — accounts, hackathons, EnvHub, tournaments, and SimStore from a single tool.
32
+
33
+ **Requires:** Python 3.10+
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install nepher-cli
39
+ ```
40
+
41
+ Both `npcli` and `nepher-cli` entry points are registered after install.
42
+
43
+ ```bash
44
+ npcli --version
45
+ npcli --help
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ | Command / Group | Description |
51
+ |-----------------|-------------|
52
+ | `npcli login` | Log in with a Nepher API key |
53
+ | `npcli whoami` | Show the currently authenticated user |
54
+ | `npcli logout` | Clear locally stored credentials |
55
+ | `npcli account` | API keys and coldkey registration |
56
+ | `npcli tournament` | Browse tournaments, submit agents, leaderboards |
57
+ | `npcli envhub` | Manage Isaac Lab environment bundles |
58
+ | `npcli hackathon` | Browse and submit to hackathons |
59
+ | `npcli simstore` | SimStore marketplace *(coming soon)* |
60
+
61
+ ## Quick Start
62
+
63
+ ### Authenticate
64
+
65
+ ```bash
66
+ npcli login --api-key nepher_xxxxxxxx
67
+ npcli whoami
68
+ npcli logout
69
+ ```
70
+
71
+ Get your API key at [nepher.ai/account](https://nepher.ai/account) → Account → API Keys.
72
+ For CI/CD, set `NEPHER_API_KEY=nepher_xxxxxxxx` instead of logging in.
73
+
74
+ ### Tournaments
75
+
76
+ ```bash
77
+ npcli tournament list
78
+ npcli tournament list-active
79
+ npcli tournament status <tournament_id>
80
+ npcli tournament leaderboard <tournament_id>
81
+ ```
82
+
83
+ Check your agent directory structure (no extra dependencies needed):
84
+
85
+ ```bash
86
+ npcli tournament check --path ./my-agent
87
+ npcli tournament check --path ./my-agent --verbose # also show recommended-file warnings
88
+ ```
89
+
90
+ Submit an agent (requires `bittensor-wallet` for wallet signing):
91
+
92
+ ```bash
93
+ pip install bittensor-wallet
94
+ # or: pip install "nepher-cli[bittensor]"
95
+ ```
96
+
97
+ ```bash
98
+ npcli tournament submit --path ./my-agent --wallet-name miner --wallet-hotkey default
99
+ ```
100
+
101
+ Full options for `submit`:
102
+
103
+ ```
104
+ --path <path> Agent directory
105
+ --wallet-name <str> Bittensor wallet name (default: miner)
106
+ --wallet-hotkey <str> Bittensor wallet hotkey (default: default)
107
+ --api-key <key> Nepher API key (falls back to stored credentials)
108
+ --tournament-id <id> Target tournament ID (required when multiple are active)
109
+ --api-url <url> Override tournament API URL
110
+ -v / --verbose Verbose output
111
+ ```
112
+
113
+ ### EnvHub
114
+
115
+ ```bash
116
+ npcli envhub list
117
+ npcli envhub download <env_id>
118
+ npcli envhub upload ./my-bundle --category navigation
119
+ npcli envhub cache list
120
+ npcli envhub cache info
121
+ npcli envhub cache clear
122
+ ```
123
+
124
+ ### Hackathons
125
+
126
+ ```bash
127
+ npcli hackathon list
128
+
129
+ npcli hackathon submit \
130
+ --title "My entry" \
131
+ --submission ./my-project \
132
+ --assets ./my-assets
133
+ ```
134
+
135
+ Full options:
136
+
137
+ ```
138
+ --hackathon-id <uuid> Required when multiple hackathons are open
139
+ --title <str> Entry title
140
+ --description <markdown> Entry description
141
+ --thumbnail <file> Cover image (JPEG, PNG, WebP, GIF)
142
+ --submission <path> Project folder or submission.zip
143
+ --assets <path> Assets folder or assets.zip (images, videos, PDFs)
144
+ --public-source Mark source as public
145
+ ```
146
+
147
+ ### API Keys
148
+
149
+ `list` / `create` / `revoke` need a **JWT session** from `npcli account login`.
150
+ Do **not** pass `--api-key` on these commands (that flag sends `X-API-Key`, which the account API rejects for key management).
151
+
152
+ ```bash
153
+ npcli account login --api-key nepher_...
154
+ npcli account api-keys list
155
+ npcli account api-keys create --name "CI key" --platform hackathon
156
+ npcli account api-keys revoke <key_id>
157
+ ```
158
+
159
+ ### Coldkey Registration
160
+
161
+ Bind a Bittensor coldkey to your account (requires `bittensor-wallet`; `btcli` is optional fallback):
162
+
163
+ ```bash
164
+ pip install bittensor-wallet
165
+ npcli account register-coldkey --wallet <wallet_name>
166
+ ```
167
+
168
+ Signing uses the local coldkey via `bittensor-wallet` and does not import `btcli`. If you still have a mixed Bittensor venv (`scalecodec` + `cyscale`), `register-coldkey` keeps working as long as `bittensor-wallet` is installed.
169
+
170
+ ## API Key Requirements
171
+
172
+ - Must start with `nepher_`
173
+ - Must be active and not expired
174
+ - `hackathon submit` requires **Hackathon** scope (or an unrestricted key)
175
+ - `tournament submit` requires **Tournament** scope (or an unrestricted key)
176
+
177
+ ## Troubleshooting
178
+
179
+ | Error | Fix |
180
+ |-------|-----|
181
+ | `npcli: command not found` | Ensure `pip install` succeeded and Python scripts dir is on `PATH` |
182
+ | `invalid api key format` | Key must start with `nepher_` |
183
+ | `api key does not have hackathon access` | Enable Hackathon scope or use an unrestricted key |
184
+ | `api key expired` | Run `npcli account api-keys create` |
185
+ | `Several hackathons are accepting submissions` | Re-run with `--hackathon-id` |
186
+ | `bittensor-wallet not installed` | `pip install bittensor-wallet` (needed for `tournament submit` and `account register-coldkey`) |
187
+ | `Not logged in` | `npcli login --api-key nepher_...` |
188
+ | `Conflict detected: 'scalecodec' ... conflicts with 'cyscale'` | Leftover `scalecodec` after a Bittensor upgrade. Prefer `pip install bittensor-wallet` so npcli never needs `btcli`. To repair `btcli` itself: `pip uninstall scalecodec cyscale -y && pip install -U cyscale --force-reinstall` |
189
+
190
+ Run any command with `--help` for full flag details:
191
+
192
+ ```bash
193
+ npcli --help
194
+ npcli hackathon submit --help
195
+ npcli tournament submit --help
196
+ ```
197
+
198
+ ## License
199
+
200
+ MIT
@@ -0,0 +1,27 @@
1
+ nepher_cli/__init__.py,sha256=22c2-MqdRvJAy4DJq3eEpetJpEB9bJOjeCRTVIf0Fd4,274
2
+ nepher_cli/__main__.py,sha256=q64BGTaI22LlpmTXteCAYk9i9MA6DkDcYHZMKPa93OA,89
3
+ nepher_cli/cli.py,sha256=15rJzrBhIYpxiYDYFrfFA4Zz01v4wClDPzTeHgTqcMw,2000
4
+ nepher_cli/config.py,sha256=-mvM3USVvFVn2ZbVAbrA1tGqT0AnhFfLQmP0KYh_9-I,1626
5
+ nepher_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ nepher_cli/commands/__init__.py,sha256=FP4vrZqkpqIrDFMATXIA9R5vlXePXeQPJ4dOb7273d4,28
7
+ nepher_cli/commands/account.py,sha256=oyUDweHD0A6nSNT0eZ6gaXhg9ua-XbFEfCvacGpQcA4,23281
8
+ nepher_cli/commands/envhub.py,sha256=Vto3l2nE5CpHiG9RxSssa9bDytRPTWhAgF-kMC9nHkc,18150
9
+ nepher_cli/commands/hackathon.py,sha256=81cdz4-Fk7-bdVQ6YUqeWuF3fNd9RC7DgJ6gp-Jyt_I,28282
10
+ nepher_cli/commands/highlight.py,sha256=4zrTEdnBt_eSjd04V-SyAp8EepBbIjdim-XgNJkgbnk,9772
11
+ nepher_cli/commands/simstore.py,sha256=EFCpLRxyOnECh08icamIGCH7SpRUcbcttuKsln0SCBI,1290
12
+ nepher_cli/commands/tournament.py,sha256=OnjtwZEKC2TUrM8m10ndXQrrU0Y9eeV2oPyTyWKWvuw,24898
13
+ nepher_cli/core/__init__.py,sha256=0oOyNOXVGVIvrMg6mhe3PKnsYfQhCMh0B7cnZo34idQ,54
14
+ nepher_cli/core/credentials.py,sha256=rtCCQTWbkLht4AeMEaRt4lcfvwF771nKsyoK_i43kNY,6480
15
+ nepher_cli/core/http.py,sha256=YJBr1FYytpYNwDMzmrJBVi8W05UAM-x7RBEx5H_sIOM,2855
16
+ nepher_cli/envhub/__init__.py,sha256=bpl_l8eEH_b14bMAlM-Uea47y5ON35gmykJgEW4Zx8I,63
17
+ nepher_cli/envhub/cache.py,sha256=hXx34NWRuGmGzuOW2AmEJQTa0xJvXtVvvSjAZgFxoY4,1759
18
+ nepher_cli/envhub/config.py,sha256=pZK18ydCbqB7IPA6gihs0mBBt_CXQlW4Bdeq71Yi8vE,5445
19
+ nepher_cli/tournament/__init__.py,sha256=VwlUaieMIX4m7dpVvosjD590sI1kGBySPJBdhdJ3d6s,64
20
+ nepher_cli/tournament/agent_check.py,sha256=8_LBGxrzEsSBxKturrJtklVCzJU50p52vJaTegbIvFc,2169
21
+ nepher_cli/tournament/api.py,sha256=yhAP_u2mUXSjqxjVqq2UWtfpSFfs9fcGg9EyPxJHmV0,2857
22
+ nepher_cli/tournament/packer.py,sha256=Orh1JTVvfMap7BiVstnDgRiRrtxs1c_RU6yZS_7tJ2E,1692
23
+ nepher_cli/tournament/wallet.py,sha256=Q_OuNA8pR4QXqkkkSuImcq3ALVIkf6UFSXgRsFMQku0,3182
24
+ tmp_nepher_cli-0.2.5.dist-info/METADATA,sha256=p3Y9WLkuuORNqu4uYKfmjfSaDOMpfcoG7T-wyqvTDao,6335
25
+ tmp_nepher_cli-0.2.5.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
26
+ tmp_nepher_cli-0.2.5.dist-info/entry_points.txt,sha256=QQ1l07o9WxwntxuU1K3Hnz7nRc0fFKRBRMAHJlkjfEQ,79
27
+ tmp_nepher_cli-0.2.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ nepher-cli = nepher_cli.cli:main
3
+ npcli = nepher_cli.cli:main