- docs/plan.md: Master roadmap with phases and priorities - docs/milestones/01-13: Detailed specs for each feature - Updated CLAUDE.md with plan references and build commands Milestones cover: - Phase 1: Temporal versioning, auto-capture, context injection, codebase indexing - Phase 2: Daily journal, content ingestion, graph visualization, import/export - Phase 3: Multi-graph, smart retrieval, TUI dashboard, browser extension, shell completions
340 lines
8.3 KiB
Markdown
340 lines
8.3 KiB
Markdown
# Milestone 12: Browser Extension
|
|
|
|
## Overview
|
|
|
|
Browser extension to save content from any webpage directly to Cortex. One-click capture for research and documentation.
|
|
|
|
## Motivation
|
|
|
|
- Web research is a major knowledge source
|
|
- Manual copy-paste is friction
|
|
- Supermemory's browser extension is popular
|
|
- Capture context while browsing
|
|
|
|
## Features
|
|
|
|
### 12.1 Quick Save
|
|
|
|
Right-click any page → "Save to Cortex"
|
|
|
|
### 12.2 Selection Save
|
|
|
|
Select text → Right-click → "Save selection to Cortex"
|
|
|
|
### 12.3 Popup Interface
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ 🧠 Save to Cortex │
|
|
├─────────────────────────────────────────┤
|
|
│ Title: [API Documentation ] │
|
|
│ Kind: [memory ▼] │
|
|
│ Tags: [docs, api, reference ] │
|
|
│ │
|
|
│ ☑ Include page content │
|
|
│ ☐ Include selection only │
|
|
│ ☐ Include screenshot │
|
|
│ │
|
|
│ [Save] [Cancel] │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
### 12.4 Auto-Extract
|
|
|
|
Automatically extract:
|
|
- Page title
|
|
- Main content (via Readability)
|
|
- Meta description
|
|
- Author/date if available
|
|
|
|
### 12.5 Tag Suggestions
|
|
|
|
Suggest tags based on:
|
|
- Page content analysis
|
|
- Existing tags in Cortex
|
|
- URL domain
|
|
|
|
### 12.6 Local Communication
|
|
|
|
Extension communicates with local Cortex server:
|
|
- Native messaging (preferred)
|
|
- Local HTTP API fallback
|
|
|
|
## Implementation
|
|
|
|
### Extension Structure
|
|
|
|
```
|
|
extension/
|
|
├── manifest.json
|
|
├── popup/
|
|
│ ├── popup.html
|
|
│ ├── popup.js
|
|
│ └── popup.css
|
|
├── content/
|
|
│ └── content.js # Injected into pages
|
|
├── background/
|
|
│ └── background.js # Service worker
|
|
├── icons/
|
|
│ ├── icon-16.png
|
|
│ ├── icon-48.png
|
|
│ └── icon-128.png
|
|
└── native/
|
|
└── cortex-native.js # Native messaging host
|
|
```
|
|
|
|
### Manifest (v3)
|
|
|
|
```json
|
|
{
|
|
"manifest_version": 3,
|
|
"name": "Cortex - Save to Memory",
|
|
"version": "1.0.0",
|
|
"description": "Save web content to your Cortex knowledge graph",
|
|
"permissions": [
|
|
"activeTab",
|
|
"contextMenus",
|
|
"storage",
|
|
"nativeMessaging"
|
|
],
|
|
"host_permissions": [
|
|
"http://localhost:3100/*"
|
|
],
|
|
"action": {
|
|
"default_popup": "popup/popup.html",
|
|
"default_icon": {
|
|
"16": "icons/icon-16.png",
|
|
"48": "icons/icon-48.png",
|
|
"128": "icons/icon-128.png"
|
|
}
|
|
},
|
|
"background": {
|
|
"service_worker": "background/background.js"
|
|
},
|
|
"content_scripts": [
|
|
{
|
|
"matches": ["<all_urls>"],
|
|
"js": ["content/content.js"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Content Script
|
|
|
|
```javascript
|
|
// content/content.js
|
|
// Extract page content using Readability
|
|
function extractContent() {
|
|
const clone = document.cloneNode(true);
|
|
const reader = new Readability(clone);
|
|
const article = reader.parse();
|
|
|
|
return {
|
|
title: article?.title || document.title,
|
|
content: article?.textContent || '',
|
|
excerpt: article?.excerpt || '',
|
|
url: window.location.href,
|
|
selection: window.getSelection()?.toString() || '',
|
|
};
|
|
}
|
|
|
|
// Listen for messages from popup/background
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
if (request.action === 'extract') {
|
|
sendResponse(extractContent());
|
|
}
|
|
});
|
|
```
|
|
|
|
### Background Script
|
|
|
|
```javascript
|
|
// background/background.js
|
|
// Context menu
|
|
chrome.runtime.onInstalled.addListener(() => {
|
|
chrome.contextMenus.create({
|
|
id: 'save-to-cortex',
|
|
title: 'Save to Cortex',
|
|
contexts: ['page', 'selection'],
|
|
});
|
|
});
|
|
|
|
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
|
if (info.menuItemId === 'save-to-cortex') {
|
|
// Get content from page
|
|
const [{ result }] = await chrome.scripting.executeScript({
|
|
target: { tabId: tab.id },
|
|
func: () => window.__cortexExtract(),
|
|
});
|
|
|
|
// Save via native messaging or HTTP
|
|
await saveToCortext(result);
|
|
}
|
|
});
|
|
|
|
async function saveToCortex(content) {
|
|
try {
|
|
// Try native messaging first
|
|
const response = await chrome.runtime.sendNativeMessage('cortex', {
|
|
action: 'save',
|
|
data: content,
|
|
});
|
|
return response;
|
|
} catch {
|
|
// Fall back to HTTP API
|
|
const response = await fetch('http://localhost:3100/api/nodes', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
kind: 'memory',
|
|
title: content.title,
|
|
content: content.content,
|
|
tags: ['web-clip'],
|
|
metadata: {
|
|
source: { type: 'url', url: content.url },
|
|
},
|
|
}),
|
|
});
|
|
return response.json();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Popup UI
|
|
|
|
```html
|
|
<!-- popup/popup.html -->
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="popup.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🧠 Save to Cortex</h1>
|
|
|
|
<div class="field">
|
|
<label>Title</label>
|
|
<input type="text" id="title" />
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Kind</label>
|
|
<select id="kind">
|
|
<option value="memory">Memory</option>
|
|
<option value="component">Component</option>
|
|
<option value="decision">Decision</option>
|
|
<option value="task">Task</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>Tags</label>
|
|
<input type="text" id="tags" placeholder="comma, separated" />
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>
|
|
<input type="checkbox" id="includeContent" checked />
|
|
Include page content
|
|
</label>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button id="save">Save</button>
|
|
<button id="cancel">Cancel</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="popup.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Native Messaging Host
|
|
|
|
```javascript
|
|
// native/cortex-native.js
|
|
#!/usr/bin/env node
|
|
const { addNode } = require('../dist/core/store');
|
|
|
|
process.stdin.on('readable', () => {
|
|
// Read message length (4 bytes)
|
|
const lengthBuf = process.stdin.read(4);
|
|
if (!lengthBuf) return;
|
|
|
|
const length = lengthBuf.readUInt32LE(0);
|
|
const messageBuf = process.stdin.read(length);
|
|
const message = JSON.parse(messageBuf.toString());
|
|
|
|
handleMessage(message).then(response => {
|
|
const responseBuf = Buffer.from(JSON.stringify(response));
|
|
const lengthBuf = Buffer.alloc(4);
|
|
lengthBuf.writeUInt32LE(responseBuf.length, 0);
|
|
process.stdout.write(lengthBuf);
|
|
process.stdout.write(responseBuf);
|
|
});
|
|
});
|
|
|
|
async function handleMessage(message) {
|
|
if (message.action === 'save') {
|
|
const node = await addNode({
|
|
kind: message.data.kind || 'memory',
|
|
title: message.data.title,
|
|
content: message.data.content,
|
|
tags: message.data.tags || ['web-clip'],
|
|
metadata: { source: message.data.source },
|
|
});
|
|
return { success: true, nodeId: node.id };
|
|
}
|
|
return { success: false, error: 'Unknown action' };
|
|
}
|
|
```
|
|
|
|
## CLI Commands
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `cortex extension install` | Install native messaging host |
|
|
| `cortex extension status` | Check extension connectivity |
|
|
|
|
## Testing
|
|
|
|
- [ ] Extension installs in Chrome/Edge
|
|
- [ ] Context menu appears
|
|
- [ ] Popup opens and pre-fills data
|
|
- [ ] Save creates node in Cortex
|
|
- [ ] Selection save captures selected text
|
|
- [ ] Native messaging works
|
|
- [ ] HTTP fallback works
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] One-click save from any webpage
|
|
- [ ] Content extracted cleanly
|
|
- [ ] Tags can be added before save
|
|
- [ ] Works without Cortex server running (queues)
|
|
- [ ] Firefox + Chrome support
|
|
|
|
## Estimated Effort
|
|
|
|
- Extension scaffold: 2 hours
|
|
- Content extraction: 3 hours
|
|
- Popup UI: 4 hours
|
|
- Background worker: 3 hours
|
|
- Native messaging: 4 hours
|
|
- Testing/polish: 4 hours
|
|
- **Total: ~20 hours**
|
|
|
|
## Dependencies
|
|
|
|
- `@anthropic/readability` for content extraction
|
|
- Chrome/Edge extension APIs
|
|
|
|
## References
|
|
|
|
- [Chrome Extension Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/)
|
|
- [Native Messaging](https://developer.chrome.com/docs/extensions/mv3/nativeMessaging/)
|
|
- [Readability](https://github.com/mozilla/readability)
|