diff --git a/assets/cursor.png b/assets/cursor.png
new file mode 100644
index 0000000..e256217
Binary files /dev/null and b/assets/cursor.png differ
diff --git a/package.json b/package.json
index 50c5574..2a838df 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"@types/bun": "latest",
"typescript": "^5"
},
- "files": ["dist", "rules", "skills", "commands", "hooks", ".cursor-plugin", ".mcp.json"],
+ "files": ["dist", "rules", "skills", "commands", "hooks", ".cursor-plugin", ".mcp.json", "assets"],
"keywords": ["cursor", "supermemory", "mcp", "memory", "ai", "plugin"],
"license": "MIT",
"repository": {
diff --git a/src/auth.ts b/src/auth.ts
index e854081..1a72b56 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -1,17 +1,137 @@
import path from "node:path";
import os from "node:os";
import fs from "node:fs";
+import { randomBytes } from "node:crypto";
const CREDENTIALS_DIR = path.join(os.homedir(), ".supermemory-cursor");
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
-const AUTH_PORT = 19878;
-const AUTH_URL = "https://console.supermemory.ai/auth/connect";
+const AUTH_URL = process.env.SUPERMEMORY_AUTH_URL || "https://app.supermemory.ai/auth/connect";
+const CURSOR_LOGO_FILE = path.join(import.meta.dir, "..", "assets", "cursor.png");
const SUCCESS_HTML = `
-
Connected to Cursor!
`;
+
+
+ Connected - Supermemory
+
+
+
+
+
+ Connected
+
+ Cursor is connected
+ Supermemory is ready to provide persistent context inside Cursor.
+ You can close this tab and return to Cursor.
+
+
+`;
export function loadCredentials(): { apiKey: string; createdAt: string } | null {
try {
@@ -47,16 +167,31 @@ export async function startAuthFlow(
): Promise<{ success: boolean; apiKey?: string; error?: string }> {
return new Promise((resolve) => {
let settled = false;
+ const stateToken = randomBytes(16).toString("hex");
const server = Bun.serve({
- port: AUTH_PORT,
+ port: 0,
hostname: "127.0.0.1",
fetch(req) {
const url = new URL(req.url);
+
+ if (url.pathname === "/cursor.png") {
+ return new Response(Bun.file(CURSOR_LOGO_FILE), {
+ headers: {
+ "Content-Type": "image/png",
+ "Cache-Control": "no-store",
+ },
+ });
+ }
+
if (url.pathname !== "/callback") {
return new Response("Not found", { status: 404 });
}
+ if (url.searchParams.get("state") !== stateToken) {
+ return new Response("Invalid state", { status: 403 });
+ }
+
const apiKey = url.searchParams.get("apikey") || url.searchParams.get("api_key");
if (!apiKey?.startsWith("sm_")) {
return new Response("Invalid API key", { status: 400 });
@@ -74,12 +209,13 @@ export async function startAuthFlow(
},
});
- const callbackUrl = `http://localhost:${AUTH_PORT}/callback`;
+ const callbackUrl = `http://127.0.0.1:${server.port}/callback?state=${stateToken}`;
const authUrl = `${AUTH_URL}?callback=${encodeURIComponent(callbackUrl)}&client=cursor`;
process.stderr.write(`\nOpen this URL to connect Supermemory to Cursor:\n\n ${authUrl}\n\nWaiting...\n`);
- const opener = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
- Bun.$`${opener} ${authUrl}`.quiet().nothrow();
+ openUrl(authUrl).catch((error) => {
+ process.stderr.write(`Failed to open browser automatically: ${error.message}\n`);
+ });
const timer = setTimeout(() => {
if (!settled) {
@@ -89,3 +225,15 @@ export async function startAuthFlow(
}, timeoutMs);
});
}
+
+async function openUrl(url: string): Promise {
+ if (process.platform === "win32") {
+ const result = await Bun.spawn(["rundll32.exe", "url.dll,FileProtocolHandler", url]).exited;
+ if (result !== 0) throw new Error(`browser opener exited with code ${result}`);
+ return;
+ }
+
+ const opener = process.platform === "darwin" ? "open" : "xdg-open";
+ const result = await Bun.spawn([opener, url]).exited;
+ if (result !== 0) throw new Error(`browser opener exited with code ${result}`);
+}
diff --git a/src/hooks/session-start.ts b/src/hooks/session-start.ts
index 6ac5dbf..af501c9 100644
--- a/src/hooks/session-start.ts
+++ b/src/hooks/session-start.ts
@@ -48,7 +48,7 @@ async function main() {
process.stdout.write(JSON.stringify({
continue: true,
hookSpecificOutput: {
- hookEventName: "sessionStart",
+ hookEventName: "SessionStart",
additionalContext: context,
},
}));