Skip to content

Registry API Reference

registry

Axium Command Registry - Dynamic command discovery and metadata storage.

Provides a central registry for all Typer commands (core and Spoke-registered) to enable dynamic command discovery in the palette and other tooling.

The registry is populated by introspecting the Typer app structure after commands are registered, capturing command names, help text, and source information.

Example
>>> from axium.core import registry
>>> registry.introspect_typer_app(app, source="core")
>>> commands = registry.get_all_commands()
>>> print(commands[0])
{'name': 'env set', 'help': 'Set active environment', 'source': 'core'}

CommandMetadata dataclass

Metadata for a registered command.

Attributes:

Name Type Description
name str

Full command path (e.g., "env set", "daemon start", "aws whoami")

help str

Short help text from docstring first line (legacy)

summary str

One-line summary (80 chars max) for display

description str

Full docstring description

source str

Command source - "core" for core commands, or spoke name

category str

Auto-computed category ("core", "spoke:creds", "gear:aws")

group str | None

Parent command group name (e.g., "env", "daemon"), None for root

callback Callable | None

Reference to the command function (for future use)

Example
>>> cmd = CommandMetadata(
...     name="env set",
...     help="Set active environment",
...     summary="Set the active environment",
...     description="Set the active environment context for all commands...",
...     source="core",
...     category="core",
...     group="env",
...     callback=env_set_func
... )
Source code in axium/core/registry.py
@dataclass
class CommandMetadata:
    """
    Metadata for a registered command.

    Attributes:
        name: Full command path (e.g., "env set", "daemon start", "aws whoami")
        help: Short help text from docstring first line (legacy)
        summary: One-line summary (80 chars max) for display
        description: Full docstring description
        source: Command source - "core" for core commands, or spoke name
        category: Auto-computed category ("core", "spoke:creds", "gear:aws")
        group: Parent command group name (e.g., "env", "daemon"), None for root
        callback: Reference to the command function (for future use)

    Example:
        ```python
        >>> cmd = CommandMetadata(
        ...     name="env set",
        ...     help="Set active environment",
        ...     summary="Set the active environment",
        ...     description="Set the active environment context for all commands...",
        ...     source="core",
        ...     category="core",
        ...     group="env",
        ...     callback=env_set_func
        ... )
        ```
    """

    name: str
    help: str
    summary: str
    description: str
    source: str
    category: str
    group: str | None = None
    callback: Callable | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        data = asdict(self)
        # Exclude callback from dict (not serializable)
        data.pop("callback", None)
        return data

to_dict()

Convert to dictionary for JSON serialization.

Source code in axium/core/registry.py
def to_dict(self) -> dict[str, Any]:
    """Convert to dictionary for JSON serialization."""
    data = asdict(self)
    # Exclude callback from dict (not serializable)
    data.pop("callback", None)
    return data

register_command(name, help, source, summary='', description='', group=None, callback=None)

Register a command in the global registry.

Parameters:

Name Type Description Default
name str

Full command path (e.g., "env set")

required
help str

Help text from docstring (legacy)

required
source str

"core" or spoke name

required
summary str

One-line summary (auto-set from help if not provided)

''
description str

Full description (auto-set from help if not provided)

''
group str | None

Parent group name if applicable

None
callback Callable | None

Command function reference

None
Example
>>> register_command(
...     name="env set",
...     help="Set active environment",
...     summary="Set the active environment",
...     description="Set the active environment context...",
...     source="core",
...     group="env"
... )
Note
  • Category is auto-computed from source
  • Summary defaults to help text (80 char limit)
  • Description defaults to help text
  • If a command with the same name already exists, it will be overwritten
