A Chrome extension that summarizes the page you are reading, explains anything you highlight, and answers follow-up questions without leaving the tab.
Add to Chrome, or read about it on the live site.
- Page summaries. Click the toolbar icon or press the shortcut and the extension reads the page and returns a summary.
- Four modes. Summary, bullets, key points, or plain English. Switching modes re-reads the page in that style and appends the result, so the conversation above it stays intact. Your choice is remembered for next time.
- Selection summaries. Highlight text and a floating Summarize button appears next to it. The card that opens follows the highlight as you scroll.
- Follow-up questions. Ask anything about the page in the same panel. The thread scrolls on its own, keeps every turn, and only follows new messages when you are already at the bottom.
- Clickable sources. Every answer lists the passages it drew on. Clicking one scrolls to that passage and highlights it, without closing the panel.
- Local caching. Summaries are kept in extension storage for thirty minutes, so reopening a page you already read costs nothing.
- Reading time, and whether it is worth it. The header shows how long the page would take to read. Beside it, a one line verdict: worth reading, worth a skim, or probably skippable, with the reason.
- Any language. Summarize and ask in one of thirteen languages regardless of what the page is written in. Source snippets stay in the original so they can still be found on the page.
- Settings. Behind the gear in the panel header. Panel side and width, selection card size, default format, answer language, and switches for the floating button, the passage highlight, the typing animation and the cache.
| Bullets mode | Follow-up questions |
|---|---|
![]() |
![]() |
| Highlight a passage | Summarize just that passage |
|---|---|
![]() |
![]() |
Cmd+Shift+S on macOS, Ctrl+Shift+S elsewhere. Esc or a click outside closes the panel. Reassign it at chrome://extensions/shortcuts.
Add it from the Chrome Web Store. It talks to a hosted backend by default, so there is nothing to configure. Works in Chrome, Brave, Edge, Arc, and other Chromium browsers.
To run a change you made, or to read what you are running first:
- Clone the repository:
git clone https://github.com/Alitleis123/Eternal-Summary.git
- Open
chrome://extensionsand turn on Developer mode. - Click Load unpacked and select the cloned folder.
A source copy and the store copy can be installed side by side. They are separate extensions to Chrome, with separate storage.
The backend is a small Express service that proxies requests to the Gemini API. Run it if you would rather not use the hosted one.
cd backend
npm install
cp .env.example .env # then put your Gemini API key in it
npm start # listens on port 3000Deploying to Fly.io:
fly launch # first time only
fly secrets set GEMINI_API_KEY=your_key_here
fly deployFly injects secrets as environment variables at runtime, and .env is excluded from the image by .dockerignore, so the key never ships in a build.
GEMINI_API_KEY is required. Without it the server still starts and serves /healthz, but every summarize and ask request fails with a clear message in the logs.
To point the extension at your own deployment, change API_BASE in background.js and the matching entry in host_permissions in manifest.json.
| Method | Path | Body | Returns |
|---|---|---|---|
GET |
/healthz |
ok |
|
POST |
/api/summarize |
{ text, mode, scope } |
{ summary, sources, verdict } |
POST |
/api/ask |
{ text, messages, selection } |
{ answer, sources } |
mode is one of tldr, bullets, key-points, or simple. scope: "selection" summarizes a highlighted passage: no verdict, and an empty summary when there is nothing to condense. Both endpoints are rate limited to 30 requests per minute per IP.
Real Chrome, driven over the DevTools Protocol. Only the chrome.* API surface and the network are stubbed, so the tests exercise the actual message path: the page posts to listener.js, which forwards to background.js, which calls the backend.
npm test # 70 end-to-end tests in real Chrome
npm run check # syntax check every entry point
npm run shots # regenerate the screenshots on the docs site
npm run package # build the Chrome Web Store zipThe fixture page ships deliberately hostile CSS (* { line-height: 1 !important }, uppercase buttons, forced letter spacing) to prove the UI stays isolated. Coverage includes style isolation, markdown rendering, cache expiry and mode persistence, source lookup and highlight cleanup, focus trapping, error and rate-limit paths, and a check that repeated opens strand nothing on the page.
Eternal-Summary/
├── manifest.json Extension manifest (MV3)
├── background.js Service worker. Owns the backend address and every network call.
├── listener.js Content script. Selection button, and the bridge to the page.
├── content.js The panel itself. Runs in page context, injected on demand.
├── ui.css Styles for the panel and the selection card
├── icons/ Extension icons, 16 through 512
├── test/ End-to-end tests and the CDP driver
├── tools/ Screenshot capture and the store package build
├── backend/ Express service that calls the Gemini API
└── docs/ Project site, published with GitHub Pages
Page-context code never sees the backend URL. It names an endpoint, listener.js forwards that to the service worker, and the worker rejects anything outside its allow list before making a request.
The UI renders inside a shadow root, with ui.css loaded into it. Page stylesheets cannot cross that boundary, so a site rule like * { line-height: 1 !important } cannot collapse the panel's text, and the panel cannot leak styles onto the page either.
Page text is sent to the backend only when you ask for a summary or an answer. Nothing is stored server side, and summaries are cached only in your own browser. See Privacy.md.
Eternal Summary License, copyright 2025 Ali Tleis. See LICENSE. You may not redistribute, modify for public use, or use this project commercially without permission.
Ali Tleis, Computer Science at Northeastern University. GitHub




