Skip to content

CLI Reference

The axm Command

axm is a thin wrapper that autodiscovers commands from installed AXM packages.

Bash
axm              # list available commands (or show install hints)
axm <command>    # run a discovered command

Available Commands

Commands depend on which AXM packages are installed:

Command Package Description
axm init_scaffold axm-init Scaffold a new project
axm init_check axm-init Check project conformity
axm init_reserve axm-init Reserve a PyPI package name
axm audit axm-audit Code quality audit
axm-bib search axm-bib Search papers
axm-bib resolve axm-bib Resolve a reference (DOI/arXiv/title) to BibTeX
axm-bib pdf axm-bib Download + extract paper PDFs
axm-bib extract axm-bib Extract local PDF to Markdown
axm-mcp axm-mcp MCP server exposing all AXM tools to AI agents

Python API

create_app()

Create an app with every command registered (eager).

This loads all entry points — convenient for tests and introspection, but NOT the path used by main (which dispatches lazily). Explicit axm.commands win over auto-generated tool commands of the same name.

Returns:

Type Description
App

A fully-populated cyclopts App.

Source code in packages/axm/src/axm/cli.py
Python
def create_app() -> cyclopts.App:
    """Create an app with *every* command registered (eager).

    This loads all entry points — convenient for tests and introspection, but
    NOT the path used by ``main`` (which dispatches lazily).  Explicit
    ``axm.commands`` win over auto-generated tool commands of the same name.

    Returns:
        A fully-populated cyclopts App.
    """
    app = _new_app()
    commands = _entry_points(_COMMANDS_GROUP)
    tools = _entry_points(_TOOLS_GROUP)

    for name, ep in commands.items():
        try:
            app.command(_load(ep), name=name)
        except Exception:  # noqa: BLE001 — a broken package must not sink the CLI
            logger.warning("Failed to load command '%s'", name, exc_info=True)

    for name, ep in tools.items():
        if name in commands:
            continue
        try:
            app.command(build_command_for_tool(name, _load(ep)), name=name)
        except Exception:  # noqa: BLE001
            logger.warning("Failed to auto-register tool '%s'", name, exc_info=True)

    return app

Tool Interface

ToolResult dataclass

Immutable result of a tool execution.

Attributes:

Name Type Description
success bool

Whether the tool execution succeeded.

data dict[str, Any]

Structured output data (backend-specific).

error str | None

Human-readable error message, if any.

hint str | None

Optional next-step suggestion for the agent.

text str | None

Optional pre-rendered text representation (e.g. Markdown).

Source code in packages/axm/src/axm/tools/base.py
Python
@dataclass(frozen=True)
class ToolResult:
    """Immutable result of a tool execution.

    Attributes:
        success: Whether the tool execution succeeded.
        data: Structured output data (backend-specific).
        error: Human-readable error message, if any.
        hint: Optional next-step suggestion for the agent.
        text: Optional pre-rendered text representation (e.g. Markdown).
    """

    success: bool
    data: dict[str, Any] = field(default_factory=dict)
    error: str | None = None
    hint: str | None = None
    text: str | None = None

AXMTool

Bases: Protocol

Structural protocol for AXM deterministic tools.

Implementors must provide: - name (property): unique tool identifier - execute(...) : deterministic execution with explicit params

Optionally provide: - agent_hint (class attribute): optional, free-form one-liner optimized for LLM consumption: what the tool does, key params, and what it replaces. Like the other discovery attributes below, it is not a protocol member and carries no guaranteed fallback — some discovery tooling reads it best-effort via getattr / :func:tool_metadata; absent, nothing is substituted. - expose_directly (class attribute, default False): when True, the MCP server registers this tool directly in tools/list (the hot path). When False (default), the tool is reachable only through the MCP facade (axm_search -> axm_describe -> axm_call), keeping the tools/list payload small. - domain (class attribute, default None): coarse capability group used by the facade for axm_capabilities and to scope axm_search (e.g. "ast", "git", "ticket"). - tags (class attribute, default frozenset()): free-form keywords feeding facade discovery (axm_search).

Uses structural typing (PEP 544) — no inheritance required. @runtime_checkable enables isinstance() checks. The discovery attributes (expose_directly / domain / tags) are not protocol members on purpose: adding data attributes to a runtime_checkable protocol would make them required for isinstance() and break the check for the many tools that satisfy AXMTool structurally without subclassing it. Read them through :func:tool_metadata (or getattr(tool, name, default)) instead, which works for subclasses and structural tools alike.

Example::

