diff --git a/nanobot/cli/entry.py b/nanobot/cli/entry.py index 534c84f82..65ae4d28e 100644 --- a/nanobot/cli/entry.py +++ b/nanobot/cli/entry.py @@ -71,7 +71,11 @@ def _run_agent(args: list[str], *, prog_name: str) -> None: def main() -> None: """Dispatch native TUI startup without importing the complete CLI graph.""" raw_args = sys.argv[1:] - agent_args = _agent_invocation_args(raw_args) + # Installed completion scripts call ``nanobot`` without positional arguments + # and pass the request through this environment variable. Keep those requests + # on the root command so subcommands remain discoverable. + shell_completion = bool(os.environ.get("_NANOBOT_COMPLETE")) + agent_args = None if shell_completion else _agent_invocation_args(raw_args) dispatch_args = ["agent", *agent_args] if agent_args is not None else raw_args set_cli_process_identity(dispatch_args) _configure_windows_console() diff --git a/tests/cli/test_entry.py b/tests/cli/test_entry.py index b3cb4c2cc..bee8a9ce2 100644 --- a/tests/cli/test_entry.py +++ b/tests/cli/test_entry.py @@ -1,3 +1,8 @@ +import os +import subprocess +import sys +from pathlib import Path + from nanobot.cli import entry from nanobot.cli.entry import _agent_invocation_args, _native_tui_candidate @@ -23,6 +28,36 @@ def test_root_metadata_and_subcommands_keep_the_root_cli() -> None: assert _agent_invocation_args(args) is None +def test_root_shell_completion_keeps_root_subcommands() -> None: + env = os.environ.copy() + env.update( + { + "_NANOBOT_COMPLETE": "complete_bash", + "COMP_WORDS": "nanobot ", + "COMP_CWORD": "1", + } + ) + script = ( + "import sys; " + "from nanobot.cli.entry import main; " + "sys.argv = ['nanobot']; " + "main()" + ) + + result = subprocess.run( + [sys.executable, "-c", script], + cwd=Path(__file__).parents[2], + env=env, + capture_output=True, + text=True, + check=False, + ) + + assert result.returncode == 0, result.stderr + assert {"agent", "gateway", "webui"} <= set(result.stdout.splitlines()) + assert "not supported" not in result.stderr + + def test_root_alias_dispatches_the_shared_agent_command(monkeypatch) -> None: calls: dict[str, object] = {} monkeypatch.setattr(entry.sys, "argv", ["nanobot", "-m", "hello"])