diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index 19a3b9b0..b821af9a 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -71,8 +71,14 @@ import { isFreeTierPlugin, normalizePluginClientId, type InstallStep, + type PluginInfo, } from "@/lib/plugin-catalog" -import { INSET, InstallSteps, PillButton } from "./integrations/install-steps" +import { + CopyButton, + INSET, + InstallSteps, + PillButton, +} from "./integrations/install-steps" import { ShortcutsConnectButtons, useShortcutsConnect, @@ -638,6 +644,206 @@ function IconBox({ ) } +const PLUGIN_COMMANDS: InstallStep[] = [ + { + code: "npx supermemory plugin", + copyLabel: "Install plugins", + title: "Install plugins", + description: + "Detect Claude Code, Cursor, OpenCode, and Codex, install your selections, then approve OAuth once in the browser.", + }, + { + code: "npx supermemory plugin login", + copyLabel: "Reconnect plugins", + title: "Reconnect plugins", + description: + "Run browser OAuth again for plugins that are already installed, without reinstalling them.", + }, + { + code: "npx supermemory plugin uninstall", + copyLabel: "Uninstall plugins", + title: "Uninstall plugins", + description: + "Remove selected plugin integrations while keeping your credentials and memories.", + }, +] + +const PLUGIN_COMMAND_CLIENTS = [ + "claude_code", + "cursor", + "codex", + "opencode", +] as const + +type PluginSetupTab = "agent" | "manual" + +const PLUGIN_CLI_TARGETS: Partial> = { + claude_code: "claude", + codex: "codex", + cursor: "cursor", + opencode: "opencode", +} + +function pluginAgentPrompt(plugin: PluginInfo): string { + const cliTarget = PLUGIN_CLI_TARGETS[plugin.id] + if (cliTarget) { + return `Install and connect the Supermemory plugin for ${plugin.name} on this machine. Run \`npx supermemory plugin --only ${cliTarget}\`, complete the browser OAuth flow when it opens, then verify the plugin is installed and authenticated.` + } + + const docsInstruction = plugin.docsUrl + ? ` Follow the official setup instructions at ${plugin.docsUrl}.` + : " Follow its official setup instructions." + return `Install and connect the Supermemory integration for ${plugin.name} on this machine.${docsInstruction} Complete authentication securely, then verify the integration is working.` +} + +function PluginSetupMethodTabs({ + value, + onChange, +}: { + value: PluginSetupTab + onChange: (value: PluginSetupTab) => void +}) { + return ( +
+ {(["agent", "manual"] as const).map((tab) => ( + + ))} +
+ ) +} + +function PluginAgentInstructions({ plugin }: { plugin: PluginInfo }) { + const prompt = pluginAgentPrompt(plugin) + return ( +
+

+ {prompt} +

+ +
+ ) +} + +function PluginCommandsDialog({ + open, + onOpenChange, +}: { + open: boolean + onOpenChange: (open: boolean) => void +}) { + return ( + + + + Supermemory plugin commands + +
+
+ {PLUGIN_COMMAND_CLIENTS.map((pluginId) => { + const plugin = PLUGIN_CATALOG[pluginId] + if (!plugin) return null + return ( + + + + ) + })} +
+
+

+ Plugin commands +

+

+ Install, reconnect, or remove integrations from one CLI. +

+
+ + + +
+
+
+ +
+
+
+

+ Run these commands from your terminal. +

+ + + +
+
+
+ ) +} + type InfoUseCase = { title: string description: string @@ -2478,10 +2684,12 @@ function SectionRail({ label, children, headerSlot, + labelSlot, }: { label: string children: ReactNode headerSlot?: ReactNode + labelSlot?: ReactNode }) { const scrollRef = useRef(null) const [canScrollLeft, setCanScrollLeft] = useState(false) @@ -2523,14 +2731,17 @@ function SectionRail({ return (
-

- {label} -

+
+

+ {label} +

+ {labelSlot} +
{headerSlot} + ) : null + } headerSlot={ cat === "ai-clients" && activeMcpKey ? (
+ + { @@ -3848,7 +4089,10 @@ export function IntegrationsView({ pluginId: open ? s.pluginId : null, loading: open ? s.loading : false, })) - if (!open) void setConnectTarget(null) + if (!open) { + setPluginSetupTab("agent") + void setConnectTarget(null) + } }} >

- {newKey.loading - ? "Generating your key…" - : "Copy your key and run these steps to finish."} + {pluginSetupTab === "agent" + ? "Copy this prompt into your coding agent." + : newKey.loading + ? "Generating your key…" + : "Follow these steps to finish manually."}

@@ -3916,17 +4162,30 @@ export function IntegrationsView({
- {newKey.loading ? ( + + {pluginSetupTab === "agent" && dialogPlugin ? ( + + ) : newKey.loading ? (
Generating your key…
- ) : ( + ) : newKey.key ? ( + ) : ( +
+

+ We couldn't generate the key for the manual setup. +

+ Try again +
)}
@@ -3940,6 +4199,7 @@ export function IntegrationsView({ pluginId: null, loading: false, }) + setPluginSetupTab("agent") void setConnectTarget(null) }} className={cn( @@ -4078,7 +4338,7 @@ export function IntegrationsView({ if (!connectedPluginId) return const pluginId = connectedPluginId setConnectedPluginId(null) - createPluginKeyMutation.mutate(pluginId) + openPluginSetup(pluginId) }} disabled={!!connectingPlugin} > @@ -4109,91 +4369,6 @@ export function IntegrationsView({ - { - if (!open) setFinishSetupPluginId(null) - }} - > - - - Finish setup {finishSetupPlugin?.name ?? "plugin"} - -
- {finishSetupPlugin && ( - - {finishSetupPlugin.name} - - )} -
-

- Finish setup {finishSetupPlugin?.name ?? "plugin"} -

-

- Complete install in the tool — this card turns active after the - first API call. -

-
- - - -
-
-
- {finishSetupSteps.length > 0 ? ( - - ) : ( -

- Open {finishSetupPlugin?.name ?? "the plugin"} and finish - authentication, then send a test memory. -

- )} -
-
-
- - - -
-
-
- {