Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
35 changes: 35 additions & 0 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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_ATTEMPTED_FILE = path.join(CREDENTIALS_DIR, ".auth-attempted");
const LOGGED_OUT_FILE = path.join(CREDENTIALS_DIR, ".logged-out");
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");

Expand Down Expand Up @@ -148,6 +150,8 @@ export function saveCredentials(apiKey: string): void {
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
const data = { apiKey, createdAt: new Date().toISOString() };
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
clearAuthAttempted();
clearLoggedOutMarker();
}

export function clearCredentials(): boolean {
Expand All @@ -162,6 +166,37 @@ export function clearCredentials(): boolean {
}
}

export function hasAuthAttempted(): boolean {
return fs.existsSync(AUTH_ATTEMPTED_FILE);
}

export function markAuthAttempted(): void {
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
fs.writeFileSync(AUTH_ATTEMPTED_FILE, new Date().toISOString());
}

export function clearAuthAttempted(): void {
try {
if (fs.existsSync(AUTH_ATTEMPTED_FILE)) fs.unlinkSync(AUTH_ATTEMPTED_FILE);
} catch {}
}

export function isLoggedOut(): boolean {
return fs.existsSync(LOGGED_OUT_FILE);
}

export function markLoggedOut(): void {
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
fs.writeFileSync(LOGGED_OUT_FILE, new Date().toISOString());
clearAuthAttempted();
}

export function clearLoggedOutMarker(): void {
try {
if (fs.existsSync(LOGGED_OUT_FILE)) fs.unlinkSync(LOGGED_OUT_FILE);
} catch {}
}

export async function startAuthFlow(
timeoutMs = 120_000,
): Promise<{ success: boolean; apiKey?: string; error?: string }> {
Expand Down
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {
loadCredentials,
startAuthFlow,
clearCredentials,
clearAuthAttempted,
clearLoggedOutMarker,
markLoggedOut,
} from "./auth.ts";

const command = process.argv[2];
Expand All @@ -19,6 +22,8 @@ switch (command) {
process.exit(0);
}
console.log("Opening browser to authenticate...");
clearAuthAttempted();
clearLoggedOutMarker();
const result = await startAuthFlow();
if (result.success) {
console.log("Authenticated successfully.");
Expand All @@ -31,6 +36,7 @@ switch (command) {

case "logout": {
const removed = clearCredentials();
markLoggedOut();
console.log(removed ? "Logged out." : "No credentials found.");
break;
}
Expand Down
24 changes: 19 additions & 5 deletions src/hooks/session-start.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { loadCredentials } from "../auth.ts";
import {
clearAuthAttempted,
hasAuthAttempted,
isLoggedOut,
markAuthAttempted,
startAuthFlow,
} from "../auth.ts";
import { loadConfig, getApiKey } from "../config.ts";
import { getUserTag, getProjectTag } from "../tags.ts";
import { createClient } from "../client.ts";
Expand All @@ -16,11 +22,19 @@ async function main() {
const raw = await Bun.stdin.text();
const input: SessionStartInput = JSON.parse(raw);

const creds = loadCredentials();
if (!creds) return ok();

const config = loadConfig(input.workspace_roots[0]);
const apiKey = getApiKey(config);
let apiKey = getApiKey(config);
if (!apiKey && !isLoggedOut() && !hasAuthAttempted()) {
try {
markAuthAttempted();
const authResult = await startAuthFlow();
if (authResult.success) {
clearAuthAttempted();
apiKey = getApiKey(config);
}
} catch {}
}

if (!apiKey) return ok();

// Inject user email from input for tag resolution
Expand Down