/** * Pi Extensions Manager Component * * Provides UI for managing Pi extensions: * - List extensions with source badges and toggle switches * - Enable/disable extensions * - Refresh extension list * - Loading and empty states */ import { useState, useEffect, useCallback } from "react"; import { Package, RefreshCw } from "lucide-react"; import { fetchPiExtensions, updatePiExtensions } from "../api"; import type { PiExtensionSettings } from "../api"; import type { ToastType } from "../hooks/useToast"; interface PiExtensionsManagerProps { addToast: (message: string, type?: ToastType) => void; projectId?: string; } /** Source to label mapping */ const SOURCE_LABELS: Record = { "fusion-global": "Fusion Global", "pi-global": "Pi Global", "fusion-project": "Fusion Project", "pi-project": "Pi Project", }; export function PiExtensionsManager({ addToast, projectId }: PiExtensionsManagerProps) { const [extensions, setExtensions] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const loadExtensions = useCallback(async () => { try { setLoading(true); const data = await fetchPiExtensions(projectId); setExtensions(data); } catch (err) { addToast(`Failed to load Pi extensions: ${err instanceof Error ? err.message : String(err)}`, "error"); } finally { setLoading(false); } }, [projectId, addToast]); useEffect(() => { void loadExtensions(); }, [loadExtensions]); const toggleExtension = async (extensionId: string, enabled: boolean) => { if (!extensions) return; const nextDisabledIds = enabled ? extensions.disabledIds.filter((id) => id !== extensionId) : Array.from(new Set([...extensions.disabledIds, extensionId])); setSaving(true); try { const nextSettings = await updatePiExtensions(nextDisabledIds, projectId); setExtensions(nextSettings); addToast("Pi extension settings saved", "success"); } catch (err) { addToast(err instanceof Error ? err.message : "Failed to save Pi extension settings", "error"); } finally { setSaving(false); } }; return (

Pi Extensions

Choose which project and global Pi extensions Fusion loads. Changes are saved to your Fusion agent settings and apply after restarting the dashboard or headless node.

{loading ? (
Loading Pi extensions…
) : !extensions || extensions.extensions.length === 0 ? (

No Pi extensions found.

Extensions are discovered from ~/.fusion/agent, ~/.pi/agent, and your project's .fusion/ directory.

) : (
{extensions.extensions.map((extension) => { const isGlobal = extension.source === "fusion-global" || extension.source === "pi-global"; return (
{extension.name} {SOURCE_LABELS[extension.source]}
{extension.path}
); })}
)}
); }