Text Only
class MyTool(AXMTool):
    agent_hint = "Frobnicate widgets — use width param."
    expose_directly = True          # hot path (read via tool_metadata)
    domain = "widget"
    tags = frozenset({"frobnicate"})

    @property
    def name(self) -> str:
        return "my-tool"

    def execute(self, *, value: int = 0) -> ToolResult:
        return ToolResult(success=True, data={"result": value})
Source code in packages/axm/src/axm/tools/base.py
Python
@runtime_checkable
class AXMTool(Protocol):
    """Structural protocol for AXM deterministic tools.

    Implementors must provide:
    - ``name`` (property): unique tool identifier
    - ``execute(...)`` : deterministic execution with explicit params

    Optionally provide:
    - ``agent_hint`` (class attribute): optional, free-form one-liner
      optimized for LLM consumption: what the tool does, key params, and
      what it replaces.  Like the other discovery attributes below, it is
      *not* a protocol member and carries no guaranteed fallback — some
      discovery tooling reads it best-effort via ``getattr`` /
      :func:`tool_metadata`; absent, nothing is substituted.
    - ``expose_directly`` (class attribute, default ``False``): when
      ``True``, the MCP server registers this tool directly in
      ``tools/list`` (the *hot path*).  When ``False`` (default), the
      tool is reachable only through the MCP facade
      (``axm_search`` -> ``axm_describe`` -> ``axm_call``), keeping the
      ``tools/list`` payload small.
    - ``domain`` (class attribute, default ``None``): coarse capability
      group used by the facade for ``axm_capabilities`` and to scope
      ``axm_search`` (e.g. ``"ast"``, ``"git"``, ``"ticket"``).
    - ``tags`` (class attribute, default ``frozenset()``): free-form
      keywords feeding facade discovery (``axm_search``).

    Uses structural typing (PEP 544) — no inheritance required.
    ``@runtime_checkable`` enables ``isinstance()`` checks.  The discovery
    attributes (``expose_directly`` / ``domain`` / ``tags``) are *not*
    protocol members on purpose: adding data attributes to a
    ``runtime_checkable`` protocol would make them required for
    ``isinstance()`` and break the check for the many tools that satisfy
    ``AXMTool`` structurally without subclassing it.  Read them through
    :func:`tool_metadata` (or ``getattr(tool, name, default)``) instead,
    which works for subclasses and structural tools alike.

    Example::

        class MyTool(AXMTool):
            agent_hint = "Frobnicate widgets — use width param."
            expose_directly = True          # hot path (read via tool_metadata)
            domain = "widget"
            tags = frozenset({"frobnicate"})

            @property
            def name(self) -> str:
                return "my-tool"

            def execute(self, *, value: int = 0) -> ToolResult:
                return ToolResult(success=True, data={"result": value})
    """

    @property
    def name(self) -> str:
        """Unique tool identifier (e.g., 'esbmc', 'dafny', 'pytest')."""
        ...

    def execute(self, **kwargs: Any) -> ToolResult:
        """Execute the tool with given arguments.

        Subclasses should override with explicit, typed parameters::

            def execute(self, *, title: str = "", body: str = "") -> ToolResult:
                ...

        The ``**kwargs`` signature here is the *structural minimum* —
        any callable accepting keyword arguments satisfies it.

        Returns:
            ToolResult with success status and structured data.
        """
        ...

name property

Unique tool identifier (e.g., 'esbmc', 'dafny', 'pytest').

execute(**kwargs)

Execute the tool with given arguments.

Subclasses should override with explicit, typed parameters::

Text Only
def execute(self, *, title: str = "", body: str = "") -> ToolResult:
    ...

The **kwargs signature here is the structural minimum — any callable accepting keyword arguments satisfies it.

Returns:

Type Description
ToolResult

ToolResult with success status and structured data.

Source code in packages/axm/src/axm/tools/base.py
Python
def execute(self, **kwargs: Any) -> ToolResult:
    """Execute the tool with given arguments.

    Subclasses should override with explicit, typed parameters::

        def execute(self, *, title: str = "", body: str = "") -> ToolResult:
            ...

    The ``**kwargs`` signature here is the *structural minimum* —
    any callable accepting keyword arguments satisfies it.

    Returns:
        ToolResult with success status and structured data.
    """
    ...

Agent hints

Tools can set an agent_hint class attribute (one-liner string) to provide LLM-optimized descriptions that propagate to MCP tool listings. When empty (default), the execute() docstring is used instead.

Hook Interface

HookResult dataclass

Result of a hook execution.

Attributes:

Name Type Description
success bool

