Skip to content

Repository files navigation

JobClaw

AI-powered job search and application automation. Upload a resume, discover roles across major ATS platforms, and apply manually or with agent-assisted form filling.

Stack: Next.js 16 (App Router) · Clerk auth · Neon Postgres + Drizzle ORM + pgvector · RetroUI · Tailwind v4 · Inngest workflows

Not using: Supabase · LangChain · Vercel Blob · Neo4j

Features

  • Resume upload & parsing — PDF/DOCX stored in Postgres (BYTEA); structured profile fields extracted via OpenRouter (google/gemini-3.1-flash-lite)
  • Vector embeddings — Resume text embedded with Google AI Studio (gemini-embedding-2, 768-dim) and stored in pgvector for future semantic matching
  • Profile management — Editable profile with completeness tracking (Personal, Summary, Skills, Experience, Education, Projects, Certifications)
  • Job discovery — Tavily-powered search across Greenhouse, Lever, Workable, Wellfound, LinkedIn, and Google Careers with match scoring and 6-hour cache
  • Save & track jobs — Bookmark listings; saved jobs are never pruned on refresh
  • Application automation — Two-phase Inngest workflow: prepare (form discovery + RAG-grounded answers) → submit (Playwright fill + submit). Supported ATS: Greenhouse, Lever, Workable
  • Authenticated browser handoff — Remote Browserbase sessions for login/MFA/CAPTCHA; seven-day encrypted context reuse per platform
  • Fail-closed safety — Unknown, ambiguous, or unsupported form fields abort the run to human review (needs_review); real submission gated by feature flag

Quick start

cp .env.example .env
# Fill in all required keys (see Environment variables below)
npm install

# First-time DB setup (new Neon branch)
npm run db:setup-pgvector      # CREATE EXTENSION vector
npm run db:push                # Push Drizzle schema
npm run db:setup-embedding-index  # HNSW index on resumes.embedding

# Terminal 1 — Next.js
npm run dev

# Terminal 2 — Inngest (required for application automation)
npx inngest-cli@latest dev -u http://localhost:3000/api/inngest

Open http://localhost:3000. Inngest dashboard: http://localhost:8288.

Environment variables

See .env.example for the full list. Required groups:

Group Variables Source
Auth NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, CLERK_WEBHOOK_SECRET Clerk Dashboard
Database DATABASE_URL Neon Console
LLM (parse + RAG + Stagehand) OPENROUTER_API_KEY, OPENROUTER_MODEL=google/gemini-3.1-flash-lite OpenRouter
Embeddings GOOGLE_GENERATIVE_AI_API_KEY, GEMINI_EMBEDDING_MODEL=gemini-embedding-2, GEMINI_EMBEDDING_DIMENSIONS=768 Google AI Studio
Job search TAVILY_API_KEY Tavily
Browser automation BROWSERBASE_API_KEY, APPLICATION_CONTEXT_ENCRYPTION_KEY Browserbase
Inngest (local) INNGEST_DEV=1, INNGEST_EVENT_KEY=local, INNGEST_SIGNING_KEY=local
Release gate AUTOMATIC_APPLICATION_SUBMISSION_ENABLED=false Keep false until a controlled end-to-end submit passes

Generate the encryption key once:

node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

Optional: TAVILY_PROJECT (dashboard grouping), JOBS_DEBUG=1 (search logging).

AI provider split

Each step uses a dedicated provider — do not mix them:

Step Provider Model
Resume parse OpenRouter google/gemini-3.1-flash-lite
Application answer RAG OpenRouter OPENROUTER_MODEL
Vector embedding Google AI Studio gemini-embedding-2
Job discovery Tavily Search API (search_depth: basic)
Form discovery / automation Browserbase + OpenRouter Remote browser + Stagehand LLM

Database scripts

npm run db:setup-pgvector         # Enable pgvector extension (once per Neon branch)
npm run db:push                   # Push schema to Neon (dev)
npm run db:setup-embedding-index  # HNSW index on resumes.embedding
npm run db:generate               # Generate SQL migrations
npm run db:migrate                # Apply migrations (production)
npm run db:studio                 # Open Drizzle Studio

First-time setup order: db:setup-pgvectordb:pushdb:setup-embedding-index.

If a database was built with db:push and later db:migrate fails with type ... already exists, baseline the migration journal instead of dropping tables. See memory/memory.md for details.

Tests

