// Background service worker for Cortex browser extension const CORTEX_API = 'http://localhost:3100/api'; // Create context menu on install chrome.runtime.onInstalled.addListener(() => { // Save page context menu chrome.contextMenus.create({ id: 'cortex-save-page', title: 'Save page to Cortex', contexts: ['page'], }); // Save selection context menu chrome.contextMenus.create({ id: 'cortex-save-selection', title: 'Save selection to Cortex', contexts: ['selection'], }); // Save link context menu chrome.contextMenus.create({ id: 'cortex-save-link', title: 'Save link to Cortex', contexts: ['link'], }); }); // Handle context menu clicks chrome.contextMenus.onClicked.addListener(async (info, tab) => { try { let content = ''; let title = tab.title; if (info.menuItemId === 'cortex-save-selection') { content = info.selectionText || ''; title = `Selection from: ${tab.title}`; } else if (info.menuItemId === 'cortex-save-link') { content = `Link: ${info.linkUrl}`; title = info.linkUrl; } else { // Get full page content const [{ result }] = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: extractPageContent, }); content = result?.content || ''; title = result?.title || tab.title; } // Save to Cortex await saveToCortex({ title, content, url: tab.url, kind: 'memory', tags: ['web-clip'], }); // Show notification (if supported) if (chrome.notifications) { chrome.notifications.create({ type: 'basic', iconUrl: 'icons/icon-48.png', title: 'Saved to Cortex', message: title.slice(0, 50), }); } } catch (err) { console.error('Context menu action failed:', err); } }); // Function to extract page content (injected into page) function extractPageContent() { // Simple content extraction // A full implementation would use Readability const title = document.title; // Try to get main content const article = document.querySelector('article'); const main = document.querySelector('main'); const content = article?.textContent || main?.textContent || document.body.textContent || ''; // Clean up content const cleanContent = content .replace(/\s+/g, ' ') .replace(/\n+/g, '\n') .trim() .slice(0, 50000); // Limit content size return { title, content: cleanContent, url: window.location.href, }; } // Save to Cortex API async function saveToCortex(data) { const response = await fetch(`${CORTEX_API}/nodes`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ kind: data.kind || 'memory', title: data.title, content: data.content, tags: data.tags || ['web-clip'], metadata: { source: { type: 'url', url: data.url, savedAt: Date.now(), }, }, }), }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); } // Listen for messages from content script or popup chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'save') { saveToCortex(request.data) .then(node => sendResponse({ success: true, nodeId: node.id })) .catch(err => sendResponse({ success: false, error: err.message })); return true; // Keep channel open for async response } });