"""Registry commands.""" import sys from pathlib import Path import yaml from ..config import load_config, set_registry_token from ..resolver import install_from_registry, uninstall_tool, ToolSpec def cmd_registry(args): """Handle registry subcommands.""" from ..registry_client import RegistryClient, RegistryError, RateLimitError, get_client if args.registry_cmd == "search": return _cmd_registry_search(args) elif args.registry_cmd == "install": return _cmd_registry_install(args) elif args.registry_cmd == "uninstall": return _cmd_registry_uninstall(args) elif args.registry_cmd == "info": return _cmd_registry_info(args) elif args.registry_cmd == "update": return _cmd_registry_update(args) elif args.registry_cmd == "publish": return _cmd_registry_publish(args) elif args.registry_cmd == "my-tools": return _cmd_registry_my_tools(args) elif args.registry_cmd == "browse": return _cmd_registry_browse(args) else: # Default: show registry help print("Registry commands:") print(" search Search for tools") print(" install Install a tool") print(" uninstall Uninstall a tool") print(" info Show tool information") print(" update Update local index cache") print(" publish [path] Publish a tool") print(" my-tools List your published tools") print(" browse Browse tools (TUI)") return 0 def _cmd_registry_search(args): """Search for tools in the registry.""" from ..registry_client import RegistryError, get_client try: client = get_client() results = client.search_tools( query=args.query, category=args.category, per_page=args.limit or 20 ) if not results.data: print(f"No tools found matching '{args.query}'") return 0 print(f"Found {results.total} tools:\n") for tool in results.data: owner = tool.get("owner", "") name = tool.get("name", "") version = tool.get("version", "") desc = tool.get("description", "") downloads = tool.get("downloads", 0) print(f" {owner}/{name} v{version}") print(f" {desc[:60]}{'...' if len(desc) > 60 else ''}") print(f" Downloads: {downloads}") print() if results.total_pages > 1: print(f"Showing page {results.page}/{results.total_pages}") except RegistryError as e: if e.code == "CONNECTION_ERROR": print("Could not connect to the registry.", file=sys.stderr) print("Check your internet connection or try again later.", file=sys.stderr) elif e.code == "RATE_LIMITED": print(f"Rate limited. Please wait and try again.", file=sys.stderr) else: print(f"Error: {e.message}", file=sys.stderr) return 1 except Exception as e: print(f"Error searching registry: {e}", file=sys.stderr) print("If the problem persists, check: cmdforge config show", file=sys.stderr) return 1 return 0 def _cmd_registry_install(args): """Install a tool from the registry.""" from ..registry_client import RegistryError from ..tool import BIN_DIR tool_spec = args.tool version = args.version print(f"Installing {tool_spec}...") try: resolved = install_from_registry(tool_spec, version) print(f"Installed: {resolved.full_name}@{resolved.version}") print(f"Location: {resolved.path}") # Show wrapper info wrapper_name = resolved.tool.name if resolved.owner: # Check for collision short_wrapper = BIN_DIR / resolved.tool.name if short_wrapper.exists(): wrapper_name = f"{resolved.owner}-{resolved.tool.name}" print(f"\nRun with: {wrapper_name}") except RegistryError as e: if e.code == "TOOL_NOT_FOUND": print(f"Tool '{tool_spec}' not found in the registry.", file=sys.stderr) print(f"Try: cmdforge registry search {tool_spec.split('/')[-1]}", file=sys.stderr) elif e.code == "VERSION_NOT_FOUND" or e.code == "CONSTRAINT_UNSATISFIABLE": print(f"Error: {e.message}", file=sys.stderr) if e.details and "available_versions" in e.details: versions = e.details["available_versions"] print(f"Available versions: {', '.join(versions[:5])}", file=sys.stderr) if e.details.get("latest_stable"): print(f"Latest stable: {e.details['latest_stable']}", file=sys.stderr) elif e.code == "CONNECTION_ERROR": print("Could not connect to the registry.", file=sys.stderr) print("Check your internet connection or try again later.", file=sys.stderr) else: print(f"Error: {e.message}", file=sys.stderr) return 1 except Exception as e: print(f"Error installing tool: {e}", file=sys.stderr) return 1 return 0 def _cmd_registry_uninstall(args): """Uninstall a tool.""" tool_spec = args.tool print(f"Uninstalling {tool_spec}...") if uninstall_tool(tool_spec): print(f"Uninstalled: {tool_spec}") else: print(f"Tool '{tool_spec}' not found", file=sys.stderr) return 1 return 0 def _cmd_registry_info(args): """Show tool information.""" from ..registry_client import RegistryError, get_client tool_spec = args.tool try: # Parse the tool spec parsed = ToolSpec.parse(tool_spec) owner = parsed.owner or "official" client = get_client() tool_info = client.get_tool(owner, parsed.name) print(f"{tool_info.owner}/{tool_info.name} v{tool_info.version}") print("=" * 50) print(f"Description: {tool_info.description}") print(f"Category: {tool_info.category}") print(f"Tags: {', '.join(tool_info.tags)}") print(f"Downloads: {tool_info.downloads}") print(f"Published: {tool_info.published_at}") if tool_info.deprecated: print() print(f"DEPRECATED: {tool_info.deprecated_message}") if tool_info.replacement: print(f"Use instead: {tool_info.replacement}") # Show versions versions = client.get_tool_versions(owner, parsed.name) if versions: print(f"\nVersions: {', '.join(versions[:5])}") if len(versions) > 5: print(f" ...and {len(versions) - 5} more") print(f"\nInstall: cmdforge registry install {tool_info.owner}/{tool_info.name}") except RegistryError as e: if e.code == "TOOL_NOT_FOUND": print(f"Tool '{tool_spec}' not found in the registry.", file=sys.stderr) print(f"Try: cmdforge registry search {parsed.name}", file=sys.stderr) elif e.code == "CONNECTION_ERROR": print("Could not connect to the registry.", file=sys.stderr) print("Check your internet connection or try again later.", file=sys.stderr) else: print(f"Error: {e.message}", file=sys.stderr) return 1 except Exception as e: print(f"Error fetching tool info: {e}", file=sys.stderr) return 1 return 0 def _cmd_registry_update(args): """Update local index cache.""" from ..registry_client import RegistryError, get_client print("Updating registry index...") try: client = get_client() index = client.get_index(force_refresh=True) tool_count = index.get("tool_count", len(index.get("tools", []))) generated = index.get("generated_at", "unknown") print(f"Index updated: {tool_count} tools") print(f"Generated: {generated}") except RegistryError as e: if e.code == "CONNECTION_ERROR": print("Could not connect to the registry.", file=sys.stderr) print("Check your internet connection or try again later.", file=sys.stderr) elif e.code == "RATE_LIMITED": print("Rate limited. Please wait a moment and try again.", file=sys.stderr) else: print(f"Error: {e.message}", file=sys.stderr) return 1 except Exception as e: print(f"Error updating index: {e}", file=sys.stderr) return 1 return 0 def _cmd_registry_publish(args): """Publish a tool to the registry.""" from ..registry_client import RegistryError, get_client # Read tool from current directory or specified path tool_path = Path(args.path) if args.path else Path.cwd() if tool_path.is_dir(): config_path = tool_path / "config.yaml" else: config_path = tool_path tool_path = config_path.parent if not config_path.exists(): print(f"Error: config.yaml not found in {tool_path}", file=sys.stderr) return 1 # Read config config_yaml = config_path.read_text() # Read README if exists readme_path = tool_path / "README.md" readme = readme_path.read_text() if readme_path.exists() else "" # Validate try: data = yaml.safe_load(config_yaml) name = data.get("name", "") version = data.get("version", "") if not name or not version: print("Error: config.yaml must have 'name' and 'version' fields", file=sys.stderr) return 1 except yaml.YAMLError as e: print(f"Error: Invalid YAML in config.yaml: {e}", file=sys.stderr) return 1 if args.dry_run: print("Dry run - validating only") print() print(f"Would publish:") print(f" Name: {name}") print(f" Version: {version}") print(f" Config: {len(config_yaml)} bytes") print(f" README: {len(readme)} bytes") return 0 # Check for token config = load_config() if not config.registry.token: print("No registry token configured.") print() print("1. Register at: https://gitea.brrd.tech/registry/register") print("2. Generate a token from your dashboard") print("3. Enter your token below") print() try: token = input("Registry token: ").strip() if not token: print("Cancelled.") return 1 set_registry_token(token) print("Token saved.") except (EOFError, KeyboardInterrupt): print("\nCancelled.") return 1 print(f"Publishing {name}@{version}...") try: client = get_client() result = client.publish_tool(config_yaml, readme) pr_url = result.get("pr_url", "") status = result.get("status", "") if status == "published" or result.get("version"): print(f"Published: {result.get('owner', '')}/{result.get('name', '')}@{result.get('version', version)}") elif pr_url: print(f"PR created: {pr_url}") print("Your tool is pending review.") else: print("Published successfully!") # Show suggestions if provided (from Phase 6 smart features) suggestions = result.get("suggestions", {}) if suggestions: print() # Category suggestion cat_suggestion = suggestions.get("category") if cat_suggestion and cat_suggestion.get("suggested"): confidence = cat_suggestion.get("confidence", 0) print(f"Suggested category: {cat_suggestion['suggested']} ({confidence:.0%} confidence)") # Similar tools warning similar = suggestions.get("similar_tools", []) if similar: print("Similar existing tools:") for tool in similar[:3]: similarity = tool.get("similarity", 0) print(f" - {tool.get('name', 'unknown')} ({similarity:.0%} similar)") except RegistryError as e: if e.code == "UNAUTHORIZED": print("Authentication failed.", file=sys.stderr) print("Your token may have expired. Generate a new one from the registry.", file=sys.stderr) elif e.code == "INVALID_CONFIG": print(f"Invalid tool config: {e.message}", file=sys.stderr) print("Check your config.yaml for errors.", file=sys.stderr) elif e.code == "VERSION_EXISTS": print(f"Version already exists: {e.message}", file=sys.stderr) print("Bump the version in config.yaml and try again.", file=sys.stderr) elif e.code == "CONNECTION_ERROR": print("Could not connect to the registry.", file=sys.stderr) print("Check your internet connection or try again later.", file=sys.stderr) elif e.code == "RATE_LIMITED": print("Rate limited. Please wait a moment and try again.", file=sys.stderr) else: print(f"Error: {e.message}", file=sys.stderr) return 1 except Exception as e: print(f"Error publishing: {e}", file=sys.stderr) return 1 return 0 def _cmd_registry_my_tools(args): """List your published tools.""" from ..registry_client import RegistryError, get_client try: client = get_client() tools = client.get_my_tools() if not tools: print("You haven't published any tools yet.") print("Publish your first tool with: cmdforge registry publish") return 0 print(f"Your published tools ({len(tools)}):\n") for tool in tools: status = "[DEPRECATED]" if tool.deprecated else "" print(f" {tool.owner}/{tool.name} v{tool.version} {status}") print(f" Downloads: {tool.downloads}") print() except RegistryError as e: if e.code == "UNAUTHORIZED": print("Not logged in. Set your registry token first:", file=sys.stderr) print(" cmdforge config set-token ", file=sys.stderr) print() print("Don't have a token? Register at the registry website.", file=sys.stderr) elif e.code == "CONNECTION_ERROR": print("Could not connect to the registry.", file=sys.stderr) print("Check your internet connection or try again later.", file=sys.stderr) elif e.code == "RATE_LIMITED": print("Rate limited. Please wait a moment and try again.", file=sys.stderr) else: print(f"Error: {e.message}", file=sys.stderr) return 1 except Exception as e: print(f"Error: {e}", file=sys.stderr) return 1 return 0 def _cmd_registry_browse(args): """Browse tools (TUI).""" try: from ..ui_registry import run_registry_browser return run_registry_browser() except ImportError: print("TUI browser requires urwid. Install with:", file=sys.stderr) print(" pip install 'cmdforge[tui]'", file=sys.stderr) print() print("Or search from command line:", file=sys.stderr) print(" cmdforge registry search ", file=sys.stderr) return 1