import React, { useEffect, useState } from "react"; import { Check, Eye, EyeOff, RefreshCw } from "lucide-react"; import { useI18n } from "../../../../application/i18n/I18nProvider"; import { decryptField } from "../../../../infrastructure/persistence/secureFieldAdapter"; import type { CursorAuthMode } from "../../../../infrastructure/ai/types"; import { Button } from "../../../ui/button"; import { cn } from "../../../../lib/utils"; import { isCursorRuntimeInstalled, type AgentPathInfo } from "./types"; export const CursorSdkCard: React.FC<{ pathInfo: AgentPathInfo | null; isResolvingPath: boolean; encryptedApiKey?: string; authMode: CursorAuthMode; onAuthModeChange: (mode: CursorAuthMode) => void; onSaveApiKey: (apiKey: string) => Promise; onRecheckPath: () => void; }> = ({ pathInfo, isResolvingPath, encryptedApiKey, authMode, onAuthModeChange, onSaveApiKey, onRecheckPath, }) => { const { t } = useI18n(); const [apiKeyDraft, setApiKeyDraft] = useState(""); const [showApiKey, setShowApiKey] = useState(false); const [isDecrypting, setIsDecrypting] = useState(false); const [isSaving, setIsSaving] = useState(false); const [saved, setSaved] = useState(false); useEffect(() => { let cancelled = false; setSaved(false); if (!encryptedApiKey) { setApiKeyDraft(""); return; } setIsDecrypting(true); decryptField(encryptedApiKey) .then((value) => { if (!cancelled) setApiKeyDraft(value ?? ""); }) .catch(() => { if (!cancelled) setApiKeyDraft(""); }) .finally(() => { if (!cancelled) setIsDecrypting(false); }); return () => { cancelled = true; }; }, [encryptedApiKey]); const installed = isCursorRuntimeInstalled(pathInfo); const hasStoredApiKey = Boolean(encryptedApiKey); const usesEnvApiKey = pathInfo?.authSource === "CURSOR_API_KEY" || ( pathInfo?.apiKeyOk && !hasStoredApiKey && pathInfo?.authSource !== "settings" ); // CLI login is only proven by the dedicated probe — never generic `authenticated` // (which is also true for env/settings API keys). const hasCliLogin = Boolean( pathInfo?.cliLoginOk || pathInfo?.authSource === "cli-login", ); const hasAnyApiKey = hasStoredApiKey || Boolean(pathInfo?.apiKeyOk) || usesEnvApiKey || pathInfo?.authSource === "settings" || pathInfo?.authSource === "CURSOR_API_KEY"; const isApiKeyMode = authMode === "api-key"; const isCliMode = authMode === "cli-login"; const available = isCliMode ? hasCliLogin : (hasAnyApiKey && Boolean(pathInfo?.sdkInstalled ?? true)); const canSave = isApiKeyMode && !isSaving && !isDecrypting && (Boolean(apiKeyDraft.trim()) || hasStoredApiKey); const installStatus = isResolvingPath ? t("ai.cursor.detecting") : installed ? t("ai.cursor.installed") : t("ai.cursor.notInstalled"); const authStatus = isCliMode ? hasCliLogin ? (pathInfo?.cliEmail ? t("ai.cursor.cliLoginAs", { email: pathInfo.cliEmail }) : t("ai.cursor.cliLoginOk")) : t("ai.cursor.cliLoginMissing") : hasAnyApiKey ? usesEnvApiKey && !hasStoredApiKey ? t("ai.cursor.apiKeyFromEnv") : t("ai.cursor.apiKeyConfigured") : t("ai.cursor.apiKeyMissing"); const installStatusClassName = isResolvingPath ? "text-muted-foreground" : installed ? "text-emerald-500" : "text-amber-500"; const authStatusClassName = isCliMode ? (hasCliLogin ? "text-emerald-500" : "text-amber-500") : (hasAnyApiKey ? "text-emerald-500" : "text-amber-500"); const handleSave = async () => { if (!isApiKeyMode) return; setIsSaving(true); setSaved(false); try { await onSaveApiKey(apiKeyDraft.trim()); setSaved(true); } finally { setIsSaving(false); } }; return (

{isCliMode ? t("ai.cursor.modeCliHint") : t("ai.cursor.modeApiKeyHint")}

{t("ai.cursor.installStatus")} {installStatus}
{isCliMode ? t("ai.cursor.cliLoginStatus") : t("ai.cursor.apiKeyStatus")} {authStatus}
{!available && (

{isCliMode ? t("ai.cursor.cliLoginHint") : (Boolean(pathInfo?.sdkInstalled) || installed) ? t("ai.cursor.notFoundHint") : t("ai.cursor.notInstalledHint")}

)} {isApiKeyMode ? (
{ setSaved(false); setApiKeyDraft(event.target.value); }} placeholder={ isDecrypting ? t("ai.providers.apiKey.decrypting") : usesEnvApiKey && !hasStoredApiKey ? t("ai.cursor.apiKeyPlaceholder.env") : t("ai.cursor.apiKeyPlaceholder") } disabled={isDecrypting} className="w-full h-8 rounded-md border border-input bg-background px-3 pr-9 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50" />
{usesEnvApiKey && !hasStoredApiKey ? (

{t("ai.cursor.apiKeyEnvHint")}

) : null} {usesEnvApiKey && hasStoredApiKey ? (

{t("ai.cursor.apiKeyOverrideHint")}

) : null}
) : (
)}
); };