diff --git a/api/admin_ui/src/App.tsx b/api/admin_ui/src/App.tsx
index c835827b..7cd8fc92 100644
--- a/api/admin_ui/src/App.tsx
+++ b/api/admin_ui/src/App.tsx
@@ -832,7 +832,7 @@ function AppContent() {
)}
{toolsTab === 2 && }
{toolsTab === 3 && }
- {toolsTab === 4 && }
+ {toolsTab === 4 && }
{toolsTab === 5 && }
{toolsTab === 6 && (
>([]);
+ const [disabled, setDisabled] = useState(false);
+
+ useEffect(() => {
+ let cancelled = false;
+ const run = async () => {
+ setLoading(true);
+ try {
+ const res = await fetch(`${window.location.origin}/admin/marketplace/plugins`, {
+ headers: { 'X-API-Key': (client as any).apiKey || '' },
+ });
+ if (res.status === 404) {
+ if (!cancelled) setDisabled(true);
+ return;
+ }
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data = await res.json();
+ if (!cancelled) setPlugins(Array.isArray(data?.plugins) ? data.plugins : []);
+ } catch {
+ if (!cancelled) setDisabled(true);
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ };
+ run();
+ return () => { cancelled = true; };
+ }, [client]);
return (
@@ -32,14 +63,42 @@ export default function PluginMarketplace(): JSX.Element {
sx={{ mb: 3 }}
/>
+ {disabled && (
+
+ Plugin Marketplace is disabled. Enable by setting ADMIN_MARKETPLACE_ENABLED=true.
+ {docsBase && (
+ <>
+ {' '}Learn more at docs.
+ >
+ )}
+
+ )}
+
+ {loading && }
+
{/* Empty state placeholder; results will be populated in later PRs */}
-
- Marketplace results will appear here. Use traits to gate provider‑specific UI.
-
+ {plugins.length === 0 ? (
+
+ {disabled ? 'Marketplace is currently disabled.' : 'No plugins found.'}
+
+ ) : (
+ <>
+ {plugins
+ .filter(p => (query ? (p.name || '').toLowerCase().includes(query.toLowerCase()) : true))
+ .map(p => (
+
+ {p.name || p.id}
+ {p.description && (
+ {p.description}
+ )}
+
+ ))}
+ >
+ )}
@@ -47,4 +106,3 @@ export default function PluginMarketplace(): JSX.Element {
);
}
-
diff --git a/api/app/routers/admin_marketplace.py b/api/app/routers/admin_marketplace.py
index f4aff289..183e839e 100644
--- a/api/app/routers/admin_marketplace.py
+++ b/api/app/routers/admin_marketplace.py
@@ -37,3 +37,15 @@ def list_plugins():
# Placeholder: will be populated via trait‑aware registry in later PRs
return {"plugins": []}
+
+@router.post("/install")
+def install_plugin(payload: Dict[str, Any] | None = None):
+ """Remote install a plugin (disabled by default).
+
+ Gate with ADMIN_MARKETPLACE_REMOTE_INSTALL_ENABLED=false by default.
+ """
+ remote_enabled = os.getenv("ADMIN_MARKETPLACE_REMOTE_INSTALL_ENABLED", "false").lower() in {"1", "true", "yes"}
+ if not remote_enabled:
+ raise HTTPException(503, detail="Remote install disabled")
+ # Placeholder only; actual implementation will be added later
+ return {"ok": False, "message": "not_implemented"}