See your CV across its historical timeline. Upload every version of your CV as it changes over the years. Seevy keeps them all and shows you how it drifted — for better or worse.
- Timeline — CV‑health score and key writing metrics per version, plus a graded, sortable table, so you can tell whether each rewrite actually helped.
- Diff — exactly what text changed between any two versions (word or line level).
- Misfits over time — where the CV regressed: quantified achievements you dropped and never brought back, bullets that got weaker, skills that vanished, buzzwords creeping in, timeline gaps, ATS regressions.
- ATS readiness (Sept 2026) — a 12‑point applicant‑tracking‑system audit per version with an ordered fix list; the score also rides the timeline chart.
- Final boss — every unique bullet you've ever written, clustered and ranked. Bulk‑select the keepers and export a Markdown draft toward one definitive CV.
- Automatic dating — Seevy reads the dates inside each CV and places it on the timeline itself; you can still override.
- Expert review (optional) — with an Anthropic API key, each version can get a written critique from a simulated staff‑engineer + recruiter + career‑coach panel.
The analysis engine is deterministic and runs offline. The only thing that needs a network or a key is the optional AI review.
| Static build (GitHub Pages) | Full build (local / Docker) | |
|---|---|---|
| Runs | 100% in the browser | Node server + SQLite |
| Storage | localStorage (this browser only) |
data/seevy.db + data/uploads/ |
| PDF / DOCX parsing | in‑browser (pdf.js / mammoth via CDN) | pdf-parse / mammoth on the server |
| AI expert review | ✗ | ✓ (with ANTHROPIC_API_KEY) |
| Original file download | ✗ | ✓ |
| Backup / restore | ✓ (JSON export/import) | ✓ |
| Deploy | git push → GitHub Actions → Pages |
npm start or docker compose up |
Both share the exact same analysis engine (src/analyze.js, src/misfits.js,
src/diff.js, src/curator.js); the static build bundles it with a
localStorage backend via esbuild.
Requires Node 22.5+ (Seevy uses the built‑in node:sqlite).
git clone git@github.com:kdbrian/Seevy.git
cd Seevy
npm install
npm start
# open http://localhost:4173Upload a couple of CV versions and every view lights up. Data persists in ./data.
docker compose up --build
# open http://localhost:4173./data is mounted into the container, so your history survives rebuilds.
cp .env.example .env
# then edit .env:
# ANTHROPIC_API_KEY=sk-ant-...
# SEEVY_MODEL=claude-sonnet-5 # optional, default claude-opus-5
npm startA Run expert review button then appears on each version. Reviews are saved with the version. Without a key, everything else is unchanged.
The repo ships .github/workflows/deploy.yml. It builds the static site and
publishes it on every push to main.
One‑time setup: on GitHub → Settings → Pages → Build and deployment → Source: GitHub Actions.
Then:
git push origin mainThe workflow runs npm ci → npm run build:web (esbuild → dist/) →
upload-pages-artifact → deploy-pages, and your site goes live at:
https://kdbrian.github.io/Seevy/
Build it locally to preview the exact artifact:
npm run build:web
npx serve dist # or any static server| Variable | Default | Purpose |
|---|---|---|
PORT |
4173 |
HTTP port (server build) |
HOST |
0.0.0.0 |
Bind address (server build) |
SEEVY_DATA_DIR |
./data |
Where the SQLite db and uploads live |
ANTHROPIC_API_KEY |
— | Enables AI expert review |
ANTHROPIC_AUTH_TOKEN |
— | Alternative to the API key |
SEEVY_MODEL |
claude-opus-5 |
Model used for reviews |
server.js loads a .env file itself — no dotenv dependency.
Every version, in both builds, goes through the same pipeline. The storage layer is the only part that differs.
flowchart TD
A["Upload file or paste text"] --> B{"File type?"}
B -->|pdf| C1["Extract text<br/>pdf-parse (server) / pdf.js (browser)"]
B -->|docx| C2["Extract text<br/>mammoth"]
B -->|txt / md| C3["Read as text"]
C1 --> N["normalize()<br/>src/normalize.js"]
C2 --> N
C3 --> N
N --> AN["analyze(rawText, ctx)<br/>src/analyze.js"]
AN --> M1["Heuristic metrics<br/>quantified %, action verbs,<br/>buzzwords, passive, sections, skills"]
AN --> M2["CV-health score 0–100<br/>+ per-factor breakdown"]
AN --> M3["ATS audit — 12 checks<br/>+ ordered fix list (Sept 2026)"]
AN --> M4["inferDate()<br/>most recent date in the CV"]
M1 --> ST[("Store<br/>SQLite || localStorage")]
M2 --> ST
M3 --> ST
M4 --> ST
ST --> V1["Timeline<br/>graded, sortable"]
ST --> V2["Diff<br/>src/diff.js"]
ST --> V3["Misfits<br/>src/misfits.js — cross-version"]
ST --> V4["Final boss<br/>src/curator.js — cluster + rank"]
V4 --> EX["Export Markdown draft"]
ST --> BK["Backup / restore JSON"]
ST -.->|"API key set,<br/>server build only"| AI["Claude expert review<br/>src/ai.js"]
analyze() is pure and dependency‑light on purpose: the browser build imports the
same file.
sequenceDiagram
participant U as User
participant App as app.js
participant BE as backend.js<br/>(localStorage adapter)
participant Eng as Engine<br/>(bundled src/*)
participant LS as localStorage
U->>App: drop CV / paste text
App->>BE: createVersion({file|text, label, dated_at})
BE->>Eng: extractText() then analyze()
Eng-->>BE: metrics + score + ATS + inferredDate
BE->>LS: persist version record
BE-->>App: public version object
App->>BE: timeline() / misfits() / curator()
BE->>Eng: detectMisfits() / buildCuration() over all versions
Eng-->>BE: assembled view data
BE-->>App: render Timeline / Misfits / Final boss
flowchart LR
subgraph Local["Your machine"]
D["Edit code"] --> C["git commit<br/>uses local user.name / user.email<br/>no co-author / URL trailers"]
C --> P["git push origin main"]
end
P --> GH["GitHub: push event on main"]
subgraph CI["GitHub Actions — .github/workflows/deploy.yml"]
GH --> J1["checkout + setup-node 22"]
J1 --> J2["npm ci"]
J2 --> J3["npm run build:web<br/>esbuild bundles src/ engine + web/ adapter"]
J3 --> J4["dist/ = index.html, app.js,<br/>style.css, backend.js"]
J4 --> J5["upload-pages-artifact"]
J5 --> J6["deploy-pages"]
end
J6 --> SITE["https://kdbrian.github.io/Seevy/"]
subgraph Full["Full build (unaffected by Pages)"]
FS["npm start / docker compose up"] --> SRV["Express + node:sqlite<br/>+ optional Claude review"]
end
| What | Server build | Static build |
|---|---|---|
| Versions, metrics, saved reviews | data/seevy.db |
localStorage["seevy.v1"] |
| Original uploaded files | data/uploads/ |
not kept |
Use backup ▾ in the sidebar to download a JSON snapshot of every version (text + reviews) or import one back — the format is identical across builds, so it's also how you move history between the two.
server.js Express API + static host + tiny .env loader
src/
db.js node:sqlite storage (server build)
normalize.js shared text tidy-up
extract.js file -> text: pdf-parse, mammoth (server build)
analyze.js per-version heuristics, CV-health score, ATS audit, inferDate
misfits.js cross-version regression / inconsistency detection
diff.js word / line diff
curator.js bullet clustering + Markdown export
ai.js optional Claude review layer (server build)
public/
index.html
app.js single-page UI (ES module) — build-agnostic
backend.js server adapter: talks to /api/*
style.css untogglable brutalist light theme
web/src/
backend-local.js browser adapter: runs the engine against localStorage
store.js localStorage persistence
parse.js in-browser pdf.js / mammoth (lazy-loaded)
scripts/
build-web.mjs esbuild bundle + copies -> dist/ (index.html is
generated from public/index.html with relative paths)
dist/ build output (gitignored) — what GitHub Pages serves
.github/workflows/
deploy.yml build + deploy to GitHub Pages on push to main
public/app.js never talks to a server directly — it only calls the backend
object it imports from ./backend.js. The server build ships the fetch adapter;
build-web.mjs swaps in the localStorage adapter. Add a feature once in the
engine and both builds get it.
CV‑health (src/analyze.js) rolls up: quantified‑bullet ratio, action‑verb
ratio, weak openers (“responsible for…”), buzzword load, passive voice,
first‑person usage, long sentences, section coverage, verb repetition, length
fit, contact completeness. The per‑factor breakdown is shown on each version.
ATS readiness checks structure a parser cares about: standard headings, machine‑readable contact block, single column, parser‑safe characters, skills as text (not rating bars), consistent date ranges, reverse‑chronological order, acronyms expanded once, bulleted experience, length band, file format, no header/footer content. Each failing check carries a specific fix.
Both are heuristics — a sanity check you watch move as you edit, not a verdict.
- Parsing is structure‑blind: there's no per‑role/date model, so “weakened bullet” and “lost achievement” are fuzzy token‑overlap matches.
- Legacy binary
.docis best‑effort — export DOCX or a text‑based PDF. - The static build needs a working CDN for PDF/DOCX parsing; if it's blocked, paste the text instead. Plain text and Markdown always work.
- Fonts load from Google Fonts when online and fall back to system fonts offline.
MIT — see LICENSE if present, otherwise treat this as MIT.