Write a plugin¶
When a command should ship on its own release cadence, publish it as a separate package. See also: the plugin reference for the full spec.
Scaffold¶
Entry point in pyproject.toml¶
Breakdown:
clickwork.commands— the entry-point group. Every clickwork CLI running in the same Python environment discovers entry points under this single group; there is no per-CLI scoping in the current implementation.deploy— the command name as it appears on the command line. Pick names carefully so they don't collide with commands from other plugins or with localcommands/files in target CLIs.my_cli_deploy:cli— the import path of the Click object.
Write the command¶
# src/my_cli_deploy/__init__.py
import click
@click.command()
@click.option("--env", default="staging", show_default=True)
def cli(env: str) -> None:
"""Deploy."""
click.echo(f"Deploying to {env}...")
Install into the target CLI's venv¶
From the target CLI's directory:
Verify¶
Ship¶
uv buildproduces the wheel.- Upload to PyPI (or a private index).
pip install my-cli-deployalongside the target CLI and your command shows up.
Collision: local wins¶
If the target CLI has a commands/deploy.py file, that wins over the
plugin's deploy. This is by design — hand-maintained local commands
are never silently overwritten by a plugin install. clickwork emits
an INFO log when the shadowing happens so stale local files don't
silently hide plugin updates.
Testing plugins independently¶
# tests/test_deploy.py in the plugin repo
from click.testing import CliRunner
from my_cli_deploy import cli
def test_deploy_defaults_to_staging() -> None:
result = CliRunner().invoke(cli, [])
assert result.exit_code == 0
assert "staging" in result.output
No clickwork dependency in the test — plugins are just Click commands that clickwork happens to discover.