diff --git a/CHANGELOG.md b/CHANGELOG.md index ebd242f..f19dbd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,13 @@ All notable changes to CmdForge will be documented in this file. - Auto-fetch registry version when opening publish dialog - Fork detection during publish workflow - Always refresh tools page after publish dialog closes +- **Startup connection validation**: GUI validates registry token on startup + - Automatically clears invalid/revoked tokens + - Shows status bar message when connection is cleared + - Prevents confusing errors when trying to publish with stale credentials + +#### CLI Features +- `cmdforge config disconnect` - Clear registry token from local configuration #### Admin Features - Maintenance section in admin dashboard diff --git a/src/cmdforge/gui/main_window.py b/src/cmdforge/gui/main_window.py index d45cab0..2b1ed03 100644 --- a/src/cmdforge/gui/main_window.py +++ b/src/cmdforge/gui/main_window.py @@ -5,7 +5,7 @@ from PySide6.QtWidgets import ( QListWidget, QListWidgetItem, QStackedWidget, QStatusBar, QLabel, QSplitter ) -from PySide6.QtCore import Qt, QSize, QSettings +from PySide6.QtCore import Qt, QSize, QSettings, QTimer from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence from .styles import STYLESHEET @@ -68,6 +68,33 @@ class MainWindow(QMainWindow): # Restore window geometry self._restore_geometry() + # Validate registry connection after window shows + QTimer.singleShot(500, self._validate_registry_connection) + + def _validate_registry_connection(self): + """Check if registry connection is valid on startup, clear if invalid.""" + try: + from ..registry_client import RegistryClient + from ..config import set_registry_token, load_config + + config = load_config() + if not config.registry.token: + return # No connection configured, nothing to validate + + client = RegistryClient() + is_valid, error = client.validate_token() + + if not is_valid: + # Token is invalid, clear it + set_registry_token(None) + self.status_bar.showMessage( + f"Registry connection cleared: {error}. Please reconnect.", + 10000 # Show for 10 seconds + ) + except Exception as e: + # Don't crash on validation errors, just log to status + self.status_bar.showMessage(f"Could not validate registry connection: {e}", 5000) + def _setup_sidebar(self): """Set up sidebar navigation items.""" items = [ diff --git a/src/cmdforge/registry_client.py b/src/cmdforge/registry_client.py index 185c5af..07876d8 100644 --- a/src/cmdforge/registry_client.py +++ b/src/cmdforge/registry_client.py @@ -605,6 +605,33 @@ class RegistryClient: return response.json().get("data", {}) + def validate_token(self) -> tuple[bool, str]: + """ + Validate that the current token is valid. + + Returns: + Tuple of (is_valid, error_message) + - (True, "") if token is valid + - (False, "reason") if token is invalid or missing + """ + if not self.token: + return False, "No token configured" + + try: + response = self._request("GET", "/me/tools", require_auth=True) + if response.status_code == 200: + return True, "" + elif response.status_code == 401: + return False, "Token is invalid or revoked" + else: + return False, f"Unexpected response: {response.status_code}" + except RegistryError as e: + if e.http_status == 401 or e.code == "UNAUTHORIZED": + return False, "Token is invalid or revoked" + return False, str(e.message) + except Exception as e: + return False, f"Connection error: {e}" + def get_my_tools(self) -> List[ToolInfo]: """ Get tools published by the authenticated user.