diff --git a/src/ucode/agents/__init__.py b/src/ucode/agents/__init__.py index 8ea3e20d..1324fc9e 100644 --- a/src/ucode/agents/__init__.py +++ b/src/ucode/agents/__init__.py @@ -13,8 +13,10 @@ from __future__ import annotations import json +import os import shutil import subprocess +import sys from ucode.agent_updates import available_npm_package_update from ucode.config_io import ToolSpec @@ -687,6 +689,19 @@ def provider_permission_error(tool: str, state: dict, err: str) -> str: return err +# Both ``ug`` and ``ucode`` are console-script entry points for this CLI (see +# pyproject ``[project.scripts]``), so echo back whichever name the user actually +# launched when we tell them how to run a tool. Falls back to the primary ``ug`` +# when argv[0] isn't a recognized name (``python -m ucode.cli``, a wrapper, tests). +_COMMAND_NAMES = frozenset({"ug", "ucode"}) + + +def invoked_command_name() -> str: + """Return the entry-point name the user launched (``ug`` or ``ucode``).""" + name = os.path.basename(sys.argv[0]) if sys.argv else "" + return name if name in _COMMAND_NAMES else "ug" + + def validate_all_tools(state: dict) -> None: from rich.panel import Panel # local to avoid bumping module-level deps @@ -730,11 +745,12 @@ def validate_all_tools(state: dict) -> None: success_tools = [(t, s) for t, s in results if s] if success_tools and not low_verbosity: console.print() + command = invoked_command_name() lines = [] for tool, _ in success_tools: spec = TOOL_SPECS[tool] lines.append( f"[green]✓[/green] [bold]{spec['display']}[/bold] — " - f"run with [cyan]ucode {tool}[/cyan]" + f"run with [cyan]{command} {tool}[/cyan]" ) console.print(Panel("\n".join(lines), title="Ready", style="green", expand=False)) diff --git a/tests/test_agents_init.py b/tests/test_agents_init.py index 9ff7d5ee..80b91f41 100644 --- a/tests/test_agents_init.py +++ b/tests/test_agents_init.py @@ -775,6 +775,30 @@ def test_empty_selection_preserves_existing(self, monkeypatch): assert result["available_tools"] == ["codex"] +class TestInvokedCommandName: + @pytest.mark.parametrize( + "argv0, expected", + [ + ("/opt/homebrew/bin/ug", "ug"), + ("/opt/homebrew/bin/ucode", "ucode"), + # Unrecognized launchers (python -m, wrappers, tests) fall back to `ug`. + ("/usr/bin/python", "ug"), + ("", "ug"), + ], + ) + def test_returns_recognized_name_or_falls_back(self, monkeypatch, argv0, expected): + import sys + + monkeypatch.setattr(sys, "argv", [argv0]) + assert agents_mod.invoked_command_name() == expected + + def test_empty_argv_falls_back(self, monkeypatch): + import sys + + monkeypatch.setattr(sys, "argv", []) + assert agents_mod.invoked_command_name() == "ug" + + class TestValidateAllToolsVerbosity: def _run(self, monkeypatch, capsys): from contextlib import nullcontext @@ -786,13 +810,30 @@ def _run(self, monkeypatch, capsys): return capsys.readouterr().out def test_normal_verbosity_renders_panels(self, monkeypatch, capsys): + import sys + import ucode.ui as ui_mod monkeypatch.setattr(ui_mod, "_verbosity", "normal") + monkeypatch.setattr(sys, "argv", ["/opt/homebrew/bin/ug", "configure"]) out = self._run(monkeypatch, capsys) assert "Testing each tool with a quick message" in out assert "Ready" in out assert "Codex is working" in out + # Launched as `ug`, the Ready panel echoes `ug`. + assert "ug codex" in out + assert "ucode codex" not in out + + def test_ready_panel_echoes_ucode_entrypoint(self, monkeypatch, capsys): + import sys + + import ucode.ui as ui_mod + + monkeypatch.setattr(ui_mod, "_verbosity", "normal") + monkeypatch.setattr(sys, "argv", ["/opt/homebrew/bin/ucode", "configure"]) + out = self._run(monkeypatch, capsys) + # Launched via the `ucode` alias, the Ready panel echoes `ucode`. + assert "ucode codex" in out def test_low_verbosity_omits_panels(self, monkeypatch, capsys): import ucode.ui as ui_mod