cartha-cli 1.0.0__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.
- cartha_cli/__init__.py +34 -0
- cartha_cli/bt.py +206 -0
- cartha_cli/commands/__init__.py +25 -0
- cartha_cli/commands/common.py +76 -0
- cartha_cli/commands/config.py +294 -0
- cartha_cli/commands/health.py +463 -0
- cartha_cli/commands/help.py +49 -0
- cartha_cli/commands/miner_password.py +283 -0
- cartha_cli/commands/miner_status.py +524 -0
- cartha_cli/commands/pair_status.py +484 -0
- cartha_cli/commands/pools.py +121 -0
- cartha_cli/commands/prove_lock.py +1260 -0
- cartha_cli/commands/register.py +274 -0
- cartha_cli/commands/shared_options.py +235 -0
- cartha_cli/commands/version.py +15 -0
- cartha_cli/config.py +75 -0
- cartha_cli/display.py +62 -0
- cartha_cli/eth712.py +7 -0
- cartha_cli/main.py +237 -0
- cartha_cli/pair.py +201 -0
- cartha_cli/utils.py +274 -0
- cartha_cli/verifier.py +342 -0
- cartha_cli/wallet.py +59 -0
- cartha_cli-1.0.0.dist-info/METADATA +180 -0
- cartha_cli-1.0.0.dist-info/RECORD +28 -0
- cartha_cli-1.0.0.dist-info/WHEEL +4 -0
- cartha_cli-1.0.0.dist-info/entry_points.txt +2 -0
- cartha_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""Health check command - verify CLI connectivity and configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import bittensor as bt
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from ..config import Settings, settings
|
|
15
|
+
from ..verifier import VerifierError, _build_url, _request
|
|
16
|
+
from .common import console
|
|
17
|
+
|
|
18
|
+
import requests # type: ignore[import-untyped]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def health_check(
|
|
22
|
+
verbose: bool = typer.Option(
|
|
23
|
+
False, "--verbose", "-v", help="Show detailed information for each check."
|
|
24
|
+
),
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Check CLI health: verifier connectivity, Bittensor network, and configuration.
|
|
27
|
+
|
|
28
|
+
USAGE:
|
|
29
|
+
------
|
|
30
|
+
cartha utils health (or: cartha u health)
|
|
31
|
+
cartha utils health --verbose (or: -v)
|
|
32
|
+
|
|
33
|
+
CHECKS:
|
|
34
|
+
-------
|
|
35
|
+
1. Verifier connectivity and response time
|
|
36
|
+
2. Bittensor network connectivity
|
|
37
|
+
3. Configuration validation
|
|
38
|
+
4. Subnet metadata (slots, tempo, block)
|
|
39
|
+
5. Environment variables status
|
|
40
|
+
|
|
41
|
+
Use this to diagnose issues before running other commands.
|
|
42
|
+
"""
|
|
43
|
+
checks_passed = 0
|
|
44
|
+
checks_failed = 0
|
|
45
|
+
checks_warning = 0
|
|
46
|
+
|
|
47
|
+
results: list[dict[str, Any]] = []
|
|
48
|
+
|
|
49
|
+
# Track subtensor connections for cleanup
|
|
50
|
+
subtensor_connections: list[Any] = []
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# Check 1: Verifier connectivity
|
|
54
|
+
console.print("\n[bold cyan]━━━ Health Check ━━━[/]")
|
|
55
|
+
console.print()
|
|
56
|
+
|
|
57
|
+
verifier_url = settings.verifier_url
|
|
58
|
+
console.print(f"[bold]Checking verifier connectivity...[/]")
|
|
59
|
+
console.print(f"[dim]URL: {verifier_url}[/]")
|
|
60
|
+
|
|
61
|
+
verifier_ok = False
|
|
62
|
+
verifier_status = "Unknown"
|
|
63
|
+
verifier_latency_ms = None
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
start_time = time.time()
|
|
67
|
+
# Try to hit a simple endpoint (health or root)
|
|
68
|
+
try:
|
|
69
|
+
# Try /health endpoint first
|
|
70
|
+
health_url = _build_url("/health")
|
|
71
|
+
response = requests.get(health_url, timeout=(5, 10))
|
|
72
|
+
if response.status_code == 200:
|
|
73
|
+
verifier_ok = True
|
|
74
|
+
verifier_status = "Healthy"
|
|
75
|
+
elif response.status_code == 404:
|
|
76
|
+
# 404 is OK - means verifier is up but endpoint doesn't exist
|
|
77
|
+
# Try a simple GET request to verify connectivity
|
|
78
|
+
try:
|
|
79
|
+
_request("GET", "/v1/miner/status", params={"hotkey": "test", "slot": "0"}, retry=False)
|
|
80
|
+
verifier_ok = True
|
|
81
|
+
verifier_status = "Reachable (no /health endpoint)"
|
|
82
|
+
except VerifierError as exc:
|
|
83
|
+
# 404 or 400 means verifier is reachable, just wrong params
|
|
84
|
+
if exc.status_code in (400, 404):
|
|
85
|
+
verifier_ok = True
|
|
86
|
+
verifier_status = "Reachable"
|
|
87
|
+
else:
|
|
88
|
+
verifier_status = f"Error: {exc}"
|
|
89
|
+
else:
|
|
90
|
+
verifier_status = f"HTTP {response.status_code}"
|
|
91
|
+
except requests.RequestException as exc:
|
|
92
|
+
# Connection error - verifier is not reachable
|
|
93
|
+
verifier_status = f"Connection error: {exc}"
|
|
94
|
+
except VerifierError as exc:
|
|
95
|
+
# This shouldn't happen for /health, but handle it anyway
|
|
96
|
+
if exc.status_code == 404:
|
|
97
|
+
verifier_ok = True
|
|
98
|
+
verifier_status = "Reachable (no /health endpoint)"
|
|
99
|
+
else:
|
|
100
|
+
verifier_status = f"Error: {exc}"
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
verifier_status = f"Error: {exc}"
|
|
103
|
+
|
|
104
|
+
if verifier_ok:
|
|
105
|
+
latency_ms = int((time.time() - start_time) * 1000)
|
|
106
|
+
verifier_latency_ms = latency_ms
|
|
107
|
+
console.print(f"[bold green]✓ Verifier is reachable[/] ({latency_ms}ms)")
|
|
108
|
+
checks_passed += 1
|
|
109
|
+
else:
|
|
110
|
+
console.print(f"[bold red]✗ Verifier check failed[/]: {verifier_status}")
|
|
111
|
+
checks_failed += 1
|
|
112
|
+
|
|
113
|
+
results.append({
|
|
114
|
+
"name": "Verifier Connectivity",
|
|
115
|
+
"status": "pass" if verifier_ok else "fail",
|
|
116
|
+
"details": verifier_status,
|
|
117
|
+
"latency_ms": verifier_latency_ms,
|
|
118
|
+
})
|
|
119
|
+
except Exception as exc:
|
|
120
|
+
console.print(f"[bold red]✗ Verifier check error[/]: {exc}")
|
|
121
|
+
checks_failed += 1
|
|
122
|
+
results.append({
|
|
123
|
+
"name": "Verifier Connectivity",
|
|
124
|
+
"status": "fail",
|
|
125
|
+
"details": str(exc),
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
# Check 2: Bittensor network connectivity
|
|
129
|
+
console.print()
|
|
130
|
+
console.print(f"[bold]Checking Bittensor network...[/]")
|
|
131
|
+
console.print(f"[dim]Network: {settings.network}, NetUID: {settings.netuid}[/]")
|
|
132
|
+
|
|
133
|
+
bt_ok = False
|
|
134
|
+
bt_status = "Unknown"
|
|
135
|
+
bt_latency_ms = None
|
|
136
|
+
bt_is_dns_error = False
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
start_time = time.time()
|
|
140
|
+
subtensor = bt.subtensor(network=settings.network)
|
|
141
|
+
subtensor_connections.append(subtensor)
|
|
142
|
+
current_block = subtensor.get_current_block()
|
|
143
|
+
latency_ms = int((time.time() - start_time) * 1000)
|
|
144
|
+
bt_latency_ms = latency_ms
|
|
145
|
+
|
|
146
|
+
if current_block and current_block > 0:
|
|
147
|
+
bt_ok = True
|
|
148
|
+
bt_status = f"Connected (block: {current_block})"
|
|
149
|
+
console.print(f"[bold green]✓ Bittensor network is reachable[/] ({latency_ms}ms, block: {current_block})")
|
|
150
|
+
checks_passed += 1
|
|
151
|
+
else:
|
|
152
|
+
bt_status = "Connected but invalid block number"
|
|
153
|
+
console.print(f"[bold yellow]⚠ Bittensor network check warning[/]: {bt_status}")
|
|
154
|
+
checks_warning += 1
|
|
155
|
+
except OSError as exc:
|
|
156
|
+
# DNS resolution errors (Errno 8)
|
|
157
|
+
error_str = str(exc)
|
|
158
|
+
if "nodename nor servname provided" in error_str or "Errno 8" in error_str:
|
|
159
|
+
bt_is_dns_error = True
|
|
160
|
+
bt_status = "DNS resolution failed - cannot resolve Bittensor network endpoints"
|
|
161
|
+
console.print(f"[bold yellow]⚠ Bittensor network check failed[/]: DNS resolution error")
|
|
162
|
+
console.print("[dim]This usually indicates a network connectivity or DNS configuration issue.[/]")
|
|
163
|
+
checks_warning += 1 # Make it a warning since verifier is working
|
|
164
|
+
else:
|
|
165
|
+
bt_status = f"Network error: {exc}"
|
|
166
|
+
console.print(f"[bold red]✗ Bittensor network check failed[/]: {exc}")
|
|
167
|
+
checks_failed += 1
|
|
168
|
+
except Exception as exc:
|
|
169
|
+
error_str = str(exc)
|
|
170
|
+
# Check if it's a DNS/network related error
|
|
171
|
+
if any(keyword in error_str.lower() for keyword in ["dns", "resolve", "nodename", "servname", "network", "connection"]):
|
|
172
|
+
bt_is_dns_error = True
|
|
173
|
+
bt_status = f"Network connectivity issue: {exc}"
|
|
174
|
+
console.print(f"[bold yellow]⚠ Bittensor network check failed[/]: {exc}")
|
|
175
|
+
console.print("[dim]This may be a temporary network issue. The CLI can still work if the verifier is accessible.[/]")
|
|
176
|
+
checks_warning += 1
|
|
177
|
+
else:
|
|
178
|
+
bt_status = f"Error: {exc}"
|
|
179
|
+
console.print(f"[bold red]✗ Bittensor network check failed[/]: {exc}")
|
|
180
|
+
checks_failed += 1
|
|
181
|
+
|
|
182
|
+
results.append({
|
|
183
|
+
"name": "Bittensor Network",
|
|
184
|
+
"status": "pass" if bt_ok else ("warning" if checks_warning > 0 else "fail"),
|
|
185
|
+
"details": bt_status,
|
|
186
|
+
"latency_ms": bt_latency_ms,
|
|
187
|
+
"is_dns_error": bt_is_dns_error,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
# Check 3: Configuration validation
|
|
191
|
+
console.print()
|
|
192
|
+
console.print(f"[bold]Checking configuration...[/]")
|
|
193
|
+
|
|
194
|
+
config_issues: list[str] = []
|
|
195
|
+
|
|
196
|
+
if not settings.verifier_url:
|
|
197
|
+
config_issues.append("Verifier URL is not set")
|
|
198
|
+
elif not settings.verifier_url.startswith(("http://", "https://")):
|
|
199
|
+
config_issues.append("Verifier URL must start with http:// or https://")
|
|
200
|
+
|
|
201
|
+
if not settings.network:
|
|
202
|
+
config_issues.append("Network is not set")
|
|
203
|
+
|
|
204
|
+
if settings.netuid <= 0:
|
|
205
|
+
config_issues.append(f"Invalid netuid: {settings.netuid}")
|
|
206
|
+
|
|
207
|
+
if config_issues:
|
|
208
|
+
console.print(f"[bold yellow]⚠ Configuration issues found[/]:")
|
|
209
|
+
for issue in config_issues:
|
|
210
|
+
console.print(f" • {issue}")
|
|
211
|
+
checks_warning += 1
|
|
212
|
+
config_status = "Issues found"
|
|
213
|
+
else:
|
|
214
|
+
console.print("[bold green]✓ Configuration is valid[/]")
|
|
215
|
+
checks_passed += 1
|
|
216
|
+
config_status = "Valid"
|
|
217
|
+
|
|
218
|
+
results.append({
|
|
219
|
+
"name": "Configuration",
|
|
220
|
+
"status": "pass" if not config_issues else "warning",
|
|
221
|
+
"details": config_status,
|
|
222
|
+
"issues": config_issues if config_issues else None,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
# Check 4: Subnet metadata
|
|
226
|
+
console.print()
|
|
227
|
+
console.print(f"[bold]Checking subnet metadata...[/]")
|
|
228
|
+
console.print(f"[dim]NetUID: {settings.netuid}[/]")
|
|
229
|
+
|
|
230
|
+
subnet_ok = False
|
|
231
|
+
subnet_status = "Unknown"
|
|
232
|
+
subnet_latency_ms = None
|
|
233
|
+
subnet_info: dict[str, Any] = {}
|
|
234
|
+
subnet_is_dns_error = False
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
start_time = time.time()
|
|
238
|
+
subtensor = bt.subtensor(network=settings.network)
|
|
239
|
+
subtensor_connections.append(subtensor)
|
|
240
|
+
metagraph = subtensor.metagraph(netuid=settings.netuid)
|
|
241
|
+
metagraph.sync(subtensor=subtensor)
|
|
242
|
+
latency_ms = int((time.time() - start_time) * 1000)
|
|
243
|
+
subnet_latency_ms = latency_ms
|
|
244
|
+
|
|
245
|
+
# Get subnet metadata
|
|
246
|
+
total_miners = len(metagraph.neurons) if hasattr(metagraph, "neurons") else 0
|
|
247
|
+
tempo = getattr(metagraph, "tempo", None)
|
|
248
|
+
block = getattr(metagraph, "block", None)
|
|
249
|
+
|
|
250
|
+
# Convert block to int if it's an array
|
|
251
|
+
if hasattr(block, "__iter__") and not isinstance(block, (str, int)):
|
|
252
|
+
try:
|
|
253
|
+
block = int(block[0]) if len(block) > 0 else None
|
|
254
|
+
except (ValueError, TypeError, IndexError):
|
|
255
|
+
block = None
|
|
256
|
+
|
|
257
|
+
subnet_info = {
|
|
258
|
+
"total_slots": total_miners,
|
|
259
|
+
"tempo": tempo,
|
|
260
|
+
"block": block,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
subnet_status_parts = []
|
|
264
|
+
if total_miners > 0:
|
|
265
|
+
subnet_status_parts.append(f"{total_miners} registered slots")
|
|
266
|
+
if tempo is not None:
|
|
267
|
+
subnet_status_parts.append(f"tempo: {tempo}")
|
|
268
|
+
if block is not None:
|
|
269
|
+
subnet_status_parts.append(f"block: {block}")
|
|
270
|
+
|
|
271
|
+
subnet_status = ", ".join(subnet_status_parts) if subnet_status_parts else "Connected"
|
|
272
|
+
subnet_ok = True
|
|
273
|
+
|
|
274
|
+
console.print(f"[bold green]✓ Subnet metadata retrieved[/] ({latency_ms}ms)")
|
|
275
|
+
if verbose:
|
|
276
|
+
console.print(f" • Registered slots: {total_miners}")
|
|
277
|
+
if tempo is not None:
|
|
278
|
+
console.print(f" • Tempo: {tempo}")
|
|
279
|
+
if block is not None:
|
|
280
|
+
console.print(f" • Block: {block}")
|
|
281
|
+
checks_passed += 1
|
|
282
|
+
except OSError as exc:
|
|
283
|
+
# DNS resolution errors (Errno 8)
|
|
284
|
+
error_str = str(exc)
|
|
285
|
+
if "nodename nor servname provided" in error_str or "Errno 8" in error_str:
|
|
286
|
+
subnet_is_dns_error = True
|
|
287
|
+
subnet_status = "DNS resolution failed - cannot resolve Bittensor network endpoints"
|
|
288
|
+
console.print(f"[bold yellow]⚠ Subnet metadata check failed[/]: DNS resolution error")
|
|
289
|
+
checks_warning += 1 # Make it a warning since verifier is working
|
|
290
|
+
else:
|
|
291
|
+
subnet_status = f"Network error: {exc}"
|
|
292
|
+
console.print(f"[bold red]✗ Subnet metadata check failed[/]: {exc}")
|
|
293
|
+
checks_failed += 1
|
|
294
|
+
except Exception as exc:
|
|
295
|
+
error_str = str(exc)
|
|
296
|
+
# Check if it's a DNS/network related error
|
|
297
|
+
if any(keyword in error_str.lower() for keyword in ["dns", "resolve", "nodename", "servname", "network", "connection"]):
|
|
298
|
+
subnet_is_dns_error = True
|
|
299
|
+
subnet_status = f"Network connectivity issue: {exc}"
|
|
300
|
+
console.print(f"[bold yellow]⚠ Subnet metadata check failed[/]: {exc}")
|
|
301
|
+
checks_warning += 1
|
|
302
|
+
else:
|
|
303
|
+
subnet_status = f"Error: {exc}"
|
|
304
|
+
console.print(f"[bold red]✗ Subnet metadata check failed[/]: {exc}")
|
|
305
|
+
checks_failed += 1
|
|
306
|
+
|
|
307
|
+
results.append({
|
|
308
|
+
"name": "Subnet Metadata",
|
|
309
|
+
"status": "pass" if subnet_ok else ("warning" if checks_warning > 0 else "fail"),
|
|
310
|
+
"details": subnet_status,
|
|
311
|
+
"latency_ms": subnet_latency_ms,
|
|
312
|
+
"info": subnet_info if subnet_ok else None,
|
|
313
|
+
"is_dns_error": subnet_is_dns_error,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
# Check 5: Environment Variables
|
|
317
|
+
console.print()
|
|
318
|
+
console.print(f"[bold]Checking Environment Variables...[/]")
|
|
319
|
+
|
|
320
|
+
env_vars_ok = True
|
|
321
|
+
env_status = "OK"
|
|
322
|
+
env_details: dict[str, dict[str, Any]] = {}
|
|
323
|
+
|
|
324
|
+
# Get default values from Settings model
|
|
325
|
+
default_settings = Settings()
|
|
326
|
+
|
|
327
|
+
# Check each configurable env var
|
|
328
|
+
env_var_mapping = {
|
|
329
|
+
"CARTHA_VERIFIER_URL": ("verifier_url", default_settings.verifier_url),
|
|
330
|
+
"CARTHA_NETWORK": ("network", default_settings.network),
|
|
331
|
+
"CARTHA_NETUID": ("netuid", str(default_settings.netuid)),
|
|
332
|
+
"CARTHA_EVM_PK": ("evm_private_key", None), # Sensitive, don't show value
|
|
333
|
+
"CARTHA_RETRY_MAX_ATTEMPTS": ("retry_max_attempts", str(default_settings.retry_max_attempts)),
|
|
334
|
+
"CARTHA_RETRY_BACKOFF_FACTOR": ("retry_backoff_factor", str(default_settings.retry_backoff_factor)),
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for env_var, (setting_key, default_value) in env_var_mapping.items():
|
|
338
|
+
env_value = os.getenv(env_var)
|
|
339
|
+
is_set = env_value is not None
|
|
340
|
+
|
|
341
|
+
# For sensitive values, don't show the actual value
|
|
342
|
+
display_value = "[REDACTED]" if env_var == "CARTHA_EVM_PK" and is_set else env_value
|
|
343
|
+
|
|
344
|
+
env_details[env_var] = {
|
|
345
|
+
"is_set": is_set,
|
|
346
|
+
"value": display_value,
|
|
347
|
+
"default": default_value,
|
|
348
|
+
"source": "environment" if is_set else "default",
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# Count how many are set vs defaults
|
|
352
|
+
set_count = sum(1 for details in env_details.values() if details["is_set"])
|
|
353
|
+
total_count = len(env_details)
|
|
354
|
+
|
|
355
|
+
env_status = f"{set_count}/{total_count} Environment Variables set"
|
|
356
|
+
|
|
357
|
+
console.print(f"[bold green]✓ Environment Variables checked[/] ({set_count}/{total_count} set)")
|
|
358
|
+
if verbose:
|
|
359
|
+
for env_var, details in env_details.items():
|
|
360
|
+
source_indicator = "[green]●[/]" if details["is_set"] else "[dim]○[/]"
|
|
361
|
+
console.print(f" {source_indicator} {env_var}: {details['value'] or details['default']} ({details['source']})")
|
|
362
|
+
|
|
363
|
+
checks_passed += 1
|
|
364
|
+
|
|
365
|
+
results.append({
|
|
366
|
+
"name": "Environment Variables",
|
|
367
|
+
"status": "pass",
|
|
368
|
+
"details": env_status,
|
|
369
|
+
"info": env_details if verbose else None,
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
# Summary
|
|
373
|
+
console.print()
|
|
374
|
+
console.print("[bold cyan]━━━ Summary ━━━[/]")
|
|
375
|
+
|
|
376
|
+
summary_table = Table(show_header=True, header_style="bold cyan")
|
|
377
|
+
summary_table.add_column("Check", style="cyan")
|
|
378
|
+
summary_table.add_column("Status", justify="center")
|
|
379
|
+
summary_table.add_column("Details", style="dim")
|
|
380
|
+
summary_table.add_column("Latency", justify="right", style="dim")
|
|
381
|
+
|
|
382
|
+
for result in results:
|
|
383
|
+
status_icon = {
|
|
384
|
+
"pass": "[bold green]✓[/]",
|
|
385
|
+
"warning": "[bold yellow]⚠[/]",
|
|
386
|
+
"fail": "[bold red]✗[/]",
|
|
387
|
+
}.get(result["status"], "?")
|
|
388
|
+
|
|
389
|
+
latency_str = f"{result['latency_ms']}ms" if result.get("latency_ms") else "-"
|
|
390
|
+
details = result["details"]
|
|
391
|
+
if result.get("issues"):
|
|
392
|
+
details += f" ({len(result['issues'])} issue(s))"
|
|
393
|
+
|
|
394
|
+
summary_table.add_row(
|
|
395
|
+
result["name"],
|
|
396
|
+
status_icon,
|
|
397
|
+
details,
|
|
398
|
+
latency_str,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
console.print(summary_table)
|
|
402
|
+
console.print()
|
|
403
|
+
|
|
404
|
+
# Overall status
|
|
405
|
+
total_checks = checks_passed + checks_failed + checks_warning
|
|
406
|
+
if checks_failed == 0 and checks_warning == 0:
|
|
407
|
+
console.print("[bold green]✓ All checks passed![/] CLI is ready to use.")
|
|
408
|
+
raise typer.Exit(code=0)
|
|
409
|
+
elif checks_failed == 0:
|
|
410
|
+
# Check if warnings are DNS-related
|
|
411
|
+
dns_warnings = [r for r in results if r.get("is_dns_error") and r.get("status") == "warning"]
|
|
412
|
+
if dns_warnings:
|
|
413
|
+
console.print(
|
|
414
|
+
f"[bold yellow]⚠ {checks_warning} warning(s) found[/] (including DNS resolution issues). "
|
|
415
|
+
"CLI should work for most commands, but Bittensor network features may be limited."
|
|
416
|
+
)
|
|
417
|
+
console.print("[dim]If you need to register or check miner status, ensure Bittensor network connectivity.[/]")
|
|
418
|
+
else:
|
|
419
|
+
console.print(
|
|
420
|
+
f"[bold yellow]⚠ {checks_warning} warning(s) found[/], but CLI should work. "
|
|
421
|
+
"Review configuration if needed."
|
|
422
|
+
)
|
|
423
|
+
raise typer.Exit(code=0)
|
|
424
|
+
else:
|
|
425
|
+
console.print(
|
|
426
|
+
f"[bold red]✗ {checks_failed} check(s) failed[/], {checks_warning} warning(s). "
|
|
427
|
+
"Please fix issues before using the CLI."
|
|
428
|
+
)
|
|
429
|
+
if verbose:
|
|
430
|
+
console.print("\n[bold]Troubleshooting:[/]")
|
|
431
|
+
console.print("• Check your network connectivity")
|
|
432
|
+
console.print(f"• Verify verifier URL: {settings.verifier_url}")
|
|
433
|
+
console.print(f"• Verify Bittensor network: {settings.network}")
|
|
434
|
+
console.print("• Check environment variables: CARTHA_VERIFIER_URL, CARTHA_NETWORK, CARTHA_NETUID")
|
|
435
|
+
|
|
436
|
+
# Check if DNS errors occurred
|
|
437
|
+
dns_errors = [r for r in results if r.get("is_dns_error")]
|
|
438
|
+
if dns_errors:
|
|
439
|
+
console.print("\n[bold yellow]DNS Resolution Issues Detected:[/]")
|
|
440
|
+
console.print("The following checks failed due to DNS resolution errors:")
|
|
441
|
+
for result in dns_errors:
|
|
442
|
+
console.print(f" • {result['name']}: {result['details']}")
|
|
443
|
+
console.print("\n[bold]DNS Troubleshooting Steps:[/]")
|
|
444
|
+
console.print(" 1. Check your internet connection")
|
|
445
|
+
console.print(" 2. Try flushing DNS cache:")
|
|
446
|
+
console.print(" • macOS: sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder")
|
|
447
|
+
console.print(" • Linux: sudo systemd-resolve --flush-caches (or sudo resolvectl flush-caches)")
|
|
448
|
+
console.print(" • Windows: ipconfig /flushdns")
|
|
449
|
+
console.print(" 3. Check if you're behind a firewall or proxy")
|
|
450
|
+
console.print(" 4. Try using a different network (e.g., mobile hotspot)")
|
|
451
|
+
console.print(" 5. Check if DNS servers are reachable: nslookup <domain>")
|
|
452
|
+
console.print("\n[dim]Note: If the verifier check passed, you can still use most CLI commands.[/]")
|
|
453
|
+
console.print("[dim]Bittensor network connectivity is only needed for registration and status checks.[/]")
|
|
454
|
+
raise typer.Exit(code=1)
|
|
455
|
+
finally:
|
|
456
|
+
# Clean up subtensor connections
|
|
457
|
+
for subtensor in subtensor_connections:
|
|
458
|
+
try:
|
|
459
|
+
if hasattr(subtensor, "close"):
|
|
460
|
+
subtensor.close()
|
|
461
|
+
except Exception:
|
|
462
|
+
pass
|
|
463
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Help and root command utilities."""
|
|
2
|
+
|
|
3
|
+
from rich import box
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.rule import Rule
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from ..display import get_clock_table
|
|
9
|
+
from .common import console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def print_root_help() -> None:
|
|
13
|
+
"""Print the root help message."""
|
|
14
|
+
console.print(Rule("[bold cyan]Cartha CLI[/]"))
|
|
15
|
+
console.print(
|
|
16
|
+
"Miner-facing command line tool for Cartha subnet miners.\n"
|
|
17
|
+
"Cartha is the Liquidity Provider for 0xMarkets DEX.\n"
|
|
18
|
+
"Register on the subnet, manage lock positions, and track your mining status."
|
|
19
|
+
)
|
|
20
|
+
console.print()
|
|
21
|
+
console.print("[bold]Usage[/]: cartha [OPTIONS] COMMAND [ARGS]...")
|
|
22
|
+
console.print()
|
|
23
|
+
|
|
24
|
+
options = Table(title="Options", box=box.SQUARE_DOUBLE_HEAD, show_header=False)
|
|
25
|
+
options.add_row("[cyan]-h[/], [cyan]--help[/]", "Show this message and exit.")
|
|
26
|
+
console.print(options)
|
|
27
|
+
console.print()
|
|
28
|
+
|
|
29
|
+
commands = Table(title="Commands", box=box.SQUARE_DOUBLE_HEAD, show_header=False)
|
|
30
|
+
commands.add_row("[green]help[/]", "Show this help message.")
|
|
31
|
+
commands.add_row("[green]version[/]", "Show CLI version.")
|
|
32
|
+
commands.add_row(
|
|
33
|
+
"[green]miner[/] [dim](or [green]m[/])[/]", "Miner management commands."
|
|
34
|
+
)
|
|
35
|
+
commands.add_row(
|
|
36
|
+
"[green]vault[/] [dim](or [green]v[/])[/]", "Vault management commands."
|
|
37
|
+
)
|
|
38
|
+
commands.add_row(
|
|
39
|
+
"[green]utils[/] [dim](or [green]u[/])[/]", "Utility commands: health checks and configuration."
|
|
40
|
+
)
|
|
41
|
+
console.print(commands)
|
|
42
|
+
console.print()
|
|
43
|
+
|
|
44
|
+
# Display clock and countdown in a separate table
|
|
45
|
+
clock_table = get_clock_table()
|
|
46
|
+
console.print(clock_table)
|
|
47
|
+
console.print()
|
|
48
|
+
|
|
49
|
+
console.print("[dim]Made with ❤ by GTV[/]")
|