diff --git a/README.md b/README.md index 4f0e32e5..9701d0e2 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ It also comes with an agent embedded inside, which we call Nova. ### Supermemory Plugins -Supermemory comes built with Plugins for Claude Code, OpenCode and OpenClaw. +Supermemory comes built with Plugins for Claude Code, OpenCode, OpenClaw, and Hermes. image @@ -106,6 +106,7 @@ You can find them here: - Openclaw plugin: https://github.com/supermemoryai/openclaw-supermemory - Claude code plugin: https://github.com/supermemoryai/claude-supermemory - OpenCode plugin: https://github.com/supermemoryai/opencode-supermemory +- Hermes agent (Supermemory memory provider): https://github.com/NousResearch/hermes-agent ### MCP - Quick install @@ -137,7 +138,7 @@ Memory is scoped with **projects** (container tags) so you can separate work and ### Supported clients -**Claude Desktop** · **Cursor** · **Windsurf** · **VS Code** · **Claude Code** · **OpenCode** · **OpenClaw** +**Claude Desktop** · **Cursor** · **Windsurf** · **VS Code** · **Claude Code** · **OpenCode** · **OpenClaw** · **Hermes** The MCP server is open source — [view the source](https://supermemory.ai/docs/supermemory-mcp/mcp). diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index d2b90952..9ad372ed 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -41,6 +41,8 @@ import { fullscreenParam, chatParam, integrationParam, + pluginsPanelParam, + type IntegrationParamValue, } from "@/lib/search-params" type DocumentsResponse = z.infer @@ -121,7 +123,17 @@ export default function NewPage() { fullscreenParam, ) const [isChatOpen, setIsChatOpen] = useQueryState("chat", chatParam) - const [, setIntegration] = useQueryState("integration", integrationParam) + const [integrationFromUrl, setIntegration] = useQueryState( + "integration", + integrationParam, + ) + const [pluginsPanelFromUrl] = useQueryState("plugins", pluginsPanelParam) + + useEffect(() => { + if (integrationFromUrl || pluginsPanelFromUrl === true) { + void setViewMode("integrations") + } + }, [integrationFromUrl, pluginsPanelFromUrl, setViewMode]) // Ephemeral local state (not worth URL-encoding) const [fullscreenInitialContent, setFullscreenInitialContent] = useState("") @@ -378,7 +390,7 @@ export default function NewPage() { ) const handleOpenIntegrations = useCallback( - (integration?: "import" | "chrome" | "connections") => { + (integration?: IntegrationParamValue) => { setViewMode("integrations") if (integration) { setIntegration(integration) diff --git a/apps/web/app/auth/connect/page.tsx b/apps/web/app/auth/connect/page.tsx index 55411177..5465c38b 100644 --- a/apps/web/app/auth/connect/page.tsx +++ b/apps/web/app/auth/connect/page.tsx @@ -67,6 +67,16 @@ const PLUGIN_INFO: Record = { ], icon: "/images/plugins/openclaw.svg", }, + hermes: { + name: "Hermes", + description: "Memory layer for Hermes agent", + features: [ + "Semantic search across previous sessions", + "Auto-capture of conversation context", + "Builds persistent user profile from interactions", + ], + icon: "/images/plugins/hermes.svg", + }, cursor: { name: "Cursor", description: @@ -339,7 +349,7 @@ function AuthConnectContent() { @@ -71,6 +73,13 @@ const cards: IntegrationCardDef[] = [ height={24} className="size-6 rounded" /> + Hermes ), }, @@ -156,17 +165,26 @@ export function IntegrationsView() { "integration", integrationParam, ) + const [pluginsPanel, setPluginsPanel] = useQueryState( + "plugins", + pluginsPanelParam, + ) const [selectedCard, setSelectedCard] = useState(null) useEffect(() => { + if (pluginsPanel === true) { + setSelectedCard("plugins") + return + } if (integration && INTEGRATION_TO_CARD[integration]) { setSelectedCard(INTEGRATION_TO_CARD[integration]) } - }, [integration]) + }, [integration, pluginsPanel]) const handleBack = () => { setSelectedCard(null) setIntegration(null) + void setPluginsPanel(null) } switch (selectedCard) { diff --git a/apps/web/components/integrations/plugins-detail.tsx b/apps/web/components/integrations/plugins-detail.tsx index f3dd8b00..3d27b246 100644 --- a/apps/web/components/integrations/plugins-detail.tsx +++ b/apps/web/components/integrations/plugins-detail.tsx @@ -83,6 +83,19 @@ const PLUGIN_CATALOG: Record = { docsUrl: "https://docs.supermemory.ai/integrations/openclaw", repoUrl: "https://github.com/supermemoryai/openclaw-supermemory", }, + hermes: { + id: "hermes", + name: "Hermes", + description: "Memory layer for Hermes agent", + features: [ + "Semantic search across previous sessions", + "Auto-capture of conversation context", + "Builds persistent user profile from interactions", + ], + icon: "/images/plugins/hermes.svg", + docsUrl: "https://docs.supermemory.ai/integrations/hermes", + repoUrl: "https://github.com/NousResearch/hermes-agent", + }, } interface ConnectedPlugin { @@ -335,7 +348,7 @@ export function PluginsDetail() { "text-[12px] text-[#737373]", )} > - Claude Code, OpenCode, OpenClaw & more + Claude Code, OpenCode, OpenClaw, Hermes & more diff --git a/apps/web/components/memories-grid.tsx b/apps/web/components/memories-grid.tsx index aefbb464..358a891a 100644 --- a/apps/web/components/memories-grid.tsx +++ b/apps/web/components/memories-grid.tsx @@ -29,7 +29,10 @@ import { QuickNoteCard } from "./quick-note-card" import { HighlightsCard, type HighlightItem } from "./highlights-card" import { GraphCard } from "./memory-graph" import { Button } from "@ui/components/button" -import { categoriesParam } from "@/lib/search-params" +import { + categoriesParam, + type IntegrationParamValue, +} from "@/lib/search-params" import { NovaEmptyState } from "@/components/nova/nova-empty-state" import { AlertDialog, @@ -143,9 +146,7 @@ interface HighlightsProps { interface NovaEmptyStateProps { onAddMemory: (tab: "note" | "link") => void - onOpenIntegrations: ( - integration?: "import" | "chrome" | "connections", - ) => void + onOpenIntegrations: (integration?: IntegrationParamValue) => void isAllSpaces: boolean spaceName?: string onSwitchToAllSpaces?: () => void diff --git a/apps/web/components/nova/nova-empty-state.tsx b/apps/web/components/nova/nova-empty-state.tsx index 24be5b79..d336f5f3 100644 --- a/apps/web/components/nova/nova-empty-state.tsx +++ b/apps/web/components/nova/nova-empty-state.tsx @@ -1,6 +1,7 @@ "use client" import { CHROME_EXTENSION_URL } from "@repo/lib/constants" +import type { IntegrationParamValue } from "@/lib/search-params" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" import { Button } from "@ui/components/button" @@ -10,9 +11,7 @@ import { ArrowRight, Link2, FileText, Zap } from "lucide-react" interface NovaEmptyStateProps { onAddMemory: (tab: "note" | "link") => void - onOpenIntegrations: ( - integration?: "import" | "chrome" | "connections", - ) => void + onOpenIntegrations: (integration?: IntegrationParamValue) => void isAllSpaces: boolean spaceName?: string onSwitchToAllSpaces?: () => void diff --git a/apps/web/lib/search-params.ts b/apps/web/lib/search-params.ts index 67d74ccb..8eed98f0 100644 --- a/apps/web/lib/search-params.ts +++ b/apps/web/lib/search-params.ts @@ -29,6 +29,8 @@ export type IntegrationParamValue = (typeof integrationLiterals)[number] export const integrationParam = parseAsStringLiteral(integrationLiterals) export type ViewParamValue = (typeof viewLiterals)[number] export const viewParam = parseAsStringLiteral(viewLiterals).withDefault("list") + +export const pluginsPanelParam = parseAsBoolean export const categoriesParam = parseAsArrayOf(parseAsString, ",").withDefault( [], ) diff --git a/apps/web/public/images/plugins/hermes.svg b/apps/web/public/images/plugins/hermes.svg new file mode 100644 index 00000000..76669e00 --- /dev/null +++ b/apps/web/public/images/plugins/hermes.svg @@ -0,0 +1 @@ +NousResearch \ No newline at end of file