Auto-generated from Python source using mkdocstrings. All signatures and docstrings reflect the current src/ztlctl/ codebase.

This page documents the stable public API surface: plugin hookspecs, contribution contracts, the action system, and API versioning. Use the section headings to navigate to the module you need. For usage examples and step-by-step tutorials, see the Plugin Authoring Guide.

Scope

This reference covers the plugin public API only — the contracts, hookspecs, and action system that plugin authors and advanced integrators interact with. Internal service and infrastructure layers are not documented here.

Plugin Hookspecs

The ZtlctlHookSpec class defines all pluggy hookspecs. Implement any subset of these in your plugin class.

ztlctl.plugins.hookspecs

Pluggy hook specifications for ztlctl lifecycle events and extensions.

ZtlctlHookSpec

Hook specifications for the ztlctl plugin system.

Source code in src/ztlctl/plugins/hookspecs.py
class ZtlctlHookSpec:
    """Hook specifications for the ztlctl plugin system."""

    # ------------------------------------------------------------------
    # Generic action hooks (PLUG-02) — preferred over per-event hooks
    # ------------------------------------------------------------------

    @hookspec(firstresult=True)
    def pre_action(
        self, action_name: str, kwargs: dict[str, Any]
    ) -> ActionRejection | dict[str, Any] | None:
        """Called before action execution.

        Return an :class:`~ztlctl.plugins.contracts.ActionRejection` to abort
        the action, a modified *kwargs* dict to replace the original keyword
        arguments, or ``None`` to pass through unchanged.

        This is a ``firstresult`` hook — the first plugin returning a non-``None``
        value wins and subsequent plugins are not called.
        """

    @hookspec
    def post_action(self, action_name: str, kwargs: dict[str, Any], result: Any) -> None:
        """Called after action execution with the ServiceResult.

        All registered plugins receive this hook regardless of the action outcome.
        """

    # ------------------------------------------------------------------
    # Plugin configuration (PLUG-03)
    # ------------------------------------------------------------------

    @hookspec(firstresult=True)
    def get_config_schema(self) -> type[BaseModel] | None:
        """Return the Pydantic model class used to validate this plugin's config.

        The schema is retrieved once at load time. If the ``[plugins.<name>]``
        TOML section exists, its contents are validated against the returned
        model and then passed to :meth:`initialize`.
        """

    @hookspec
    def initialize(self, config: BaseModel | None) -> None:
        """Called once after plugin loading with validated configuration.

        *config* is a validated Pydantic model instance if a schema was
        declared via :meth:`get_config_schema` and a matching TOML section
        exists, otherwise ``None``.
        """

    # ------------------------------------------------------------------
    # Deprecated per-event lifecycle hooks
    # Prefer implementing post_action and filtering by action_name.
    # ------------------------------------------------------------------

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_create is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_create(
        self,
        content_type: str,
        content_id: str,
        title: str,
        path: str,
        tags: list[str],
    ) -> None:
        """Called after content creation."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_update is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_update(
        self,
        content_type: str,
        content_id: str,
        fields_changed: list[str],
        path: str,
    ) -> None:
        """Called after content update."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_close is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_close(
        self,
        content_type: str,
        content_id: str,
        path: str,
        summary: str,
    ) -> None:
        """Called after close/archive."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_reweave is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_reweave(
        self,
        source_id: str,
        affected_ids: list[str],
        links_added: int,
    ) -> None:
        """Called after reweave completes."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_session_start is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_session_start(self, session_id: str) -> None:
        """Called after a session begins."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_session_close is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_session_close(
        self,
        session_id: str,
        stats: dict[str, Any],
    ) -> None:
        """Called after a session closes."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_check is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_check(
        self,
        issues_found: int,
        issues_fixed: int,
    ) -> None:
        """Called after integrity check."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_init is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_init(
        self,
        vault_name: str,
        client: str,
        tone: str,
    ) -> None:
        """Called after vault init."""

    @hookspec(
        warn_on_impl=DeprecationWarning(
            "post_init_profile is deprecated since plugin API v2; "
            "implement post_action and filter by action_name instead"
        )
    )
    def post_init_profile(
        self,
        vault_name: str,
        profile: str,
        tone: str,
        managed_paths: list[str],
    ) -> None:
        """Called after vault init with canonical workspace profile metadata.

        ``managed_paths`` reports the scaffold surface associated with the
        selected profile during init. It is descriptive metadata, not a promise
        of future lifecycle management by ztlctl.
        """

    # ------------------------------------------------------------------
    # Extension contribution hooks
    # ------------------------------------------------------------------

    @hookspec
    def register_content_models(self) -> dict[str, type[ContentModel]] | None:
        """Return subtype -> ContentModel mappings to extend CONTENT_REGISTRY."""

    @hookspec
    def register_cli_commands(self) -> list[CliCommandContribution] | None:
        """Return plugin CLI command contributions."""

    @hookspec
    def register_mcp_tools(self) -> list[McpToolContribution] | None:
        """Return plugin MCP tool contributions."""

    @hookspec
    def register_mcp_resources(self) -> list[McpResourceContribution] | None:
        """Return plugin MCP resource contributions."""

    @hookspec
    def register_mcp_prompts(self) -> list[McpPromptContribution] | None:
        """Return plugin MCP prompt contributions."""

    @hookspec
    def register_workflow_modules(self) -> list[WorkflowModuleContribution] | None:
        """Return plugin workflow export modules."""

    @hookspec
    def register_workspace_profiles(self) -> list[WorkspaceProfileContribution] | None:
        """Return plugin workspace profile contributions."""

    @hookspec
    def register_vault_init_steps(self) -> list[VaultInitStepContribution] | None:
        """Return ordered plugin steps to run during `ztlctl init`."""

    @hookspec
    def register_source_providers(self) -> list[SourceProviderContribution] | None:
        """Return plugin-provided source ingestion providers."""

    # ------------------------------------------------------------------
    # Custom note type + rendering hooks (PLUG-05, PLUG-06)
    # ------------------------------------------------------------------

    @hookspec
    def register_note_types(self) -> list[NoteTypeDefinition] | None:
        """Return NoteTypeDefinitions to register into NoteTypeRegistry + ActionRegistry.

        PluginManager will auto-create create/update/close ActionDefinitions for
        each registered NoteTypeDefinition and add them to the ActionRegistry so
        the CLI and MCP generators pick them up automatically.
        """

    @hookspec
    def register_render_contributions(self) -> list[RenderContribution] | None:
        """Return render contributions for custom note types.

        Each :class:`~ztlctl.plugins.contracts.RenderContribution` provides a
        ``rich_formatter`` and an ``mcp_formatter`` callable for one note type,
        enabling custom terminal and MCP output formatting.
        """

    # ------------------------------------------------------------------
    # Security — capability declarations (SECU-02)
    # ------------------------------------------------------------------

    @hookspec
    def declare_capabilities(self) -> set[str] | None:
        """Return the set of capabilities this plugin requires.

        Valid values: ``{"filesystem", "network", "database", "git"}``.

        Missing declaration is treated as a warning (not an error) in plugin
        API v2 to avoid breaking existing plugins. Future API versions may
        enforce declarations. Returns ``None`` or does not implement to
        indicate no declaration.
        """
pre_action
pre_action(action_name: str, kwargs: dict[str, Any]) -> ActionRejection | dict[str, Any] | None

Called before action execution.

Return an :class:~ztlctl.plugins.contracts.ActionRejection to abort the action, a modified kwargs dict to replace the original keyword arguments, or None to pass through unchanged.

This is a firstresult hook — the first plugin returning a non-None value wins and subsequent plugins are not called.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(firstresult=True)
def pre_action(
    self, action_name: str, kwargs: dict[str, Any]
) -> ActionRejection | dict[str, Any] | None:
    """Called before action execution.

    Return an :class:`~ztlctl.plugins.contracts.ActionRejection` to abort
    the action, a modified *kwargs* dict to replace the original keyword
    arguments, or ``None`` to pass through unchanged.

    This is a ``firstresult`` hook — the first plugin returning a non-``None``
    value wins and subsequent plugins are not called.
    """
post_action
post_action(action_name: str, kwargs: dict[str, Any], result: Any) -> None

Called after action execution with the ServiceResult.

All registered plugins receive this hook regardless of the action outcome.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def post_action(self, action_name: str, kwargs: dict[str, Any], result: Any) -> None:
    """Called after action execution with the ServiceResult.

    All registered plugins receive this hook regardless of the action outcome.
    """
get_config_schema
get_config_schema() -> type[BaseModel] | None

Return the Pydantic model class used to validate this plugin's config.

The schema is retrieved once at load time. If the [plugins.<name>] TOML section exists, its contents are validated against the returned model and then passed to :meth:initialize.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(firstresult=True)
def get_config_schema(self) -> type[BaseModel] | None:
    """Return the Pydantic model class used to validate this plugin's config.

    The schema is retrieved once at load time. If the ``[plugins.<name>]``
    TOML section exists, its contents are validated against the returned
    model and then passed to :meth:`initialize`.
    """
initialize
initialize(config: BaseModel | None) -> None

Called once after plugin loading with validated configuration.

config is a validated Pydantic model instance if a schema was declared via :meth:get_config_schema and a matching TOML section exists, otherwise None.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def initialize(self, config: BaseModel | None) -> None:
    """Called once after plugin loading with validated configuration.

    *config* is a validated Pydantic model instance if a schema was
    declared via :meth:`get_config_schema` and a matching TOML section
    exists, otherwise ``None``.
    """
post_create
post_create(content_type: str, content_id: str, title: str, path: str, tags: list[str]) -> None

Called after content creation.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_create is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_create(
    self,
    content_type: str,
    content_id: str,
    title: str,
    path: str,
    tags: list[str],
) -> None:
    """Called after content creation."""
post_update
post_update(content_type: str, content_id: str, fields_changed: list[str], path: str) -> None

Called after content update.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_update is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_update(
    self,
    content_type: str,
    content_id: str,
    fields_changed: list[str],
    path: str,
) -> None:
    """Called after content update."""
post_close
post_close(content_type: str, content_id: str, path: str, summary: str) -> None

Called after close/archive.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_close is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_close(
    self,
    content_type: str,
    content_id: str,
    path: str,
    summary: str,
) -> None:
    """Called after close/archive."""
post_reweave
post_reweave(source_id: str, affected_ids: list[str], links_added: int) -> None

Called after reweave completes.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_reweave is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_reweave(
    self,
    source_id: str,
    affected_ids: list[str],
    links_added: int,
) -> None:
    """Called after reweave completes."""
post_session_start
post_session_start(session_id: str) -> None

Called after a session begins.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_session_start is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_session_start(self, session_id: str) -> None:
    """Called after a session begins."""
post_session_close
post_session_close(session_id: str, stats: dict[str, Any]) -> None

Called after a session closes.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_session_close is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_session_close(
    self,
    session_id: str,
    stats: dict[str, Any],
) -> None:
    """Called after a session closes."""
post_check
post_check(issues_found: int, issues_fixed: int) -> None

Called after integrity check.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_check is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_check(
    self,
    issues_found: int,
    issues_fixed: int,
) -> None:
    """Called after integrity check."""
post_init
post_init(vault_name: str, client: str, tone: str) -> None

Called after vault init.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_init is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_init(
    self,
    vault_name: str,
    client: str,
    tone: str,
) -> None:
    """Called after vault init."""
post_init_profile
post_init_profile(vault_name: str, profile: str, tone: str, managed_paths: list[str]) -> None

Called after vault init with canonical workspace profile metadata.

managed_paths reports the scaffold surface associated with the selected profile during init. It is descriptive metadata, not a promise of future lifecycle management by ztlctl.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec(
    warn_on_impl=DeprecationWarning(
        "post_init_profile is deprecated since plugin API v2; "
        "implement post_action and filter by action_name instead"
    )
)
def post_init_profile(
    self,
    vault_name: str,
    profile: str,
    tone: str,
    managed_paths: list[str],
) -> None:
    """Called after vault init with canonical workspace profile metadata.

    ``managed_paths`` reports the scaffold surface associated with the
    selected profile during init. It is descriptive metadata, not a promise
    of future lifecycle management by ztlctl.
    """
register_content_models
register_content_models() -> dict[str, type[ContentModel]] | None

Return subtype -> ContentModel mappings to extend CONTENT_REGISTRY.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_content_models(self) -> dict[str, type[ContentModel]] | None:
    """Return subtype -> ContentModel mappings to extend CONTENT_REGISTRY."""
register_cli_commands
register_cli_commands() -> list[CliCommandContribution] | None

Return plugin CLI command contributions.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_cli_commands(self) -> list[CliCommandContribution] | None:
    """Return plugin CLI command contributions."""
register_mcp_tools
register_mcp_tools() -> list[McpToolContribution] | None

Return plugin MCP tool contributions.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_mcp_tools(self) -> list[McpToolContribution] | None:
    """Return plugin MCP tool contributions."""
register_mcp_resources
register_mcp_resources() -> list[McpResourceContribution] | None

Return plugin MCP resource contributions.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_mcp_resources(self) -> list[McpResourceContribution] | None:
    """Return plugin MCP resource contributions."""
register_mcp_prompts
register_mcp_prompts() -> list[McpPromptContribution] | None

Return plugin MCP prompt contributions.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_mcp_prompts(self) -> list[McpPromptContribution] | None:
    """Return plugin MCP prompt contributions."""
register_workflow_modules
register_workflow_modules() -> list[WorkflowModuleContribution] | None

Return plugin workflow export modules.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_workflow_modules(self) -> list[WorkflowModuleContribution] | None:
    """Return plugin workflow export modules."""
register_workspace_profiles
register_workspace_profiles() -> list[WorkspaceProfileContribution] | None

Return plugin workspace profile contributions.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_workspace_profiles(self) -> list[WorkspaceProfileContribution] | None:
    """Return plugin workspace profile contributions."""
register_vault_init_steps
register_vault_init_steps() -> list[VaultInitStepContribution] | None

Return ordered plugin steps to run during ztlctl init.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_vault_init_steps(self) -> list[VaultInitStepContribution] | None:
    """Return ordered plugin steps to run during `ztlctl init`."""
register_source_providers
register_source_providers() -> list[SourceProviderContribution] | None

Return plugin-provided source ingestion providers.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_source_providers(self) -> list[SourceProviderContribution] | None:
    """Return plugin-provided source ingestion providers."""
register_note_types
register_note_types() -> list[NoteTypeDefinition] | None

Return NoteTypeDefinitions to register into NoteTypeRegistry + ActionRegistry.

PluginManager will auto-create create/update/close ActionDefinitions for each registered NoteTypeDefinition and add them to the ActionRegistry so the CLI and MCP generators pick them up automatically.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_note_types(self) -> list[NoteTypeDefinition] | None:
    """Return NoteTypeDefinitions to register into NoteTypeRegistry + ActionRegistry.

    PluginManager will auto-create create/update/close ActionDefinitions for
    each registered NoteTypeDefinition and add them to the ActionRegistry so
    the CLI and MCP generators pick them up automatically.
    """
register_render_contributions
register_render_contributions() -> list[RenderContribution] | None

Return render contributions for custom note types.

Each :class:~ztlctl.plugins.contracts.RenderContribution provides a rich_formatter and an mcp_formatter callable for one note type, enabling custom terminal and MCP output formatting.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def register_render_contributions(self) -> list[RenderContribution] | None:
    """Return render contributions for custom note types.

    Each :class:`~ztlctl.plugins.contracts.RenderContribution` provides a
    ``rich_formatter`` and an ``mcp_formatter`` callable for one note type,
    enabling custom terminal and MCP output formatting.
    """
declare_capabilities
declare_capabilities() -> set[str] | None

Return the set of capabilities this plugin requires.

Valid values: {"filesystem", "network", "database", "git"}.

Missing declaration is treated as a warning (not an error) in plugin API v2 to avoid breaking existing plugins. Future API versions may enforce declarations. Returns None or does not implement to indicate no declaration.

Source code in src/ztlctl/plugins/hookspecs.py
@hookspec
def declare_capabilities(self) -> set[str] | None:
    """Return the set of capabilities this plugin requires.

    Valid values: ``{"filesystem", "network", "database", "git"}``.

    Missing declaration is treated as a warning (not an error) in plugin
    API v2 to avoid breaking existing plugins. Future API versions may
    enforce declarations. Returns ``None`` or does not implement to
    indicate no declaration.
    """

Plugin Contracts

Data classes returned from and passed to hookspecs.

ztlctl.plugins.contracts

Typed plugin contribution contracts for public extension points.

CliCommandContribution dataclass

One plugin-provided CLI command.

McpToolContribution dataclass

One plugin-provided MCP tool.

McpResourceContribution dataclass

One plugin-provided MCP resource.

McpPromptContribution dataclass

One plugin-provided MCP prompt.

WorkflowModuleContribution dataclass

One plugin-provided workflow export module.

WorkspaceProfileContribution dataclass

One workspace profile definition exposed by core or plugins.

managed_paths identifies the vault roots a profile scaffolds during init so ownership and post-init hooks can classify them. It does not imply that ztlctl will later validate or rewrite those files.

VaultInitContext dataclass

Normalized context passed to plugin-contributed vault init steps.

VaultInitInstruction dataclass

One user-facing instruction emitted during vault initialization.

VaultInitStepResult dataclass

Files, warnings, and user-visible setup guidance from one init step.

VaultInitStepContribution dataclass

One plugin-contributed step in the ordered vault init pipeline.

SourceFetchRequest dataclass

Normalized source-provider request.

SourceFetchResult dataclass

Normalized source-provider response.

SourceProviderContribution dataclass

One plugin-provided source acquisition provider.

RenderContribution dataclass

Plugin-provided rendering for a custom note type.

Fields

note_type The note type name this contribution applies to (e.g. "sprint"). rich_formatter Callable that receives a dict[str, Any] of note data and returns a Rich-formatted string for terminal display. mcp_formatter Callable that receives a dict[str, Any] of note data and returns a dict[str, Any] suitable for MCP tool responses.

ActionRejection dataclass

Returned from pre_action to abort action execution.

A plugin's pre_action implementation can return an instance of this class to prevent the registered action handler from running. The caller (BaseController._dispatch_pre_action) converts this into an appropriate error ServiceResult.

PluginMetadata dataclass

Structured metadata from [tool.ztlctl-plugin] in a plugin's pyproject.toml.

Used for future plugin discoverability and marketplace listing. Plugin authors include this section in their pyproject.toml to declare compatibility and capabilities.

Fields

name Plugin display name. version Plugin version string (e.g. "1.0.0"). author Author or organization name. capabilities Tuple of capability identifiers the plugin provides (e.g. ("register_note_types", "register_cli_commands")). ztlctl_api_version The PLUGIN_API_VERSION integer this plugin was built against. description Optional human-readable description.

API Versioning

ztlctl.plugins._version

Plugin API versioning helpers (PLUG-01).

Kept in a private module to allow manager.py to import without creating a circular dependency through plugins/init.py.

PluginLoadError

Bases: Exception

Raised when a plugin declares an incompatible API version.

check_plugin_api_version

check_plugin_api_version(plugin: object, plugin_name: str) -> list[str]

Check a plugin's declared API version against the host.

Returns:

Type Description
list[str]

A list of warning strings (may be empty) if the plugin is compatible.

Raises:

Type Description
PluginLoadError

If the plugin requires a newer API version than the host provides, or if its declared version is below the compatibility window.

Action System

ActionDefinition and ActionParam are the frozen dataclasses that describe every registered action. Plugin authors use these when implementing register_note_types() — PluginManager auto-creates ActionDefinition instances for each NoteTypeDefinition returned.

ztlctl.actions.definitions

ActionParam and ActionDefinition frozen dataclasses.

ActionParam is a single-source-of-truth parameter descriptor that captures all metadata needed to auto-generate CLI options, MCP tool parameters, and interactive prompts.

ActionDefinition is a frozen descriptor for one operation in the system. All controller methods are registered as ActionDefinitions, enabling auto-generation of CLI and MCP surfaces.

Both dataclasses are frozen for thread safety and hashability.

ActionParam dataclass

One parameter descriptor — single source of truth for CLI and MCP.

Fields

name Parameter name (e.g. "query", "limit"). type Python built-in type: str, int, bool, list[str]. required Whether the parameter is required. Defaults to True. default Default value when not required. Defaults to None. description Human-readable description for CLI help and MCP docs. choices Tuple of valid string choices (used for Click choice type and MCP enum). None means unrestricted. cli_multiple When True, the CLI option accepts multiple values (--tag x --tag y). cli_is_argument When True, rendered as a Click positional argument instead of an option. cli_flag When True, rendered as a boolean Click flag (--verbose). cli_name Override the Click option/argument name. When None, the CLI name is derived from name by replacing underscores with hyphens (e.g. content_type--content-type). Use this to expose a friendlier flag name (e.g. cli_name="type"--type) while keeping a Python-safe internal name. mcp_example Example value shown in MCP tool docs ("find notes about python").

ActionDefinition dataclass

One operation in the system. Frozen for thread safety and hashability.

All controller methods are registered as ActionDefinitions. The define-once model allows CLI and MCP surfaces to be auto-generated from this single descriptor.

Fields

name Unique dotted name (e.g. "note.search", "note.create"). description Human-readable description of what this action does. category Logical category for grouping (e.g. "query", "create", "graph"). params Tuple of :class:ActionParam descriptors in argument order. handler Callable that performs the action; returns a ServiceResult. Typed as Callable[..., Any] to avoid circular imports. side_effect "read" for non-mutating operations; "write" for mutating.

MCP-specific metadata

mcp_when_to_use Guidance for the agent about when to invoke this action. mcp_avoid_when Guidance for the agent about when not to invoke this action. mcp_common_errors Tuple of common error messages that callers should handle.

CLI-specific metadata

cli_group Click command group name (None means top-level). cli_examples Example CLI invocations shown in --help output. cli_interactive_params Param names to prompt interactively when --interactive is set.

Presentation

custom_presentation Escape hatch for actions with complex output that can't be rendered by the generic presenter.

cli_name class-attribute instance-attribute
cli_name: str | None = None

Explicit CLI command name (cli_name) override.

When None, the generator derives the name from action.name by stripping the cli_group prefix (if present) and replacing underscores with hyphens.

ztlctl.actions.registry

ActionRegistry and module-level singleton accessor.

ActionRegistry stores ActionDefinitions and enforces name-uniqueness on registration. The module-level singleton _REGISTRY is the shared registry for all built-in and plugin-contributed actions.

Use get_action_registry() to access the singleton. Plugin authors register actions via get_action_registry().register().

ActionRegistry

Registry of all ActionDefinitions (built-in + plugin-contributed).

Thread safety: registrations are expected to happen at module-load time only — no locking is performed.

register
register(action: ActionDefinition) -> None

Register action.

Raises

ValueError If an action with the same name is already registered.

get
get(name: str) -> ActionDefinition

Return the ActionDefinition for name.

Raises

KeyError If no action is registered under name.

list_actions
list_actions(*, category: str | None = None, side_effect: Literal['read', 'write'] | None = None, custom_presentation: bool | None = None) -> list[ActionDefinition]

Return a filtered list of registered actions.

All provided filters are combined with AND logic. Omit a parameter to skip that filter.

get_action_registry

get_action_registry() -> ActionRegistry

Return the module-level ActionRegistry singleton.