From 3cf7e77872a5a6459e9e2a5ed5e6fcd7030decba Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:42:25 +0000 Subject: [PATCH] fix: pro plugin and free plugin confusion (#843) --- apps/web/components/integrations-view.tsx | 3 +- .../integrations/plugins-detail.tsx | 937 +++++++++++------- 2 files changed, 590 insertions(+), 350 deletions(-) diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index b192c73f..a2304023 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -48,8 +48,7 @@ const cards: IntegrationCardDef[] = [ id: "plugins", title: "Plugins", description: - "Claude Code, OpenCode, Hermes, and other AI tool integrations", - pro: true, + "Hermes on every plan; Claude Code, OpenCode, OpenClaw, and more with Pro", icon: (
void }) { + return ( +
+
+
+
+ +
+
+

+ Unlock Pro plugins +

+

+ Connect Claude Code, OpenCode, OpenClaw, Cursor, and more with a + Pro plan. +

+
+
+ +
+ {[ + { + icon: Brain, + title: "Context Retention", + desc: "AI remembers your preferences across sessions", + }, + { + icon: Zap, + title: "Instant Recall", + desc: "Past decisions surface automatically when relevant", + }, + { + icon: Key, + title: "Secure & Private", + desc: "Your data stays yours with encrypted storage", + }, + ].map(({ icon: Icon, title, desc }) => ( +
+ +
+

+ {title} +

+

+ {desc} +

+
+
+ ))} +
+ +
+ {Object.values(PLUGIN_CATALOG) + .filter((p) => !isFreeTierPlugin(p.id)) + .map((plugin) => ( +
+ {plugin.name} +
+ ))} + + Claude Code, OpenCode, OpenClaw & more + +
+ + +
+
+ ) +} + +function ConnectedPluginRow({ + plugin, + info, + onRevoke, +}: { + plugin: ConnectedPlugin + info: PluginInfo | undefined + onRevoke: (keyId: string) => void +}) { + return ( +
+
+ {info && ( +
+ {info.name} +
+ )} +
+

+ {info?.name || plugin.pluginId} +

+
+
+ + Connected + + {plugin.keyStart && ( + + {plugin.keyStart}... + + )} +
+
+ +
+
+ ) +} + +function PluginCard({ + plugin, + pluginId, + isConnected, + isCurrentlyConnecting, + connectingPlugin, + needsProUpgrade, + onConnect, + onUpgrade, +}: { + plugin: PluginInfo + pluginId: string + isConnected: boolean + isCurrentlyConnecting: boolean + connectingPlugin: string | null + needsProUpgrade: boolean + onConnect: (id: string) => void + onUpgrade: () => void +}) { + return ( +
+
+
+ {plugin.name} +
+
+
+ + {plugin.name} + + {isConnected && ( + + Connected + + )} +
+

+ {plugin.description} +

+
+
+ +
    + {plugin.features.map((feature) => ( +
  • + + + {feature} + +
  • + ))} +
+ +
+ {isConnected ? ( + + ) : needsProUpgrade ? ( + + ) : ( + + )} +
+ {plugin.docsUrl && ( + + Docs + + )} + {plugin.repoUrl && ( + + GitHub + + )} +
+
+
+ ) +} + export function PluginsDetail() { const { org } = useAuth() const autumn = useCustomer() @@ -178,6 +524,16 @@ export function PluginsDetail() { [connectedPlugins], ) + const freeConnected = useMemo( + () => connectedPlugins.filter((p) => isFreeTierPlugin(p.pluginId)), + [connectedPlugins], + ) + + const proConnected = useMemo( + () => connectedPlugins.filter((p) => !isFreeTierPlugin(p.pluginId)), + [connectedPlugins], + ) + const createPluginKeyMutation = useMutation({ mutationFn: async (pluginId: string) => { const API_URL = @@ -188,7 +544,9 @@ export function PluginsDetail() { }) if (!res.ok) { if (res.status === 403) { - throw new Error("A Pro plan is required to connect plugins.") + throw new Error( + "This plugin requires a Pro plan. Hermes is available on the Free plan.", + ) } const errorData = (await res.json().catch(() => ({}))) as { message?: string @@ -249,139 +607,62 @@ export function PluginsDetail() { const isLoading = autumn.isLoading const availablePlugins = pluginsData?.plugins ?? Object.keys(PLUGIN_CATALOG) + const freePluginIds = useMemo(() => { + const ids = new Set( + availablePlugins.filter( + (id) => PLUGIN_CATALOG[id] && isFreeTierPlugin(id), + ), + ) + if (PLUGIN_CATALOG.hermes) ids.add("hermes") + return [...ids] + }, [availablePlugins]) + + const proPluginIds = useMemo( + () => + availablePlugins.filter( + (id) => PLUGIN_CATALOG[id] && !isFreeTierPlugin(id), + ), + [availablePlugins], + ) + + const allCatalogPluginIds = useMemo( + () => availablePlugins.filter((id) => PLUGIN_CATALOG[id]), + [availablePlugins], + ) + + const showPaidAllInOne = !isLoading && hasProProduct + return ( <> - {/* Marketing hero for free users */} - {!hasProProduct && !isLoading && ( -
-
-
-
- -
-
-

- Unlock Persistent Memory for Your Tools -

-

- Upgrade to Pro to connect plugins and give your AI tools - long-term memory -

-
-
- -
- {[ - { - icon: Brain, - title: "Context Retention", - desc: "AI remembers your preferences across sessions", - }, - { - icon: Zap, - title: "Instant Recall", - desc: "Past decisions surface automatically when relevant", - }, - { - icon: Key, - title: "Secure & Private", - desc: "Your data stays yours with encrypted storage", - }, - ].map(({ icon: Icon, title, desc }) => ( -
- -
-

- {title} -

-

- {desc} -

-
-
- ))} -
- -
- {Object.values(PLUGIN_CATALOG).map((plugin) => ( -
- {plugin.name} -
- ))} - - Claude Code, OpenCode, OpenClaw, Hermes & more - -
- - -
-
- )} -
-
- {/* Connected plugins */} - {connectedPlugins.length > 0 && ( + {showPaidAllInOne ? ( +
+ {connectedPlugins.length > 0 && ( +
+ + Connected + + {connectedPlugins.map((plugin) => ( + + ))} +
+ )} +
- Connected Plugins + {connectedPlugins.length > 0 + ? "Add more plugins" + : "Available plugins"} - {connectedPlugins.map((plugin) => { - const info = PLUGIN_CATALOG[plugin.pluginId] - return ( -
-
- {info && ( -
- {info.name} -
- )} -
-

- {info?.name || plugin.pluginId} -

-
-
- - Connected - - {plugin.keyStart && ( - - {plugin.keyStart}... - - )} -
-
- -
-
- ) - })} -
- )} - - {/* Available plugins */} -
- - {connectedPlugins.length > 0 - ? "Add More Plugins" - : "Available Plugins"} - -
- {availablePlugins.map((pluginId) => { - const plugin = PLUGIN_CATALOG[pluginId] - if (!plugin) return null - - const isConnected = connectedPluginIds.includes(pluginId) - const isCurrentlyConnecting = connectingPlugin === pluginId - - return ( -
-
-
- {plugin.name} -
-
-
- - {plugin.name} - - {isConnected && ( - - Connected - - )} -
-

- {plugin.description} -

-
-
- -
    - {plugin.features.map((feature) => ( -
  • - - - {feature} - -
  • - ))} -
- -
- {isConnected ? ( - - ) : ( - - )} -
- {plugin.docsUrl && ( - - Docs - - )} - {plugin.repoUrl && ( - - GitHub - - )} -
-
-
- ) - })} +
+ {allCatalogPluginIds.map((pluginId) => { + const plugin = PLUGIN_CATALOG[pluginId] + if (!plugin) return null + const isConnected = connectedPluginIds.includes(pluginId) + const isCurrentlyConnecting = connectingPlugin === pluginId + return ( + createPluginKeyMutation.mutate(id)} + onUpgrade={handleUpgrade} + /> + ) + })} +
-
+ ) : ( + + + + Free plugins + + + Pro plugins + + + + +

+ Included on every plan — connect with no upgrade. +

+ + {freeConnected.length > 0 && ( +
+ + Connected + + {freeConnected.map((plugin) => ( + + ))} +
+ )} + +
+ + {freeConnected.length > 0 ? "Add or manage" : "Available"} + +
+ {freePluginIds.map((pluginId) => { + const plugin = PLUGIN_CATALOG[pluginId] + if (!plugin) return null + const isConnected = connectedPluginIds.includes(pluginId) + const isCurrentlyConnecting = connectingPlugin === pluginId + return ( + createPluginKeyMutation.mutate(id)} + onUpgrade={handleUpgrade} + /> + ) + })} +
+
+
+ + + {!hasProProduct && !isLoading && ( + + )} + + {proConnected.length > 0 && ( +
+ + Connected + + {proConnected.map((plugin) => ( + + ))} +
+ )} + +
+ + {proConnected.length > 0 ? "Add more" : "Available plugins"} + +
+ {proPluginIds.map((pluginId) => { + const plugin = PLUGIN_CATALOG[pluginId] + if (!plugin) return null + const isConnected = connectedPluginIds.includes(pluginId) + const isCurrentlyConnecting = connectingPlugin === pluginId + const needsProUpgrade = !hasProProduct + return ( + createPluginKeyMutation.mutate(id)} + onUpgrade={handleUpgrade} + /> + ) + })} +
+
+
+
+ )}
- {/* API Key modal */}