From 21513bb6f89519b3afbdf5c0e1260c389f1cc239 Mon Sep 17 00:00:00 2001 From: iliya Date: Wed, 1 Apr 2026 16:49:07 +0300 Subject: [PATCH 1/5] Improve Extensions CLI preflight and plugin install diagnostics Refs: https://github.com/777genius/claude_agent_teams_ui/issues/37 --- .../extensions/ExtensionStoreView.tsx | 73 +++++++++++++++++++ .../extensions/common/InstallButton.tsx | 47 ++++++++---- .../extensions/plugins/PluginCard.tsx | 2 +- src/renderer/store/slices/extensionsSlice.ts | 33 +++++++++ 4 files changed, 140 insertions(+), 15 deletions(-) diff --git a/src/renderer/components/extensions/ExtensionStoreView.tsx b/src/renderer/components/extensions/ExtensionStoreView.tsx index 8f0c7130..fbcf7d5e 100644 --- a/src/renderer/components/extensions/ExtensionStoreView.tsx +++ b/src/renderer/components/extensions/ExtensionStoreView.tsx @@ -30,6 +30,7 @@ import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger'; export const ExtensionStoreView = (): React.JSX.Element => { const tabId = useTabIdOptional(); const fetchPluginCatalog = useStore((s) => s.fetchPluginCatalog); + const fetchCliStatus = useStore((s) => s.fetchCliStatus); const fetchApiKeys = useStore((s) => s.fetchApiKeys); const fetchSkillsCatalog = useStore((s) => s.fetchSkillsCatalog); const mcpBrowse = useStore((s) => s.mcpBrowse); @@ -38,7 +39,9 @@ export const ExtensionStoreView = (): React.JSX.Element => { const mcpBrowseLoading = useStore((s) => s.mcpBrowseLoading); const skillsLoading = useStore((s) => s.skillsLoading); const cliStatus = useStore((s) => s.cliStatus); + const cliStatusLoading = useStore((s) => s.cliStatusLoading); const cliInstalled = cliStatus?.installed ?? true; // assume installed until checked + const openDashboard = useStore((s) => s.openDashboard); const hasOngoingSessions = useStore((s) => s.sessions.some((sess) => sess.isOngoing)); const projects = useStore((s) => s.projects); const extensionsTabProjectId = useStore((s) => @@ -97,6 +100,10 @@ export const ExtensionStoreView = (): React.JSX.Element => { void fetchPluginCatalog(projectPath ?? undefined); }, [fetchPluginCatalog, projectPath]); + useEffect(() => { + void fetchCliStatus(); + }, [fetchCliStatus]); + // Fetch MCP installed state on mount useEffect(() => { void mcpFetchInstalled(projectPath ?? undefined); @@ -121,6 +128,71 @@ export const ExtensionStoreView = (): React.JSX.Element => { }, [fetchPluginCatalog, fetchSkillsCatalog, mcpBrowse, mcpFetchInstalled, projectPath]); const isRefreshing = pluginCatalogLoading || mcpBrowseLoading || skillsLoading; + const cliStatusBanner = useMemo(() => { + if (cliStatusLoading || cliStatus === null) { + return ( +
+ +
+

Checking Claude CLI availability

+

+ Extensions need Claude CLI to install plugins, run MCP servers, and validate auth. +

+
+
+ ); + } + + if (!cliStatus.installed) { + return ( +
+ +
+

Claude CLI is not available

+

+ Plugin installs are disabled until Claude CLI is installed. Open the Dashboard to + install it and retry. +

+
+ +
+ ); + } + + if (!cliStatus.authLoggedIn) { + return ( +
+ +
+

Claude CLI needs sign-in

+

+ Claude CLI was found + {cliStatus.installedVersion ? ` (${cliStatus.installedVersion})` : ''}, but plugin + installs are disabled until you sign in from the Dashboard. +

+
+ +
+ ); + } + + return ( +
+ +
+

Claude CLI is ready

+

+ Plugins can be installed from this page + {cliStatus.installedVersion ? ` using Claude CLI ${cliStatus.installedVersion}` : ''}. +

+
+
+ ); + }, [cliStatus, cliStatusLoading, openDashboard]); // Browser mode guard if (!api.plugins && !api.mcpRegistry && !api.skills) { @@ -138,6 +210,7 @@ export const ExtensionStoreView = (): React.JSX.Element => { return (
+ {cliStatusBanner}
{/* Header */}
diff --git a/src/renderer/components/extensions/common/InstallButton.tsx b/src/renderer/components/extensions/common/InstallButton.tsx index 5d45a257..ea609745 100644 --- a/src/renderer/components/extensions/common/InstallButton.tsx +++ b/src/renderer/components/extensions/common/InstallButton.tsx @@ -37,8 +37,20 @@ export const InstallButton = ({ errorMessage, }: InstallButtonProps) => { const cliStatus = useStore((s) => s.cliStatus); - const cliMissing = cliStatus !== null && !cliStatus.installed; - const isDisabled = disabled || cliMissing; + const cliStatusLoading = useStore((s) => s.cliStatusLoading); + const cliUnknown = cliStatus === null; + const cliMissing = cliStatus?.installed === false; + const authMissing = cliStatus?.installed === true && !cliStatus.authLoggedIn; + const disableReason = cliStatusLoading + ? 'Checking Claude CLI status...' + : cliUnknown + ? 'Checking Claude CLI availability...' + : cliMissing + ? 'Claude CLI required. Install it from the Dashboard.' + : authMissing + ? 'Claude CLI is installed but not signed in. Open the Dashboard to sign in.' + : null; + const isDisabled = disabled || Boolean(disableReason); const [lastAction, setLastAction] = useState<'install' | 'uninstall' | null>(null); useEffect(() => { @@ -91,23 +103,30 @@ export const InstallButton = ({ ); - if (errorMessage) { + const tooltipMessage = disableReason ?? errorMessage; + + if (tooltipMessage) { return ( - - - - {retryButton} - - {errorMessage} - - +
+ + + + {retryButton} + + {tooltipMessage} + + + {errorMessage && !disableReason ? ( +

{errorMessage}

+ ) : null} +
); } return retryButton; } - // idle — wrap in tooltip when CLI missing + // idle — wrap in tooltip when install is unavailable const button = isInstalled ? (