A Chrome side-panel extension that lets you save, search, and chat with your browsing history via an optional self-hosted backend.
All requests use Content-Type: application/json.
The base URL is whatever the user sets in the Settings panel (settings.serverUrl), stored without a trailing slash.
When the user has configured an Auth Token in Settings, every request includes:
Authorization: Bearer <token>
The token is stored in chrome.storage.local, which is sandboxed to this extension and inaccessible to web pages. It is never appended to URLs (query params appear in server logs; headers don't).
On the backend, validate the header on every endpoint. Unauthenticated requests should return 401 or 403 — the extension surfaces these specifically as "Authentication failed. Check your token."
Liveness check. Called when the user saves a new backend URL in Settings.
{ "ok": true }Any non-2xx response is treated as a configuration error and should be surfaced to the user.
Called every time a page is saved to local storage. Use this to index the page on the backend (e.g. fetch content, generate embeddings, persist to a DB).
{
url: string; // full URL — "https://example.com/article"
name: string; // page <title> — "My Article | Example"
savedAt: number; // Unix ms — 1711234567890
}Body is ignored by the extension.
Called every time a saved page is removed. :url is the full URL, percent-encoded.
DELETE /urls/https%3A%2F%2Fexample.com%2Farticle
Body is ignored.
Called when the user sends a message to Remy. The extension passes the full saved-URL list as context so the backend can filter or embed before calling an LLM.
{
message: string; // user's plain-text question
savedUrls: SavedUrl[]; // full saved list for RAG context (see types below)
}{
reply: string; // Remy's response — rendered as plain text in the chat UI
}Any non-2xx response shows the user:
"Couldn't reach the backend. Check your server URL in Settings."
A missing serverUrl (not yet configured) throws the sentinel "no_backend" error, which shows:
"No backend URL configured. Go to Settings to add one."
Mirror these on the backend — they are the canonical wire format.
/** A single saved page entry. */
interface SavedUrl {
url: string; // fully-qualified URL
name: string; // page <title> at save time
savedAt: number; // Date.now() — milliseconds since Unix epoch
}
/** User-configurable extension settings. */
interface AppSettings {
serverUrl: string; // backend base URL, no trailing slash
authToken: string; // bearer token; empty string if not configured
}The extension persists state in chrome.storage.local under these keys:
| Key | Type | Description |
|---|---|---|
savedUrls |
SavedUrl[] |
Ordered list of saved pages (newest last) |
serverUrl |
string |
Backend base URL entered in Settings; empty string if unset |
authToken |
string |
Bearer token; empty string if not configured |
npm install
npm run dev # Vite + CRXJS with HMRLoad the project root as an unpacked extension at chrome://extensions (enable Developer mode first).
After editing
manifest.config.tsor the background service worker, click Reload onchrome://extensions— HMR does not cover these files.