A self-hosted job search command center. Track prospects, scrape target company career pages, and generate ATS-safe resumes and cover letters with any AI model — no vendor lock-in, no database, no monthly fee.
→ Product overview — features, screenshots, and context on why it exists.
→ View live demo — interactive, no login required. It's a shared board seeded with sample data that resets nightly; scraping is disabled, everything else works.
- Personal job tracker — manage everything from first look to final decision; track prospects you're watching, applications in flight, and roles you've passed on, all in one private board
- Automated scraping — Playwright-based scraper hits target career pages on a schedule via GitHub Actions
- AI throughout — chat assistant, job fit scoring, ATS-safe resume tailoring, and cover letter generation; bring your own model (Anthropic, OpenAI, Ollama, or any OpenAI-compatible endpoint)
- Zero infrastructure — data lives in flat JSON files in your own private GitHub repo; deploy the UI to Vercel for free
Prospects filtered to strong-fit roles — one board for everything from first look to final decision.
![]() Job detail |
![]() AI chat assistant |
![]() Onboarding wizard |
# 1. Clone and install
git clone https://github.com/creativereason/deckhandAI.git
cd deckhandAI
pnpm install
# 2. Run the setup wizard
node scripts/setup.mjs
# Prompts for GitHub token, data repo, app password, candidate profile,
# and AI provider — then writes .env.local, data/config.json, and data/profile.json
# 3. Run locally
pnpm dev # http://localhost:3000Want sample data to start from? Run the data repo init script:
node scripts/init-sample-repo.mjs
# Creates a GitHub repo pre-populated with a realistic fictional candidate profile,
# job tracker data, work history, and scrape targets.Prefer to configure manually? Copy the sample files and edit them directly:
cp data/config.sample.json data/config.json
cp data/profile.sample.json data/profile.json
cp data/jobs.sample.json data/jobs.jsonSet these environment variables in your Vercel project settings:
| Variable | Required | Description |
|---|---|---|
GITHUB_TOKEN |
Yes | Personal access token with repo scope |
GITHUB_DATA_REPO |
Yes | username/repo-name of your private data repo |
GITHUB_DATA_BRANCH |
No | Branch to read/write (default: main) |
APP_PASSWORD |
Yes | Password to protect the tracker UI |
COOKIE_SECRET |
Yes | Random 32+ character string for signing session cookies |
AI_API_KEY |
No | API key for Anthropic, OpenAI, etc. |
AI_PROVIDER |
No | anthropic | openai | ollama | custom (default: anthropic) |
AI_BASE_URL |
No | Ollama or custom endpoint base URL |
BRAVE_SEARCH_API_KEY |
No | Brave Search API key. Used by the weekly job search script and, in "Evaluate a job," as a fallback to find a plain-fetchable cross-post when the given URL can't be fetched directly (e.g. Workday). Without it, that fallback is silently skipped. |
DEMO_MODE |
No | true to enable demo mode: bypasses auth, disables the scraper, and reads/writes GITHUB_DATA_REPO (point it at a public sample-data repo) |
NEXT_PUBLIC_DEMO_MODE |
No | true to show the demo-mode banner in the UI |
The setup wizard generates these values for you locally. For Vercel, copy the values from your .env.local into your project's environment variable settings.
The quickest way to get a data repo with realistic starter data:
node scripts/init-sample-repo.mjsThis creates a new GitHub repo under your account and uploads pre-filled jobs.json, config.json, profile.json, and scrape-targets.json based on a fictional candidate (Alex Chen, Director of Product Design in Austin, TX). Use it as a starting point — swap in your own details in Settings after first login.
The same sample files power the live demo.
To host your own demo deployment:
- Run
init-sample-repo.mjswith a public repo - Deploy deckhandAI to Vercel with:
No
GITHUB_DATA_REPO=yourhandle/deckhandai-sample-data DEMO_MODE=true NEXT_PUBLIC_DEMO_MODE=trueAPP_PASSWORDneeded — demo mode bypasses auth. Visitors can add/edit/move jobs and use AI generation; the scraper stays disabled since it launches a real headless browser against live career pages. - Add repo secrets
DEMO_DATA_REPO(yourhandle/deckhandai-sample-data) andDEMO_DATA_REPO_TOKEN(a token with write access to it), then enable theReset demo dataGitHub Actions workflow. It runs nightly and overwrites the demo repo's data files back todata/*.sample.json, so a public, unauthenticated demo doesn't drift or fill up with junk. Trigger it manually any time viaworkflow_dispatchif you want an immediate reset. - Consider using separate, low-limit keys for the demo deployment's
AI_API_KEYandBRAVE_SEARCH_API_KEY— both are reachable by anonymous visitors. Brave's free tier caps at 2,000 queries/month, which is plenty to bound the blast radius for a demo.
data/config.json controls your candidate profile, job filter preferences, and AI settings. The setup wizard writes this file for you, or copy from data/config.sample.json and edit manually.
Key fields:
candidate— name, email, phone, website, LinkedInpreferences.titles— target job titles used for scrape filteringpreferences.salary— minimum FTE base and contract hourly ratepreferences.locations— remote/hybrid flags, hub city, hybrid radius in milesai— provider, model, and optional base URL
data/profile.json is your structured work history — the source material the AI uses to write cover letters and resume tailoring notes. The setup wizard creates an initial version; edit it directly to add your experience bullets, strengths, and writing style rules.
See data/profile.sample.json for the full schema.
Add and manage scrape targets in two ways:
- Settings UI — go to Settings → Scrape Sources to add, edit, or remove targets without touching any files. Changes are saved to
data/scrape-targets.jsonin your data repo. - Script — edit
scripts/scrape-careers.mjsdirectly for bulk setup or to define custom selectors. The script reads fromdata/scrape-targets.jsonat runtime if it exists, falling back to the targets defined in the file.
On-demand scrape from the UI: ask the Deckhand chat assistant to "scrape for new jobs" — it runs your configured remote and local targets and streams per-target progress into the chat. Scheduled runs (below) remain the primary path for hands-off scraping; the chat prompt is for a manual run without leaving the board.
Run the scraper manually or let GitHub Actions run it on a schedule:
node scripts/scrape-careers.mjsRequires Playwright with Chromium:
npx playwright install chromiumVercel deployments cannot run the scraper. Vercel's serverless environment does not support Chromium. To use automated scraping, run the scraper from your own machine or a self-hosted server — either on a schedule via cron or via the GitHub Actions workflow (
.github/workflows/scrape.yml), which runs the script in a standard Ubuntu runner that supports Playwright.
Scheduling your own runs? .github/workflows/scrape.yml in this repo is a template — never add your own secrets to the shared upstream repo. See docs/scheduled-scraping.md for hosting it in your own fork or (recommended) directly in your private data repo.
Beyond scraping your target companies, deckhandAI can search job boards automatically and populate the pending queue without any manual effort. Three approaches are supported — GitHub Actions, system cron, and Claude Code's built-in scheduler — depending on how you run the app.
→ docs/scheduled-job-search.md
deckhandAI uses AI throughout the workflow — not just for document output. All features run through the same provider you configure, so one API key covers everything.
Chat assistant ("Your Deckhand") An always-visible panel on the board — stacked above the tracker on mobile, a sticky rail alongside it on larger screens — plus a per-job chat on the job detail page. Ask questions about your pipeline, evaluate a job URL, scan for ghost jobs, trigger an on-demand scrape of your configured career-page targets, or have it add, move, or update jobs through natural language.
Job fit evaluation Each job in your tracker can be scored against your target titles, salary floor, and location preferences. The AI explains why a role is a strong fit or a caution, surfacing signal you'd otherwise have to read manually.
At-a-glance role summaries
Every job gets a 1–2 sentence AI summary of what the role is and what the company does — shown on the board card and in the job detail header, so you get the gist without re-reading notes or the full JD. Summaries are generated automatically when jobs are added; bring an existing board up to date once with pnpm backfill:summaries.
Cover letter generation
Drafts a tailored cover letter from your work history (profile.json) and the job description. Streamed in real time, editable inline, and exportable as a styled DOCX or print-to-PDF.
ATS-safe resume tailoring Takes your base resume and rewrites the summary and experience bullets to match the specific role — single-column layout, no tables or text boxes, bullets via proper list markup so they survive every ATS parser. Outputs a ready-to-submit DOCX.
Provider options deckhandAI supports several AI providers. Choose whichever fits your setup.
Requires an Anthropic Console account at console.anthropic.com. This is separate from a Claude.ai subscription — the API is pay-as-you-go with credits you add to your account. A standard Claude.ai plan does not include API access.
Cost in practice: generating a cover letter with claude-sonnet-4-6 costs a few cents per call. $5–10 in credits goes a long way for personal use.
{
"ai": {
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"base_url": null
}
}AI_API_KEY=sk-ant-...
Requires an OpenAI account at platform.openai.com with credits loaded. Same pay-as-you-go model.
{
"ai": {
"provider": "openai",
"model": "gpt-4o",
"base_url": null
}
}AI_API_KEY=sk-...
If you have Ollama running on your machine or local network, you can use it with no API key and no per-call cost. Good models for this use case: llama3, mistral, phi3.
ollama pull llama3 # one-time download
ollama serve # runs at http://localhost:11434{
"ai": {
"provider": "ollama",
"model": "llama3",
"base_url": "http://localhost:11434/v1"
}
}No AI_API_KEY needed.
Google Gemini has a free tier with generous rate limits — a good starting point if you don't want to add a credit card. Get an API key at aistudio.google.com (free, just needs a Google account).
{
"ai": {
"provider": "custom",
"model": "gemini-2.0-flash",
"base_url": "https://generativelanguage.googleapis.com/openai/v1"
}
}AI_API_KEY=your-google-ai-studio-key
gemini-2.0-flash is fast and capable for document generation. gemini-1.5-pro is stronger if quality matters more than speed.
Grok is OpenAI-compatible and tends to have competitive pricing. Get an API key at console.x.ai.
{
"ai": {
"provider": "custom",
"model": "grok-3",
"base_url": "https://api.x.ai/v1"
}
}AI_API_KEY=your-xai-key
Any OpenAI-compatible API works the same way — LM Studio, vLLM, or any other local server. Set provider to custom, point base_url at your server, and set the model name to whatever your server expects.
{
"ai": {
"provider": "custom",
"model": "your-model-name",
"base_url": "http://your-server:port/v1"
}
}Set your provider via the setup wizard (node scripts/setup.mjs) or edit data/config.json directly. The API key goes in .env.local as AI_API_KEY.
jobs.json, config.json, and profile.json are gitignored by default. Your job search data, salary notes, application history, and work history never leave your own deployment. AI generation happens server-to-model — your data is not sent to any third party beyond the AI provider you configure.
See ROADMAP.md for planned features including an AI chat assistant, ghost job detection, and more.
See CONTRIBUTING.md.
MIT