Source code in axium/core/registry.py
def register_command(
    name: str,
    help: str,
    source: str,
    summary: str = "",
    description: str = "",
    group: str | None = None,
    callback: Callable | None = None,
) -> None:
    """
    Register a command in the global registry.

    Args:
        name: Full command path (e.g., "env set")
        help: Help text from docstring (legacy)
        source: "core" or spoke name
        summary: One-line summary (auto-set from help if not provided)
        description: Full description (auto-set from help if not provided)
        group: Parent group name if applicable
        callback: Command function reference

    Example:
        ```python
        >>> register_command(
        ...     name="env set",
        ...     help="Set active environment",
        ...     summary="Set the active environment",
        ...     description="Set the active environment context...",
        ...     source="core",
        ...     group="env"
        ... )
        ```

    Note:
        - Category is auto-computed from source
        - Summary defaults to help text (80 char limit)
        - Description defaults to help text
        - If a command with the same name already exists, it will be overwritten
    """
    if name in COMMAND_REGISTRY:
        existing = COMMAND_REGISTRY[name]
        logger.warning(
            "Command '%s' already registered (source: %s), overwriting with source: %s",
            name,
            existing.source,
            source,
        )

    # Auto-compute category from source
    if source == "core":
        category = "core"
    elif source.startswith("gear:"):
        category = source  # Already has gear: prefix
    else:
        category = f"spoke:{source}"

    # Default summary and description to help text if not provided
    if not summary:
        summary = help.strip()[:80]  # Limit to 80 chars
    if not description:
        description = help.strip()

    COMMAND_REGISTRY[name] = CommandMetadata(
        name=name,
        help=help.strip(),
        summary=summary.strip(),
        description=description.strip(),
        source=source,
        category=category,
        group=group,
        callback=callback,
    )
    logger.debug("Registered command: %s [%s/%s]", name, source, category)

get_all_commands(sort_by='alpha')

Get all registered commands with full metadata.

Parameters:

Name Type Description Default
sort_by str

Sort method - "alpha" (alphabetical), "grouped" (source grouped), or "usage" (future: by usage frequency)

'alpha'

Returns:

Type Description
list[dict[str, Any]]

List of command dictionaries with name, help, summary, description,

list[dict[str, Any]]

source, category, and group fields

Example
>>> commands = get_all_commands()
>>> print(commands[0])
{'name': 'daemon start', 'help': 'Start daemon', 'summary': 'Start daemon',
 'description': 'Start the Axium daemon...', 'source': 'core',
 'category': 'core', 'group': 'daemon'}

>>> commands = get_all_commands(sort_by="grouped")
# Returns core commands first, then spokes alphabetically
Note

The callback field is excluded from returned dictionaries to keep output clean for display purposes.

Source code in axium/core/registry.py
def get_all_commands(sort_by: str = "alpha") -> list[dict[str, Any]]:
    """
    Get all registered commands with full metadata.

    Args:
        sort_by: Sort method - "alpha" (alphabetical), "grouped" (source grouped),
                or "usage" (future: by usage frequency)

    Returns:
        List of command dictionaries with name, help, summary, description,
        source, category, and group fields

    Example:
        ```python
        >>> commands = get_all_commands()
        >>> print(commands[0])
        {'name': 'daemon start', 'help': 'Start daemon', 'summary': 'Start daemon',
         'description': 'Start the Axium daemon...', 'source': 'core',
         'category': 'core', 'group': 'daemon'}

        >>> commands = get_all_commands(sort_by="grouped")
        # Returns core commands first, then spokes alphabetically
        ```

    Note:
        The callback field is excluded from returned dictionaries to keep
        output clean for display purposes.
    """
    commands = [cmd.to_dict() for cmd in COMMAND_REGISTRY.values()]

    if sort_by == "alpha":
        commands.sort(key=lambda c: c["name"])
    elif sort_by == "grouped":
        # Sort by: core first, then by source name, then by command name
        commands.sort(key=lambda c: (c["source"] != "core", c["source"], c["name"]))
    elif sort_by == "usage":
        # TODO: Implement usage-based sorting with history
        logger.warning("Usage-based sorting not yet implemented, using alpha")
        commands.sort(key=lambda c: c["name"])

    return commands

get_commands_by_source(source)

Get commands filtered by source.

Parameters:

Name Type Description Default
source str

Source filter - "core" or spoke name

required

Returns:

Type Description
list[dict[str, Any]]

List of command dictionaries matching the source

Example
>>> core_commands = get_commands_by_source("core")
>>> aws_commands = get_commands_by_source("aws")
Source code in axium/core/registry.py
def get_commands_by_source(source: str) -> list[dict[str, Any]]:
    """
    Get commands filtered by source.

    Args:
        source: Source filter - "core" or spoke name

    Returns:
        List of command dictionaries matching the source

    Example:
        ```python
        >>> core_commands = get_commands_by_source("core")
        >>> aws_commands = get_commands_by_source("aws")
        ```
    """
    return [cmd.to_dict() for cmd in COMMAND_REGISTRY.values() if cmd.source == source]

