Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions src/chutes-miner-cli/chutes_miner_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from chutes_miner_cli import tee_images
from chutes_miner_cli import tee_maintenance
from chutes_miner_cli import tee_status
from chutes_miner_cli.util import sign_request
from chutes_miner_cli.util import sign_request, sort_servers, filter_server
from loguru import logger
import yaml

Expand Down Expand Up @@ -340,6 +340,12 @@ def display_remote_inventory(servers):

def local_inventory(
raw_json: bool = typer.Option(False, help="Display raw JSON output"),
name: Optional[str] = typer.Option(
None,
"--name",
"-n",
help="Show only the server matching this name or ID",
),
hotkey: str = typer.Option(
...,
help="Path to the hotkey file for your miner",
Expand All @@ -356,7 +362,7 @@ def local_inventory(
"""

async def _local_inventory():
nonlocal hotkey, miner_api, raw_json
nonlocal hotkey, miner_api, raw_json, name
async with aiohttp.ClientSession(raise_for_status=True) as session:
headers, _ = sign_request(hotkey, purpose="management")
async with session.get(
Expand All @@ -365,6 +371,12 @@ async def _local_inventory():
timeout=30,
) as resp:
inventory = await resp.json()
inventory = sort_servers(filter_server(inventory, name))
if name and not inventory:
typer.echo(
f"No server matching '{name}' found in local inventory.", err=True
)
raise typer.Exit(1)
if raw_json:
print(json.dumps(inventory, indent=2))
else:
Expand All @@ -375,6 +387,12 @@ async def _local_inventory():

def remote_inventory(
raw_json: bool = typer.Option(False, help="Display raw JSON output"),
name: Optional[str] = typer.Option(
None,
"--name",
"-n",
help="Show only the server matching this name or ID",
),
hotkey: str = typer.Option(
...,
help="Path to the hotkey file for your miner",
Expand All @@ -391,7 +409,7 @@ def remote_inventory(
"""

async def _remote_inventory():
nonlocal hotkey, validator_api, raw_json
nonlocal hotkey, validator_api, raw_json, name
async with aiohttp.ClientSession(raise_for_status=True) as session:
headers, _ = sign_request(hotkey, purpose="miner", remote=True)
async with session.get(
Expand Down Expand Up @@ -420,6 +438,12 @@ async def _remote_inventory():
"inst_verified_at": item["last_verified_at"],
}
)
servers = sort_servers(filter_server(servers, name))
if name and not servers:
typer.echo(
f"No server matching '{name}' found in remote inventory.", err=True
)
raise typer.Exit(1)
if raw_json:
print(json.dumps({"servers": servers}, indent=2))
else:
Expand Down
17 changes: 15 additions & 2 deletions src/chutes-miner-cli/chutes_miner_cli/tee_maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import asyncio
import json
from typing import Any
from typing import Any, Optional

import aiohttp
import typer
Expand All @@ -16,7 +16,7 @@
from rich import box

from chutes_miner_cli.constants import HOTKEY_ENVVAR, MINER_API_ENVVAR, VALIDATOR_API_ENVVAR
from chutes_miner_cli.util import sign_request
from chutes_miner_cli.util import sign_request, sort_servers, filter_server

console = Console()

Expand Down Expand Up @@ -141,6 +141,12 @@ def maintenance_status(
raw_json: bool = typer.Option(
False, "--raw-json", help="Output raw JSON for programmatic use"
),
name: Optional[str] = typer.Option(
None,
"--name",
"-n",
help="Show only the server matching this name or ID",
),
hotkey: str = typer.Option(
..., help="Path to the hotkey file for your miner", envvar=HOTKEY_ENVVAR
),
Expand All @@ -161,6 +167,13 @@ async def _run():
raise typer.Exit(1)
data = await resp.json()

data["servers"] = sort_servers(filter_server(data.get("servers"), name))
if name and not data["servers"]:
typer.echo(
f"No server matching '{name}' found in maintenance status.", err=True
)
raise typer.Exit(1)

if raw_json:
print(json.dumps(data, indent=2))
else:
Expand Down
27 changes: 27 additions & 0 deletions src/chutes-miner-cli/chutes_miner_cli/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ def get_signing_message(
raise ValueError("Either payload_str or purpose must be provided")


def sort_servers(servers: list | None) -> list:
"""
Sort a list of server dicts consistently by name (case-insensitive),
falling back to server_id as a tiebreaker. Used across local-inventory,
remote-inventory, and tee maintenance-status so ordering is stable.
"""
return sorted(
servers or [],
key=lambda s: ((s.get("name") or "").lower(), s.get("server_id") or ""),
)


def filter_server(servers: list | None, name_or_id: str | None) -> list:
"""
Filter server dicts down to those matching name_or_id exactly, by either
name or server_id (same semantics as the miner API lock/unlock/delete
routes). Returns the list unchanged when name_or_id is falsy.
"""
if not name_or_id:
return servers or []
return [
s
for s in (servers or [])
if s.get("name") == name_or_id or s.get("server_id") == name_or_id
]


def sign_request(
hotkey: str,
payload: Dict[str, Any] | str | None = None,
Expand Down
Loading