Whether the hook succeeded

error str | None

Error message if failed

metadata dict[str, Any]

Optional execution metadata

text str | None

Pre-rendered text representation of the result

Source code in packages/axm/src/axm/hooks/base.py
Python
@dataclass(frozen=True)
class HookResult:
    """Result of a hook execution.

    Attributes:
        success: Whether the hook succeeded
        error: Error message if failed
        metadata: Optional execution metadata
        text: Pre-rendered text representation of the result
    """

    success: bool
    error: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)
    text: str | None = None

    @classmethod
    def ok(cls, *, text: str | None = None, **metadata: Any) -> HookResult:
        """Create a successful result.

        Args:
            text: Pre-rendered text representation of the result.
            **metadata: Arbitrary key-value pairs stored in metadata.
        """
        return cls(success=True, text=text, metadata=metadata)

    @classmethod
    def fail(
        cls, error: str, *, text: str | None = None, **metadata: Any
    ) -> HookResult:
        """Create a failed result.

        Args:
            error: Human-readable error message.
            text: Pre-rendered text representation of the result.
            **metadata: Arbitrary key-value pairs stored in metadata.
        """
        return cls(success=False, error=error, text=text, metadata=metadata)

    @classmethod
    def skip(cls, reason: str = "condition not met") -> HookResult:
        """Create a skipped result (success, hook intentionally not run)."""
        return cls(success=True, metadata={"skipped": True, "reason": reason})

fail(error, *, text=None, **metadata) classmethod

Create a failed result.

Parameters:

Name Type Description Default
error str

Human-readable error message.

required
text str | None

Pre-rendered text representation of the result.

None
**metadata Any

Arbitrary key-value pairs stored in metadata.

{}
Source code in packages/axm/src/axm/hooks/base.py
Python
@classmethod
def fail(
    cls, error: str, *, text: str | None = None, **metadata: Any
) -> HookResult:
    """Create a failed result.

    Args:
        error: Human-readable error message.
        text: Pre-rendered text representation of the result.
        **metadata: Arbitrary key-value pairs stored in metadata.
    """
    return cls(success=False, error=error, text=text, metadata=metadata)

ok(*, text=None, **metadata) classmethod

Create a successful result.

Parameters:

Name Type Description Default
text str | None

Pre-rendered text representation of the result.

None
**metadata Any

Arbitrary key-value pairs stored in metadata.

{}
Source code in packages/axm/src/axm/hooks/base.py
Python
@classmethod
def ok(cls, *, text: str | None = None, **metadata: Any) -> HookResult:
    """Create a successful result.

    Args:
        text: Pre-rendered text representation of the result.
        **metadata: Arbitrary key-value pairs stored in metadata.
    """
    return cls(success=True, text=text, metadata=metadata)

skip(reason='condition not met') classmethod

Create a skipped result (success, hook intentionally not run).

Source code in packages/axm/src/axm/hooks/base.py
Python
@classmethod
def skip(cls, reason: str = "condition not met") -> HookResult:
    """Create a skipped result (success, hook intentionally not run)."""
    return cls(success=True, metadata={"skipped": True, "reason": reason})

HookAction

Bases: Protocol

Protocol for hook implementations.

All hook actions receive a context dict with session info: - session_id: str - protocol_name: str - phase_name: str - task_name: str | None - working_dir: str | None - artifacts_path: str | None

Source code in packages/axm/src/axm/hooks/base.py
Python
@runtime_checkable
class HookAction(Protocol):
    """Protocol for hook implementations.

    All hook actions receive a context dict with session info:
    - session_id: str
    - protocol_name: str
    - phase_name: str
    - task_name: str | None
    - working_dir: str | None
    - artifacts_path: str | None
    """

    def execute(self, context: dict[str, Any], **params: Any) -> HookResult:
        """Execute the hook action.

        Args:
            context: Session context dictionary
            **params: Hook-specific parameters

        Returns:
            HookResult indicating success/failure with metadata
        """
        ...

execute(context, **params)

Execute the hook action.

Parameters:

Name Type Description Default
context dict[str, Any]

Session context dictionary

required
**params Any

Hook-specific parameters

{}

Returns:

Type Description
HookResult

HookResult indicating success/failure with metadata

Source code in packages/axm/src/axm/hooks/base.py
Python
def execute(self, context: dict[str, Any], **params: Any) -> HookResult:
    """Execute the hook action.

    Args:
        context: Session context dictionary
        **params: Hook-specific parameters

    Returns:
        HookResult indicating success/failure with metadata
    """
    ...