M12: Browser Extension - Chrome/Edge Manifest V3 extension - Popup UI for saving pages - Context menu integration (save page/selection/link) - Background service worker - Content script for extraction M13: Shell Completions - Bash, Zsh, Fish, PowerShell completions - Dynamic node ID completion - Dynamic tag completion - Dynamic graph completion - Auto-install command (--install)
137 lines
3.5 KiB
JavaScript
137 lines
3.5 KiB
JavaScript
// 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
|
|
}
|
|
});
|