Merge pull request #1794 from paperclipai/fix/cursor-native-auth-check
fix(cursor): check native auth before warning about missing API key
This commit is contained in:
commit
1a4ed8c953
2 changed files with 133 additions and 7 deletions
|
|
@ -12,6 +12,8 @@ import {
|
||||||
ensurePathInEnv,
|
ensurePathInEnv,
|
||||||
runChildProcess,
|
runChildProcess,
|
||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { DEFAULT_CURSOR_LOCAL_MODEL } from "../index.js";
|
import { DEFAULT_CURSOR_LOCAL_MODEL } from "../index.js";
|
||||||
import { parseCursorJsonl } from "./parse.js";
|
import { parseCursorJsonl } from "./parse.js";
|
||||||
|
|
@ -49,6 +51,41 @@ function summarizeProbeDetail(stdout: string, stderr: string, parsedError: strin
|
||||||
return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
|
return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CursorAuthInfo {
|
||||||
|
email: string | null;
|
||||||
|
displayName: string | null;
|
||||||
|
userId: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cursorConfigPath(cursorHome?: string): string {
|
||||||
|
return path.join(cursorHome ?? path.join(os.homedir(), ".cursor"), "cli-config.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readCursorAuthInfo(cursorHome?: string): Promise<CursorAuthInfo | null> {
|
||||||
|
let raw: string;
|
||||||
|
try {
|
||||||
|
raw = await fs.readFile(cursorConfigPath(cursorHome), "utf8");
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof parsed !== "object" || parsed === null) return null;
|
||||||
|
const obj = parsed as Record<string, unknown>;
|
||||||
|
const authInfo = obj.authInfo;
|
||||||
|
if (typeof authInfo !== "object" || authInfo === null) return null;
|
||||||
|
const info = authInfo as Record<string, unknown>;
|
||||||
|
const email = typeof info.email === "string" && info.email.trim().length > 0 ? info.email.trim() : null;
|
||||||
|
const displayName = typeof info.displayName === "string" && info.displayName.trim().length > 0 ? info.displayName.trim() : null;
|
||||||
|
const userId = typeof info.userId === "number" ? info.userId : null;
|
||||||
|
if (!email && !displayName && userId == null) return null;
|
||||||
|
return { email, displayName, userId };
|
||||||
|
}
|
||||||
|
|
||||||
const CURSOR_AUTH_REQUIRED_RE =
|
const CURSOR_AUTH_REQUIRED_RE =
|
||||||
/(?:authentication\s+required|not\s+authenticated|not\s+logged\s+in|unauthorized|invalid(?:\s+or\s+missing)?\s+api(?:[_\s-]?key)?|cursor[_\s-]?api[_\s-]?key|run\s+'?agent\s+login'?\s+first|api(?:[_\s-]?key)?(?:\s+is)?\s+required)/i;
|
/(?:authentication\s+required|not\s+authenticated|not\s+logged\s+in|unauthorized|invalid(?:\s+or\s+missing)?\s+api(?:[_\s-]?key)?|cursor[_\s-]?api[_\s-]?key|run\s+'?agent\s+login'?\s+first|api(?:[_\s-]?key)?(?:\s+is)?\s+required)/i;
|
||||||
|
|
||||||
|
|
@ -109,12 +146,25 @@ export async function testEnvironment(
|
||||||
detail: `Detected in ${source}.`,
|
detail: `Detected in ${source}.`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
checks.push({
|
const cursorHome = isNonEmpty(env.CURSOR_HOME) ? env.CURSOR_HOME : undefined;
|
||||||
code: "cursor_api_key_missing",
|
const cursorAuth = await readCursorAuthInfo(cursorHome).catch(() => null);
|
||||||
level: "warn",
|
if (cursorAuth) {
|
||||||
message: "CURSOR_API_KEY is not set. Cursor runs may fail until authentication is configured.",
|
checks.push({
|
||||||
hint: "Set CURSOR_API_KEY in adapter env or run `agent login`.",
|
code: "cursor_native_auth_present",
|
||||||
});
|
level: "info",
|
||||||
|
message: "Cursor is authenticated via `agent login`.",
|
||||||
|
detail: cursorAuth.email
|
||||||
|
? `Logged in as ${cursorAuth.email}.`
|
||||||
|
: `Credentials found in ${cursorConfigPath(cursorHome)}.`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
checks.push({
|
||||||
|
code: "cursor_api_key_missing",
|
||||||
|
level: "warn",
|
||||||
|
message: "CURSOR_API_KEY is not set. Cursor runs may fail until authentication is configured.",
|
||||||
|
hint: "Set CURSOR_API_KEY in adapter env or run `agent login`.",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canRunProbe =
|
const canRunProbe =
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
@ -28,6 +28,13 @@ console.log(JSON.stringify({
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("cursor environment diagnostics", () => {
|
describe("cursor environment diagnostics", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubEnv("CURSOR_API_KEY", "");
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
});
|
||||||
|
|
||||||
it("creates a missing working directory when cwd is absolute", async () => {
|
it("creates a missing working directory when cwd is absolute", async () => {
|
||||||
const cwd = path.join(
|
const cwd = path.join(
|
||||||
os.tmpdir(),
|
os.tmpdir(),
|
||||||
|
|
@ -116,4 +123,73 @@ describe("cursor environment diagnostics", () => {
|
||||||
expect(args).not.toContain("--trust");
|
expect(args).not.toContain("--trust");
|
||||||
await fs.rm(root, { recursive: true, force: true });
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("emits cursor_native_auth_present when cli-config.json has authInfo and CURSOR_API_KEY is unset", async () => {
|
||||||
|
const root = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`paperclip-cursor-auth-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
);
|
||||||
|
const cursorHome = path.join(root, ".cursor");
|
||||||
|
const cwd = path.join(root, "workspace");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(cursorHome, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(cursorHome, "cli-config.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
authInfo: {
|
||||||
|
email: "test@example.com",
|
||||||
|
displayName: "Test User",
|
||||||
|
userId: 12345,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await testEnvironment({
|
||||||
|
companyId: "company-1",
|
||||||
|
adapterType: "cursor",
|
||||||
|
config: {
|
||||||
|
command: process.execPath,
|
||||||
|
cwd,
|
||||||
|
env: { CURSOR_HOME: cursorHome },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.checks.some((check) => check.code === "cursor_native_auth_present")).toBe(true);
|
||||||
|
expect(result.checks.some((check) => check.code === "cursor_api_key_missing")).toBe(false);
|
||||||
|
const authCheck = result.checks.find((check) => check.code === "cursor_native_auth_present");
|
||||||
|
expect(authCheck?.detail).toContain("test@example.com");
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits cursor_api_key_missing when neither env var nor native auth exists", async () => {
|
||||||
|
const root = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`paperclip-cursor-noauth-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
);
|
||||||
|
const cursorHome = path.join(root, ".cursor");
|
||||||
|
const cwd = path.join(root, "workspace");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(cursorHome, { recursive: true });
|
||||||
|
// No cli-config.json written
|
||||||
|
|
||||||
|
const result = await testEnvironment({
|
||||||
|
companyId: "company-1",
|
||||||
|
adapterType: "cursor",
|
||||||
|
config: {
|
||||||
|
command: process.execPath,
|
||||||
|
cwd,
|
||||||
|
env: { CURSOR_HOME: cursorHome },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.checks.some((check) => check.code === "cursor_api_key_missing")).toBe(true);
|
||||||
|
expect(result.checks.some((check) => check.code === "cursor_native_auth_present")).toBe(false);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue