A private workshop for a few good tools.
First tool in the fire: Socrates — upload a book, get a real summary, keep both.
You drop in a PDF, EPUB, DOCX, TXT or Markdown file. Your browser pulls the text out locally — the file itself never leaves your device. The text goes to a small API, a runner on your own machine reads it end to end with Claude, and a structured summary comes back: core argument, key themes, chapter-by-chapter, takeaways, and the passages worth quoting.
Both the summary and the full text stay in your library, so you can come back and re-read either. Export the summary as PDF, Word or Markdown.
GitHub Pages cannot run server code, and a Claude Pro/Max subscription has no HTTP API — so the work splits three ways:
| Piece | Runs on | Job |
|---|---|---|
apps/web |
GitHub Pages | React app. Google sign-in, text extraction, reading, export |
apps/api |
Cloudflare Worker | Verifies identity, enforces the allowlist, stores books (R2) and jobs (D1) |
apps/runner |
Your Mac or home server | Polls for jobs, drives claude -p on your subscription |
The runner pulls work. Nothing connects to your machine — no open port, no tunnel. The Worker never holds a Claude credential.
See docs/ARCHITECTURE.md for the reasoning and the trust boundaries.
Four things to create: a Google OAuth client, a D1 database, an R2 bucket, and two secrets. Roughly 20 minutes.
git clone https://github.com/rafsunsheikh/prometheus.git
cd prometheus
npm install- Create a project, then Create Credentials → OAuth client ID → Web application.
- Under Authorized JavaScript origins, add both:
https://rafsunsheikh.github.iohttp://localhost:5173(for local development)
- Leave redirect URIs empty — Google Identity Services does not use them here.
- Copy the Client ID. It is a public value; it ships in the bundle by design.
On the OAuth consent screen, set the publishing status to Testing and add the Gmail accounts you want as test users. That is Google's own gate; the allowlist below is yours, and both apply.
npx wrangler login
# Database
npx wrangler d1 create prometheus-db
# → copy the printed database_id into apps/api/wrangler.toml
# Object storage
npx wrangler r2 bucket create prometheus-libraryThen edit apps/api/wrangler.toml:
[vars]
ALLOWED_ORIGINS = "https://rafsunsheikh.github.io,http://localhost:5173"
GOOGLE_CLIENT_ID = "<the client ID from step 2>"
[[d1_databases]]
database_id = "<the id wrangler printed>"The allowlist is deliberately not here — it is a secret, set below.
Set the two secrets and deploy:
cd apps/api
openssl rand -base64 48 | npx wrangler secret put SESSION_SECRET
openssl rand -hex 32 | npx wrangler secret put RUNNER_TOKEN # save this, the runner needs it
# Who may sign in. A secret rather than a var, because this repo is public and
# these are real addresses. "," separates people, "|" joins one person's.
echo 'you@example.com|you.alt@example.com' | npx wrangler secret put ALLOWED_EMAILS
npx wrangler d1 migrations apply prometheus-db --remote
npx wrangler deployNote the deployed URL — https://prometheus-api.<subdomain>.workers.dev.
The machine that runs this must already be signed into Claude Code. Run claude
once interactively and complete the login if you have not.
cp apps/runner/.env.example apps/runner/.envPROMETHEUS_API_URL="https://prometheus-api.<subdomain>.workers.dev"
PROMETHEUS_RUNNER_TOKEN="<the RUNNER_TOKEN from step 3>"
CLAUDE_BIN="/Users/you/.local/bin/claude"
CLAUDE_MODEL="sonnet"
CLAUDE_BINmust point at the real binary, not a shell function. Check withtype claude— if it prints a function body, use the path inside it.
npm run dev:runner # foreground
npm run once -w @prometheus/runner # drain one job and exitTo keep it alive, see apps/runner/com.prometheus.runner.plist.example (macOS)
or apps/runner/prometheus-runner.service.example (Linux).
The web app needs nothing — it is a website. Sign in with Google from any browser and your whole library is there, because books and summaries live in D1 and R2 rather than on any one computer. Uploading works from anywhere too: extraction runs in whatever browser you are sitting at, and only the text is sent on.
The runner is the only part tied to a machine, because it needs your Claude login. Two options:
- Run it on your always-on box (home server, spare laptop). Jobs you queue from a phone or a work laptop get picked up whether or not your Mac is awake.
- Run it on several of your machines at once. This is safe: claiming a job
is an atomic conditional
UPDATE, so two runners can never take the same job, and a long queue simply drains faster.
Each machine needs the repo, apps/runner/.env with the same RUNNER_TOKEN,
and Claude Code already signed in as that user.
Running multiple runners is safe because Prometheus is single-user. Every runner claims from one shared queue regardless of who owns the job, so before adding a second person to
ALLOWED_EMAILS, the claim query must be scoped by owner — otherwise their runner would summarize your books on their machine.
In the GitHub repo, under Settings → Secrets and variables → Actions → Variables, add two repository variables:
| Name | Value |
|---|---|
VITE_API_URL |
https://prometheus-api.<subdomain>.workers.dev |
VITE_GOOGLE_CLIENT_ID |
the client ID from step 2 |
Then Settings → Pages → Source: GitHub Actions, and push to main. The site
lands at https://rafsunsheikh.github.io/prometheus/.
cp apps/api/.dev.vars.example apps/api/.dev.vars # fill in both secrets
cp apps/web/.env.example apps/web/.env.local # point at http://127.0.0.1:8787
npm run db:migrate:local
npm run dev:api # → http://127.0.0.1:8787
npm run dev:web # → http://localhost:5173
npm run dev:runner| Command | What it does |
|---|---|
npm run dev:web |
Vite dev server |
npm run dev:api |
Worker on 127.0.0.1:8787 |
npm run dev:runner |
Poll and summarize continuously |
npm run typecheck |
Typecheck every workspace |
npm run build |
Typecheck the API, build the web bundle |
npm run db:migrate:remote |
Apply migrations to the live D1 |
ALLOWED_EMAILS is a Cloudflare secret, not a committed value — this repo is
public and those are real addresses. To change it:
cd apps/api
echo 'you@example.com|you.alt@example.com, friend@example.com' \
| npx wrangler secret put ALLOWED_EMAILSNo redeploy needed. It is re-read on every request, so removing someone cuts them off immediately rather than when their token happens to expire. If the secret is never set, nobody gets in — it fails closed, and says so distinctly rather than pretending you are simply not on the list.
Two separators, and the difference matters:
ALLOWED_EMAILS = "you@example.com|you.alt@example.com, friend@example.com"
# └──── one person, two addresses ────┘ └─ a different person,separates people. Each gets their own private library.|joins one person's addresses. They all open the same library, and the first address is the identity the books are filed under.
If you forget a |, you get two separate libraries — never an accidentally
shared one. Adding an alias to an existing person is safe: their books stay put,
because ownership follows the first address in the group. Reordering a group,
though, re-files that person's library under a different address, so don't.
Gmail ignores dots and
+tags, and treatsgooglemail.comasgmail.com, so those variants all resolve to the same account automatically. Dots are significant on every other domain.
Whichever address you sign in with, the header shows the library you are in and which account you arrived by.
The consent screen in Testing mode only admits accounts listed as test users. Add every address there too, or sign-in fails before Prometheus ever sees it.
- Scanned PDFs do not work. If the pages are images, there is no text to extract. Run OCR first.
- PDF export goes through the browser's print dialog. That gives real, selectable text and correct pagination, which a canvas-rasterizing library does not — the trade is one extra click on "Save".
- The runner is a single worker. Jobs run one at a time, in order. Parallel headless Claude sessions contend for the same subscription limits.
- A 500-page book takes several minutes and burns a real slice of your subscription allowance. The runner logs the cost of each run.
- Your subscription is for your own use. Keep the allowlist small and personal.
MIT