npm run test:jobs           # Job search, cache, URL filter, match score (13 tests)
npm run test:applications   # State machine, adapters, URL allowlist (18 tests)
npm run test:browser-discovery  # Live Browserbase discovery (requires keys)

Routes

Route Access Description
/ Public Landing page
/sign-in, /sign-up Public Auth (signed-in users redirect to /dashboard)
/onboarding Protected Resume upload gate
/dashboard Protected Job search & listings
/dashboard/profile Protected Profile editor
/dashboard/resume Protected Resume upload
/dashboard/application-status Protected Application list
/dashboard/applications/[id] Protected Application detail + auth handoff
/dashboard/hunt Protected Hunt control (placeholder)
/dashboard/settings Protected Settings
/api/webhooks/clerk Public Clerk webhook (signature verified)
/api/inngest Public Inngest function handler
/api/applications/[id]/authentication Protected Browserbase sign-in redirect
/api/applications/[id]/screenshot Protected Application screenshot (ownership check)
/api/resumes/[id]/download Protected Resume file download

Application workflow

User clicks Apply
  → startJobApplication (server action)
  → Inngest: prepare-application
      → Browserbase/Stagehand form discovery (Greenhouse / Lever / Workable)
      → OpenRouter RAG answers from resume chunks
      → State: ready_to_submit (or awaiting_authentication / needs_review / job_unavailable)
  → Inngest: submit-application (when enabled + user continues)
      → Playwright locators fill + submit
      → State: submitted / needs_review / submission_uncertain
  • AUTOMATIC_APPLICATION_SUBMISSION_ENABLED=false (default) — workflow pauses at ready_to_submit; discovery and answers still run, but nothing is submitted.
  • Authentication — if a platform requires login, the app enters awaiting_authentication and opens a remote Browserbase session for the user to sign in manually. JobClaw never stores third-party credentials.
  • DeletedeleteJobApplication cancels queued work and hard-deletes the application and all related artifacts.

Clerk Dashboard setup

  1. Email + Password — User & Authentication → enable Email + Password.
  2. Google OAuth — User & Authentication → Social connections → enable Google.
  3. Redirect URLs — add http://localhost:3000 (and production domain).
  4. Webhook — endpoint https://your-domain/api/webhooks/clerk. Subscribe to user.created and user.updated. Copy signing secret to CLERK_WEBHOOK_SECRET.

Local tip: without a webhook, the first visit to /dashboard or /profile upserts a profiles row via ensureProfileSynced().

Architecture overview

┌─────────────┐     ┌──────────────┐     ┌─────────────────┐
│  RetroUI    │────▶│  Next.js 16  │────▶│  Neon Postgres  │
│  (React 19) │     │  App Router  │     │  + Drizzle ORM  │
└─────────────┘     │  + Clerk     │     │  + pgvector     │
                    └──────┬───────┘     └─────────────────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────────┐
        │ OpenRouter│ │  Tavily  │ │ Browserbase  │
        │ (LLM/RAG)│ │ (search) │ │ + Stagehand  │
        └──────────┘ └──────────┘ └──────────────┘
              │                         │
              ▼                         ▼
        ┌──────────┐            ┌──────────────┐
        │  Google  │            │   Inngest    │
        │ (embed)  │            │  (workflows) │
        └──────────┘            └──────────────┘

Key directories

Area Path
Resume parse & upload lib/resume/, lib/actions/upload-resume.ts
Embeddings lib/embeddings/
Job search lib/jobs/, lib/actions/search-jobs.ts
Applications lib/applications/, inngest/functions/
Database schema lib/db/schema.ts
UI components components/
Agent memory log memory/memory.md

Manual test checklist

  1. Sign up at /sign-up, sign in, confirm redirect to /dashboard.
  2. Upload a resume at /onboarding or /dashboard/resume; verify profile fields populate at /dashboard/profile.
  3. Search for jobs on /dashboard; confirm listings appear and platform filters work.
  4. Save a job; refresh search; confirm saved job persists.
  5. Start a manual or automatic application on a Greenhouse/Lever/Workable posting.
  6. With AUTOMATIC_APPLICATION_SUBMISSION_ENABLED=false, confirm workflow stops at ready_to_submit.
  7. Sign out; confirm protected routes redirect to /sign-in.

Learn more

For AI agent conventions and pitfalls, see AGENTS.md. For a detailed development log, see memory/memory.md.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages