import os from 'os'; import path from 'path'; import fs from 'node:fs'; import fsp from 'node:fs/promises'; import readline from 'readline'; import crypto from 'node:crypto'; import { sessionsDb } from '@/shared/database/repositories/sessions.db.js'; import { extractFirstValidJsonlData, findFilesRecursivelyCreatedAfterLastScan } from '@/modules/providers/shared/session-parser.utils.js'; import { SessionData } from '@/shared/types/session.js'; function md5(input: string): string { return crypto.createHash('md5').update(input).digest('hex'); } export async function extractWorkspacePathFromWorkerLog(filePath: string): Promise { try { const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' }); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); for await (const line of rl) { const match = line.match(/workspacePath=(.*)$/); const firstMatch = match?.[1]; if (firstMatch) { rl.close(); fileStream.close(); return firstMatch; } } } catch { // ignore errors } return null; } export async function processCursorSessionFile(file: string): Promise { const sessionId = path.basename(file, '.jsonl'); const grandparentDir = path.dirname(path.dirname(file)); const workerLogPath = path.join(grandparentDir, 'worker.log'); const workspacePath = await extractWorkspacePathFromWorkerLog(workerLogPath); if (!workspacePath) return null; return extractFirstValidJsonlData(file, (lineJson) => { if (lineJson.role === 'user') { const rawText = lineJson.message?.content?.[0]?.text || ''; // Strip tags and trim const cleanName = rawText.replace(/<\/?user_query>/g, '').trim().split('\n'); return { sessionId: sessionId as string, workspacePath, sessionName: cleanName[0] || "Untitled Cursor Session" }; } return null; }); } export async function processCursorSessions() { try { const cursorBase = path.join(os.homedir(), '.cursor'); const projectsDir = path.join(cursorBase, 'projects'); const projectDirs = await fsp.readdir(projectsDir); const seenWorkspacePaths = new Set(); for (const projectDir of projectDirs) { const workerLogPath = path.join(projectsDir, projectDir, 'worker.log'); const workspacePath = await extractWorkspacePathFromWorkerLog(workerLogPath); if (!workspacePath || seenWorkspacePaths.has(workspacePath)) continue; seenWorkspacePaths.add(workspacePath); const workspaceHash = md5(workspacePath); const chatsDir = path.join(cursorBase, 'chats', workspaceHash); const sessionFiles = await findFilesRecursivelyCreatedAfterLastScan(chatsDir, '.jsonl'); for (const file of sessionFiles) { const result = await processCursorSessionFile(file); if (result) { sessionsDb.createSession(result.sessionId, 'cursor', result.workspacePath, result.sessionName); } } } } catch (e) { // Base cursor directory or projects directory likely doesn't exist } }