From df29b281673cbcfd96bc12903c9a40cd58a56a23 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 26 Jan 2026 23:34:40 -0400 Subject: [PATCH] Improve cmdforge add UX for locally-installed tools - Check if tool is already installed locally before trying registry - Show clear message: "Already installed globally/locally: " - Better error messages for VERSION_NOT_FOUND vs TOOL_NOT_FOUND - Skip unnecessary registry calls when tool is already available This fixes confusing UX where `cmdforge add` would show an error for unpublished tools that were actually satisfied by local installation. Co-Authored-By: Claude Opus 4.5 --- src/cmdforge/cli/project_commands.py | 44 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/cmdforge/cli/project_commands.py b/src/cmdforge/cli/project_commands.py index c5c469b..e197dcf 100644 --- a/src/cmdforge/cli/project_commands.py +++ b/src/cmdforge/cli/project_commands.py @@ -374,6 +374,9 @@ def cmd_add(args): parsed = ToolSpec.parse(tool_spec) full_name = parsed.full_name + # Check if already installed locally before trying registry + local_tool = find_tool(tool_spec) + # Add dependency manifest.add_dependency(full_name, version) @@ -383,23 +386,32 @@ def cmd_add(args): # Install if requested if not args.no_install: - print(f"Installing {full_name}...") - try: - resolved = install_from_registry(tool_spec, version if version != "*" else None) - print(f"Installed: {resolved.full_name}@{resolved.version}") - except RegistryError as e: - if e.code == "TOOL_NOT_FOUND": - print(f"Tool not found in registry.", file=sys.stderr) - print(f"It's been added to your dependencies - you can install it manually later.", file=sys.stderr) - elif e.code == "CONNECTION_ERROR": - print(f"Could not connect to registry.", file=sys.stderr) - print("Run 'cmdforge install' to try again later.", file=sys.stderr) - else: - print(f"Install failed: {e.message}", file=sys.stderr) + # If already installed locally, no need to fetch from registry + if local_tool: + source_label = "locally" if local_tool.source == "local" else "globally" + version_info = f"@{local_tool.version}" if local_tool.version else "" + print(f"Already installed {source_label}: {local_tool.full_name}{version_info}") + else: + print(f"Installing {full_name}...") + try: + resolved = install_from_registry(tool_spec, version if version != "*" else None) + print(f"Installed: {resolved.full_name}@{resolved.version}") + except RegistryError as e: + if e.code == "TOOL_NOT_FOUND": + print(f"Not found in registry: {full_name}", file=sys.stderr) + print(f"Dependency added - install manually or create the tool locally.", file=sys.stderr) + elif e.code == "VERSION_NOT_FOUND": + print(f"No published version of '{full_name}' found in registry.", file=sys.stderr) + print(f"Dependency added - publish the tool or install manually.", file=sys.stderr) + elif e.code == "CONNECTION_ERROR": + print(f"Could not connect to registry.", file=sys.stderr) + print("Run 'cmdforge install' to try again later.", file=sys.stderr) + else: + print(f"Install failed: {e.message}", file=sys.stderr) + print("Run 'cmdforge install' to try again.", file=sys.stderr) + except Exception as e: + print(f"Install failed: {e}", file=sys.stderr) print("Run 'cmdforge install' to try again.", file=sys.stderr) - except Exception as e: - print(f"Install failed: {e}", file=sys.stderr) - print("Run 'cmdforge install' to try again.", file=sys.stderr) return 0