Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/renderer/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,32 @@ body {
margin: 0;
}

.welcome-graphnosis-card {
margin-top: 24px;
padding: 16px 18px;
border: 1px solid var(--border-subtle);
border-radius: 12px;
background: var(--surface-elevated, rgba(255, 255, 255, 0.03));
display: flex;
flex-direction: column;
gap: 12px;
max-width: 420px;
text-align: left;
}

.welcome-graphnosis-title {
font-size: 15px;
font-weight: 600;
margin-bottom: 6px;
}

.welcome-graphnosis-body {
margin: 0;
font-size: 13px;
line-height: 1.5;
color: var(--text-muted);
}

/* ========================================================================
INSTALL SCREEN
======================================================================== */
Expand Down Expand Up @@ -12899,6 +12925,25 @@ body {
border-radius: var(--radius-md);
}

.tools-graphnosis-promo {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
padding: 14px 16px;
border: 1px solid var(--border-subtle);
border-radius: 12px;
background: var(--surface-elevated, rgba(255, 255, 255, 0.03));
}

.tools-graphnosis-promo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

.tools-mcp-card {
cursor: default;
}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/screens/Memory/MemoryProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const PROVIDER_URLS: Record<string, string> = {
retaindb: "https://retaindb.com",
supermemory: "https://supermemory.ai",
byterover: "https://app.byterover.dev",
graphnosis: "https://graphnosis.com/download",
};

