diff --git a/agentdescent/integrations/__init__.py b/agentdescent/integrations/__init__.py index 76616152..614ab20b 100644 --- a/agentdescent/integrations/__init__.py +++ b/agentdescent/integrations/__init__.py @@ -1810,6 +1810,21 @@ def _uninstall_opencode_file(path: str, e: _Eraser) -> None: } +def _empty_uninstall_note(host: str) -> str: + """Line printed when uninstall finds none of this package's files. + + Claude Code and Codex can also be wired through the host's own + marketplace (``/plugin install``, ``codex plugin add``), which leaves + nothing here. Grok Bot, dsh and OpenCode are only installed by + ``agentdescent install`` (#890). + """ + msg = f"nothing to remove: agentdescent is not installed for {host}" + if host in ("claude-code", "codex"): + msg += (" (files from the host's own marketplace are removed " + "with its plugin command)") + return msg + + def uninstall(host: str, *, dry_run: bool = False, home: Optional[str] = None) -> List[str]: """Undo ``install(host)``. Returns the lines to print: what was removed or kept. @@ -1817,9 +1832,11 @@ def uninstall(host: str, *, dry_run: bool = False, home: Optional[str] = None) - host's config. A config entry that is no longer exactly what ``install`` writes -- you edited the launcher, or wrote it yourself -- is kept and reported, and a config file with comments (OpenCode's ``opencode.jsonc``) is - never rewritten. The run store under ``~/.agentdescent`` is untouched. An - install made through the host's own marketplace is removed with that host's - plugin command. ``dry_run`` reports without changing anything. + never rewritten. The run store under ``~/.agentdescent`` is untouched. + Claude Code and Codex can also be wired through the host's own marketplace, + which leaves no files here; those hosts' empty-state note points at the + host's plugin command. Grok Bot, dsh and OpenCode are only installed by + ``agentdescent install``. ``dry_run`` reports without changing anything. """ host = canonical_host(host) if host not in HOSTS: @@ -1828,6 +1845,5 @@ def uninstall(host: str, *, dry_run: bool = False, home: Optional[str] = None) - e = _Eraser(dry_run, honour_env=honour_env) _UNINSTALL[host](home, e) if not e.lines: - e.note(f"nothing to remove: agentdescent is not installed for {host} " - "(files from the host's own marketplace are removed with its plugin command)") + e.note(_empty_uninstall_note(host)) return e.lines diff --git a/changelog.d/890-uninstall-marketplace-hint.md b/changelog.d/890-uninstall-marketplace-hint.md new file mode 100644 index 00000000..9e32cc1a --- /dev/null +++ b/changelog.d/890-uninstall-marketplace-hint.md @@ -0,0 +1,4 @@ +Fixed +`uninstall` no longer tells grok-bot, dsh, or OpenCode users to remove files +via a host marketplace those hosts do not have. The marketplace aside stays +only for Claude Code and Codex, which can be installed that way (#890). diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py index 018b88ac..32da4a1b 100644 --- a/tests/test_uninstall.py +++ b/tests/test_uninstall.py @@ -299,6 +299,26 @@ def test_opencode_keeps_the_other_servers_and_settings(tmp_path): assert after["theme"] == "dark" +def test_empty_uninstall_omits_marketplace_for_file_only_hosts(tmp_path): + """#890: grok-bot / dsh / opencode are not installed via a host marketplace.""" + for host in ("grok-bot", "dsh", "opencode"): + lines = uninstall(host, home=str(tmp_path)) + assert lines == [ + f"nothing to remove: agentdescent is not installed for {host}" + ], lines + + +def test_empty_uninstall_mentions_marketplace_for_claude_code_and_codex(tmp_path): + """#890: those two hosts really can be wired through a marketplace.""" + for host in ("claude-code", "codex"): + lines = uninstall(host, home=str(tmp_path)) + assert len(lines) == 1, lines + assert lines[0].startswith( + f"nothing to remove: agentdescent is not installed for {host}") + assert "marketplace" in lines[0] + assert "plugin command" in lines[0] + + # -- the CLI ---------------------------------------------------------------------- @@ -329,6 +349,21 @@ def test_cli_uninstall_rejects_an_unknown_host(): cli.main(["uninstall", "vim"]) +def test_cli_empty_uninstall_grok_bot_omits_marketplace(tmp_path, capsys): + """#890: the user-facing grok-bot empty state has no marketplace aside.""" + home = str(tmp_path) + assert cli.main(["uninstall", "grok-bot", "--home", home]) == 0 + out = capsys.readouterr().out + assert "nothing to remove: agentdescent is not installed for grok-bot" in out + assert "marketplace" not in out + assert cli.main(["uninstall", "grok-bot", "--home", home, "--json"]) == 0 + payload = json.loads(capsys.readouterr().out) + assert payload["ok"] is True + assert payload["lines"] == [ + "nothing to remove: agentdescent is not installed for grok-bot" + ] + + def test_opencode_uninstall_undoes_the_opencode_json_fallback(tmp_path): """#516: install beside a commented opencode.jsonc, then uninstall: the commented file is byte for byte the same and opencode.json is gone again."""