Free, open-source job application tracker. Captures applications from LinkedIn, Greenhouse and Lever, then builds a tailored CV from your real experience. Hosted at jlog.ai, or self-host it on Cloudflare.
The Chrome extension auto-captures from LinkedIn, Wellfound, Ashby, Greenhouse, Lever, and YC Jobs.
jlog is an open-source job tracker built on Cloudflare's edge stack. Apply to a job on LinkedIn — it appears in your dashboard automatically. On any other job site, click the extension and let the LLM extract company, role, and location from the page.
Stack: Astro 4 + React islands · Hono on Cloudflare Workers · D1 + Drizzle · Better Auth (GitHub / Google / Apple / email) · Chrome MV3 · Anthropic / OpenAI / Gemini / Ollama
- Auto-capture from LinkedIn Easy Apply, Wellfound, Ashby, Greenhouse, Lever, and YC Jobs
- LLM extraction fallback — any job page, any site; extract and confirm in one click
- Kanban-style status tracking: saved → applied → interviewing → offer / rejected / withdrawn
- Activity timeline per application with status changes and notes
- Markdown notes with live preview
- Dark / light theme, keyboard navigation (j/k/Enter in the table)
- Encrypted API key storage at rest using AES-GCM-256
- Node 22+, pnpm 9+ — Wrangler 4 requires Node 22, so an older runtime cannot run the worker or apply migrations
- Wrangler CLI (
npm i -g wrangler) - At least one sign-in method. The quickest is a GitHub OAuth App with callback URL
http://localhost:8787/api/auth/callback/github
git clone https://github.com/brgv-code/jlog
cd jlog
pnpm installcp apps/api/.dev.vars.example apps/api/.dev.varsEdit apps/api/.dev.vars. The minimum to get running:
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
SESSION_SECRET=any-32-char-random-string
WEB_ORIGIN=http://localhost:4321
COOKIE_DOMAIN=localhostThe file's comments cover the other sign-in methods — see Signing in below.
cd apps/api
pnpm exec wrangler d1 migrations apply jlog --local
cd ../..This creates a local D1 database and runs every migration in packages/db/migrations.
Run it again after pulling new migrations.
echo "PUBLIC_API_URL=http://localhost:8787" > apps/web/.envPUBLIC_SITE_URL is the other one, and it is set in the build environment
rather than in .env — Astro loads .env for the app through Vite, but
astro.config.mjs is evaluated before that and sees only process.env:
PUBLIC_SITE_URL=https://jobs.example.com pnpm --filter @jlog/web buildIt is the origin the site publishes as its canonical link and in its Open Graph and Twitter cards. Leave it unset and those tags are omitted entirely, which is the right default for a local or preview build — set it for anything public, or search engines and link previews have nothing to attribute the pages to.
pnpm dev # starts web on :4321 and API on :8787 in parallelpnpm --filter @jlog/extension buildOpen chrome://extensions, enable Developer mode, click Load unpacked, and select apps/extension/dist.
Note on the published extension. The build above talks to whatever
VITE_API_BASEis set to inapps/extension/.env. The copy published to the Chrome Web Store is pinned to the hosted instance —api.jlog.aiis in the manifest'shost_permissions, and an extension cannot be repointed after installation. If you self-host, build your own copy with your own API URL and load it unpacked, as above.To produce an uploadable bundle:
pnpm --filter @jlog/extension package, which writesapps/extension/jlog-extension.zip. Seeapps/extension/STORE_LISTING.mdfor the listing copy and what is still outstanding before it can be submitted.
- Sign in at
http://localhost:4321 - Go to Settings → Chrome Extension → Generate token
- Copy the token
- Open the jlog extension popup and paste the token
The extension now auto-captures on LinkedIn. On any other job page, click Extract with AI (requires an LLM configured in Settings).
The API is a Cloudflare Worker and the web app is a Cloudflare Pages site. You will need:
- A Cloudflare account, and
wrangler login - A D1 database (
wrangler d1 create jlog), with its id inapps/api/wrangler.toml - An R2 bucket for CV files (
wrangler r2 bucket create jlog-cv-files) - The migrations applied, from
apps/api:cd apps/api pnpm exec wrangler d1 migrations apply jlog --remote
- Secrets set with
wrangler secret put— the same names asapps/api/.dev.vars.example PUBLIC_API_URLset for the web build, pointing at the deployed Worker
Publishing the API is done by CI, not by hand. .github/workflows/deploy-api.yml is the
real procedure: it overlays a private implementation of the paid routes, verifies the overlay
landed, and then runs wrangler deploy.
Note that the root pnpm deploy script runs wrangler versions upload, which uploads a
version without routing traffic to it — useful for previewing, but it does not make
anything live. Use wrangler deploy for that. If you are self-hosting and do not have the
private overlay, packages/pro stays as its public stub and the paid routes remain locked;
everything else works.
On Pages, set PUBLIC_SITE_URL to the origin you serve from — it is a build
variable, so a deployment made before it is set will carry no canonical link
and no social card.
Authentication is handled by Better Auth. Four methods are supported, and every one is optional — the login page asks the API which are configured and shows only those, so running with GitHub alone is a perfectly ordinary setup.
| Method | What you need |
|---|---|
| GitHub | An OAuth App: client id and secret |
| An OAuth 2.0 Web application client: client id and secret | |
| Apple | A paid Apple Developer membership, plus a Services ID, Team ID, Key ID and .p8 key |
| A Resend API key and a verified sending address |
Every provider's callback lives at /api/auth/callback/<provider>. Register
exactly that URL with the provider — for local GitHub, that is
http://localhost:8787/api/auth/callback/github.
Email sign-in is a magic link. You type your address, we email a link, and following it signs you in. The link works once and expires in 15 minutes. Only a hash of it is stored, so the copy in your inbox is the only working one.
Three caveats worth knowing:
-
Sign in with Apple needs a paid Apple Developer Program membership ($99/year). There is no free tier for it, so this is a cost decision before it is a configuration one. Leaving the four
APPLE_*settings unset is a perfectly ordinary way to run jlog: the provider is never registered and the login page does not show the button.Note this is unrelated to accepting Apple Pay, which the hosted Stripe Checkout used for the paid plan supports with no Apple account at all — Stripe performs the Apple merchant validation on your behalf.
-
Apple cannot be tested against
http://localhost. It refuses localhost redirect URIs, and it answers the callback with a cross-site form POST, which browsers only accompany with aSecurecookie — so it needs real HTTPS. Configure it against your deployed origin. -
APPLE_CLIENT_IDis the Services ID, not the app's bundle id. Apple also issues no client secret: jlog signs a short-lived one per request from your.p8key.
Sign-in methods attach to accounts, not the other way round. If you signed up with GitHub and later press Continue with Google, you land in your existing account rather than an empty new one — provided the provider tells us the address is verified. An unverified address never links, because trusting one would be a way into someone else's account by claiming their address.
Configure any of these in Settings → LLM Provider:
| Provider | Default model | Notes |
|---|---|---|
| Anthropic | claude-3-5-haiku-20241022 | Fastest, cheapest |
| OpenAI | gpt-4o-mini | Good balance |
| Gemini | gemini-1.5-flash | Google alternative |
| Ollama | llama3 (local) | 100% local, no API key |
API keys are encrypted with AES-GCM-256 before being stored in D1.
| Site | Detection method |
|---|---|
| MutationObserver on Easy Apply modal + external apply click | |
| Wellfound | Apply button click + DOM confirmation |
| Ashby HQ | Submit button click + thank-you page content |
| Greenhouse | Form submit + confirmation content |
| Lever | Submit button click + thank-you content |
| YC / Work at a Startup | Apply click + confirmation |
| Any other site | LLM extraction via popup ("Extract with AI") |
jlog/
├── apps/
│ ├── web/ Astro 4 frontend (dashboard, settings, landing)
│ ├── api/ Hono on Cloudflare Workers (REST API + auth)
│ └── extension/ Chrome extension MV3
├── packages/
│ ├── db/ Drizzle schema, migrations
│ ├── llm/ LLM provider adapters
└── shared/ Zod schemas, error classes
- User and self-hosting docs, also in this repo under
apps/web/src/pages/docs: using every feature, AI providers, and a step-by-step deploy with the full configuration reference. - Design system — the rules, the palette, and why primary is not accent.
Rendered live, with every animation, at
/designsystem. - Store listing — the Chrome Web Store copy, permission justifications, and what is still outstanding before the extension can ship.
- Project spec — what this was built to be.
MIT — see LICENSE.