diff --git a/src/cmdforge/gui/dialogs/publish_dialog.py b/src/cmdforge/gui/dialogs/publish_dialog.py index c17c0dd..6fd1316 100644 --- a/src/cmdforge/gui/dialogs/publish_dialog.py +++ b/src/cmdforge/gui/dialogs/publish_dialog.py @@ -183,14 +183,16 @@ class PublishDialog(QDialog): self.status_label.setText("Published successfully!") self.status_label.setStyleSheet("color: #38a169; font-weight: 600;") - # Save registry_hash to local config for publish state tracking + # Save registry_hash and moderation status to local config for publish state tracking config_hash = result.get("config_hash") + moderation_status = result.get("status", "pending") if config_hash and hasattr(self._tool, 'path') and self._tool.path: try: config_path = self._tool.path if config_path.exists(): config_data = yaml.safe_load(config_path.read_text()) or {} config_data["registry_hash"] = config_hash + config_data["registry_status"] = moderation_status config_path.write_text(yaml.dump(config_data, default_flow_style=False, sort_keys=False)) except Exception: pass # Non-critical - just won't show publish state diff --git a/src/cmdforge/gui/pages/tools_page.py b/src/cmdforge/gui/pages/tools_page.py index ff70392..4633980 100644 --- a/src/cmdforge/gui/pages/tools_page.py +++ b/src/cmdforge/gui/pages/tools_page.py @@ -29,7 +29,8 @@ def get_tool_publish_state(tool_name: str) -> Tuple[str, Optional[str]]: Returns: Tuple of (state, registry_hash) where state is: - - "published" - has registry_hash and current hash matches + - "published" - approved in registry and current hash matches + - "pending" - submitted but awaiting moderation - "modified" - has registry_hash but current hash differs - "local" - no registry_hash (never published/downloaded) """ @@ -40,6 +41,7 @@ def get_tool_publish_state(tool_name: str) -> Tuple[str, Optional[str]]: try: config = yaml.safe_load(config_path.read_text()) registry_hash = config.get("registry_hash") + registry_status = config.get("registry_status", "pending") if not registry_hash: return ("local", None) @@ -47,10 +49,15 @@ def get_tool_publish_state(tool_name: str) -> Tuple[str, Optional[str]]: # Compute current hash (excluding hash fields) current_hash = compute_config_hash(config) - if current_hash == registry_hash: + if current_hash != registry_hash: + return ("modified", registry_hash) + + # Hash matches - check moderation status + if registry_status == "approved": return ("published", registry_hash) else: - return ("modified", registry_hash) + # pending, rejected, or unknown + return ("pending", registry_hash) except Exception: return ("local", None) @@ -156,6 +163,14 @@ class ToolsPage(QWidget): btn_layout.addStretch() + # Sync status button (only when connected) + self.btn_sync = QPushButton("Sync Status") + self.btn_sync.setObjectName("secondary") + self.btn_sync.clicked.connect(self._sync_tool_status) + self.btn_sync.setEnabled(False) + self.btn_sync.setToolTip("Check registry for updated moderation status") + btn_layout.addWidget(self.btn_sync) + # Connect/Publish button config = load_config() if config.registry.token: @@ -219,8 +234,12 @@ class ToolsPage(QWidget): # Show state indicator in display name if state == "published": display_name = f"{name} ✓" - tooltip = "Published to registry - up to date" + tooltip = "Published to registry - approved" color = QColor(56, 161, 105) # Green + elif state == "pending": + display_name = f"{name} ◐" + tooltip = "Submitted to registry - pending review" + color = QColor(214, 158, 46) # Yellow/amber elif state == "modified": display_name = f"{name} ●" tooltip = "Published to registry - local modifications" @@ -298,7 +317,13 @@ class ToolsPage(QWidget): lines.append( "
" - "✓ Published to registry - up to date
" + "✓ Published to registry - approved" + ) + elif state == "pending": + lines.append( + "" + "◐ Submitted to registry - pending review
" ) elif state == "modified": lines.append( @@ -360,9 +385,16 @@ class ToolsPage(QWidget): if config.registry.token: # Connected - enable Publish when tool selected self.btn_publish.setEnabled(has_selection) + # Enable Sync when tool selected and has registry_hash + if has_selection: + state, registry_hash = get_tool_publish_state(self._current_tool.name) + self.btn_sync.setEnabled(registry_hash is not None) + else: + self.btn_sync.setEnabled(False) else: # Not connected - Connect button is always enabled self.btn_publish.setEnabled(True) + self.btn_sync.setEnabled(False) def _create_tool(self): """Create a new tool.""" @@ -413,8 +445,54 @@ class ToolsPage(QWidget): if not self._current_tool: return + tool_name = self._current_tool.name # Save before refresh clears it from ..dialogs.publish_dialog import PublishDialog dialog = PublishDialog(self, self._current_tool) if dialog.exec(): self.refresh() # Refresh to show updated publish state indicator - self.main_window.show_status(f"Published '{self._current_tool.name}'") + self.main_window.show_status(f"Published '{tool_name}'") + + def _sync_tool_status(self): + """Sync the moderation status of the selected tool from the registry.""" + if not self._current_tool: + return + + tool_name = self._current_tool.name + config_path = get_tools_dir() / tool_name / "config.yaml" + + if not config_path.exists(): + return + + try: + from ...registry_client import RegistryClient, RegistryError + + config = load_config() + client = RegistryClient() + client.token = config.registry.token + + # Get status from registry + status_data = client.get_my_tool_status(tool_name) + new_status = status_data.get("status", "pending") + + # Update local config + config_data = yaml.safe_load(config_path.read_text()) or {} + old_status = config_data.get("registry_status", "pending") + + if old_status != new_status: + config_data["registry_status"] = new_status + config_path.write_text(yaml.dump(config_data, default_flow_style=False, sort_keys=False)) + + self.refresh() + if new_status == "approved": + self.main_window.show_status(f"Tool '{tool_name}' has been approved!") + elif new_status == "rejected": + self.main_window.show_status(f"Tool '{tool_name}' was rejected") + else: + self.main_window.show_status(f"Status updated: {new_status}") + else: + self.main_window.show_status(f"Status unchanged: {new_status}") + + except RegistryError as e: + QMessageBox.warning(self, "Sync Error", f"Could not sync status:\n{e.message}") + except Exception as e: + QMessageBox.warning(self, "Sync Error", f"Could not sync status:\n{e}") diff --git a/src/cmdforge/registry/app.py b/src/cmdforge/registry/app.py index 91bbdd8..daa13dd 100644 --- a/src/cmdforge/registry/app.py +++ b/src/cmdforge/registry/app.py @@ -1900,6 +1900,38 @@ def create_app() -> Flask: }) return jsonify({"data": data}) + @app.route("/api/v1/me/tools/