Files
cortex/docs/milestones/12-browser-extension.md
omigamedev d484f61b29 Add development plan with 13 milestone specifications
- 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
2026-02-03 09:36:08 +01:00

8.3 KiB

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)

{
  "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

// 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

// 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

<!-- 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

// 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