dell_ai/cli/main.py (231 lines of code) (raw):
"""Command-line interface for Dell AI."""
import json
import typer
from typing import Optional
from dell_ai import __version__, auth
from dell_ai.exceptions import (
AuthenticationError,
ResourceNotFoundError,
ValidationError,
GatedRepoAccessError,
)
from dell_ai.cli.utils import (
get_client,
print_json,
print_error,
)
app = typer.Typer(
name="dell-ai",
help="CLI for interacting with the Dell Enterprise Hub (DEH)",
add_completion=False,
)
models_app = typer.Typer(help="Model commands")
platforms_app = typer.Typer(help="Platform commands")
apps_app = typer.Typer(help="Application commands")
app.add_typer(models_app, name="models")
app.add_typer(platforms_app, name="platforms")
app.add_typer(apps_app, name="apps")
def version_callback(value: bool):
"""Show version and exit."""
if value:
typer.echo(f"dell-ai version: {__version__}")
raise typer.Exit()
@app.callback()
def main(
version: Optional[bool] = typer.Option(
None,
"--version",
"-v",
callback=version_callback,
help="Show the application version and exit.",
)
):
"""
Dell AI CLI - Interact with the Dell Enterprise Hub (DEH)
"""
pass
@app.command("login")
def auth_login(
token: Optional[str] = typer.Option(
None,
"--token",
help="Hugging Face API token. If not provided, you will be prompted to enter it.",
)
) -> None:
"""
Log in to Dell AI using a Hugging Face token.
If no token is provided, you will be prompted to enter it. You can get a token from:
https://huggingface.co/settings/tokens
"""
if not token:
typer.echo(
"You can get a token from https://huggingface.co/settings/tokens\n"
"The token will be stored securely in your Hugging Face token cache."
)
token = typer.prompt("Enter your Hugging Face token", hide_input=True)
try:
auth.login(token)
user_info = auth.get_user_info(token)
typer.echo(f"Successfully logged in as {user_info.get('name', 'Unknown')}")
except AuthenticationError as e:
typer.echo(f"Error: {str(e)}", err=True)
raise typer.Exit(code=1)
@app.command("logout")
def auth_logout() -> None:
"""
Log out from Dell AI and remove the stored token.
"""
if not auth.is_logged_in():
typer.echo("You are not currently logged in.")
return
if typer.confirm("Are you sure you want to log out?"):
try:
auth.logout()
typer.echo("Successfully logged out")
except Exception as e:
typer.echo(f"Error during logout: {str(e)}", err=True)
raise typer.Exit(code=1)
else:
typer.echo("Logout cancelled")
@app.command("whoami")
def auth_status() -> None:
"""
Show the current authentication status and user information.
"""
if not auth.is_logged_in():
typer.echo("Status: Not logged in")
typer.echo("To log in, run: dell-ai login")
return
try:
user_info = auth.get_user_info()
typer.echo("Status: Logged in")
typer.echo(f"User: {user_info.get('name', 'Unknown')}")
typer.echo(f"Email: {user_info.get('email', 'Not available')}")
typer.echo(
f"Organizations: {', '.join([org.get('name', 'Unknown') for org in user_info.get('orgs', [])])}"
)
except AuthenticationError as e:
typer.echo(f"Status: Error ({str(e)})")
typer.echo("Please try logging in again: dell-ai login")
raise typer.Exit(code=1)
@models_app.command("list")
def models_list() -> None:
"""
List all available models from the Dell Enterprise Hub.
Returns a JSON array of model IDs in the format "organization/model_name".
"""
try:
client = get_client()
models = client.list_models()
print_json(models)
except Exception as e:
print_error(f"Failed to list models: {str(e)}")
@models_app.command("show")
def models_show(model_id: str) -> None:
"""
Show detailed information about a specific model.
Args:
model_id: The model ID in the format "organization/model_name"
"""
try:
client = get_client()
model_info = client.get_model(model_id)
print_json(model_info)
except ResourceNotFoundError:
print_error(f"Model not found: {model_id}")
except Exception as e:
print_error(f"Failed to get model information: {str(e)}")
@models_app.command("check-access")
def models_check_access(model_id: str) -> None:
"""
Check if you have access to a specific model repository.
This is particularly useful for gated repositories that require specific permissions.
If you don't have access to a gated repository, you'll need to request access on the
Hugging Face Hub before you can use it.
Args:
model_id: The model ID in the format "organization/model_name"
"""
try:
client = get_client()
# If check_model_access completes without raising an exception, we have access
client.check_model_access(model_id)
typer.echo(f"✅ You have access to model: {model_id}")
except (GatedRepoAccessError, ResourceNotFoundError, AuthenticationError) as e:
# Handle expected errors with proper error messages
print_error(str(e))
except Exception as e:
# Unexpected errors get a generic message
print_error(f"Failed to check model access: {str(e)}")
@models_app.command("get-snippet")
def models_get_snippet(
model_id: str = typer.Option(
...,
"--model-id",
"-m",
help="Model ID in the format 'organization/model_name'",
),
platform_id: str = typer.Option(
...,
"--platform-id",
"-p",
help="Platform SKU ID",
),
engine: str = typer.Option(
"docker",
"--engine",
"-e",
help="Deployment engine (docker or kubernetes)",
),
gpus: int = typer.Option(
1,
"--gpus",
"-g",
help="Number of GPUs to use",
min=1,
),
replicas: int = typer.Option(
1,
"--replicas",
"-r",
help="Number of replicas to deploy",
min=1,
),
) -> None:
"""
Get a deployment snippet for running a model on a specific platform.
This command generates a deployment snippet (Docker command or Kubernetes manifest)
for running the specified model on the given platform with the provided configuration.
Args:
model_id: Model ID in the format 'organization/model_name'
platform_id: Platform SKU ID
engine: Deployment engine (docker or kubernetes)
gpus: Number of GPUs to use
replicas: Number of replicas to deploy
Examples:
dell-ai models get-snippet --model-id google/gemma-3-27b-it --platform-id xe9680-nvidia-h100 --engine docker --gpus 1 --replicas 1
dell-ai models get-snippet -m google/gemma-3-27b-it -p xe9680-nvidia-h100 -e kubernetes -g 2 -r 3
"""
try:
# Create client and get deployment snippet
client = get_client()
snippet = client.get_deployment_snippet(
model_id=model_id,
platform_id=platform_id,
engine=engine,
num_gpus=gpus,
num_replicas=replicas,
)
typer.echo(snippet)
except (ValidationError, ResourceNotFoundError, GatedRepoAccessError) as e:
# Handle expected errors with proper error messages
print_error(str(e))
except Exception as e:
# Unexpected errors get a generic message
print_error(f"Failed to get deployment snippet: {str(e)}")
@platforms_app.command("list")
def platforms_list() -> None:
"""
List all available platforms from the Dell Enterprise Hub.
Returns a JSON array of platform SKU IDs.
"""
try:
client = get_client()
platforms = client.list_platforms()
print_json(platforms)
except Exception as e:
print_error(f"Failed to list platforms: {str(e)}")
@platforms_app.command("show")
def platforms_show(platform_id: str) -> None:
"""
Show detailed information about a specific platform.
Args:
platform_id: The platform SKU ID
"""
try:
client = get_client()
platform_info = client.get_platform(platform_id)
print_json(platform_info)
except ResourceNotFoundError:
print_error(f"Platform not found: {platform_id}")
except Exception as e:
print_error(f"Failed to get platform information: {str(e)}")
@apps_app.command("list")
def apps_list() -> None:
"""
List all available applications from the Dell Enterprise Hub.
Returns a JSON array of application names.
"""
try:
client = get_client()
apps = client.list_apps()
print_json(apps)
except Exception as e:
print_error(f"Failed to list applications: {str(e)}")
@apps_app.command("show")
def apps_show(app_id: str) -> None:
"""
Show detailed information about a specific application.
Args:
app_id: The application ID
"""
try:
client = get_client()
app_info = client.get_app(app_id)
print_json(app_info.model_dump())
except ResourceNotFoundError:
print_error(f"Application not found: {app_id}")
except Exception as e:
print_error(f"Failed to get application information: {str(e)}")
@apps_app.command("get-snippet")
def apps_get_snippet(
app_id: str = typer.Argument(..., help="Application ID"),
config_json: str = typer.Option(
"{}",
"--config",
"-c",
help="JSON configuration string for the application",
),
) -> None:
"""
Get a deployment snippet for an application with the provided configuration.
This command generates a Helm installation command for deploying the specified
application with the provided configuration parameters.
Example configuration format:
{
"config": [
{
"helmPath": "main.config.storageClassName",
"type": "string",
"value": "custom-storage-class"
},
{
"helmPath": "main.config.enableOpenAI",
"type": "boolean",
"value": true
}
]
}
Examples:
dell-ai apps get-snippet openwebui --config '{"config":[{"helmPath":"main.config.storageClassName","type":"string","value":"custom-storage-class"}]}'
"""
try:
# Parse the JSON configuration
config_data = json.loads(config_json)
config = config_data.get("config", [])
# Create client and get deployment snippet
client = get_client()
snippet = client.get_app_snippet(app_id=app_id, config=config)
typer.echo(snippet)
except json.JSONDecodeError:
print_error("Invalid JSON configuration format")
except (ValidationError, ResourceNotFoundError) as e:
# Handle expected errors with proper error messages
print_error(str(e))
except Exception as e:
# Unexpected errors get a generic message
print_error(f"Failed to get application deployment snippet: {str(e)}")
if __name__ == "__main__":
app()