import { Database } from "bun:sqlite"; import { describe, expect, it } from "bun:test"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { getCursorSessionsStreaming, parseCursorSessionDb, resolveCursorWorkspacePath, } from "./cursor"; function createCursorStoreDb(storeDbPath: string, payloads: string[]): void { const db = new Database(storeDbPath); try { db.run("CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT)"); db.run("CREATE TABLE blobs (id TEXT PRIMARY KEY, data BLOB NOT NULL)"); const meta = { agentId: "cursor-thread-1", name: "Cursor Session", createdAt: Date.UTC(2026, 1, 9, 12, 0, 0), latestRootBlobId: "root", lastUsedModel: "gpt-5", }; const metaHex = Buffer.from(JSON.stringify(meta), "utf8").toString("hex"); db.run("INSERT INTO meta(key, value) VALUES (?, ?)", ["0", metaHex]); for (let i = 0; i < payloads.length; i++) { db.run("INSERT INTO blobs(id, data) VALUES (?, ?)", [ `blob-${i + 1}`, payloads[i], ]); } } finally { db.close(); } } describe("parseCursorSessionDb", () => { it("parses blob messages, deduplicates snapshots, and normalizes text", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-cursor-test-")); const storeDbPath = join(dir, "store.db"); try { createCursorStoreDb(storeDbPath, [ [ 'prefix {"id":"m1","role":"user","content":[{"type":"text","text":"Hello from Cursor"}]}', '{"id":"m1-duplicate","role":"user","content":[{"type":"text","text":"Hello from Cursor"}]}', ].join(""), '{"id":"m2","role":"assistant","content":[{"type":"reasoning","text":"Thinking step"},{"type":"tool-call","name":"list_files","input":{"path":"."}}]}', '{"id":"m3","role":"tool","content":[{"type":"tool-result","output":{"ok":true,"files":["a.ts","b.ts"]}}]}', ]); const session = await parseCursorSessionDb( storeDbPath, "feedbeefdeadbeeffeedbeefdeadbeef", new Date("2026-02-09T12:15:00.000Z"), ); expect(session).not.toBeNull(); expect(session?.providerId).toBe("cursor"); expect(session?.externalThreadId).toBe("cursor-thread-1"); expect(session?.title).toBe("Cursor Session"); expect(session?.projectHash).toBe("feedbeefdeadbeeffeedbeefdeadbeef"); expect(session?.messages).toHaveLength(3); expect(session?.messages[0]?.role).toBe("user"); expect(session?.messages[0]?.text).toContain("Hello from Cursor"); expect(session?.messages[1]?.role).toBe("assistant"); expect(session?.messages[1]?.text).toContain("Thinking step"); expect(session?.messages[1]?.text).toContain("Tool call: list_files"); expect(session?.messages[2]?.role).toBe("tool"); expect(session?.messages[2]?.text).toContain("Tool result"); } finally { await rm(dir, { recursive: true, force: true }); } }); it("resolves workspace hashes from Cursor workspaceStorage workspace.json", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-cursor-workspace-test-")); const workspaceStorageRoot = join(dir, "workspaceStorage"); const workspaceHash = "0123456789abcdef0123456789abcdef"; const workspaceDir = join(workspaceStorageRoot, workspaceHash); try { await mkdir(workspaceDir, { recursive: true }); await writeFile( join(workspaceDir, "workspace.json"), JSON.stringify({ folder: "file:///Users/satchmo/code/scribe", }), "utf8", ); const resolved = await resolveCursorWorkspacePath(workspaceHash, [ workspaceStorageRoot, ]); expect(resolved).toBe("/Users/satchmo/code/scribe"); } finally { await rm(dir, { recursive: true, force: true }); } }); it("attaches resolved workspace projectPath in streaming mode", async () => { const dir = await mkdtemp(join(tmpdir(), "scribe-cursor-stream-test-")); const workspaceHash = "abcdef0123456789abcdef0123456789"; const chatDir = join(dir, "chats", workspaceHash, "chat-1"); const storeDbPath = join(chatDir, "store.db"); const workspaceStorageRoot = join(dir, "workspaceStorage"); const workspaceDir = join(workspaceStorageRoot, workspaceHash); try { await mkdir(chatDir, { recursive: true }); await mkdir(workspaceDir, { recursive: true }); await writeFile( join(workspaceDir, "workspace.json"), JSON.stringify({ folder: "file:///Users/satchmo/code/scribe", }), "utf8", ); createCursorStoreDb(storeDbPath, [ '{"id":"m1","role":"user","content":[{"type":"text","text":"Hello from streaming"}]}', ]); const sessions = []; for await (const session of getCursorSessionsStreaming( ["/Users/satchmo/code"], undefined, { workspaceStorageRoots: [workspaceStorageRoot], dataRoots: [join(dir, "chats")], }, )) { sessions.push(session); } expect(sessions).toHaveLength(1); expect(sessions[0]?.projectHash).toBe(workspaceHash); expect(sessions[0]?.projectPath).toBe("/Users/satchmo/code/scribe"); expect(sessions[0]?.metadata?.workspaceResolved).toBe(true); } finally { await rm(dir, { recursive: true, force: true }); } }); });