Add browser extension and shell completions (Milestones 12-13)
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)
This commit is contained in:
136
extension/background/background.js
Normal file
136
extension/background/background.js
Normal file
@@ -0,0 +1,136 @@
|
||||
// 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
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user