Skip to content
brgv-codePublic

About

Open-source job application tracker. Auto-captures applications from LinkedIn, Greenhouse and Lever, then builds a tailored CV.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

jlog

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.


What it is

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


Features

  • 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

Quick start (local)

Prerequisites

  • 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

1. Clone and install

git clone https://github.com/brgv-code/jlog
cd jlog
pnpm install

2. Configure the API

cp apps/api/.dev.vars.example apps/api/.dev.vars

Edit 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=localhost

The file's comments cover the other sign-in methods — see Signing in below.

3. Create and migrate the local database

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.

4. Configure the web app

echo "PUBLIC_API_URL=http://localhost:8787" > apps/web/.env

PUBLIC_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 build

It 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.

5. Start everything

pnpm dev          # starts web on :4321 and API on :8787 in parallel

6. Install the extension

pnpm --filter @jlog/extension build

Open 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_BASE is set to in apps/extension/.env. The copy published to the Chrome Web Store is pinned to the hosted instance — api.jlog.ai is in the manifest's host_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 writes apps/extension/jlog-extension.zip. See apps/extension/STORE_LISTING.md for the listing copy and what is still outstanding before it can be submitted.


Connecting the extension

  1. Sign in at http://localhost:4321
  2. Go to Settings → Chrome Extension → Generate token
  3. Copy the token
  4. 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).


Deploy to Cloudflare

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 in apps/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 as apps/api/.dev.vars.example
  • PUBLIC_API_URL set 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.


Signing in

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
Google 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
Email 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 a Secure cookie — so it needs real HTTPS. Configure it against your deployed origin.

  • APPLE_CLIENT_ID is 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 .p8 key.

Using more than one method

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.

LLM providers

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 detectors

Site Detection method
LinkedIn 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")

Project structure

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

Docs

  • 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.

License

MIT — see LICENSE.

About

Open-source job application tracker. Auto-captures applications from LinkedIn, Greenhouse and Lever, then builds a tailored CV.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages