Skip to content

Chrome extension

The Chrome extension is a Manifest V3 build (@crxjs/vite-plugin) with three jobs: pull a web page into your knowledge base with one click, enforce the not-now feed quotas you’ve set for distracting sites, and act as the automation host that lets instant messaging and outreach send and receive through the browser you’re already logged into. All server calls happen in the service worker; the popup and any content scripts reach it through chrome.runtime.sendMessage, never the server directly.

The extension needs activeTab, scripting, tabs, storage, and alarms permissions, and host access to http://127.0.0.1/*, http://localhost/*, and https://*/*. It reads which server to talk to from chrome.storage.local.serverUrl (http://127.0.0.1:4780 by default).

The popup shows a connected indicator (green once health.ping succeeds), a “This page” section naming the active tab, and a Save page + PDF to knowledge base button.

  1. Extract

    The popup grabs the tab’s DOM via chrome.scripting.executeScript, runs it through @mozilla/readability to pull the article content, then converts that to Markdown with turndown, all inside the popup, before anything crosses the wire.

  2. Screenshot

    The service worker captures the visible tab (chrome.tabs.captureVisibleTab) and uploads the image to POST /files.

  3. File it

    ingest.capture lands the result as a page under ingestion/raw, with the source URL stored in metadata.capture.url. The popup confirms with a “Saved” message and a link to the new page.

You can also capture individual posts rather than a whole page, the same Readability + screenshot pipeline runs against a narrower selection, landing under the same ingestion/raw area.

A content script enforces per-site feed quotas set up on not-now, directly in the DOM of the site you’re browsing:

  • The site key is the hostname with a leading www. stripped.
  • Feed items ([data-feed-item], falling back to article on real sites without that marker) are counted as they cross 50% viewport visibility, the same “you actually saw this” threshold rather than counting everything that merely loaded.
  • Counts sync to the server every 3 items and on every visibilitychange (notnow.counters.sync), bucketed into the rule’s configured window (windowStart rounds down to the nearest windowMinutes boundary) and merged server-side with GREATEST, so a sync is idempotent even if it fires twice for the same item.
  • Once the server-side total reaches the rule’s limit, the extension hides the feed container, hides the site’s notification-count button ([data-notif-button]), and overlays a full-page “not now, quota spent, reopens in N min” message.
  • A fresh tab re-checks the running total on load, so the quota is enforced consistently across every device and tab you have open on that site, not just the one that tipped it over.

The service worker doubles as an automation host: it opens the shared WebSocket bus, subscribes to the browser-jobs topic, and drains queued jobs (browser.jobs.pull) on three triggers, a WS invalidate push, a 1-minute chrome.alarms tick (the practical floor for MV3 timers), and every time the service worker restarts. A 20-second WebSocket ping keeps the worker alive longer than its normal idle timeout on Chrome 116 and newer, so an approved job typically runs within about a minute of being queued, even though MV3 service workers are designed to die after roughly 30 seconds idle.

Managed sites are WhatsApp Web, Messenger, and LinkedIn, plus any site with an open fixture tab in development. A per-site opt-out (chrome.storage.local.siteOptOut[site] = true) stops the host from (re)opening that site’s tab automatically, for when you’d rather it stay hands-off.

Opens and pins the site’s tab if it isn’t already, scrapes [data-im-message] elements, and completes with {ok, result: {messages}}. If the page shows [data-logged-out], it completes with {result: {loggedOut: true}} instead, and the server files a connector-logged-out task card with a log-back-in link, routed to whichever user’s job it was (see per-user comms).

Because jobs run through your real, already-logged-in browser session rather than a headless bot account, this is the lowest-ban-risk way to automate sites that actively fight automation. It’s also why the automation host is scoped per user: a job is attributed to the user who triggered it, and a paired device only drains its own user’s jobs (an admin device additionally drains unowned system sweep jobs).