interface MemoryProvidersProps {
Expand Down
67 changes: 67 additions & 0 deletions src/renderer/src/screens/Tools/Tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,36 @@ function Tools({
})
: mcpServers;

const graphnosisMcpInstalled = mcpServers.some((s) => s.name === "graphnosis");

async function handleInstallGraphnosisMcp(): Promise<void> {
setMcpError("");
setMcpMessage("");
setMcpBusy("install:graphnosis");
try {
const result = await window.hermesAPI.addMcpServer(
{
name: "graphnosis",
type: "stdio",
command: "npx",
args: ["-y", "@graphnosis/mcp-relay", "${HOME}/.graphnosis/mcp.sock"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Home Variable Remains Literal

The MCP config writer preserves ${HOME}/.graphnosis/mcp.sock as a literal argv value, and stdio servers launched directly do not receive shell expansion. Unless the relay explicitly expands this syntax itself, the installed server cannot find the user's socket even though installation reports success; the value is also not portable to Windows.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hermes-agent interpolates ${VAR} placeholders in MCP server config before spawn (tools/mcp_tool.py, "Recursively resolve ${VAR} placeholders"), so on macOS/Linux ${HOME} resolves to the real socket path — this matches the convention the other Graphnosis surfaces already use (the relay's own README and the prior catalog preset). You're right that it's not robust on Windows, where HOME is typically unset and the placeholder would stay literal. That's a pre-existing, cross-surface gap rather than something specific to this PR; the proper fix is to have @graphnosis/mcp-relay default to os.homedir()/.graphnosis/mcp.sock when no path arg is given, which I'm tracking on the relay package. Keeping ${HOME} here for consistency with the existing providers.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: the relay fix has landed. @graphnosis/mcp-relay@1.0.3 (published) now expands a leading ~ and ${HOME}/$HOME itself via os.homedir() (and defaults to ~/.graphnosis/mcp.sock when no path arg is given), so the socket path resolves correctly even when the host spawns the server without a shell — including on Windows where HOME is unset. The ${HOME} value written here is therefore portable once users are on relay ≥1.0.3.

env: {},
},
profile,
);
if (!result.success) {
setMcpError(result.error || t("tools.mcpInstallFailed"));
return;
}
setMcpMessage(t("tools.mcpInstalled"));
await reloadMcp();
} catch (err) {
setMcpError((err as Error).message || t("tools.mcpInstallFailed"));
} finally {
setMcpBusy("");
}
}

if (loading) {
return (
<div className="tools-container">
Expand Down Expand Up @@ -490,6 +520,43 @@ function Tools({
{mcpError && <div className="tools-error">{mcpError}</div>}
{mcpMessage && <div className="tools-success">{mcpMessage}</div>}

{!graphnosisMcpInstalled && (
<div className="tools-graphnosis-promo">
<div>
<div className="tools-card-label">
{t("tools.graphnosisMcpTitle")}
</div>
<div className="tools-card-description">
{t("tools.graphnosisMcpDescription")}
</div>
</div>
<div className="tools-graphnosis-promo-actions">
<button
type="button"
className="btn btn-primary btn-sm"
disabled={mcpBusy === "install:graphnosis"}
onClick={() => void handleInstallGraphnosisMcp()}
>
<TinyIcon kind="install" />
{mcpBusy === "install:graphnosis"
? t("tools.mcpInstallWorking")
: t("tools.graphnosisMcpInstall")}
</button>
<button
type="button"
className="btn btn-secondary btn-sm"
onClick={() =>
void window.hermesAPI.openExternal(
"https://graphnosis.com/download",
)
}
>
{t("tools.graphnosisMcpDownload")}
</button>
</div>
</div>
)}

{mcpServers.length === 0 ? (
<div className="tools-empty">
<div className="tools-card-icon">
Expand Down
20 changes: 20 additions & 0 deletions src/renderer/src/screens/Welcome/Welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,26 @@ function Welcome({
</button>
<p className="welcome-note">{t("welcome.installSizeHint")}</p>

<div className="welcome-graphnosis-card">
<div className="welcome-graphnosis-copy">
<div className="welcome-graphnosis-title">
{t("welcome.graphnosisTitle")}
</div>
<p className="welcome-graphnosis-body">{t("welcome.graphnosisBody")}</p>
</div>
<button
type="button"
className="btn btn-secondary btn-sm"
onClick={() =>
void window.hermesAPI.openExternal(
"https://graphnosis.com/download",
)
}
>
{t("welcome.graphnosisDownload")}
</button>
</div>

<div className="welcome-divider">
<span>{t("welcome.dividerOr")}</span>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/shared/i18n/locales/en/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,7 @@ export default {
openviking:
"Session-managed memory with tiered retrieval and knowledge browsing",
byterover: "Persistent knowledge tree with tiered retrieval via brv CLI",
graphnosis:
"Local encrypted engram graph — auto-prefetch, recall, remember. Requires Graphnosis app (no API key)",
Comment on lines +61 to +62

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Local Discovery Misses Description Key

When a local graphnosis plugin directory is discovered, the provider metadata falls back to the bare name because Graphnosis is absent from the known-provider map. MemoryProviders therefore looks up t("graphnosis") instead of this new memory.providers.graphnosis key, so the card displays only graphnosis rather than the added description.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 847ed5b. graphnosis was missing from the KNOWN_PROVIDERS map in src/main/installer.ts (and the remote-mode map in src/main/ssh-remote.ts), so discovery fell back to the bare name and the card looked up t("graphnosis") instead of this key. Added graphnosis: { description: "memory.providers.graphnosis", envVars: [] } to both.

},
} as const;
6 changes: 6 additions & 0 deletions src/shared/i18n/locales/en/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,11 @@ export default {
mcpCatalogLoadFailed: "Failed to load MCP catalog.",
mcpCatalogEmpty: "No catalog entries available.",
mcpInstall: "Install",
mcpInstallWorking: "Installing…",
mcpInstalledStatus: "Installed",
graphnosisMcpTitle: "Graphnosis memory (recommended)",
graphnosisMcpDescription:
"Install the Graphnosis MCP server for recall, remember, edit, and cross-engram search. Requires the Graphnosis app running with cortex unlocked.",
graphnosisMcpInstall: "Install Graphnosis MCP",
graphnosisMcpDownload: "Get Graphnosis app",
} as const;
4 changes: 4 additions & 0 deletions src/shared/i18n/locales/en/welcome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ export default {
connect: "Connect",
remoteHint:
"Leave the key empty if the server accepts unauthenticated requests (e.g. via SSH tunnel to localhost).",
graphnosisTitle: "Pair with Graphnosis",
graphnosisBody:
"Add local encrypted memory to Hermes — auto-prefetch, recall, and MCP tools. Install Graphnosis, unlock your cortex, then enable the Graphnosis memory provider and MCP catalog in Settings.",
graphnosisDownload: "Download Graphnosis",
} as const;