Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies = [
"pyyaml>=6.0.3",
"pyopenssl>=25.3.0",
"qrcode>=8.0.0",
"vantage-sdkpy>=0.1.21",
"vantage-sdkpy>=0.1.22",
]

[tool.hatch.build.targets.wheel]
Expand Down
184 changes: 92 additions & 92 deletions uv.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions v8x/commands/cluster/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from v8x import AsyncTyper

from .blueprint import blueprint_app
from .compute_pool import compute_pool_app
from .configuration_preset import configuration_preset_app
from .create import create_cluster
Expand All @@ -26,8 +27,11 @@
from .list import list_clusters
from .model_registry import model_registry_app
from .namespace import namespace_app
from .nemo import nemo_app
from .network import network_app
from .ngc_container import ngc_container_app
from .nim import nim_app
from .ray import ray_app
from .secret import secret_app
from .service import service_app
from .size_preset import size_preset_app
Expand All @@ -52,16 +56,20 @@
cluster_app.command("update")(update_cluster)

# Add nested command groups
cluster_app.add_typer(blueprint_app, name="blueprint")
cluster_app.add_typer(compute_pool_app, name="compute-pool")
cluster_app.add_typer(configuration_preset_app, name="configuration-preset")
cluster_app.add_typer(dynamo_app, name="dynamo")
cluster_app.add_typer(federation_app, name="federation")
cluster_app.add_typer(inference_endpoint_app, name="inference-endpoint")
cluster_app.add_typer(model_registry_app, name="model-registry")
cluster_app.add_typer(namespace_app, name="namespace")
cluster_app.add_typer(nemo_app, name="nemo")
cluster_app.add_typer(network_app, name="network")
cluster_app.add_typer(ngc_container_app, name="ngc-container")
cluster_app.add_typer(nim_app, name="nim")
cluster_app.add_typer(kubeflow_app, name="kubeflow")
cluster_app.add_typer(ray_app, name="ray")
cluster_app.add_typer(secret_app, name="secret")
cluster_app.add_typer(service_app, name="service")
cluster_app.add_typer(size_preset_app, name="size-preset")
Expand Down
18 changes: 18 additions & 0 deletions v8x/commands/cluster/blueprint/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (C) 2025 Vantage Compute Corporation
# GPL-3.0 — see LICENSE.
"""NVIDIA AI Blueprint catalog commands."""

from v8x import AsyncTyper

from .get import get_blueprint
from .list import list_blueprints

blueprint_app = AsyncTyper(
name="blueprint",
help="Browse the curated NVIDIA AI Blueprint catalog.",
invoke_without_command=True,
no_args_is_help=True,
)

blueprint_app.command("list")(list_blueprints)
blueprint_app.command("get")(get_blueprint)
73 changes: 73 additions & 0 deletions v8x/commands/cluster/blueprint/get.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright (C) 2025 Vantage Compute Corporation
# GPL-3.0 — see LICENSE.
"""Get blueprint."""

import json

import typer
from typing_extensions import Annotated
from vantage_sdk.exceptions import Abort
from vantage_sdk.workbench.nvidia_catalogs import blueprint_sdk

from v8x.auth import attach_persona
from v8x.config import attach_settings
from v8x.exceptions import handle_abort
from v8x.vantage_rest_api_client import attach_vantage_rest_client


@handle_abort
@attach_settings
@attach_persona
@attach_vantage_rest_client
async def get_blueprint(
ctx: typer.Context,
blueprint_id: Annotated[str, typer.Argument(help="Blueprint ID (e.g. nvidia-rag)")],
cluster_name: Annotated[str, typer.Option("--cluster", "-c", help="Cluster name")],
):
"""Get one curated NVIDIA AI Blueprint.

Examples:
v8x cluster blueprint get nvidia-rag -c my-cluster
"""
console = ctx.obj.console
try:
response = await blueprint_sdk.get(
ctx, cluster_name=cluster_name, blueprint_id=blueprint_id
)

