"""Base command dataclass for composable CLI trees.""" from __future__ import annotations from argparse import ArgumentParser from dataclasses import dataclass, fields from typing import Any, ClassVar, Dict, List, Optional, Type @dataclass class CLICommand: """Base class for CLI commands. Subclasses define their identity (name, help, description) as dataclass fields. These are passed as kwargs to ``subparsers.add_parser()``. Override ``add_arguments`` to register flags and positionals. Override ``execute`` to implement the command's logic. Nest subcommands by setting ``_subcommands`` as a class variable. """ name: str = "" help: str = "" description: str = "" _subcommands: ClassVar[List[Type[Command]]] = [] def add_arguments(self, parser: ArgumentParser) -> None: """Add arguments to the parser. Override in subclasses.""" def execute(self, args: Any) -> int: """Run the command. Override in subclasses. Returns an exit code (0 = success). """ return 0 def parser_kwargs(self) -> Dict[str, Any]: """Return the dataclass fields as kwargs for add_parser. Excludes ``name`` (used as the positional parser name) and any empty-string fields so argparse defaults apply. """ skip = {"name"} kwargs = {} for f in fields(self): if f.name in skip or f.name.startswith("_"): continue val = getattr(self, f.name) if val != "": kwargs[f.name] = val return kwargs