// Name: Open Recent Cursor Project // Description: Select a recently opened Cursor project/folder/file to open. // Shortcut: opt c import "@johnlindquist/kit" import sqlite3 from "sqlite3" import { homedir, platform } from "node:os" import { existsSync } from "node:fs" import { readFile } from "node:fs/promises" import { join } from "node:path" import { URI } from 'vscode-uri'; // For robust URI parsing function getCursorDbPath(): string { const os = platform() const home = process.env.HOME || process.env.USERPROFILE || '' switch (os) { case 'darwin': { // macOS return join(home, 'Library/Application Support/Cursor/User/globalStorage/state.vscdb') } case 'linux': { // Linux return join(home, '.config/Cursor/User/globalStorage/state.vscdb') } case 'win32': { // Windows return join(process.env.APPDATA || join(home, 'AppData/Roaming'), 'Cursor/User/globalStorage/state.vscdb') } default: { throw new Error(`Unsupported platform: ${os}`) } } } const getVSCodeDbPath = (): string | null => { const home = homedir() let dbPath: string switch (platform()) { case "win32": // Windows dbPath = path.join(home, "AppData", "Roaming", "Code", "User", "globalStorage", "state.vscdb") break case "darwin": // macOS dbPath = path.join(home, "Library", "Application Support", "Code", "User", "globalStorage", "state.vscdb") break case "linux": // Linux dbPath = path.join(home, ".config", "Code", "User", "globalStorage", "state.vscdb") break default: console.error(`Unsupported platform: ${platform}`) return null } if (existsSync(dbPath)) { return dbPath } // Fallback for Insiders or other versions if the main one isn't found const insidersPath = dbPath.replace("Code", "Code - Insiders"); if (existsSync(insidersPath)) { return insidersPath; } console.error(`VS Code state.vscdb not found at ${dbPath} or ${insidersPath}`) return null } const getRecentItems = async (dbPath: string): Promise<Array<{ name: string; value: string; description: string; type: string }>> => { return new Promise((resolve, reject) => { const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => { if (err) { console.error(`[ERROR] Failed to connect to Cursor state database: ${err.message}`) return reject(new Error(`Failed to connect to Cursor state database: ${err.message}`)) } }) db.get("SELECT value FROM ItemTable WHERE key = 'history.recentlyOpenedPathsList'", (err, row: { value: string } | undefined) => { if (err) { db.close() console.error(`[ERROR] Failed to query database: ${err.message}`) return reject(new Error(`Failed to query database: ${err.message}`)) } if (!row || !row.value) { db.close() console.warn(`[WARN] No recent paths found or key doesn't exist`) return resolve([]) } try { const recentPathsData = JSON.parse(row.value) const entries: { folderUri?: string; fileUri?: string; workspace?: { id: string; configPath: string }; workspaceUri?: string }[] = recentPathsData?.entries || [] const choices = entries.map((entry) => { let uriPath: string | undefined let type = "project" if (entry.workspaceUri) { uriPath = entry.workspaceUri type = "workspace" console.log(`[INFO] Found workspaceUri: ${uriPath}`) } else if (entry.workspace && entry.workspace.configPath) { // VS Code sometimes stores workspaces this way uriPath = URI.file(entry.workspace.configPath).toString() type = "workspace" } else if (entry.folderUri) { uriPath = entry.folderUri type = "folder" } else if (entry.fileUri) { uriPath = entry.fileUri type = "file" } if (!uriPath) return null try { const parsedUri = URI.parse(uriPath) const itemPath = parsedUri.fsPath const itemName = type === "workspace" ? `${path.basename(itemPath)}` : path.basename(itemPath) return { name: itemName, value: itemPath, description: `${itemPath}`, type, } } catch (parseError) { console.warn(`[WARN] Skipping entry due to URI parse error: ${uriPath}`, parseError) return null } }).filter(Boolean) as Array<{ name: string; value: string; description: string; type: string }> resolve(choices) } catch (jsonError) { console.error(`[ERROR] Failed to parse JSON from database: ${(jsonError as Error).message}`) reject(new Error(`Failed to parse JSON from database: ${(jsonError as Error).message}`)) } finally { db.close() } }) }) } const dbPath = getCursorDbPath() if (!dbPath) { await div(md(`## Error\nCould not find the VS Code state database. Please ensure VS Code is installed correctly.\n\nChecked paths based on OS: ${platform() === 'darwin' ? '~/Library/Application Support/Code/User/globalStorage/state.vscdb' : platform() === 'win32' ? '%APPDATA%\\Code\\User\\globalStorage\\state.vscdb' : '~/.config/Code/User/globalStorage/state.vscdb'}`)) exit() } try { const recentItems = await getRecentItems(dbPath) if (recentItems.length === 0) { await div(md("## No Recent Projects or Workspaces Found\n\nCursor's history is empty or couldn't be read.")) exit() } const selectedItem = await arg("Select a recent Cursor project, workspace, or file to open:", recentItems.map(p => ({ name: p.name, value: p.value, tag: p.type, description: p.description, }))) await hide() if (selectedItem) { try { await exec(`/usr/local/bin/cursor "${selectedItem}"`) console.log(`[INFO] Opening ${selectedItem} in Cursor...`) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) await div(md(`## Error Opening Item\nFailed to open "${selectedItem}" with Cursor.\nError: ${errorMessage}\n\nMake sure Cursor is installed and the 'cursor' command is in your PATH.`)) } } else { console.log("[INFO] No item selected.") } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) console.error(`[ERROR] An error occurred: ${errorMessage}`) await div(md(`## An Unexpected Error Occurred\n${errorMessage}\nPlease check the console for more details.`)) }