if response.status_code == 404:
console.print(f"[yellow]Blueprint '{blueprint_id}' not found[/yellow]")
return
if response.status_code != 200:
raise Abort(f"Failed: {response.text}", subject="API Error")

data = response.json() or {}
if ctx.obj.json_output:
print(json.dumps(data, default=str))
return

console.print(
f"[bold]{data.get('name', blueprint_id)}[/bold] ({data.get('id', blueprint_id)})"
)
console.print(f" Category: {data.get('category', 'N/A')}")
console.print(f" Status: {data.get('status', 'N/A')}")
console.print(f" Preset Kind: {data.get('configuration_preset_kind') or 'N/A'}")
presets = ", ".join(data.get("configuration_preset_names") or []) or "N/A"
console.print(f" Presets: {presets}")
console.print(f" Docs: {data.get('docs_url') or 'N/A'}")
console.print(f" Description: {data.get('description', 'N/A')}")
components = data.get("components") or []
if components:
console.print(" Components:")
for comp in components:
console.print(
f" {comp.get('role', '')}: {comp.get('catalog_id', '')} "
f"([dim]{comp.get('catalog', '')}[/dim])"
)

except Abort:
raise
except Exception as e:
ctx.obj.formatter.render_error(
error_message="Failed to get blueprint.", details={"error": str(e)}
)
82 changes: 82 additions & 0 deletions v8x/commands/cluster/blueprint/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright (C) 2025 Vantage Compute Corporation
# GPL-3.0 — see LICENSE.
"""List blueprints."""

import json

import typer
from typing_extensions import Annotated
from vantage_sdk.exceptions import Abort
from vantage_sdk.workbench.nvidia_catalogs import blueprint_sdk

from v8x.auth import attach_persona
from v8x.config import attach_settings
from v8x.exceptions import handle_abort
from v8x.vantage_rest_api_client import attach_vantage_rest_client


@handle_abort
@attach_settings
@attach_persona
@attach_vantage_rest_client
async def list_blueprints(
ctx: typer.Context,
cluster_name: Annotated[str, typer.Option("--cluster", "-c", help="Cluster name")],
status: Annotated[
str | None,
typer.Option("--status", "-s", help="Filter: available, roadmap"),
] = None,
):
"""List curated NVIDIA AI Blueprints.

Examples:
v8x cluster blueprint list -c my-cluster --status available
"""
console = ctx.obj.console
try:
response = await blueprint_sdk.list(ctx, cluster_name=cluster_name, status=status)

if response.status_code != 200:
raise Abort(f"Failed: {response.text}", subject="API Error")

data = response.json() or {}
items = data.get("entries", []) if isinstance(data, dict) else data

if ctx.obj.json_output:
print(json.dumps(items, default=str))
return

if not items:
console.print("No blueprints found")
return

from rich.table import Table

table = Table(title=f"NVIDIA AI Blueprints on '{cluster_name}'")
table.add_column("ID", style="bold")
table.add_column("Name")
table.add_column("Category")
table.add_column("Status")
table.add_column("Preset Kind")
table.add_column("Presets")

for entry in items:
status_val = entry.get("status", "")
table.add_row(
entry.get("id", ""),
entry.get("name", ""),
entry.get("category", ""),
"[green]available[/green]"
if status_val == "available"
else f"[dim]{status_val}[/dim]",
entry.get("configuration_preset_kind") or "[dim]-[/dim]",
", ".join(entry.get("configuration_preset_names") or []) or "[dim]-[/dim]",
)
console.print(table)

except Abort:
raise
except Exception as e:
ctx.obj.formatter.render_error(
error_message="Failed to list blueprints.", details={"error": str(e)}
)
18 changes: 18 additions & 0 deletions v8x/commands/cluster/nemo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (C) 2025 Vantage Compute Corporation
# GPL-3.0 — see LICENSE.
"""NeMo microservices catalog commands."""

from v8x import AsyncTyper

from .catalogs import customization_configs, customization_targets, evaluation_configs

nemo_app = AsyncTyper(
name="nemo",
help="Browse the in-cluster NeMo microservices catalogs (Customizer / Evaluator).",
invoke_without_command=True,
no_args_is_help=True,
)

