Improve Extensions CLI preflight and plugin install diagnostics
Refs: https://github.com/777genius/claude_agent_teams_ui/issues/37
This commit is contained in:
parent
e26310870c
commit
21513bb6f8
4 changed files with 140 additions and 15 deletions
|
|
@ -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 (
|
||||
<div className="bg-surface/70 mx-4 mt-3 flex items-start gap-3 rounded-md border border-border px-4 py-3">
|
||||
<Info className="mt-0.5 size-4 shrink-0 text-text-secondary" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-text">Checking Claude CLI availability</p>
|
||||
<p className="mt-0.5 text-xs text-text-muted">
|
||||
Extensions need Claude CLI to install plugins, run MCP servers, and validate auth.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!cliStatus.installed) {
|
||||
return (
|
||||
<div className="mx-4 mt-3 flex items-start gap-3 rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-amber-300">Claude CLI is not available</p>
|
||||
<p className="mt-0.5 text-xs text-text-muted">
|
||||
Plugin installs are disabled until Claude CLI is installed. Open the Dashboard to
|
||||
install it and retry.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" onClick={openDashboard}>
|
||||
Open Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!cliStatus.authLoggedIn) {
|
||||
return (
|
||||
<div className="mx-4 mt-3 flex items-start gap-3 rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-amber-300">Claude CLI needs sign-in</p>
|
||||
<p className="mt-0.5 text-xs text-text-muted">
|
||||
Claude CLI was found
|
||||
{cliStatus.installedVersion ? ` (${cliStatus.installedVersion})` : ''}, but plugin
|
||||
installs are disabled until you sign in from the Dashboard.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" onClick={openDashboard}>
|
||||
Open Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-4 mt-3 flex items-start gap-3 rounded-md border border-emerald-500/30 bg-emerald-500/5 px-4 py-3">
|
||||
<Info className="mt-0.5 size-4 shrink-0 text-emerald-300" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-emerald-300">Claude CLI is ready</p>
|
||||
<p className="mt-0.5 text-xs text-text-muted">
|
||||
Plugins can be installed from this page
|
||||
{cliStatus.installedVersion ? ` using Claude CLI ${cliStatus.installedVersion}` : ''}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [cliStatus, cliStatusLoading, openDashboard]);
|
||||
|
||||
// Browser mode guard
|
||||
if (!api.plugins && !api.mcpRegistry && !api.skills) {
|
||||
|
|
@ -138,6 +210,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|||
return (
|
||||
<TooltipProvider>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{cliStatusBanner}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
</Button>
|
||||
);
|
||||
|
||||
if (errorMessage) {
|
||||
const tooltipMessage = disableReason ?? errorMessage;
|
||||
|
||||
if (tooltipMessage) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span tabIndex={0}>{retryButton}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-64 text-red-300">{errorMessage}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<div className="flex max-w-64 flex-col items-end gap-1">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span tabIndex={0}>{retryButton}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-64 text-red-300">{tooltipMessage}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{errorMessage && !disableReason ? (
|
||||
<p className="text-right text-[11px] leading-4 text-red-300">{errorMessage}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return retryButton;
|
||||
}
|
||||
|
||||
// idle — wrap in tooltip when CLI missing
|
||||
// idle — wrap in tooltip when install is unavailable
|
||||
const button = isInstalled ? (
|
||||
<Button
|
||||
size={size}
|
||||
|
|
@ -138,14 +157,14 @@ export const InstallButton = ({
|
|||
</Button>
|
||||
);
|
||||
|
||||
if (cliMissing) {
|
||||
if (disableReason) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span tabIndex={0}>{button}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Claude CLI required</TooltipContent>
|
||||
<TooltipContent className="max-w-64">{disableReason}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export const PluginCard = ({ plugin, index, onClick }: PluginCardProps): React.J
|
|||
</p>
|
||||
|
||||
{/* Footer: author + version + install button */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-3 text-xs text-text-muted">
|
||||
<span className="truncate">{plugin.author?.name ?? 'Unknown author'}</span>
|
||||
{plugin.version && (
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
import { api } from '@renderer/api';
|
||||
import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli';
|
||||
|
||||
import type { AppState } from '../types';
|
||||
import type {
|
||||
|
|
@ -143,6 +144,10 @@ function getSkillsCatalogKey(projectPath?: string): string {
|
|||
|
||||
/** Duration to show "success" state before returning to idle */
|
||||
const SUCCESS_DISPLAY_MS = 2_000;
|
||||
const CLI_AUTH_REQUIRED_MESSAGE =
|
||||
'Claude CLI is installed but not signed in. Go to the Dashboard and sign in to enable plugin installs.';
|
||||
const CLI_STATUS_UNKNOWN_MESSAGE =
|
||||
'Unable to verify Claude CLI status. Open the Dashboard and check the CLI before retrying.';
|
||||
|
||||
export const createExtensionsSlice: StateCreator<AppState, [], [], ExtensionsSlice> = (
|
||||
set,
|
||||
|
|
@ -552,8 +557,36 @@ export const createExtensionsSlice: StateCreator<AppState, [], [], ExtensionsSli
|
|||
installPlugin: async (request: PluginInstallRequest) => {
|
||||
if (!api.plugins) return;
|
||||
|
||||
const preflightState = get();
|
||||
if (preflightState.cliStatus === null || preflightState.cliStatusLoading) {
|
||||
try {
|
||||
await preflightState.fetchCliStatus();
|
||||
} catch {
|
||||
// fetchCliStatus stores the error in cliStatusError; map to a user-facing install error below.
|
||||
}
|
||||
}
|
||||
|
||||
const cliStatus = get().cliStatus;
|
||||
const preflightError =
|
||||
cliStatus === null
|
||||
? CLI_STATUS_UNKNOWN_MESSAGE
|
||||
: !cliStatus.installed
|
||||
? CLI_NOT_FOUND_MESSAGE
|
||||
: !cliStatus.authLoggedIn
|
||||
? CLI_AUTH_REQUIRED_MESSAGE
|
||||
: null;
|
||||
|
||||
if (preflightError) {
|
||||
set((prev) => ({
|
||||
pluginInstallProgress: { ...prev.pluginInstallProgress, [request.pluginId]: 'error' },
|
||||
installErrors: { ...prev.installErrors, [request.pluginId]: preflightError },
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
set((prev) => ({
|
||||
pluginInstallProgress: { ...prev.pluginInstallProgress, [request.pluginId]: 'pending' },
|
||||
installErrors: { ...prev.installErrors, [request.pluginId]: '' },
|
||||
}));
|
||||
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue