UNPKG

cursor-export

Version:

Export Cursor chat and composer history

162 lines (132 loc) 5.36 kB
const fs = require('fs').promises; const path = require('path'); const { convertToMarkdown, convertToHtml } = require('./converters'); const { getSafeFilename } = require('./formatters'); async function createExportDirectories (outputDir) { // Create base output directory await fs.mkdir(outputDir, { recursive: true }); // Create format-specific directories const formats = ['html', 'markdown']; for (const format of formats) { await fs.mkdir(path.join(outputDir, format), { recursive: true }); } // Create json directory separately since it doesn't need workspace subdirectories await fs.mkdir(path.join(outputDir, 'json'), { recursive: true }); } async function exportWorkspace (workspace, outputDir) { // Check if workspace has any data const hasChatTabs = workspace.chatData.tabs && workspace.chatData.tabs.length > 0; const hasComposers = workspace.chatData.composers && workspace.chatData.composers.allComposers && workspace.chatData.composers.allComposers.length > 0; const workspaceName = workspace.workspaceInfo.folder ? path.basename(workspace.workspaceInfo.folder) : workspace.workspaceInfo.id; if (!hasChatTabs && !hasComposers) { console.log('Skipping empty workspace:', workspaceName); return null; } console.log('Processing workspace:', workspaceName); // Create workspace directories for markdown and html only const formats = ['html', 'markdown']; for (const format of formats) { await fs.mkdir(path.join(outputDir, format, workspaceName), { recursive: true }); } // Prepare workspace JSON data const workspaceData = { workspace: workspaceName, workspaceInfo: workspace.workspaceInfo, chats: [], composers: [] }; // Export chat tabs if (hasChatTabs) { for (const tab of workspace.chatData.tabs) { await exportChatTab(tab, workspace, workspaceName, outputDir, workspaceData); } } // Export composers if (hasComposers) { for (const composer of workspace.chatData.composers.allComposers) { await exportComposer(composer, workspace, workspaceName, outputDir, workspaceData); } } // Only save workspace JSON file if there's data if (workspaceData.chats.length > 0 || workspaceData.composers.length > 0) { const jsonPath = path.join(outputDir, 'json', `${workspaceName}.json`); await fs.writeFile(jsonPath, JSON.stringify(workspaceData, null, 2), 'utf-8'); } return workspaceData; } async function exportChatTab (tab, workspace, workspaceName, outputDir, workspaceData) { const filename = getSafeFilename(tab.timestamp, tab.title); // Convert to markdown const markdown = convertToMarkdown(tab, workspace.workspaceInfo); // Save markdown version const mdPath = path.join(outputDir, 'markdown', workspaceName, `${filename}.md`); await fs.writeFile(mdPath, markdown, 'utf-8'); // Convert to HTML and save const html = await convertToHtml(markdown); const htmlPath = path.join(outputDir, 'html', workspaceName, `${filename}.html`); await fs.writeFile(htmlPath, html, 'utf-8'); // Add to workspace JSON data workspaceData.chats.push({ title: tab.title, timestamp: tab.timestamp, conversation: tab.bubbles ? tab.bubbles.map(bubble => ({ role: bubble.type === 'ai' ? 'Cursor' : 'User', text: bubble.text || '', codeBlocks: bubble.codeBlocks || [] })) : [] }); } async function exportComposer (composer, workspace, workspaceName, outputDir, workspaceData) { // Skip empty composers if (!composer.text && (!composer.conversation || composer.conversation.length === 0)) { return; } // Use name if available, otherwise use composerId const title = composer.name || `Composer ${composer.composerId}`; const filename = composer.name ? getSafeFilename(composer.lastUpdatedAt || Date.now(), composer.name) : `composer-${composer.composerId}`; // Create a chat-like object for the composer to reuse convertToMarkdown const composerData = { title: title, bubbles: composer.conversation.map(msg => ({ type: msg.type === 1 ? 'user' : 'ai', text: msg.text || '', codeBlocks: msg.suggestedCodeBlocks || [] })) }; // Convert to markdown const markdown = convertToMarkdown(composerData, workspace.workspaceInfo); // Save markdown version const mdPath = path.join(outputDir, 'markdown', workspaceName, `${filename}.md`); await fs.writeFile(mdPath, markdown, 'utf-8'); // Convert to HTML and save const html = await convertToHtml(markdown); const htmlPath = path.join(outputDir, 'html', workspaceName, `${filename}.html`); await fs.writeFile(htmlPath, html, 'utf-8'); // Add to workspace JSON data workspaceData.composers.push({ title: title, composerId: composer.composerId, lastUpdatedAt: composer.lastUpdatedAt, conversation: composer.conversation.map(msg => ({ role: msg.type === 1 ? 'User' : 'Cursor', text: msg.text || '', codeBlocks: msg.suggestedCodeBlocks || [] })) }); } async function exportAllWorkspaces (chatHistory, outputDir) { await createExportDirectories(outputDir); const results = []; for (const workspace of chatHistory) { const workspaceData = await exportWorkspace(workspace, outputDir); results.push(workspaceData); } return results; } module.exports = { exportAllWorkspaces };