| name | nmp-cli |
| description | Use when developing the NeMo Platform CLI - adding commands, modifying the generator, templates, or configuration. Covers both manual commands (use-cases, config) and auto-generated API commands. Also helps troubleshoot CLI generation failures (e.g., Typer errors after updates). |
NeMo Platform CLI Development
The CLI provides a command-line interface for NeMo Platform, implemented using Typer in packages/nemo_platform_ext/src/nemo_platform_ext/cli/.
Command Categories
| Category | Location | Description | Editable? |
|---|
| API | commands/api/ | Auto-generated from SDK | ❌ Never edit |
| Configuration | commands/config.py, commands/configure.py | Config management | ✅ Yes |
| Use-case | commands/use_cases/ | High-level commands (e.g., chat) | ✅ Yes |
| Quickstart | commands/quickstart/ | Local deployment | ✅ Yes |
Directory Structure
packages/nemo_platform_ext/src/nemo_platform_ext/cli/
├── app.py # Entry point, command registration, global options
├── core/ # Shared utilities
└── commands/
├── config.py # kubectl-style config management
├── configure.py # Interactive configuration
├── quickstart/ # Local deployment commands
├── use_cases/ # High-level commands (chat, wait)
└── api/ # ⚠️ AUTO-GENERATED - DO NOT EDIT
Building the CLI
Development Shortcut
During local CLI development, you can run _nemo to execute the CLI directly from packages/nemo_platform_ext.
This lets you test changes immediately without running vendoring first.
uv run _nemo --help
Use vendoring (make vendor-nemo-platform-ext) when you need to validate behavior in sdk/python/nemo-platform.
Build everything (recommended):
make update-cli
Individual steps:
make generate-cli-commands
make vendor-nemo-platform-ext
make generate-cli-reference-docs
Adding a Manual Command
Manual commands go in commands/use_cases/ or directly in commands/.
Pattern
from __future__ import annotations
from typing import Annotated
import typer
from nemo_platform_ext.cli.core.context import CLIContext
from nemo_platform_ext.cli.core.errors import handle_errors
@handle_errors
def my_command(
ctx: typer.Context,
name: Annotated[str, typer.Argument(help="The name argument")],
option: Annotated[str | None,
typer.Option(
"--option",
"-o",
help="An optional flag",
rich_help_panel="Options",
),
] = None,
) -> None:
"""
Short description of what the command does.
Examples:
nemo my-command foo
nemo my-command bar --option value
"""
state: CLIContext = ctx.obj
client = state.get_client()
result = client.some_resource.some_method(name=name)
state.output(result)
Register in app.py
from nemo_platform_ext.cli.commands.my_module import my_command
app.command(name="my-command", rich_help_panel="Use cases")(my_command)
app.add_typer(my_sub_app, name="my-group", rich_help_panel="Category")
CLI Generator
The generator creates commands in commands/api/ from SDK introspection.
Key Files
tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/sdk/cli_generator/
├── cli_config.yaml # Generation configuration
├── generator.py # Main generation logic
├── sdk_introspector.py # SDK introspection
├── operation_classifier.py # Classifies methods (list, get, create, etc.)
├── templates/ # Jinja2 templates
├── context_collectors/ # Collect template context per operation type
└── overrides/ # Custom command implementations
Configuration (cli_config.yaml)
Configure table columns for list commands:
- resource: [customization, jobs]
methods:
list:
columns:
- name
- description
- status
- created_at
Skip a method:
- resource: [filesets]
methods:
upload_file:
skip: true
Use custom implementation:
- resource: [secrets]
methods:
create:
override: secrets/create.py
Command Overrides
For methods requiring custom handling (file uploads, streaming), create overrides:
-
Add config in cli_config.yaml:
- resource: [files]
additional_methods:
upload:
override: files/upload.py
-
Create override file in overrides/files/upload.py:
from typing import Annotated, Any, cast
import typer
from nemo_platform_ext.cli.core.context import CLIContext
from nemo_platform_ext.cli.core.errors import handle_errors
app = cast(Any, None)
@app.command("upload")
@handle_errors
def upload_files(
ctx: typer.Context,
path: Annotated[str, typer.Argument(help="Local path to upload")],
) -> None:
"""Upload files to a fileset."""
state: CLIContext = ctx.obj
client = state.get_client()
The # override-skip comment strips that line from output (useful for linter satisfaction).
Templates
Templates use Jinja2. Key variables available:
command_name - CLI command name (e.g., "list")
method_name - SDK method name (e.g., "list")
resource_path - Resource path (e.g., ["customization", "jobs"])
params - List of parameter info
docstring - Method docstring
Core Utilities
CLIContext
Access via ctx.obj:
state: CLIContext = ctx.obj
client = state.get_client()
state.output(data)
state.verbosity
Error Handling
Always use @handle_errors decorator:
from nemo_platform_ext.cli.core.errors import handle_errors
@handle_errors
def my_command(ctx: typer.Context) -> None:
...
Building API Request Bodies
from nemo_platform_ext.cli.core.api import build_kwargs
body = build_kwargs(
model="my-model",
messages=[{"role": "user", "content": "Hello"}],
stream=True,
temperature=0.7,
)
Testing
Run CLI tests:
uv run pytest packages/nemo_platform_ext/tests/cli/ -v
Run generator tests:
uv run --package nemo-platform-sdk-tools pytest tools/nemo-platform-sdk-tools/tests/sdk/cli_generator/ -v
Common Tasks
Add table columns for a new resource
Edit cli_config.yaml:
- resource: [my_service, my_resource]
methods:
list:
columns:
- name
- status
- created_at
Then rebuild: make update-cli
Add a new use-case command
- Create file in
commands/use_cases/my_command.py
- Implement using pattern above
- Register in
app.py
- Vendor:
make vendor-nemo-platform-ext
Modify how a command is generated
- Check which template is used (see
operation_classifier.py)
- Edit template in
templates/
- Rebuild:
make update-cli
Override an auto-generated command
- Add override config in
cli_config.yaml
- Create override file in
overrides/
- Rebuild:
make update-cli
Troubleshooting
Typer errors after CLI update
If tests fail with Typer errors after running make update-cli, the issue is usually one of:
1. Unsupported type in Typer
Typer doesn't support all Python types (e.g., complex unions, nested TypedDicts). Fix by updating the generator code to handle the type:
- Check
type_formatter.py for type formatting
- Check
typed_dict_utils.py for TypedDict handling
- Add type conversion or simplification in the relevant context collector
2. Method miscategorized
The operation classifier may assign the wrong template (e.g., "default" instead of "create"). Fix via cli_config.yaml:
- resource: [my_service]
methods:
my_method:
operation_type: create
Available operation types: list, get, create, update, delete, download, default
3. Skip the method temporarily
If the fix is non-trivial, skip the method to unblock your work:
- resource: [my_service]
methods:
problematic_method:
skip: true
Create a GitLab issue to track the fix, then rebuild: make update-cli