| import { NextResponse } from "next/server"; |
| import { access, constants } from "fs/promises"; |
| import { homedir } from "os"; |
| import { join } from "path"; |
| import { execFile } from "child_process"; |
| import { promisify } from "util"; |
|
|
| const execFileAsync = promisify(execFile); |
|
|
| const ACCESS_TOKEN_KEYS = ["cursorAuth/accessToken", "cursorAuth/token"]; |
| const MACHINE_ID_KEYS = [ |
| "storage.serviceMachineId", |
| "storage.machineId", |
| "telemetry.machineId", |
| ]; |
|
|
| |
| function getCandidatePaths(platform) { |
| const home = homedir(); |
|
|
| if (platform === "darwin") { |
| return [ |
| join( |
| home, |
| "Library/Application Support/Cursor/User/globalStorage/state.vscdb", |
| ), |
| join( |
| home, |
| "Library/Application Support/Cursor - Insiders/User/globalStorage/state.vscdb", |
| ), |
| ]; |
| } |
|
|
| if (platform === "win32") { |
| const appData = process.env.APPDATA || join(home, "AppData", "Roaming"); |
| const localAppData = |
| process.env.LOCALAPPDATA || join(home, "AppData", "Local"); |
| return [ |
| join(appData, "Cursor", "User", "globalStorage", "state.vscdb"), |
| join( |
| appData, |
| "Cursor - Insiders", |
| "User", |
| "globalStorage", |
| "state.vscdb", |
| ), |
| join(localAppData, "Cursor", "User", "globalStorage", "state.vscdb"), |
| join( |
| localAppData, |
| "Programs", |
| "Cursor", |
| "User", |
| "globalStorage", |
| "state.vscdb", |
| ), |
| ]; |
| } |
|
|
| return [ |
| join(home, ".config/Cursor/User/globalStorage/state.vscdb"), |
| join(home, ".config/cursor/User/globalStorage/state.vscdb"), |
| ]; |
| } |
|
|
| const normalize = (value) => { |
| if (typeof value !== "string") return value; |
| try { |
| const parsed = JSON.parse(value); |
| return typeof parsed === "string" ? parsed : value; |
| } catch { |
| return value; |
| } |
| }; |
|
|
| |
| |
| |
| |
| function extractTokensViaBetterSqlite(dbPath) { |
| |
| |
| const Database = require("better-sqlite3"); |
| const db = new Database(dbPath, { readonly: true, fileMustExist: true }); |
|
|
| const query = (key) => { |
| const row = db.prepare("SELECT value FROM itemTable WHERE key=? LIMIT 1").get(key); |
| return row?.value || null; |
| }; |
|
|
| const normalize = (value) => { |
| if (typeof value !== "string") return value; |
| try { |
| const parsed = JSON.parse(value); |
| return typeof parsed === "string" ? parsed : value; |
| } catch { |
| return value; |
| } |
| }; |
|
|
| let accessToken = null; |
| for (const key of ACCESS_TOKEN_KEYS) { |
| const raw = query(key); |
| if (raw) { accessToken = normalize(raw); break; } |
| } |
|
|
| let machineId = null; |
| for (const key of MACHINE_ID_KEYS) { |
| const raw = query(key); |
| if (raw) { machineId = normalize(raw); break; } |
| } |
|
|
| db.close(); |
| return { accessToken, machineId }; |
| } |
|
|
| |
| |
| |
| |
| async function extractTokensViaCLI(dbPath) { |
| const normalize = (raw) => { |
| const value = raw.trim(); |
| try { |
| const parsed = JSON.parse(value); |
| return typeof parsed === "string" ? parsed : value; |
| } catch { |
| return value; |
| } |
| }; |
|
|
| const query = async (sql) => { |
| const { stdout } = await execFileAsync("sqlite3", [dbPath, sql], { |
| timeout: 10000, |
| }); |
| return stdout.trim(); |
| }; |
|
|
| |
| let accessToken = null; |
| for (const key of ACCESS_TOKEN_KEYS) { |
| try { |
| const raw = await query( |
| `SELECT value FROM itemTable WHERE key='${key}' LIMIT 1`, |
| ); |
| if (raw) { |
| accessToken = normalize(raw); |
| break; |
| } |
| } catch { |
| |
| } |
| } |
|
|
| let machineId = null; |
| for (const key of MACHINE_ID_KEYS) { |
| try { |
| const raw = await query( |
| `SELECT value FROM itemTable WHERE key='${key}' LIMIT 1`, |
| ); |
| if (raw) { |
| machineId = normalize(raw); |
| break; |
| } |
| } catch { |
| |
| } |
| } |
|
|
| return { accessToken, machineId }; |
| } |
|
|
| |
| |
| |
| |
| |
| export async function GET() { |
| try { |
| const platform = process.platform; |
| const candidates = getCandidatePaths(platform); |
|
|
| let dbPath = null; |
| for (const candidate of candidates) { |
| try { |
| await access(candidate, constants.R_OK); |
| dbPath = candidate; |
| break; |
| } catch { |
| |
| } |
| } |
|
|
| if (!dbPath) { |
| return NextResponse.json({ |
| found: false, |
| error: `Cursor database not found. Checked locations:\n${candidates.join("\n")}\n\nMake sure Cursor IDE is installed and opened at least once.`, |
| }); |
| } |
|
|
| |
| if (platform === "linux") { |
| let cursorInstalled = false; |
| try { |
| await execFileAsync("which", ["cursor"], { timeout: 5000 }); |
| cursorInstalled = true; |
| } catch { |
| try { |
| const desktopFile = join(homedir(), ".local/share/applications/cursor.desktop"); |
| await access(desktopFile, constants.R_OK); |
| cursorInstalled = true; |
| } catch { } |
| } |
| if (!cursorInstalled) { |
| return NextResponse.json({ |
| found: false, |
| error: "Cursor config files found but Cursor IDE does not appear to be installed. Skipping auto-import.", |
| }); |
| } |
| } |
|
|
| |
| try { |
| const tokens = extractTokensViaBetterSqlite(dbPath); |
| if (tokens.accessToken && tokens.machineId) { |
| return NextResponse.json({ |
| found: true, |
| accessToken: tokens.accessToken, |
| machineId: tokens.machineId, |
| }); |
| } |
| } catch { |
| |
| } |
|
|
| |
| try { |
| const tokens = await extractTokensViaCLI(dbPath); |
| if (tokens.accessToken && tokens.machineId) { |
| return NextResponse.json({ |
| found: true, |
| accessToken: tokens.accessToken, |
| machineId: tokens.machineId, |
| }); |
| } |
| } catch { |
| |
| } |
|
|
| |
| return NextResponse.json({ found: false, windowsManual: true, dbPath }); |
| } catch (error) { |
| console.log("Cursor auto-import error:", error); |
| return NextResponse.json( |
| { found: false, error: error.message }, |
| { status: 500 }, |
| ); |
| } |
| } |
|
|