From 62f5fb652e1304e839eb8d0975b0c7a4fecb062f Mon Sep 17 00:00:00 2001 From: anti Date: Wed, 17 Jun 2026 15:21:06 -0400 Subject: [PATCH] feat(pro): add pro CLI/daemon extension surface The core CLI scans decnet/pro/cli/ and calls each module's register(app), registered before the master-only gate so pro commands are mode-filtered like the rest. Lets the Professional tier add commands and standalone daemon entry points (decnet pro- serve, supervised by a systemd unit). No-op in the Community build (no decnet.pro). Test asserts the shipped pro group registers when mounted; skips otherwise. --- decnet/cli/__init__.py | 15 +++++++++++++++ tests/services/test_pro_tier.py | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/decnet/cli/__init__.py b/decnet/cli/__init__.py index 51e0ba41..04e30527 100644 --- a/decnet/cli/__init__.py +++ b/decnet/cli/__init__.py @@ -65,6 +65,21 @@ for _mod in ( ): _mod.register(app) +# Professional tier (optional): each module in decnet/pro/cli/ exposes +# register(app) and attaches its commands — e.g. a standalone daemon entry point +# that a systemd unit ExecStarts. Registered BEFORE the gate so pro commands are +# mode-filtered like the rest. Absent in the Community build (no decnet.pro). +try: + import decnet.pro.cli as _pro_cli_pkg +except ModuleNotFoundError: + _pro_cli_pkg = None +if _pro_cli_pkg is not None: + import importlib as _importlib + import pkgutil as _pkgutil + + for _pmi in _pkgutil.iter_modules(_pro_cli_pkg.__path__): + _importlib.import_module(f"decnet.pro.cli.{_pmi.name}").register(app) + _gate_commands_by_mode(app) # Backwards-compat re-exports. Tests and third-party tooling import these diff --git a/tests/services/test_pro_tier.py b/tests/services/test_pro_tier.py index 22f3a11b..0d3f1c1f 100644 --- a/tests/services/test_pro_tier.py +++ b/tests/services/test_pro_tier.py @@ -86,3 +86,21 @@ def test_pro_tier_seams(): finally: pro_routes.ROUTERS = saved importlib.reload(web_router) # rebuild a pro-free api_router for others + + +def test_pro_cli_registered_when_mounted(): + """When decnet/pro/ is mounted, its CLI modules' commands join the root app. + + Read-only against the already-built decnet.cli.app — no reload, no fs + mutation. Skips on the Community build, where decnet.pro is absent.""" + import importlib.util + + if importlib.util.find_spec("decnet.pro.cli") is None: + import pytest + + pytest.skip("decnet.pro not mounted (community build)") + + import decnet.cli as cli + + group_names = {g.name for g in cli.app.registered_groups} + assert "pro-intel" in group_names # shipped example pro daemon group