clear_registry()

Clear all registered commands.

Used for testing and when reloading spokes with --reload flag.

Example
>>> clear_registry()
>>> len(get_all_commands())
0
Source code in axium/core/registry.py
def clear_registry() -> None:
    """
    Clear all registered commands.

    Used for testing and when reloading spokes with --reload flag.

    Example:
        ```python
        >>> clear_registry()
        >>> len(get_all_commands())
        0
        ```
    """
    COMMAND_REGISTRY.clear()
    logger.debug("Registry cleared")

introspect_typer_app(app, source='core', prefix='')

Walk Typer app structure and register all commands.

Recursively discovers commands by examining app.registered_commands and app.registered_groups. Captures command metadata including names, help text, and source tags.

Parameters:

Name Type Description Default
app Any

Typer application instance to introspect

required
source str

Source tag for commands ("core" or spoke name)

'core'
prefix str

Command prefix for nested groups (used internally for recursion)

''
Example
>>> from typer import Typer
>>> app = Typer()
>>> @app.command()
... def test():
...     '''Test command'''
...     pass
>>> introspect_typer_app(app, source="core")
>>> commands = get_all_commands()
>>> print(commands[0]['name'])
test
Note
  • Handles both root commands and nested subgroups
  • Extracts help text from command docstrings if not explicitly set
  • Called after load_spokes() to capture all registered commands
  • Safe to call multiple times (will overwrite existing entries)
Source code in axium/core/registry.py
def introspect_typer_app(app: Any, source: str = "core", prefix: str = "") -> None:
    """
    Walk Typer app structure and register all commands.

    Recursively discovers commands by examining app.registered_commands
    and app.registered_groups. Captures command metadata including names,
    help text, and source tags.

    Args:
        app: Typer application instance to introspect
        source: Source tag for commands ("core" or spoke name)
        prefix: Command prefix for nested groups (used internally for recursion)

    Example:
        ```python
        >>> from typer import Typer
        >>> app = Typer()
        >>> @app.command()
        ... def test():
        ...     '''Test command'''
        ...     pass
        >>> introspect_typer_app(app, source="core")
        >>> commands = get_all_commands()
        >>> print(commands[0]['name'])
        test
        ```

    Note:
        - Handles both root commands and nested subgroups
        - Extracts help text from command docstrings if not explicitly set
        - Called after load_spokes() to capture all registered commands
        - Safe to call multiple times (will overwrite existing entries)
    """
    # Register direct commands at this level
    for cmd_info in app.registered_commands:
        # Use explicit name or fall back to function name
        cmd_name = cmd_info.name or cmd_info.callback.__name__
        full_name = f"{prefix}{cmd_name}".strip()

        # Extract help text and full docstring
        help_text = cmd_info.help
        summary = ""
        description = ""

        if cmd_info.callback.__doc__:
            # Extract full docstring
            doc_lines = [
                line.strip()
                for line in cmd_info.callback.__doc__.split("\n")
                if line.strip()
            ]
            if doc_lines:
                # First line becomes summary (80 char limit)
                summary = doc_lines[0][:80]
                # Full docstring becomes description
                description = " ".join(doc_lines)

        # Use help text if no docstring summary
        if not help_text and summary:
            help_text = summary
        elif not summary and help_text:
            summary = help_text[:80]

        # Default both to help text if still empty
        if not summary:
            summary = help_text or ""
        if not description:
            description = help_text or ""

        # Determine group name
        group_name = None
        if prefix:
            # Extract group from prefix (e.g., "daemon " -> "daemon")
            group_name = prefix.strip().split()[-1] if prefix.strip() else None

        register_command(
            name=full_name,
            help=help_text or "",
            summary=summary,
            description=description,
            source=source,
            group=group_name,
            callback=cmd_info.callback,
        )

    # Recursively process command groups (subcommands)
    for group_info in app.registered_groups:
        group_prefix = f"{prefix}{group_info.name} "
        introspect_typer_app(
            app=group_info.typer_instance,
            source=source,
            prefix=group_prefix,
        )

    logger.debug(
        "Introspected Typer app [%s]: %d commands registered",
        source,
        len(COMMAND_REGISTRY),
    )