nepher-cli-test 0.2.4__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,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: nepher-cli-test
3
+ Version: 0.2.4
4
+ Summary: npcli-test — Nepher CLI for staging (api.staging.nepher.ai)
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
+ ```bash
150
+ npcli account api-keys list
151
+ npcli account api-keys create --name "CI key" --platform hackertone
152
+ npcli account api-keys revoke <key_id>
153
+ ```
154
+
155
+ ### Coldkey Registration
156
+
157
+ Bind a Bittensor coldkey to your account (requires `bittensor-wallet`; `btcli` is optional fallback):
158
+
159
+ ```bash
160
+ pip install bittensor-wallet
161
+ npcli account register-coldkey --wallet <wallet_name>
162
+ ```
163
+
164
+ 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.
165
+
166
+ ## API Key Requirements
167
+
168
+ - Must start with `nepher_`
169
+ - Must be active and not expired
170
+ - `hackathon submit` requires **Hackathon** scope (or an unrestricted key)
171
+ - `tournament submit` requires **Tournament** scope (or an unrestricted key)
172
+
173
+ ## Troubleshooting
174
+
175
+ | Error | Fix |
176
+ |-------|-----|
177
+ | `npcli: command not found` | Ensure `pip install` succeeded and Python scripts dir is on `PATH` |
178
+ | `invalid api key format` | Key must start with `nepher_` |
179
+ | `api key does not have hackathon access` | Enable Hackathon scope or use an unrestricted key |
180
+ | `api key expired` | Run `npcli account api-keys create` |
181
+ | `Several hackathons are accepting submissions` | Re-run with `--hackathon-id` |
182
+ | `bittensor-wallet not installed` | `pip install bittensor-wallet` (needed for `tournament submit` and `account register-coldkey`) |
183
+ | `Not logged in` | `npcli login --api-key nepher_...` |
184
+ | `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` |
185
+
186
+ Run any command with `--help` for full flag details:
187
+
188
+ ```bash
189
+ npcli --help
190
+ npcli hackathon submit --help
191
+ npcli tournament submit --help
192
+ ```
193
+
194
+ ## License
195
+
196
+ MIT
@@ -0,0 +1,28 @@
1
+ nepher_cli/__init__.py,sha256=hQUx9QL1sjnY171Z5JMHPYCjw7RWM-3Oo58w0VPwNPE,78
2
+ nepher_cli/__main__.py,sha256=q64BGTaI22LlpmTXteCAYk9i9MA6DkDcYHZMKPa93OA,89
3
+ nepher_cli/cli.py,sha256=TX-1OSZYD5c9vqPd4QfpdheqkpH89wpAwxx5gqDC6Ok,1795
4
+ nepher_cli/cli_test.py,sha256=IClp81bUbS0mwPFPMD_zNTXojXHrnqs_4GD7plk_l4Y,501
5
+ nepher_cli/config.py,sha256=1uU-MSKoukwJKSVNIUHJJRGv4xUHFzAoPvnt_dPaiAs,1744
6
+ nepher_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ nepher_cli/commands/__init__.py,sha256=FP4vrZqkpqIrDFMATXIA9R5vlXePXeQPJ4dOb7273d4,28
8
+ nepher_cli/commands/account.py,sha256=q4PKzx8DppdUqi7BdS7UOhlJKW6WcTKcSJTercjJjXw,22866
9
+ nepher_cli/commands/envhub.py,sha256=Vto3l2nE5CpHiG9RxSssa9bDytRPTWhAgF-kMC9nHkc,18150
10
+ nepher_cli/commands/hackathon.py,sha256=81cdz4-Fk7-bdVQ6YUqeWuF3fNd9RC7DgJ6gp-Jyt_I,28282
11
+ nepher_cli/commands/highlight.py,sha256=4zrTEdnBt_eSjd04V-SyAp8EepBbIjdim-XgNJkgbnk,9772
12
+ nepher_cli/commands/simstore.py,sha256=EFCpLRxyOnECh08icamIGCH7SpRUcbcttuKsln0SCBI,1290
13
+ nepher_cli/commands/tournament.py,sha256=mPhqSTtEEQTDqvyAIssxk6vndfXozwcKfYmlg8HiTbw,24850
14
+ nepher_cli/core/__init__.py,sha256=0oOyNOXVGVIvrMg6mhe3PKnsYfQhCMh0B7cnZo34idQ,54
15
+ nepher_cli/core/credentials.py,sha256=rtCCQTWbkLht4AeMEaRt4lcfvwF771nKsyoK_i43kNY,6480
16
+ nepher_cli/core/http.py,sha256=YJBr1FYytpYNwDMzmrJBVi8W05UAM-x7RBEx5H_sIOM,2855
17
+ nepher_cli/envhub/__init__.py,sha256=bpl_l8eEH_b14bMAlM-Uea47y5ON35gmykJgEW4Zx8I,63
18
+ nepher_cli/envhub/cache.py,sha256=hXx34NWRuGmGzuOW2AmEJQTa0xJvXtVvvSjAZgFxoY4,1759
19
+ nepher_cli/envhub/config.py,sha256=pZK18ydCbqB7IPA6gihs0mBBt_CXQlW4Bdeq71Yi8vE,5445
20
+ nepher_cli/tournament/__init__.py,sha256=VwlUaieMIX4m7dpVvosjD590sI1kGBySPJBdhdJ3d6s,64
21
+ nepher_cli/tournament/agent_check.py,sha256=8_LBGxrzEsSBxKturrJtklVCzJU50p52vJaTegbIvFc,2169
22
+ nepher_cli/tournament/api.py,sha256=yhAP_u2mUXSjqxjVqq2UWtfpSFfs9fcGg9EyPxJHmV0,2857
23
+ nepher_cli/tournament/packer.py,sha256=Orh1JTVvfMap7BiVstnDgRiRrtxs1c_RU6yZS_7tJ2E,1692
24
+ nepher_cli/tournament/wallet.py,sha256=Q_OuNA8pR4QXqkkkSuImcq3ALVIkf6UFSXgRsFMQku0,3182
25
+ nepher_cli_test-0.2.4.dist-info/METADATA,sha256=AsHtAH9xRJHM3U0mVI-2ZORItKvTV541zfjZQAzUn-A,6040
26
+ nepher_cli_test-0.2.4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
27
+ nepher_cli_test-0.2.4.dist-info/entry_points.txt,sha256=pvx9-uUe_9gcxMSQEtS3_CNXuJ--Pu19OioWlyyMg5M,56
28
+ nepher_cli_test-0.2.4.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,2 @@
1
+ [console_scripts]
2
+ npcli-test = nepher_cli.cli_test:main