Add startup connection validation to GUI

- Add validate_token() method to RegistryClient
  - Returns (is_valid, error_message) tuple
  - Catches 401/UNAUTHORIZED errors and connection issues
- Add startup validation in MainWindow
  - Runs 500ms after window shows (non-blocking)
  - Clears invalid tokens automatically
  - Shows status bar message explaining what happened
- Document cmdforge config disconnect command

This prevents confusing errors when trying to publish with stale or
revoked credentials - the GUI now auto-clears bad connections on restart.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-17 01:59:16 -04:00
parent 66fd121ee4
commit 85d1212c0d
3 changed files with 62 additions and 1 deletions

View File

@ -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

View File

@ -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 = [

View File

@ -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.