nemo_app.command("customization-configs")(customization_configs)
nemo_app.command("customization-targets")(customization_targets)
nemo_app.command("evaluation-configs")(evaluation_configs)
99 changes: 99 additions & 0 deletions v8x/commands/cluster/nemo/catalogs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright (C) 2025 Vantage Compute Corporation
# GPL-3.0 — see LICENSE.
"""NeMo microservices catalog passthrough commands.

The payload schemas are owned by the installed NeMo microservices version —
responses are rendered as JSON verbatim rather than tabulated.
"""

import json

import typer
from typing_extensions import Annotated
from vantage_sdk.exceptions import Abort
from vantage_sdk.workbench.nvidia_catalogs import nemo_catalog_sdk

from v8x.auth import attach_persona
from v8x.config import attach_settings
from v8x.exceptions import handle_abort
from v8x.vantage_rest_api_client import attach_vantage_rest_client

_ClusterOpt = Annotated[str, typer.Option("--cluster", "-c", help="Cluster name")]


async def _render_catalog(ctx: typer.Context, response, error_message: str) -> None:
"""Render a NeMo catalog response as JSON (verbatim passthrough)."""
if response.status_code != 200:
raise Abort(f"Failed: {response.text}", subject="API Error")

data = response.json()
if ctx.obj.json_output:
print(json.dumps(data, default=str))
return
ctx.obj.console.print_json(json.dumps(data, default=str))


@handle_abort
@attach_settings
@attach_persona
@attach_vantage_rest_client
async def customization_configs(ctx: typer.Context, cluster_name: _ClusterOpt):
"""List NeMo Customizer fine-tuning recipes (model x technique).

Examples:
v8x cluster nemo customization-configs -c my-cluster
"""
try:
response = await nemo_catalog_sdk.customization_configs(ctx, cluster_name=cluster_name)
await _render_catalog(ctx, response, "customization configs")
except Abort:
raise
except Exception as e:
ctx.obj.formatter.render_error(
error_message="Failed to list NeMo customization configs.",
details={"error": str(e)},
)


@handle_abort
@attach_settings
@attach_persona
@attach_vantage_rest_client
async def customization_targets(ctx: typer.Context, cluster_name: _ClusterOpt):
"""List NeMo Customizer fine-tunable base models.

Examples:
v8x cluster nemo customization-targets -c my-cluster
"""
try:
response = await nemo_catalog_sdk.customization_targets(ctx, cluster_name=cluster_name)
await _render_catalog(ctx, response, "customization targets")
except Abort:
raise
except Exception as e:
ctx.obj.formatter.render_error(
error_message="Failed to list NeMo customization targets.",
details={"error": str(e)},
)


@handle_abort
@attach_settings
@attach_persona
@attach_vantage_rest_client
async def evaluation_configs(ctx: typer.Context, cluster_name: _ClusterOpt):
"""List NeMo Evaluator configs.

Examples:
v8x cluster nemo evaluation-configs -c my-cluster
"""
try:
response = await nemo_catalog_sdk.evaluation_configs(ctx, cluster_name=cluster_name)
await _render_catalog(ctx, response, "evaluation configs")
except Abort:
raise
except Exception as e:
ctx.obj.formatter.render_error(
error_message="Failed to list NeMo evaluation configs.",
details={"error": str(e)},
)
18 changes: 18 additions & 0 deletions v8x/commands/cluster/ngc_container/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (C) 2025 Vantage Compute Corporation
# GPL-3.0 — see LICENSE.
"""NGC framework container catalog commands."""

from v8x import AsyncTyper

from .get import get_ngc_container
from .list import list_ngc_containers

ngc_container_app = AsyncTyper(
name="ngc-container",
help="Browse the curated NGC framework container catalog.",
invoke_without_command=True,
no_args_is_help=True,
)

ngc_container_app.command("list")(list_ngc_containers)
ngc_container_app.command("get")(get_ngc_container)
Loading
Loading