Skip to content

CLI Commands

Experimental

Command is a small, experimental CLI surface for prototypes and internal tools. It is not a replacement for typer or click, and the API may still change.

Command is xnano's process-argument surface: options, subcommands, and help for small tools. It is not a TUI host.

Use it when a project needs a real argv entrypoint. Pair it with Terminal only when the same project also runs an interactive session.

A Command can:

  • Register a root callback (@cli or register_callback)
  • Declare options and flags (@Command.option)
  • Nest subcommands (@cli.command() or add_subcommand)
  • Coerce values from type annotations (optionally strict=True)
  • Emit help (--help / -h)

A root command

A Root Command
from xnano.cli import Command

cli = Command(name="tool", description="A small utility")

@cli # (1)!
@Command.option("--name", default="world", help="Who to greet")
def greet(name: str = "world") -> None:
    print(f"hello, {name}")

if __name__ == "__main__":
    cli.run() # (2)!
  1. @cli registers the function as the main callback. Also valid: cli(greet) or cli.register_callback(greet).
  2. run() parses sys.argv[1:] by default. Pass a list in tests: cli.run(["--name", "hammad"]).
Usage
uv run python tool.py
# hello, world

uv run python tool.py --name hammad
# hello, hammad

uv run python tool.py --help

Types on the signature drive coercion when values are parseable. Set Command(strict=True) to raise on bad values instead of leaving the raw string.

Options and flags

Options and Flags
from xnano.cli import Command

cli = Command(name="build", description="Compile the project")

@cli
@Command.option("--count", default=1, help="How many times")
@Command.option(["--verbose", "-v"], is_flag=True, help="Verbose output")
def main(count: int = 1, verbose: bool = False) -> None: # (1)!
    mode = "verbose" if verbose else "quiet"
    print(f"building ×{count} ({mode})")
  1. --count takes a value; -v / --verbose is a bare flag. Parameter names match the long flag form (count, verbose).
Usage
uv run python build.py --count 3 -v
# building ×3 (verbose)

Parameters without an explicit @Command.option still appear as --param-name flags derived from the signature.

Flags vs. values
  • is_flag=True (or a bool annotation / default) treats presence as True.
  • Otherwise the next argument is the value (--count 3 or --count=3).

Subcommands

Subcommands
from xnano.cli import Command

cli = Command(name="ship", description="Release helpers")

@cli.command(name="greet", description="Print a greeting")
@Command.option("--name", default="world", help="Who to greet")
def greet(name: str = "world") -> None:
    print(f"hello, {name}")

@cli.command(name="bump")
@Command.option("--major", is_flag=True, help="Bump the major version")
def bump(major: bool = False) -> None:
    kind = "major" if major else "patch"
    print(f"bumping {kind}")

if __name__ == "__main__":
    cli.run()
Usage
uv run python ship.py greet --name crew
# hello, crew

uv run python ship.py bump --major
# bumping major

uv run python ship.py --help
uv run python ship.py bump --help

Subcommand names default from the function name (dry_rundry-run) when name= is omitted. You can also use add_subcommand(Command(...)).

Help

With help=True (the default), --help / -h print usage and exit. get_help() returns the same text as a string.

Help Text
from xnano.cli import Command

cli = Command(name="tool", description="A small utility")

@cli
@Command.option("--name", default="world", help="Who to greet")
def greet(name: str = "world") -> None:
    print(f"hello, {name}")

print(cli.get_help())
Example help
Usage: tool [OPTIONS]

A small utility

Options:
  --name               Who to greet [default: world]
  --help, -h           Show this message and exit.

Strict mode

Strict Mode
from xnano.cli import Command

cli = Command(name="count", strict=True)

@cli
def main(n: int) -> None:
    print(n * 2)

cli.run(["--n", "21"])   # prints 42
# cli.run(["--n", "nope"])  # Error: Invalid value for parameter 'n': ...

Parsing without running

Parse Only
from xnano.cli import Command

cli = Command(name="tool")

@cli
@Command.option("--name", default="world")
def greet(name: str = "world") -> None:
    ...

target, values = cli.parse_arguments(["--name", "hammad"])
assert values["name"] == "hammad"
assert target is cli

Next

API

Command · xnano.cli