Job Search Terminal
A free, source-available, local-first job-search dashboard for people who need structure, privacy, and better tools in a brutal job market.
Scan jobs. Score fit with AI. Generate tailored resumes. Track applications. Prepare for interviews. Keep your data on your machine.
No account. No subscription. No hosted database. Bring your own AI API key.
The job search is hard enough without another bill.
Too many people are looking for work right now. And too many tools ask those same people to pay monthly fees just to organize the chaos.
That never sat right with me.
This app is my attempt to make the job search a little less punishing. It is not magic. It will not apply for you. It will not guarantee interviews. But it can help you see what is worth your time, keep track of what you have done, and stop rebuilding the same resume and application answers from scratch every night.
See Job Search Terminal in action
What the app helps you do
Job Search Terminal is built around the work people actually do while job hunting: finding roles, evaluating fit, tailoring material, applying, following up, and preparing for conversations.
Scan job sources
Discover new roles from company career pages and configured job sources.
Evaluate fit
Use AI to compare roles against your profile, resume, skills, preferences, and deal breakers.
Tailor resumes
Generate role-specific resume drafts from your own base resumes, then edit before export.
Draft application answers
Prepare answers for application questions. The app never submits anything for you.
Track applications
Use table, list, and Kanban views to move applications through your funnel.
Prepare for interviews
Build a STAR story bank and practice answers using voice transcription.
What you can do in the first hour
The first goal is not perfection. The first goal is control.
Once the app is running, you can:
- Add your AI API key.
- Upload or add your resume.
- Complete your career profile.
- Scan for jobs.
- Evaluate which roles are worth your time.
- Start tracking applications.
If you are not technical, use the AI setup prompt in the Documentation tab. Paste it into Claude Code or Codex and let it walk through the installation with you.
See how it works
Click any image to open a larger view.
Setup, features, and documentation
The documentation below is powered by Markdown so it can stay current as the app changes. Replace the files
in src/content/job-search-terminal/ and redeploy to update.
Overview
What this is
Job Search Terminal is a local job-search dashboard that runs on your computer. It helps you discover jobs, evaluate fit, tailor resumes, draft application answers, track applications, and prepare for interviews.
It is free to use, inspect, and adapt for non-commercial purposes under CC BY-NC 4.0. Commercial use requires written permission.
What this is not
This is not a job placement service. It does not guarantee interviews. It does not apply to jobs automatically. It does not replace judgment, networking, or human effort.
It is a tool to help you keep the work organized.
What you need
You need a computer, Node.js, Git, Google Chrome, and at least one AI API key if you want to use AI features.
The app can use OpenAI, Anthropic, or Google Gemini depending on what you configure.
What it costs
The app is free.
There is no subscription, no paid account, and no access fee. The only cost may be your own AI API key, paid directly to the AI provider you choose. I do not charge for that and I do not sit between you and the provider.
Your data stays with you
Your job search data is stored locally on your machine. The app does not use a cloud account or hosted database.
AI requests are only sent when you choose to use an AI feature, such as job evaluation, resume tailoring, profile extraction, or answer drafting.
Who this is for
This is for people who want more control over their job search and are willing to run a local tool on their own computer.
It is especially useful if you are applying to many roles, tailoring resumes often, tracking multiple applications, or trying to make better decisions about where to spend your time.
Getting started
Follow this guide to install and run Job Search Terminal on your computer. You do not need to know how to code, but you do need to be comfortable opening a terminal or using an AI coding assistant to help with setup.
If you are not technical, open the “Prompt for AI on this page” section and use that prompt to have an AI assistant help you install the app step by step.
Go to AI setup promptGetting Started
This guide explains what the app is, what you need to run it, and how to get it running on your computer — even if you have no development background.
What is this app?
Job Search Terminal is a local job-search dashboard that runs entirely on your computer. It helps you:
- Discover new job postings automatically from company career pages.
- Score each job against your career profile using AI.
- Generate tailored resumes for specific roles.
- Draft application answers for copy-paste (it never submits anything on your behalf).
- Track every application through the full funnel.
- Prepare for interviews with a STAR story bank and voice practice.
Your stored account data stays on your machine. There are no accounts, no subscriptions, and no cloud storage. The app contacts the internet only for the features you run: AI-provider requests, job scans, source discovery, and browser-board scanning.
What you need
| Requirement | Details |
|---|---|
| Computer | Mac, Windows (WSL), or Linux |
| Node.js | Version 18 or later — download from nodejs.org |
| Git | Download from git-scm.com |
| An AI API key | At least one of: OpenAI, Anthropic, or Google Gemini (free tier available) |
| Google Chrome | Recommended for PDF generation — the app falls back to a bundled Chromium if Chrome is not found |
You do not need to know how to code. You just need to be comfortable opening a terminal (the black or white window where you type commands).
How to open a terminal
Mac: Press Command + Space, type Terminal, press Enter.
Windows: Press Windows key, type PowerShell, press Enter. (Or install
Windows Terminal for a better experience.)
Linux: Press Ctrl + Alt + T.
Step-by-step setup
1. Install Node.js
Go to nodejs.org, download the LTS version, and run the installer. Accept all defaults.
Verify it worked by opening a terminal and typing:
node --version
You should see something like v20.11.0.
2. Install Git
Go to git-scm.com, download the installer for your operating system, and run it. Accept all defaults.
Verify it worked:
git --version
3. Download the app
In your terminal, navigate to where you want to store the app (your Desktop or Documents folder works fine):
cd ~/Desktop
Then download the app:
git clone https://github.com/uxdesignlab/job-search-terminal.git
Once cloned, move into the project folder:
cd job-search-terminal
4. Install dependencies
This downloads everything the app needs to run:
npm install
It may take a minute or two. You will see a lot of text scrolling by — that is normal.
5. Add your AI API key
The app needs at least one AI provider key to evaluate jobs and generate content. You can use whichever service you have access to:
- Google Gemini (free tier): aistudio.google.com/apikey
- OpenAI: platform.openai.com/api-keys
- Anthropic: console.anthropic.com/settings/keys
Once you have a key, you configure it inside the app itself (Settings → AI Providers). You do not need to create any configuration files manually.
5a. Optional: Add job aggregator and source discovery keys
These are not required to use the app, but they significantly expand how many jobs and sources the app can find automatically.
Adzuna (job aggregator — free tier, 2,000 queries/month):
- Register at developer.adzuna.com to get a free App ID and API Key.
- Once configured in Settings → AI Provider → Discovery & Aggregators, the Dashboard Scan for new jobs button automatically runs an Adzuna scan in parallel with the ATS scan — no extra step needed.
- You can also run it independently from Settings → Sources → Job aggregators.
Brave Search (ATS source discovery — free tier, 2,000 queries/month):
- Register at brave.com/search/api to get a free API key.
- Once configured, the Search discover button appears in Settings → Sources and finds new Ashby, Greenhouse, and Lever companies from live web search results (not just the Common Crawl archive).
Add both keys in Settings → AI Provider → Discovery & Aggregators.
6. Start the app
npm run dev
The terminal will print something like:
▲ Next.js 15.x
- Local: http://localhost:3000
Open http://localhost:3000 in your browser. The app will be running.
7. First-time setup in the app
When you first open the app at http://localhost:3000, the dashboard opens a guided setup flow:
Step 1: Add an AI API key Click the provider you want to use to expand the instructions. Pick one — you only need one:
- Google Gemini (free tier): go to aistudio.google.com/apikey, sign in with Google, click Create API key. Copy the key.
- OpenAI: go to platform.openai.com/api-keys, sign in, click Create new secret key. Copy the key.
- Anthropic (Claude): go to console.anthropic.com/settings/keys, sign in, click Create Key. Copy the key.
Choose one provider, paste its key, test the connection if needed, and save. Model selection and discovery integrations remain optional advanced settings.
Step 2: Upload your resume Click “Go to Profile → Resumes”. On the Resumes tab, each lane represents a different resume version. For any lane, click the blue “Upload PDF” button and select your resume file. The app extracts the text automatically.
After uploading, go to Profile → Overview and click “Extract with AI”. The AI reads your resume and populates your skills, target roles, preferences, and experience. Review each tab (Skills & Roles, Preferences, Constraints) and make any corrections.
Step 3: Confirm preferences and start scanning Confirm desired roles, title filters, and location mode. The Ready step offers an optional six-hour scan schedule while the local app is running. Open the Dashboard and click “Scan for new jobs” for an immediate scan.
Everyday use
| What you want to do | Where to go |
|---|---|
| Find new jobs (ATS + Adzuna if configured) | Dashboard → Scan for new jobs |
| Scan Adzuna independently | Settings → Sources → Job aggregators |
| Add a job from anywhere | Jobs → Add Job button |
| See all your jobs | Jobs |
| Evaluate a job | Jobs → click a job → Evaluation tab |
| Generate a tailored resume | Jobs → click a job → Resume tab |
| Prepare application answers | Jobs → click a job → Apply tab |
| Research a company | Jobs → click a job → Research |
| Draft a recruiter message | Jobs → click a job → Outreach |
| Track your applications | Applications |
| Practice for interviews | Interview Prep |
| Update your profile | Account → Profile |
| Upload or replace a resume | Account → Profile → Resumes tab |
| Add an AI key | Account → Settings → AI Providers |
| Add Adzuna or Brave Search keys | Account → Settings → AI Providers → Discovery & Aggregators |
| Discover new ATS sources | Settings → Sources → Scan for new sources or Search discover |
| Review and approve discovered sources | Settings → Sources → Discovered sources |
| Check which sources are still live | Settings → Sources → Validate sources |
| See role fit strategy | Account → Strategy |
| Back up or restore the local account | Account → Settings → Data & Backup |
Adding jobs from anywhere
You are not limited to the jobs the scanner finds. Any job you see on LinkedIn, a company careers page, a job board, or hear about through a referral can be added and evaluated with AI.
How to add a job manually:
- Go to Jobs in the top navigation.
- Click the Add Job button.
- Fill in the details:
- Company — the employer name.
- Job Title — the exact title from the posting.
- Job URL — the link to the posting, so you can return to it later.
- Job Description — paste the full text of the job description. The more detail you include, the better the AI fit score.
- Click Submit Job. The app creates the job and opens it immediately.
Manual jobs go through the same pipeline as scanned jobs — fit scoring, resume tailoring, application tracking, company research, and outreach drafting all work exactly the same way. The fit score tells you whether it is worth applying before you invest time in a cover letter or tailored resume.
Good sources to add jobs from manually:
- LinkedIn job postings
- Wellfound, Work at a Startup, Glassdoor, Indeed, and Monster postings if you are not using the browser-board scanner
- Company careers pages
- Referrals and warm introductions (add what you know about the role)
- Jobs emailed to you by recruiters
- Any board the scanner doesn’t cover (Dice, Handshake, etc.)
Stopping and restarting
To stop the app: go to your terminal and press Ctrl + C.
To restart:
npm run dev
Updating the app
When a new version is available:
git pull
npm install
npm run dev
Backing up your data
Open Account → Settings → Data & Backup to create a portable .jst-backup
archive before migrations, cleanup, or moving the app to another machine. The
archive includes the database, resume files referenced by your resume lanes,
generated documents, approved source configuration, and scanner import
history. Every other file under assets/ is ignored.
Password protection is optional. If you create an unencrypted archive, the app asks you to acknowledge that it contains private resume data and locally stored provider credentials.
To restore an archive, choose the file in the same screen and click Inspect
backup. The app validates and previews the archive first. After you confirm,
it creates an automatic rollback backup before replacing managed local account
data. Unrelated files under assets/ remain untouched.
For a database-only SQLite snapshot, run:
npm run data:backup
Snapshots are saved to output/backups/. Export a readable JSON snapshot:
npm run data:export
Troubleshooting
The app won’t start
- Make sure you ran
npm installfirst. - Make sure no other app is using port 3000.
- Try stopping with
Ctrl + Cand runningnpm run devagain.
Pages show stale or missing data
- Restart the dev server.
- Run
npm run db:checkto verify the database.
PDF generation fails
- Make sure Google Chrome is installed.
- On Mac, Chrome is usually at
/Applications/Google Chrome.app. On Windows, it is inC:\Program Files\Google\Chrome\Application\chrome.exe. - If Chrome is in an unusual location, set the environment variable:
CHROME_EXECUTABLE_PATH=/path/to/chrome
No AI results
- Go to Settings → AI Providers and check that your key is entered and the correct provider is set as active. Use the “Test connection” button to verify.
Reset everything and start fresh
This deletes all local data and resets to an empty local profile:
npm run db:reset
Use with caution — this cannot be undone unless you have a backup.
Prompt for Claude Code or Codex (self-service setup)
If you want an AI coding assistant to set up and run this app with minimal manual work, paste the following prompt into Claude Code or Codex:
Set up and run Job Search Terminal for me as a self-service local app.
Repository:
https://github.com/uxdesignlab/job-search-terminal.git
Goal:
Do the setup yourself end to end. Do not give me a list of terminal commands to
run. Use the terminal and browser tools available to you, verify each step, and
only ask me for information you cannot safely obtain or create yourself, such as
an AI API key or the local path to my resume PDF.
What to do:
1. Inspect the computer for required tools:
- Node.js 18 or newer
- npm
- Git
- Google Chrome or another Chromium browser for PDF generation
2. If a required tool is missing, try to install it using the normal package
manager for this computer, such as Homebrew on macOS, winget on Windows, or
apt/dnf/pacman on Linux. If installation requires administrator permission or
an approval prompt, ask me for that approval once, then continue after it is
granted. Do not stop at "please install this" unless there is no safe
automated installation path.
3. Create or reuse a local project folder:
- If the repository is already cloned, use the existing checkout.
- If it is not cloned, clone it from the repository URL above.
- Avoid overwriting or deleting existing user data.
4. Install project dependencies with npm.
5. Run the project's verification checks:
- npm run lint
- npm run typecheck
- npm run build
6. Start the development server. If port 3000 is busy, use the next available
local port and tell me which URL is running.
7. Open the app in a browser yourself and verify the dashboard loads. Do not
simply tell me to open the URL.
8. Complete as much first-time setup as the app allows:
- If no AI provider is configured, ask me for one API key, then enter it in
Settings -> AI Providers and use the app's connection test if available.
- If a resume is needed, ask me for the local PDF path or use the browser file
picker if your tools support it. Upload it through Profile -> Resumes.
- Preserve multiple resume lanes. Do not merge everything into one universal
resume.
- Run profile extraction from Profile -> Overview after a resume is uploaded.
- Return to the dashboard and scan for jobs when setup is complete.
9. Safety rules:
- Never submit job applications, send emails, or message recruiters for me.
- Never delete resumes, generated documents, reports, backups, or tracked
application data unless I explicitly ask.
- Keep all data local.
10. If anything fails, read the actual error, fix what you can, retry once, and
then explain the blocker in plain language with the exact next approval or
input needed from me.
Finish by giving me the running local URL, what you verified, and anything still
needed from me.
Is this the same as Career-Ops?
No. Job Search Terminal was inspired by Career-Ops, but it is a separate project with a different product direction.
Career-Ops is a powerful CLI-first, agentic job-search system. Job Search Terminal is a browser-based, local-first dashboard designed to make the workflow more approachable for people who do not want to manage everything through AI coding tools and config files.
License note
Job Search Terminal is free for non-commercial use under CC BY-NC 4.0.
Commercial use requires written permission from the copyright holder.
Features
Summaries below; full documentation lives in Markdown and updates when you replace features.md.
Dashboard
A command center for scan results, action queue, source status, recent activity, XP, and job-search progress.
Jobs
A full job pipeline with fit scores, filters, saved presets, bulk actions, archive tools, and manual job entry.
Job Detail
A tabbed workspace for evaluating a role, reviewing fit, generating a tailored resume, preparing answers, researching the company, and drafting outreach.
Applications
A funnel tracker with table, list, and Kanban views, including overdue follow-up alerts and status movement.
Resumes
A resume studio for multiple base resume lanes, generated documents, editing, keyword coverage, preview, and PDF export.
Interview Prep
A STAR story bank and voice-practice workflow for turning spoken answers into structured interview stories.
Profile
A career profile system that captures goals, skills, role direction, preferences, constraints, resumes, and writing voice.
Strategy
A role-fit map that helps users understand direct-fit, adjacent, selective, and avoid-role categories.
Analytics
Search-performance metrics that show application funnel conversion, score-to-outcome patterns, archetype performance, and gap coverage.
Settings
Controls for AI providers, job sources, title filters, custom job boards, preferences, and fallback models.
Data and privacy
Data is stored locally in a SQLite database on the user's machine, with backup and export commands available.
View full feature documentation
Features
This document describes every feature in the current application. Use it as a reference for what the app does and how each section works.
Navigation
The Shell header provides two navigation groups:
Primary nav (always visible): Dashboard · Jobs · Applications · Interview Prep · Analytics · Resumes
Account dropdown (hover on “Account”): Profile · Strategy · Settings
Help link appears immediately after Account and opens the in-app help site at
/help in a new browser tab (target="_blank") so the user doesn’t lose their current context.
The Account menu shows a live AI provider health dot. The indicator reads the
provider priority chain from providerOrderJson and checks each provider for a
credential (API key for cloud providers; a non-empty base URL for Ollama):
- Green: the first enabled provider in the chain has a credential configured
- Yellow: a credential exists somewhere but the first-in-chain provider is missing one
- Red: no credential is configured for any provider in the chain
The app redirects / to /dashboard on load.
The app also serves /favicon.ico (redirected to the shared logo.svg) so
browser default favicon requests resolve without 404 noise.
Help Site /help
The in-app help site is a self-service documentation surface for open-source users. It is designed as a mini website inside the product, with a landing page, search, workflow cards, a persistent documentation sidebar, screenshots, related guides, and per-topic pages.
Help home:
- Hero with product screenshot and calls to start the guide or open the resume and ATS guide.
- Search across all help pages.
- Workflow groups for setup, profile, jobs, applying, tracking, interview prep, privacy, and troubleshooting.
Guide pages:
/help/getting-started— setup, onboarding, and daily workflow./help/ai-providers— how to configure OpenAI, Anthropic, Google Gemini, or Ollama (local); create and add API keys; set the provider priority chain; test providers; and protect keys./help/resume-lanes— resume lanes, resume upload, ATS-friendly formatting, PDF guidance, and bullet quality./help/job-search— dashboard scans, job sources, manual job entry, filters, and saved presets./help/linkedin-scanner— Claude/Codex browser-board scanning for LinkedIn, Wellfound, Work at a Startup, Glassdoor, Indeed, and Monster; imports, duplicates, limits, and safety notes./help/evaluate-tailor— evaluation, tailored resume generation, PDF export, application answers, research, and outreach drafting./help/applications— statuses, table and kanban tracking, follow-ups, and archive vs. delete behavior./help/interview-prep— STAR stories and voice practice./help/privacy-data— local data, AI-provider data flow, backups, and safety boundaries./help/troubleshooting— common setup, AI, resume/PDF, scan, and LinkedIn fixes.
The help content is sourced from src/lib/help/content.ts and rendered through
the shared help components under src/components/help/.
Dashboard /dashboard
The command center. Has two states depending on setup progress.
First-run onboarding (shown until setup is complete and dismissed): Opens as an isolated dashboard modal so the user can finish setup without leaving the flow. Closing before completion shows a warning that the app will fail to generate useful matches, resumes, and answer drafts until setup is finished.
The modal has 5 steps — 4 required and 1 optional:
- AI provider — saves one OpenAI, Anthropic, or Google Gemini API key, or configures an Ollama base URL, inline.
- Resume lanes — uses the normal multi-lane resume upload cards. Uploading a PDF seeds desired positions and positive title filters from extracted resume titles, and AI extraction can enrich the full profile. The “Add another lane” button only appears once all existing lanes have a file uploaded (to prevent accidentally adding duplicate empty lanes).
- Job preferences — requires the user to review and explicitly save desired positions, include/exclude title filters, and location work modes. Resume upload or extraction may prefill these values, but the step does not become complete until the user confirms them.
- Integrations (optional) — covers two free API keys that extend job
coverage. Each card shows a short explanation, a “Help →” link to the
relevant help section, and inline input fields with a “Leave blank to keep
existing” placeholder when keys are already saved.
- Adzuna (job aggregator) — App ID + API Key. Links to
/help/job-search#aggregator. Enables Adzuna API scanning alongside ATS sources on every dashboard scan. - Brave Search (source discovery) — API Key. Links to
/help/ai-providers#discovery-aggregators. Enables the “Search discover” button in Settings → Sources. The sidebar marks this step with a dashed “Optional” badge and a·in the step circle when not yet configured. Clicking “Skip for now” advances to Ready without saving. “Save and continue” saves any non-blank fields (blank fields keep existing keys) and also advances to Ready.
- Adzuna (job aggregator) — App ID + API Key. Links to
- Ready — explains the next operational steps: review scan sources in Settings, run Scan for new jobs on the Dashboard, then review and evaluate imported matches.
The resume step is considered complete as soon as a PDF has been uploaded to any lane (regardless of whether AI extraction succeeded). Job preferences and the dashboard are gated until an AI key, at least one uploaded resume, and confirmed preferences all exist. The Integrations step is never a gate — completing it only enables optional features.
Normal dashboard (after full setup):
- Top dashboard tiles — metric cards appear before “Fresh matches” and “Apply next”. “Priority matches”, “Applications sent”, “Follow-ups due”, and “Interviews active” are pinned in a two-row half-width block, with “This week” occupying the other half.
- Fresh matches — only unprocessed jobs discovered by scheduled or manually
triggered scans inside the selected freshness window. Applied, rejected,
manually added, stale, archived, and duplicate jobs stay out. Each row badge
shows
Posted <date>when a publish date exists, otherwiseFetched <date>from the discovery timestamp. Rows use the same compact list treatment as “Apply next” for consistent scanning, and row text wraps (no ellipsis truncation) so full titles and location lines remain visible on narrower viewports. - Action queue — “Apply next” shows high-score jobs not yet applied to and “In flight” shows active applications (interviewing, follow-up needed). Each card shows company, title, fit score, and recommended next action.
- Stat cards — supporting metrics for priority matches, applications sent, new jobs this week, generated PDFs, follow-ups, interviews, and skipped jobs.
- Recent activity log — “Latest scan” summary appears first (status badges and per-source errors with inline “Disable source”), followed by the timestamped list of user actions.
- Scan for new jobs button in page header (hidden for new users).
Jobs /jobs
The full job pipeline. Lists every discovered job with filtering, preference status, posting maintenance, and bulk tools.
Features:
- Fit score badge, status badge, freshness label, and role archetype per row.
- Sort by fit score, date, company, preference, or workflow status.
- Filter by workflow status, preference status, score range, location, company, recommendation, posted date availability, and added date availability.
- The last sort and filter settings are restored automatically on the next visit; named presets are still available for recurring review modes.
- Preference column — shows
Matchwhen a job still fits the current profile preferences and constraints, orOut of scopewhen saved preferences have changed and the job no longer fits. This is a derived display/filter value, not a separate stored job status. - Posted column — shows a short
MM/DD/YYwhen a real posted date is known; the cell is left empty when there is no date or the stored value is not a parseable calendar date (no em dash or placeholder text). - Text search across company and title.
- Bulk operations: change status on multiple jobs, archive, or delete in bulk.
Select jobs with the row checkboxes, then use the bulk action bar:
- Evaluate selected — runs AI evaluation on all selected jobs in sequence.
A per-row progress indicator shows
Pending,Evaluating,Done, orError. - Retry failed (N) — appears after a batch run if any jobs returned an evaluation error. Re-runs evaluation only on the jobs that failed, without re-evaluating already-successful ones.
- Evaluate selected — runs AI evaluation on all selected jobs in sequence.
A per-row progress indicator shows
- Marking a job Skipped (individually or in bulk) removes it from this list immediately — it is auto-archived and moves to the Archived page.
- Bulk delete asks for confirmation. If selected jobs have user activity, the confirmation warns before deleting.
- Maintenance tool to verify posting liveness, confirm deletion for expired
untouched jobs, and identify active jobs whose titles no longer match saved
title filters. Out-of-scope cleanup only bulk-deletes untouched jobs; jobs with
user activity must be removed through explicit selected-job actions. Clicking
Verify active postings opens a blocking
ProgressModalwhile the liveness check runs; on completion the modal shows a badge summary (checked / active / uncertain / expired counts) and prompts the user to close and take action on expired jobs in the section below. - Bulk evaluate — selecting jobs and clicking Evaluate N opens a blocking
ProgressModalthat tracks per-job progress (“Job X of N”) while the AI evaluation streams for each selected job. On completion the modal shows how many evaluated successfully and how many failed. - Add job manually via modal (paste URL or fill in details). Jobs added this
way are stored with
source = 'manual'and display a Manual badge in the Source column. - Column filters — click any column header to open a sort + multi-value checkbox filter dropdown. Active filters show a count summary (“X of Y jobs”) with a “Clear all filters” link.
- Saved filter presets — name and save up to 5 filter+sort combinations as reusable chips above the table. Presets are persisted to the database and survive page reloads. Click a chip to re-apply; click × to delete.
- Review queue banner — when low-confidence imports are present (jobs with a
description under 100 characters), a yellow banner appears at the top of the
Jobs page showing the count of jobs pending review. Per-row Approve and
Dismiss buttons let you promote a low-confidence job into the normal
pipeline (clears
review_status) or archive it. The banner auto-hides when the queue is empty.
Job Detail /jobs/[id]
Tabbed view for a single job. Four tabs:
Overview tab
- Company, title, location, remote type, ATS source, freshness.
- Fit score, recommendation badge, role archetype.
- Match rationale, main concern, salary notes.
- Requirement match table showing which JD requirements the profile covers.
- Gap list: requirements not yet addressed.
- Red flags list.
- Job description — collapsed panel showing the saved description text.
- Edit job details — collapsed form to overwrite position, company, job posting URL, and job description without creating a duplicate record. Useful when LinkedIn or other scanner sources capture only partial metadata. All four fields are pre-filled with the current values. A reminder to re-run evaluation is shown after saving, since any description change makes the existing AI analysis stale.
Analysis tab
- Run evaluation: triggers AI streaming evaluation with real-time output.
- Evaluation sections: strengths, gaps, red flags, resume recommendation, keyword list, legitimacy signal.
- User correction: override the AI recommendation and score with a note.
- Provider and model metadata for the last evaluation run.
AI evaluation data sources (all fed into the analysis):
- Full job description (up to 6,000 characters — captures required qualifications that appear deep in the posting).
- Candidate profile: goal, urgency, direction, compensation needs, work preferences, target roles, deal breakers, constraints.
- Skill inventory (up to 30 skills with strength level and evidence source).
- Role strategy (role-fit scores and rationale from the profile).
- Active resume excerpts (up to 2 resumes × 1,800 chars each) — ensures Block B CV-match assessment and proof-point citations are grounded in actual resume text, not inferred from skill abstractions alone.
ATS keyword extraction (Block E):
- Extracts 20–25 keywords per job posting using verbatim-phrase discipline: the LLM is instructed to pull the exact phrasing from the JD, never paraphrase.
- The target job title and close variants are always extracted first as
"title"category keywords — these carry the highest ATS weight and drive summary positioning. - Named tools, platforms, certifications, and frameworks are extracted exactly as written (Figma, Workday, AWS, PMP, etc.) for exact-match ATS signals.
- Hard-skill phrases from Required/Qualifications sections →
priority: "required". - Soft-skill and context phrases from Preferred/Nice-to-have →
priority: "preferred". - Domain context phrases (e.g. “healthcare SaaS”, “B2B enterprise”) are captured to distinguish the role from generic postings.
- For “X+ years of [skill]” requirements, the skill phrase itself is captured (not the number) so it can be verified against the candidate’s resume.
- Categories:
"title"|"technical"|"soft"|"domain"|"tool"|"methodology"|"credential". - Keywords are stored in priority order (required first, title category first within required) and used during resume tailoring.
Resume tab
- Generate tailored resume for this job: picks best base resume, produces HTML and PDF output with tailoring summary and keyword coverage %.
- Resume draft editor: edit the tailored resume before export.
- Keyword coverage progress bar.
- Download PDF button.
Tailored resume AI context:
- Source resume full text (up to 5,000 chars) — the AI must verify every keyword and strength against this text before using it.
- All evaluation keywords in priority order (required first, then preferred); the previous cap of 12 has been removed — all 20-25 extracted keywords are passed.
- Missing keywords — keywords absent from the pre-AI source draft are identified before the AI call and passed as a separate priority list so the AI knows exactly which terms to weave in where the source resume provides supporting evidence.
- Job-specific gaps and red flags — the evaluation’s
gaps(up to 5) andredFlags(up to 3) are included so the AI tailors content to address the specific shortfalls identified for this position, not just generic keyword coverage. - Evaluation strengths (top 4) as suggested emphasis signals.
- Gap responses — user-supplied notes addressing identified experience gaps. Gaps and red flags are addressed via a structured modal (see below).
- Profile supplements — any extra context the user has added.
- Gap answer quality checks — vague gap responses and supplements are saved as drafts with a follow-up question, and only confirmed answers are used during resume tailoring.
Gap addressing modal (on the job Overview tab, “Gaps and red flags” card):
- Clicking Address (or Edit) opens a modal instead of an inline form.
- Company checkboxes — loaded from the user’s resume experience entries via
/api/resume-companies; selecting companies pre-structures the response as “At Company A, Company B: [description]”. - What did you do? — editable textarea, prefilled by parsing the gap text into a first-person statement (strips “The posting requires…” boilerplate, extracts the core activity).
- Key metrics or outcomes — optional single-line field; appended as “Key results: …” in the saved response.
- Polish with AI — sends the structured response for AI polishing and quality
assessment in one step; closes the modal when
qualityStatus === "addressed". - Save — saves raw without polish; also closes on “addressed”.
- If the AI returns
needs_followup, the modal transitions to a follow-up step showing the AI question and the saved response, with a textarea for more detail. - Escape key closes the modal.
- Modal slides up from the bottom on mobile, centers on desktop.
- Job description excerpt (up to 3,000 chars) — allows the AI to verify keyword context and understand requirement weight, not just the extracted keyword list.
- Skills preference flags — skills the user wants to emphasize or de-emphasize
(derived from
use_more/use_lesspreference on each skill record).
Keyword placement strategy (added to tailoring prompt):
- “required” and title-category keywords that are supported by the candidate’s evidence should appear at least once as an exact verbatim phrase — ATS systems do phrase-level matching, so split words don’t reliably score.
- Tool/methodology keywords belong in Skills or within the experience bullet where that tool was actually used.
- Soft-skill phrases fit best in the summary or a high-impact bullet.
- The target job title or a close variant is worked into the summary when supported.
- Aim for supported required keywords to appear 1-2 times (once for ATS; twice for human reviewers).
Three-tier keyword coverage (resume draft editor): The keyword panel in the draft editor classifies each keyword into one of three tiers:
| Tier | Display | Meaning |
|---|---|---|
| Exact phrase | ✓ green chip | Full verbatim phrase present — ATS will reliably score this |
| Partial | ~ amber chip | Individual terms present but not as a phrase — ATS may miss; add as exact phrase |
| Missing | + or ! chip | Not found; either add to Skills (if evidence confirmed) or confirm evidence first |
- The header score shows ATS coverage % (exact phrases only) — this is the realistic number an ATS parser would produce.
- Clicking any chip highlights the keyword in the preview panel (exact phrase = bright yellow outline; term occurrences = faint yellow).
supportedKeywordsdetection uses the same phrase-aware matching algorithm as coverage (previously used rawstring.includes()which gave false positives).
Keyword coverage metric:
- For each evaluation keyword, first tries an exact phrase match in the resume text; if that fails, splits the keyword into significant words (stripping stop words and single-character tokens), then checks whether any of those words appears in the resume. This word-level fallback handles multi-word phrases like “agile methodologies” or “Healthcare SaaS” that the LLM evaluator commonly produces. Specific acronyms (HIPAA, HL7, FHIR) still require an exact match and correctly flag as gaps when missing.
- Displayed as a percentage on the edit-draft page subtitle and is recomputed live from the current evaluation keywords each time the page loads (not cached from generation time).
- Color-coded threshold: green ≥ 70% (target), orange 40–69%, red < 40%. The target label “(target: 70%+)” appears when coverage is below 70%.
Apply tab
- Prepare application answers: paste common or custom application questions,
generate AI answers for copy-paste. App never auto-submits anything.
- Pressing Prepare answers opens a blocking
ProgressModal(“Drafting answers grounded in your resume and evaluation…” + spinner) while the request is in flight. On success the modal transitions to a done state (“Answers prepared — scroll down to review them.”) with a Close button. On error it transitions to an error state with the failure message and a Close button (questions are preserved). - After answers return, the question inputs reset to a single empty row and the drafts list refreshes in place — no page reload is required to submit another batch of questions.
- Gap responses flow into answer generation: all gap and red flag
responses with
qualityStatus === "addressed"are loaded and injected into the AI system prompt as verified evidence. The AI uses polished responses (or raw if no polish) to strengthen “why fit” and custom answers. The template fallback (no AI key) surfaces the most relevant addressed gap in the “why fit” answer and lists all gaps as supporting evidence for custom questions.
- Pressing Prepare answers opens a blocking
- Application status selector: move the job through the 11-status funnel.
- Follow-up date picker.
- Contact field.
Sub-pages from Job Detail:
Research /jobs/[id]/research
AI-generated company intelligence:
- AI strategy and product direction
- Recent company movements (hiring, layoffs, expansions)
- Engineering / design culture
- Technical and organizational challenges
- Candidate positioning angle
Clicking Start research (or Re-research) opens a blocking progress modal (“Researching company” + spinner) that streams a live status line as each of the six axes completes. On completion the modal shows “Research complete — scroll down to read all six sections.” The modal cannot be dismissed while the request is in flight; the page-level Cancel button stops the stream and closes the modal, and an X button and Close button appear only in the done state.
Outreach /jobs/[id]/outreach
Generate a recruiter or hiring manager outreach message tailored to the job and user profile. Shows character count. User copies the message manually.
Clicking Generate messages (or Regenerate) opens a blocking progress modal (“Generating outreach messages” + spinner). On success the modal shows “3 messages ready — scroll down to copy and send them.” The modal cannot be dismissed while the request is in flight.
Applications /applications
Application funnel tracker with two view modes:
- Table view — sortable list of all active applications with status, company, role, applied date, score, follow-up date, and overdue indicator.
- Kanban view — drag-and-drop board organized by status column.
Features:
- Follow-up overdue alerts (highlighted when past the follow-up date).
- Status transition buttons inline in each row.
- Filter by status, overdue, or company.
- Column filters and saved filter presets (same system as the Jobs table).
- Summary funnel metrics at the top: applied, in progress, responded, interviewing.
Archived /archived
Jobs that have been manually archived or skipped.
- Table of archived jobs with original score and archival date.
- Column filters and saved filter presets on the archived jobs table.
- Restore action: move a job back to active.
- Delete action: permanently remove the job and all associated records.
Auto-archive on skip: marking a job as Skipped automatically moves it to the archive. The job leaves the active pipeline immediately — it will no longer appear on the Jobs page or the Dashboard action queue. It remains visible on the Archived page and can be restored at any time.
Resumes /resumes
Resume studio showing all resume lanes and generated documents.
Two sections:
Base resumes — the source PDF lanes uploaded by the user, or resumes built from scratch inside the app. Each lane represents a different career angle (e.g., “Leadership”, “IC / Individual Contributor”, “Domain Specialist”). The app ships with five default lane names that can be renamed; new lanes can be added at any time.
Each lane shows extraction status, word count, and resume-builder approval state. Existing uploaded resumes are backfilled from stored extracted text, so current users do not need to upload them again.
The /resumes page uses the same dashboard table pattern as the Jobs page on
desktop, with compact cards on smaller screens. The table shows lane name,
builder status badge, source word count, extraction date, and an action button.
Each lane has a direct Review and approve or Edit approved version action
that opens the builder.
Create new resume button appears in the page header on /resumes and in the
Resumes tab of /profile. Clicking it creates a new blank lane with starter
sections (Contact, Summary, Experience, Skills, Education) pre-populated and
immediately opens the Resume Builder.
Resume Builder /profile/resumes/[id]/builder
Structured source editor for each resume lane — works for both PDF-extracted and from-scratch resumes:
- Parses the uploaded resume into editable sections, or starts from blank starter sections when the lane was created from scratch.
- Preserves custom sections such as Recognition when detected.
- Supports editing, adding, removing, renaming, and reordering sections.
- Add section menu — dropdown picker with section types: Summary, Key Achievements, Experience, Skills, Awards & Recognition, Education, Custom.
- Add role / Add entry buttons inside Experience and Education sections to append additional entries without leaving the section.
- ✨ Improve with AI — available on Summary, Key Achievements, Skills, Awards & Recognition, Experience bullets, and Custom sections. Sends the section content to the active AI provider, which returns an improved version. The suggestion is shown inline with Accept and Discard buttons; the original is preserved until the user accepts.
- Helpful placeholder text in every input guides users building from scratch.
- Uses the same split editor/preview layout as the generated resume editor, so source edits can be checked against the rendered resume while reviewing.
- Saves a draft or approves the lane version used by job-specific generation.
- Remove button in the builder header — deletes the resume lane from the system after inline confirmation (“Delete this resume? / Yes, delete / Cancel”). Available for all resumes, not just new ones.
- Back button — navigates to
/resumes. For newly created (unsaved) resumes, clicking Back shows a leave confirmation dialog with four choices:- Save draft and leave — saves the current state as a draft, then navigates away.
- Delete and leave — permanently deletes the resume lane.
- Leave without saving — navigates away without saving (lane is kept but blank).
- Keep editing — dismisses the dialog and stays on the page.
The browser’s native
beforeunloadprompt also fires if the user tries to close the tab or navigate directly while a new resume has not yet been saved.
The HTML resume template renders experience entries with the organization and location left-aligned and the date range right-aligned on the same line, matching standard resume layout conventions.
Generated documents — tailored resumes produced for specific jobs:
- Table showing job, lane used, keyword coverage %, generation date, and status.
- Generated documents keep a stable link to their source resume lane, so PDF export continues to work after the lane is renamed.
- Column filters and saved filter presets on the generated documents table.
- Links to preview HTML and download PDF.
- Delete document action.
Resume Editor /generated-documents/[id]/edit
Full draft editor for a tailored resume before exporting to PDF. Matches the approved-resume builder experience with identical section controls on every section:
- Section title — editable input that updates the heading printed in the PDF.
- ✨ Improve — AI rewrites the section content; user can accept or discard the suggestion. Not shown on the Experience section (improvement is per-entry).
- ↑ Move up / ↓ Move down — reorders sections; order is reflected in the preview and the generated PDF.
- Remove — hides a section from the PDF (data is not deleted).
- Experience entries each have a ✨ Improve bullets button with the same accept/discard flow as the resume builder.
- Header (name, headline, contact) is always pinned at the top and is not moveable or removable.
- Education is always shown last and is display-only (pulled from the base resume).
- Keyword coverage panel — collapsible panel between the help text and the first
section showing all job keywords as chips. Green ✓ chips = exact phrase or strong
term-overlap coverage in the current resume text;
+chips = supported by existing evidence and ready to add;!chips = missing evidence. Clicking a+chip adds the keyword to Skills. Clicking a!chip opens a guided wizard: select the companies and roles where the skill was used, optionally add context, then review distinct resume-writer suggestions grounded in each role’s existing bullets. Each suggestion rewrites the strongest relevant bullet instead of appending a generic line. The user can edit or remove any change before approval. Company confirmation is required; writing is optional. When AI is unavailable or returns an unsafe suggestion, the app uses a claim-preserving fallback rewrite. Updates instantly as the user types (no debounce — pure JS computation). Starts expanded when coverage is below 70%. Collapses to just thecovered/totalcounter when the user has seen enough. The page header uses the same live matcher. - Job-aware AI improvement — ✨ Improve (and ✨ Improve bullets for experience) include the job keywords in the API call. The AI naturally incorporates missing keywords into suggestions without forcing them.
- Evidence guard — AI-proposed headline, summary, impact, skill, recognition, experience, and extra-section claims are checked against the approved resume lane plus confirmed gap answers and supplements. Unsupported AI changes revert to source wording. PDF export is blocked if manual edits still introduce an unsupported metric or substantive claim.
- Live preview pane updates automatically with a 400 ms debounce; Refresh button forces an immediate update.
- Keyword coverage percentage shown in the page header (color-coded green/yellow/red).
Resume Preview /generated-documents/[id]/preview
Read-only HTML preview of the tailored resume.
Interview Prep /interview-prep
Tools to prepare for interviews using stored experience.
Interactive Story Builder:
- Type or Record: Toggle between “Type draft” (typing a raw text response or notes) and “Record audio” (spoken practice transcribed by AI).
- AI STAR Structuring: AI automatically parses the raw text or spoken recording transcript into the structured STAR + Reflection format (Title, Situation, Task, Action, Result, Reflection), identifying demonstrated skills and themes.
- Section-by-Section Editing: Once structured, the story is displayed as separate sections. Each section can be independently edited and saved directly to the database, ensuring you can refine details piece-by-piece.
- Writing Voice Integration: Optionally opt-in to update your writing voice style profile with your custom answers, refining future AI-generated drafts.
STAR Story Bank:
- Collates and displays all saved stories with visual badges for S/T/A/R/Reflection components.
- Support inline editing using the interactive section-by-section editor. Clicking Edit on any card launches the editor immediately.
- Tags stories with skills and themes, and shows the origin (e.g. “From Job eval” or “Voice practice”).
Job Evaluation Integration (Section F. Interview plan):
- Direct entry point from the Job Detail → Analysis page. Next to each suggested question in Section F, clicking
"Draft / Record Answer"opens the interactive builder inline. - Allows preparing, structuring, and editing answers tailored to the job’s context before saving them straight to the Story Bank.
Profile /profile
Career profile editor. The profile is the foundation for all evaluations and
resume tailoring. The page is split into six tabs, navigated via URL
(?tab=<id>), each with its own save action.
The AI extraction card is always visible on the Overview tab as a 2-step flow. Step 1 shows active (blue) when no PDF has been uploaded, and green ✓ once a PDF is ready. Step 2 (Extract button) is disabled until Step 1 is complete. The Resumes tab shows an upload banner when no extracted resumes exist.
Tab: Overview (?tab=overview)
- Summary card: name, current search goal, location, portfolio, urgency, direction.
- AI profile extraction card — 2-step flow: Step 1 (upload) shows active/✓
state; Step 2 (Extract with AI button) is disabled until at least one resume
PDF is uploaded. Runs AI extraction on all uploaded resumes and populates
skills, role directions, and experience automatically. Clicking Extract with
AI opens a blocking
ProgressModal(“Analyzing your resume…” + spinner); on success it shows the number of skills extracted; on error it shows the failure message. - Edit form: current search goal, search direction, urgency (select), career intent, career change interest, confidence level.
Tab: Resumes (?tab=resumes)
- Upload banner (shown when no extracted resumes exist): instructs the user to upload a PDF, then go to Overview to run extraction.
- Resume lanes card — each lane is a different resume version. Per-lane actions:
- Upload PDF (blue solid button): shown when the lane has no PDF; opens file picker, uploads and auto-extracts text.
- Replace PDF (outlined button): shown when the lane already has content; replaces the file and re-extracts.
- Edit resume / Edit approved version (text link): always shown for every lane; opens the Resume Builder for that lane.
- Remove (text link): always shown for every lane; deletes the entire resume lane after inline confirm (“Remove this resume? / Yes, remove / Cancel”). This replaces the old PDF-only removal behavior — the lane itself is deleted.
- Rename (✎ pencil icon): inline rename with keyboard support (Enter saves, Escape cancels).
- Add resume (PDF) button at the bottom of the lanes list: creates a new empty lane named “New Resume”. User then renames it and uploads a PDF.
- Create new resume button: creates a blank lane with starter sections (Contact, Summary, Experience, Skills, Education) and opens the Resume Builder immediately — no PDF required. User types or pastes their content directly.
- Skill inventory card (shown only after at least one AI extraction): lists extracted skills with category and evidence source.
Tab: Skills & Roles (?tab=skills)
- Read-only badge displays for: strongest skills (from AI extraction), skills to use more, skills to use less, target roles.
- Edit form: target roles (one per line), skills to use more (one per line), skills to use less (one per line).
Tab: Preferences (?tab=preferences)
- Summary cards: location mode, compensation, desired industries, preferred locations.
- Edit form: location mode checkboxes (
Remote,Hybrid,On-site), preferred locations, desired industries, compensation needs, and free-form work preferences. - Preferred locations use an OpenStreetMap Nominatim lookup that supports city,
state/region, and country selections. You can save precise locations such as
Nashville, Tennessee, United States, broader targets such asTennessee, United States, or country-only values such asCanada. Each saved place displays as one label; legacy split values such asNashville,Tennessee,United Statesare normalized back into one preferred-location label. - Work preferences are reserved for non-location preferences such as
small team,async-first, ormission-driven; location modes are stored separately.
Tab: Constraints (?tab=constraints)
- Read-only list of current constraints and deal breakers.
- Edit form: constraints (soft limits, one per line), deal breakers (hard-no conditions flagged as red flags in evaluations, one per line), career change interest.
Tab: Writing Voice (?tab=voice)
- Displays the current tone profile if already extracted (tone, formality, sentence style, style guide).
- Form to paste 2–5 writing samples (emails, cover letters, LinkedIn posts)
separated by
---. Submitting runs AI style extraction and saves the tone profile. Used to match AI-generated content to the user’s authentic voice.
Strategy /strategy
Role-fit map derived from the profile and skill inventory.
- Direct fit roles: score ≥ 80, strong evidence across the profile.
- Adjacent roles: score 60–79, achievable with some positioning.
- Selective roles: score 40–59, situational fit depending on the company.
- Avoid roles: score < 40, significant gaps or misalignment.
Each archetype shows a score, rationale, and gap list. The user can edit the classification or rationale to correct AI judgments.
The page has two tabs:
- Strategy (default) — role-fit map, search focus, AI-generated role directions, how-to-use guide, and evaluation corrections. Use Generate with AI when no role directions exist, or Regenerate with AI to rebuild them from the current profile and skill inventory. The app replaces the current role-direction set after generation and keeps the editable correction workflow below each direction.
- AI Prompts — prompt overrides for resume tailoring, application answers, and outreach. Prompt overrides are stored locally and can be reset to the app defaults; locked resume-safety rules remain enforced in code.
Analytics /analytics
Search performance metrics drawn from actual evaluation and application data.
Charts and metrics:
- Score-to-outcome correlation: do high-score jobs convert to interviews?
- Archetype performance: which role types get the most responses.
- Remote policy conversion: how remote / hybrid / onsite jobs track through the funnel.
- Gap coverage: what percentage of evaluated gaps have been addressed.
- Application funnel conversion rates.
Settings /settings
Four configuration tabs:
AI Providers
- Provider priority list — enable up to four providers and order them by priority. The first enabled provider in the list is used for every task; the rest act as automatic fallbacks. Drag the grip handle on each row to reorder.
- Cloud providers — Anthropic (Claude), OpenAI (GPT), Google (Gemini). Enter an API key and select a default model for each.
- Ollama (local) — free, runs entirely on your machine; no API key required. Enable in the priority list to reveal the configuration section:
- Base URL — Ollama server address (default
http://localhost:11434). - Model picker — click “Choose…” to fetch the list of locally installed models from the running server and select one.
- Quality guide — ≥64 GB:
qwen2.5:72b/llama3.1:70b(near cloud quality); ≥12 GB:qwen2.5:14b/mistral-nemo; ≥8 GB:llama3.1:8b/qwen2.5:7b. - Unreachability warning — when Ollama is in the priority chain and the server is not reachable, an inline warning banner appears with a Retry button.
- Base URL — Ollama server address (default
- Test connection for any provider to verify credentials and measure latency.
- Model attribution — every AI-generated result (evaluation, research, outreach drafts, application answers) shows the model and provider that produced it.
Job Sources
- All configured sources appear in a unified table — companies from
portals.example.ymland any manually added sources are treated equally. - Enable or disable individual sources (disabled sources are skipped on the next scan).
- Column filters and saved filter presets on the sources table.
- Sources from
portals.example.ymlcannot be removed (they reload from the config file); manually added sources have a Remove button. - Add any company by pasting its careers page URL — Greenhouse, Ashby, and Lever are auto-detected.
- “Scan for new sources” queries the Common Crawl index to discover additional ATS boards automatically.
- Discovered sources stay pending until the user reviews and explicitly selects the validated companies to add.
- Cleanup review lists disabled or malformed user-added sources for explicit removal. Existing sources are never removed automatically.
- “Search discover” queries Brave Search API for ATS job boards not in Common
Crawl (requires Brave Search API key in AI Provider settings). Merges new
findings into
data/discovered-sources.jsonwithout overwriting existing entries. - “Validate sources” opens a modal (progress, then summary counts and a scrollable list of dead/unknown boards — same interaction pattern as Scan for new jobs on the Dashboard). It checks each tracked board’s public ATS JSON URL (same
host as CareerOps scans). Results: Live / N jobs when HTTP 200 and JSON
parse succeeds, Dead on HTTP 404, Unknown for other HTTP codes, timeouts,
or non-JSON. The validator uses a 45s per-source ceiling, browser-like
User-Agent/Acceptheaders, up to three attempts with backoff on transient errors (HTTP 429 / 5xx / network aborts), lower concurrency (5), and longer pauses between batches so Ashby and other hosts are less likely to rate-limit when hundreds of sources are validated at once. Hover an Unknown badge to see the last error string (for exampleHTTP 429). Re-validate sources re-runs the full check.
Preferences
- Edit title include / exclude filters.
- Add profile supplements for gap filling. Supplements are checked for concrete role, project, action, and outcome detail before they are treated as confirmed resume-tailoring context.
- Adjust other search preferences.
Data & Backup
- Enable or disable automatic scans every six hours while the local app is running.
- Select the fresh-posting window: 24 hours, 72 hours by default, or 7 days. CareerOps and Adzuna scans use the selected window.
- Create a portable
.jst-backuparchive with optional password protection. - Restore only after archive validation, preview, explicit confirmation, and an automatic rollback backup.
AI Capabilities
The app supports four AI providers interchangeably:
| Provider | Default model | Used for |
|---|---|---|
| OpenAI | gpt-5.4-mini | Evaluation, answers, outreach, research, transcription |
| Anthropic | claude-sonnet-4-6 | Evaluation, answers, outreach, research |
| Google Gemini | gemini-2.5-flash | Evaluation, answers, outreach, research, transcription |
| Ollama (local) | user-selected | Evaluation, answers, outreach, research |
Providers are configured as an ordered priority chain in Settings → AI
Provider. The app tries providers from top to bottom and automatically fails over
to the next one when a provider is unavailable or returns a recoverable error.
The first provider in the chain that has a credential configured is the active
provider. Ollama uses a base URL (default http://localhost:11434) instead of an
API key and supports any model installed on the local Ollama server. All AI calls
use the src/lib/ai/ provider abstraction with retry and failover logic.
AI-powered features:
- Job fit evaluation (streaming, real-time output)
- Resume tailoring
- Application answer generation
- Company research
- Outreach message drafting
- Profile extraction from PDF
- STAR story transcription and parsing
- Gap response polishing
- Writing style extraction
Job Scanning
The scanner discovers new jobs from ATS APIs (Greenhouse, Ashby, Lever) and custom URLs configured in Settings.
How a scan works:
- User clicks “Scan for new jobs” on the Dashboard.
- The CareerOps ATS scanner queries each enabled Ashby/Greenhouse/Lever source in parallel.
- If Adzuna credentials are configured (Settings → AI Provider → Discovery & Aggregators), an Adzuna aggregator scan runs in parallel alongside the ATS scan.
- Title filters remove irrelevant roles.
- Profile location and remote preferences remove listings outside the user’s constraints.
- Listings outside the selected fresh-posting window are filtered out.
- Duplicate URLs are skipped.
- New jobs are written to the
jobstable withstatus = found. - A
scan_runsrecord is created with metrics. - The Dashboard updates with a combined scan summary (ATS + Adzuna totals merged).
Scan results dialog (Dashboard “Scan for new jobs” and Settings → Sources per-company scan): the modal is scrollable when there are many errors or new listings. Each error shows a category badge — Dead or missing (404/410, bad URL, unknown host), Timed out (no response within the fetch limit; the board may still be live), or Other error. A summary line counts how many sources reported issues, how many can be disabled as YAML/custom career sources, and a breakdown by category. Select all / Clear selection / Disable selected bulk-update scan_source_overrides; per-row Disable does the same for one company. Aggregator-only rows (e.g. Adzuna) are not disabled as career sources — the UI points to AI Provider settings instead.
The Jobs page can also verify whether saved postings still exist. The liveness
check updates liveness_status but does not automatically archive or delete
anything. Expired jobs with no user activity are shown for confirmation before
deletion. Jobs with activity, such as reviewed, skipped, resume-generated, or
applied jobs, are kept unless the user explicitly selects and deletes them.
The Jobs table also re-checks current profile preferences at render time and is
refreshed after Preferences or Constraints are saved. Jobs that still fit show
Match in the Preference column; jobs that no longer fit show Out of scope.
Location matching uses the selected Location mode checkboxes:
Remoteincludes all remote opportunities, even if the posting mentions a region outside the preferred locations.Hybridincludes hybrid opportunities only when the posting location matches one of the preferred locations.On-siteincludes on-site opportunities only when the posting location matches one of the preferred locations.
Configuration:
- Built-in sources: enable/disable per company in Settings → Job Sources.
- Custom sources: add any careers page URL.
- Title filters: positive list (must match) and negative list (exclude if matched).
- Profile filters: selected location modes and preferred locations constrain scan inserts.
- Settings → Sources table: Above the table, counts show sources total | enabled (enabled reflects optimistic checkbox toggles until the server round-trip completes). Scan all enabled runs the same full CareerOps job fetch as the Dashboard scan’s ATS leg: every enabled source is queried in parallel, independent of any prior “Validate sources” result — use it after re-enabling boards or when you want a fresh pull without opening the Dashboard. Scan jobs on a single row calls the same scanner with
companyExactfor that company even when the row is disabled, so you can verify a careers URL before turning the source back on. The Live column uses the same sort/filter header pattern as the other data columns; until Validate sources has been run, Live shows Not validated for each row.
Performance tuning constants (in src/lib/scanner/careerops-scanner.ts):
CONCURRENCY = 20— parallel ATS API requests.ATS_JOB_LIST_FETCH_MS = 60_000— per-source ceiling for the full job-list request: HTTP response andresponse.json()parsing. Large Ashby boards often need far more than a few seconds to download JSON; the previous 12s budget could mark live boards as timed out. (jd-fetcher.tsstill usesFETCH_TIMEOUT_MS = 12_000for individual job-description fetches.)ATS_JOB_LIST_FETCH_RETRIES = 1— on timeout/abort, one immediate retry after a short pause (transient CDN saturation).
Pruning dead sources: npx tsx scripts/prune-dead-sources.ts validates
every enabled source (YAML + custom) and writes scan_source_overrides rows
to disable any that return HTTP 404 in pass 1, plus any that are unreachable
in both passes. Disabling is non-destructive — sources can be re-enabled
from Settings → Job Sources. Useful after a bulk source-discovery import,
which tends to add many slugs that do not actually exist on the ATS host.
Browser Job Board Scanner (Claude and Codex Integration)
An optional feature for users with Claude Desktop or Codex Chrome. An agent browses visible job-board results on your behalf and writes discovered jobs directly into Job Search Terminal — no copy-paste required. Supported browser-board sources are LinkedIn, Wellfound, Work at a Startup, Glassdoor, Indeed, and Monster.
How it works:
- Ask Claude or Codex to scan LinkedIn, Wellfound, Work at a Startup, Glassdoor, Indeed, or Monster
- The agent reads your target roles and location preferences from the JST database
- The agent opens the requested board in Chrome and extracts matching visible postings
- A JSON file is written to
data/job-board-imports/(data/linkedin-imports/remains supported for legacy LinkedIn files) - Job Search Terminal detects the file, imports jobs with duplicate detection, and shows a notification
Adzuna Job Aggregator (Direct API Scanner)
Adzuna is a job aggregator that indexes listings from many sources including Indeed, CareerBuilder, and direct employer feeds. Unlike browser-board scanning, Adzuna requires no browser or logged-in session — the app queries its public API directly.
How it works:
- Register at developer.adzuna.com for a free App ID and API Key (free tier: 2,000 queries/month)
- Paste both keys in Settings → AI Provider → Discovery & Aggregators
- Open Settings → Sources — the Job aggregators card appears at the bottom
- Click Scan with Adzuna; clicking opens a blocking
ProgressModal(“Scanning Adzuna” + spinner). On completion the modal shows “Found N listings — X new, Y duplicates.” and can be dismissed with Close. - New jobs enter the same import pipeline as browser-board scans — duplicate detection, title filtering, and source badges all apply
What it covers: Adzuna aggregates from multiple sources and covers roles that may not appear in direct ATS portals or browser-board searches. It is best used alongside browser-board and CareerOps ATS scans.
Limits: Up to 5 target roles × 3 locations per scan, 50 results per query, and the selected fresh-posting window (24 hours, 72 hours by default, or 7 days). Adzuna’s coverage varies by country (default: us).
Scan type recorded: adzuna-api-scan. Jobs appear in the Jobs table with an Adzuna source badge.
UI indicators on the Jobs table:
- LinkedIn, Wellfound, Work at a Startup, Glassdoor, Indeed, Monster, or Adzuna badge (neutral gray) — source column — identifies jobs discovered via browser-board scans or aggregator API scans
- Manual badge (neutral gray) — source column — identifies jobs added manually via the Add Job modal
- Duplicate badge (amber, clickable) — flagged jobs whose URL or company+title already existed in the database. Clicking the badge instantly filters the table to show only duplicate-flagged jobs. Clicking again clears the filter.
- Source column — filterable and sortable; options are “LinkedIn”, “Wellfound”, “Work at a Startup”, “Glassdoor”, “Indeed”, “Monster”, “Adzuna”, “Manual”, and “Scanner”
URL behavior: Browser-board imports prefer a visible job-specific employer/ATS apply URL. If one is not available, the platform job URL is used and preserved as provenance.
Duplicate detection: Jobs are marked as possible duplicates (not dropped) when their original posting key, URL, or company+title+location matches an existing record. The user can review and act on flagged jobs normally.
Adzuna URL stability: Adzuna’s API returns session-scoped redirect URLs that include tracking tokens which change on every API call. The importer normalises these to a stable canonical URL (https://www.adzuna.com/land/ad/<id>) so the same job always maps to the same database record across scan runs, preventing previously-imported Adzuna jobs from appearing as new. The same stable ID is also used for within-scan deduplication when the same listing appears under multiple search queries.
Import notification: A fixed-bottom green alert appears on the Jobs page within 30 seconds of a completed import, showing the count of new jobs and duplicates. Auto-dismissed after 5 minutes.
Requirements: Claude Desktop with Claude in Chrome, or Codex with the Codex Chrome Extension. The user must already be logged into boards that require a session.
Full documentation:
- User guide:
docs/linkedin-scanner-guide.md - Technical reference:
docs/browser-board-scanner-technical.md - Agent instructions:
CLAUDE.mdandAGENTS.md
Shared UI Patterns
Modal (src/components/ui/modal.tsx)
A reusable dialog shell used for form-entry and action-confirmation dialogs. It provides the overlay, dialog frame, header, scrollable body, optional sticky footer, Escape-key handling, and accessibility attributes so individual components don’t repeat that boilerplate.
Props: open, onClose?, title, description?, size? (“sm”|“md”|“lg”),
sheet? (bottom-sheet on mobile, centered on desktop), children, footer?.
- When
onCloseis provided, the X button appears in the header and Escape closes the modal. When omitted (e.g. during a pending submission) neither is active. sheetproducesitems-end sm:items-centeralignment with no padding on mobile androunded-t-panel sm:rounded-panelcorners — the standard bottom-drawer pattern.- Non-sheet modals are always centered with
rounded-2xl.
Used by:
| Component | size | sheet | Purpose |
|---|---|---|---|
AddJobModal | lg | — | Add job manually form |
EditJobModal | lg | — | Edit job details form |
GapAddressingPanel | md | ✓ | Address gap / add detail (two-phase modal) |
GlobalGapAddressingPanel | md | — | Follow-up evidence detail per top gap |
ProfileSupplementsEditor | md | — | Follow-up evidence detail per supplement |
ProgressModal (src/components/ui/progress-modal.tsx)
A reusable blocking progress dialog used by all AI generation and long-running data-fetch actions. It wraps the same visual pattern established by the “Scan for new jobs” modal.
States:
- Running (
phase="running"): spinning border circle + title + primary message + optionalstatusLine(animated pulse, used for streaming labels like current research axis) + optionalsubtitle(smaller muted text). The backdrop is not clickable; the modal cannot be dismissed. - Done (
phase="done"): showschildren(success content) or anerrorstring in a danger callout. An X button appears in the header and a Close button appears in the footer; clicking either or the backdrop closes the modal.
Props: open, phase, title, message, subtitle?, statusLine?,
error?, children?, onClose.
Used by:
| Action | Title | Success message |
|---|---|---|
| Evaluate N selected jobs | ”Evaluating N jobs" | "{N} evaluated successfully” |
| Verify active postings | ”Verifying active postings” | Badge summary + “close to take action” |
| Extract profile with AI | ”Extracting profile with AI" | "{N} skills extracted” |
| Scan with Adzuna | ”Scanning Adzuna" | "Found N listings — X new, Y duplicates” |
| Start/Re-research | ”Researching company" | "Research complete — scroll down…” |
| Generate/Regenerate outreach | ”Generating outreach messages" | "3 messages ready — scroll down…” |
| Prepare application answers | ”Preparing application answers" | "Answers prepared — scroll down…” |
Data and Privacy
- All data is stored locally in
data/job-search-terminal.sqliteon the user’s machine. - No data is sent to any server except AI provider API calls (evaluation, generation, etc.).
- The database file is excluded from git.
- Portable account backup: Account → Settings → Data & Backup creates a
.jst-backuparchive with the database, database-referenced resume lane files, generated documents, source configuration, and scanner history. Optional password protection encrypts the complete payload. Creation streams files into the archive and shows aProgressModal(cycling through three phase labels) while the local snapshot is packaged. Other files underassets/are always ignored. - Restore: Account → Settings → Data & Backup validates the archive, previews its contents in a bounded disk staging area, creates a rollback backup, then replaces the managed local snapshot after explicit confirmation.
- Database-only backup:
npm run data:backupwrites a SQLite snapshot tooutput/backups/. - Export:
npm run data:exportwrites a JSON snapshot tooutput/exports/. - To reset all data:
npm run db:reset(drops local data and initializes an empty profile).
Inspired by Career-Ops
Job Search Terminal was inspired by Career-Ops by Santiago Fernández.
Career-Ops is a powerful MIT-licensed AI job-search system built around agentic CLI workflows, Claude Code, Gemini CLI, job scanning, scoring, tailored PDF generation, and pipeline tracking.
Job Search Terminal follows a different product direction: a local-first visual dashboard with guided setup, structured application tracking, resume lanes, interview prep, and support for OpenAI, Anthropic, and Google Gemini.
Both projects share the same basic belief: job seekers need better tools, and those tools should not add more friction to an already exhausting process.
Career-Ops GitHub repository · Career-Ops system article
| Career-Ops | Job Search Terminal |
|---|---|
| CLI / agentic workflow | Browser-based visual dashboard |
| Optimized for Claude Code and power users | Designed for broader job seekers |
| Markdown, YAML, TSV, and generated reports | SQLite-backed local app |
| Strong batch automation | Strong application tracking and UX flow |
| Terminal dashboard | Web dashboard with guided setup |
| Claude Code first | OpenAI, Anthropic, and Gemini provider support |
| MIT licensed | CC BY-NC 4.0, non-commercial use only |
Want to help improve it?
This started as a practical tool for people trying to survive the job search. It can become better if more people touch it.
Developers can help simplify installation, improve scanning, package the app, and fix bugs.
Designers can help improve onboarding, accessibility, empty states, and workflow clarity.
Career coaches and job seekers can help test the real-world process and tell us where the app breaks down.
If you want to contribute, start with the GitHub repository or reach out to me directly.
Job Search Terminal is a free, source-available tool licensed for non-commercial use under CC BY-NC 4.0. It does not guarantee interviews, job offers, recruiter responses, or application outcomes. You are responsible for reviewing all generated content before using it. You are also responsible for following the terms of service of any job boards, AI providers, or third-party services you choose to use.
License
Job Search Terminal is licensed under Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0).
You may use, share, and adapt it for non-commercial purposes with attribution.
Commercial use is not permitted without written permission from the copyright holder.
View license: https://creativecommons.org/licenses/by-nc/4.0/