llm-job-tracker
v0.4.5
Published
Scaffold a local job-search workspace for LLM agents.
Maintainers
Readme
LLM Job Tracker
A local-first job-search workspace for LLM agents. Everything is Markdown, everything stays on your machine.
The npm package scaffolds the workspace and installs Claude and Codex integrations. Your agent then runs the workflows: find jobs, research companies, tailor CVs, draft outreach, review fit, prepare interview stories, fill application forms, and export PDFs.
Contents
- Create a workspace
- What it creates
- Quick start
- Local LLM integrations
- LLM hooks
- Dependency check
- Typical workflow
- Agent CLI utilities
- Hard rules
- Skills / commands
- Profile mechanics
- Configuration
- Repository layout
- Tracker rules
- Safety rules
- Porting to another candidate
- Using with other LLMs
Create Or Update A Workspace
Create a new workspace in a named directory:
npx llm-job-tracker my-job-search
cd my-job-searchCreate in the current directory:
mkdir my-job-search
cd my-job-search
npx llm-job-trackerUpdate an existing workspace in place:
cd my-job-search
npx llm-job-tracker
# then in your LLM tool:
# job-tracker:healthThe CLI updates managed files (skills/, scripts/, templates/, migrations/, shared config/ files). It never overwrites your data: candidate/, data/, strategy/search-profiles/, and personal config/ files are protected. After the CLI update, run job-tracker:health — it seeds any missing protected-zone files and applies pending in-file migrations (adding new sections to existing files) with your confirmation.
The CLI defaults to the current directory. Without an explicit command, it initializes empty/non-workspace targets and updates existing job-tracker workspaces.
Explicit commands are also available:
npx llm-job-tracker init .
npx llm-job-tracker update .
npx llm-job-tracker update . --dry-runA global install exposes a shorter job-tracker binary:
npm install -g llm-job-tracker
job-tracker
job-tracker update .Flags:
--no-installcopies or updates files only and skipsnode scripts/install.js all.--forceallows init into a non-empty non-workspace target. Use it only when you mean to.--dry-runshows update actions without writing files.
What It Creates
The workspace separates reusable workflows from candidate-specific data:
skills/contains generic agent workflows.config/contains settings, paths, language, source registry, next-actions, tracker schema, session report schema, and durable agent instructions.candidate/contains candidate facts, stories, and base CVs undercandidate/cv/.strategy/contains search profiles, sources, and scoring criteria.style/contains CV and outreach house style.templates/contains reusable document templates.data/companies/anddata/tracker.mdcontain live job-search artifacts.
Quick Start Inside The Workspace
Review and update the candidate configuration:
candidate/candidate.mdcandidate/cv/cv-base.mdconfig/language.mdstrategy/search-profiles/default.mdstrategy/criteria.mdstrategy/sources.mdconfig/settings.md
Install local LLM integrations if you used
--no-install:node scripts/install.js allVerify local script, hook, browser MCP, and PDF dependencies:
node scripts/check-deps.jsRun the interactive workspace readiness check in your LLM tool:
job-tracker:setupStart from current pipeline status:
job-tracker:status
Local LLM Integrations
Use node scripts/install.js to install the canonical skills, instructions, and hook configs for a tool:
node scripts/install.js
node scripts/install.js claude
node scripts/install.js codex
node scripts/install.js allThe script creates symlinks where possible. Use --copy when the target tool cannot follow symlinks:
node scripts/install.js codex --copyTargets:
claudeinstallsskills/into.claude/skills, writesCLAUDE.md, and installs.claude/settings.jsononly when it is missing.codexinstalls each skill under.codex/skills/, installs.codex/hooks.json, installs.codex/rules/, and writesAGENTS.md.allinstalls both targets.
Canonical source files:
config/agent-instructions.md->CLAUDE.mdandAGENTS.mdskills/->.claude/skillsand.codex/skillsscripts/llm-hooks/claude-settings.json->.claude/settings.jsononly when local settings are missingscripts/llm-hooks/codex-hooks.json->.codex/hooks.jsonscripts/llm-hooks/codex-rules/->.codex/rules/
Codex may require reviewing or trusting changed local hooks via /hooks. Claude Code may also prompt depending on local policy.
Claude permissions.allow and Codex permissions are not the same format. Codex command allow rules live in .codex/rules/*.rules; filesystem/network permission profiles live in .codex/config.toml or ~/.codex/config.toml.
Generated local integration targets are ignored by git:
.claude/.codex/CLAUDE.mdAGENTS.md
Install As A Claude Code / Cowork Plugin
The same skills ship as a Claude Code plugin. Use this if you prefer Claude Cowork (desktop) or the Claude Code marketplace over the npx workspace scaffolder.
- Download the plugin zip from the latest GitHub release.
- In Claude Cowork, open Plugins in the sidebar, click +, choose Upload plugin, and drop the zip.
- Open the folder you want to keep your job search in (this becomes the workspace).
- Run
/job-tracker:setupto scaffold and configure the workspace.
Commands are namespaced the same way in both the plugin and the npx workspace: /job-tracker:run, /job-tracker:find, /job-tracker:status, and so on.
Your data (candidate/, config/, data/) lives in the working directory you open, not inside the plugin. Update the plugin by uploading a newer release zip; /job-tracker:health reports when a newer version is available.
LLM Hooks
The project uses minimal command hooks that are compatible with both Codex and Claude Code. They are conservative and do not use Claude-only prompt, agent, HTTP, or MCP hook types.
Installed hooks:
- block agent-side outreach sending, applying, connecting, or pushing;
- allow final ATS application submit only when
job-tracker:applyuses the explicit confirmation marker after user approval; - block shell redirection overwrites of
data/tracker.md, CV, and prep-note files; - block direct prep-note claims that outreach, applications, LinkedIn messages, connection requests, or email were sent/submitted/contacted by the agent;
- remind the agent that
### Manual Message Draftsshould be produced throughjob-tracker:draft; - remind the agent about CV/tracker/prep-note rules after edits;
- remind the agent at turn end to report changed files, verification status, and relevant next actions.
Shared implementation lives in scripts/llm-hooks/*.js.
Dependency Check
Run:
node scripts/check-deps.jsThe check verifies:
python3pandoc- Python
weasyprint - Node.js and
npxfor npx-based MCP servers - Codex browser MCP setup for Playwright and Chrome DevTools when available
- Claude browser MCP setup via
claude mcp listwith no config-file fallback - PDF generator and CSS files
- hook scripts and JSON configs
- Python syntax for local scripts
- installed Codex hook/rules sync and Claude hook presence in local settings
- active profile settings and target file existence
- workspace integrity script availability
- ATS probe and structured tracker CLI availability
It reports missing dependencies without installing anything.
For Claude MCP verification the script runs claude mcp list. If claude is not in the non-interactive shell PATH, pass an explicit binary path:
CLAUDE_BIN=/path/to/claude node scripts/check-deps.jsPDF export uses scripts/generate_pdf.py. The generator looks for weasyprint in the current Python, then scripts/.venv/bin/python3, then /tmp/resume-venv/bin/python3.
Typical Workflow
job-tracker:run [profile?] [target]can orchestrate the full path and updatedata/tracker.mdafter each stage.job-tracker:findsearches configured sources, verifies new leads, and adds accepted roles toRaw Pipeline. For a single posting URL discovered manually, usejob-tracker:import <url>as a faster single-URL bridge intoRaw Pipeline.job-tracker:company [Company]researches a promising company and creates or updatesdata/companies/[company]/prep-notes.md.job-tracker:draft [Company]prepares manual recruiter, engineering, founder, or referral message drafts in prep notes. It never sends them.job-tracker:cv [Company]creates or updatesdata/companies/[company]/resume.md.job-tracker:fit data/companies/[company]/resume.md [job-url]checks fit, risks, and edits before user-side application decisions.job-tracker:stories [Company]maps factual interview stories to the role when interview preparation or fit gaps need coverage.job-tracker:pdf data/companies/[company]/resume.mdgenerates the recruiter-ready PDF.job-tracker:apply [Company|application-url]scouts/fills an ATS form and submits only after explicit user confirmation.- Update
data/tracker.mdafter outreach, application, replies, interviews, closures, or deferrals. Agents should prefernode scripts/tracker.jsfor row updates instead of manual Markdown table surgery.
During job-tracker:run, child-skill results are internal progress, not user-facing stop points. job-tracker:run should continue through its plan until the final summary or a hard blocker. It must not report done while selected leads remain in the internal queue, background subagents are running, or skipped selected leads lack tracker-recorded real reasons. If a turn must end while the run can continue automatically, it should return the paused-resumable state with the single Continue Run next action.
Agent CLI Utilities
These scripts are for agents and maintainers. They are not separate user-facing workflows; skills call them when useful.
ATS Probe
Use the ATS probe for supported provider boards before writing one-off curl or inline JSON parsing:
node scripts/ats-probe.js ashby langfuse
node scripts/ats-probe.js batch ashby checkly sentry posthog --limit 10
node scripts/ats-probe.js discover langfuse.com
node scripts/ats-probe.js lever company-slug --json
node scripts/ats-probe.js derive-source https://jobs.lever.co/acme/123
node scripts/ats-probe.js derive-source https://boards.greenhouse.io/acme --jsonderive-source <url> resolves the tracker Source value from a canonical job URL using the ## Source Derivation table in config/source-registry.md. Pass --json to get { url, source }. Falls back to the bare root domain when no pattern matches.
Supported provider IDs, discovery feed templates, default search keywords, and default locations live in config/source-registry.md. node scripts/check-workspace.js checks that the registry matches the providers implemented by scripts/ats-probe.js.
Default output is:
title | location | id | urldiscover <company-or-domain> derives likely ATS slugs from a company name or domain and probes the supported providers. It is deterministic provider probing, not web search.
batch <provider> <slug...> probes multiple known ATS slugs without shell loops or jq.
--json returns normalized records for filtering or tests. The probe filters for frontend/product/fullstack/platform-style roles in EU-compatible locations. --profile <slug> adds keyword hints from the configured profile; it is not a fit/reject engine. Final profile fit, work-mode, priority, and reject decisions still belong to the skills and profile rules.
Tracker CLI
Use the tracker CLI for narrow, structured edits to data/tracker.md:
node scripts/tracker.js list --section raw
node scripts/tracker.js validate --strict --json
node scripts/tracker.js add-lead --company Acme --profile frontend --role "Senior Frontend Engineer" --url https://example.com/job --date 2026-06-29
node scripts/tracker.js add-lead --company Acme --profile frontend --role "Senior Frontend Engineer" --url https://example.com/job --source ashby --date 2026-06-29
node scripts/tracker.js move --company Acme --role "Senior Frontend Engineer" --from raw --to archive --date 2026-06-29 --reason closed
node scripts/tracker.js set-status --company Acme --role "Senior Frontend Engineer" --section raw --status "🟡 unclear" --date 2026-06-29 --dry-run
node scripts/tracker.js bump-date --company Acme --role "Senior Frontend Engineer" --field Updated --date 2026-06-29The CLI parses Markdown tables structurally, preserves user notes outside the target row, treats emoji/status text as opaque strings, and refuses ambiguous updates unless an explicit --url identifies the row. Use --dry-run to inspect changes before writing, --json for list/validate, --strict to make validation warnings fail, and --section to narrow row matching for set-status, bump-date, and duplicate checks during add-lead.
add-lead derives --source automatically from --url using config/source-registry.md when --source is omitted. Pass --source explicitly to override.
Tracker section and field aliases live in config/tracker-schema.md. The starter schema is English, and localized or custom labels are supported by adding them to that config instead of changing JavaScript code.
Tables without a role or URL column, such as compact submitted/application-status tables, need a unique selector. Prefer --url when available; otherwise use section plus enough fields to avoid ambiguous company-only updates.
Hard Rules
- Do not bypass
job-tracker:*skills by writing their expected artifacts directly. - Company research sections in
data/companies/*/prep-notes.mdmust be produced throughjob-tracker:company. ### Manual Message Draftsmust be produced throughjob-tracker:draft.- Having research, contact, or draft data already in context is not a valid reason to skip the relevant skill.
job-tracker:runmust call the relevantjob-tracker:*skill instead of reconstructing that skill's output manually.- Never mark outreach, applications, LinkedIn messages, connection requests, or email as sent/submitted unless the user explicitly says they did it outside the tool and asks to update status.
job-tracker:applyis the only workflow that may submit an ATS/job application from the browser, and only after explicit user confirmation in the same run. It must never send LinkedIn messages, connection requests, emails, or referral outreach.
Skills / Commands
Use job-tracker:action commands to run skills:
Entry — start here
| Command | Runs |
|---|---|
| job-tracker:status | home base: pipeline state, top priorities, and what to do next |
| job-tracker:run [profile?] [target] | autonomous pass: search, company prep, manual message drafts, CV, fit, PDF, tracker updates, and final summary |
Helpers
| Command | Runs |
|---|---|
| job-tracker:setup | first-step interactive readiness check before job-tracker:run |
| job-tracker:health | workspace integrity lint and pending migrations |
| job-tracker:verify | liveness re-check of tracked roles at source |
| job-tracker:find | find new leads, verify them at source, add them to Raw Pipeline |
| job-tracker:import <url> | import one posting URL: verify, auto-select best-fit profile, add to Raw Pipeline |
| job-tracker:profile [action] | inspect, switch, validate, add, or remove job-search profiles |
Per-company
| Command | Runs |
|---|---|
| job-tracker:company Company | research one company, ATS, active roles, contacts, tech stack, and prep notes |
| job-tracker:draft Company | prepare and save manual recruiter, engineering-manager, founder, or referral message drafts |
| job-tracker:cv Company | create or update a company-specific Markdown CV |
| job-tracker:fit resume.md job-url | score a CV against a vacancy via subagent and suggest concrete edits |
| job-tracker:stories [Company] | map or maintain factual STAR stories for interview preparation |
| job-tracker:pdf resume.md | export a Markdown CV or cover letter to PDF using the configured generator |
| job-tracker:apply Company\|url | scout, prepare, fill, and optionally submit an ATS application after explicit confirmation |
Each skill reads config/language.md and replies in Ukrainian by default. CVs, cover letters, and manual message drafts are in English unless requested otherwise.
Profile Mechanics
Profiles are positioning configs for different search strategies. They control search-specific fit signals, reject rules, priority rules, source/query focus, and CV/outreach emphasis.
Profile selection is simple:
- New discovery uses the active profile from
config/settings.md. - Existing tracked jobs use the row-level
Profilevalue indata/tracker.md. - Other job skills resolve the right profile themselves from those two rules.
job-tracker:profile manages profile configuration:
job-tracker:profile statusshows the active profile and profile health.job-tracker:profile use <slug>switches the active profile for future discovery.job-tracker:profile add <slug>creates a new profile.job-tracker:profile remove <slug>deletes an unused profile.job-tracker:profile validatechecks settings, profile files, and tracker profile values.
Do not pass profile slugs to other job commands. Two exceptions may switch the active profile via job-tracker:profile use: job-tracker:run (takes a profile slug argument) and job-tracker:import (takes no slug — auto-selects the best-fit profile and switches to it, asking on ties).
Configuration
candidate/candidate.md— identity, contacts, real skills, and hard constraints.config/settings.md— active profile and profile selection rules.config/language.md— assistant reply language and document language.config/paths.md— tracker, company notes, CV, PDF generator paths.config/source-registry.md— source values, host patterns, ATS probe providers, and browser-required source policy.config/browser-patterns.md— Browser MCP interaction rules for JavaScript-rendered and login-required sources.strategy/criteria.md— shared scoring labels and tracker row format.strategy/sources.md— job-search sources and verification rules.config/tracker-schema.md— tracker sections, update rules, and CLI aliases for localized section/field labels.style/outreach-style.md— outreach tone and message strategy.style/cv-style.md— CV house style and review rules.candidate/stories.md— factual STAR story bank for interview preparation.candidate/application-answers.md— profile-aware reusable answers for ATS forms and outreach.config/next-actions.md— dynamic next-action shortcut rules.strategy/search-profiles/*.md— named profiles with search-specific fit/reject/priority rules.
All configured paths are relative to the repository root.
Repository Layout
.
├── skills/
│ └── [skill]/
│ └── SKILL.md
├── config/
│ ├── settings.md
│ ├── paths.md
│ ├── language.md
│ ├── next-actions.md
│ ├── agent-instructions.md
│ ├── source-registry.md
│ ├── browser-patterns.md
│ ├── tracker-schema.md
│ └── session-reports.md
├── candidate/
│ ├── candidate.md
│ ├── stories.md
│ └── cv/
│ └── cv-base.md
├── strategy/
│ ├── search-profiles/
│ ├── sources.md
│ └── criteria.md
├── style/
│ ├── cv-style.md
│ └── outreach-style.md
├── templates/
├── data/
│ ├── tracker.md
│ └── companies/
│ └── [company]/
│ ├── prep-notes.md
│ ├── resume.md
│ └── pr-backlog.md
└── scripts/
├── ats-probe.js
├── check-deps.js
├── check-workspace.js
├── generate_pdf.py
├── install.js
├── tracker.js
├── llm-hooks/
└── cv.cssTracker Rules
- Job tables must include a
Profilecolumn. - New leads must store the active profile in
Profile.job-tracker:importuses the auto-selected best-fit profile (made active before the row is written). - Existing tracked vacancies must be processed using their row-level
Profile, not the active profile. - Add unresearched new leads to
Raw Pipelinefirst. - Move researched, active, prioritized roles to the active pipeline.
- Move closed, disappeared, rejected, skipped, or dead roles to Archive with date and reason.
- Move useful companies without active relevant roles to Monitoring.
- Keep tracker edits narrow and preserve user-authored notes.
- Prefer
node scripts/tracker.jsfor tracker row updates when the target row can be identified structurally. - Do not duplicate companies across active pipeline and Raw Pipeline unless there are separate roles.
Safety Rules
- Do not invent experience, metrics, contacts, job requirements, company facts, or source status.
- Verify job leads on the company careers page or ATS before adding them as active.
- Treat LinkedIn-only leads as unverified until confirmed at the source of truth.
- Outreach workflows prepare manual message drafts only and save them in prep notes. The user writes/sends manually outside the skills.
- Broad
job-tracker:findruns must end with a source report showing checked, skipped, blocked, and found sources. - Sources marked browser-required in
config/source-registry.md, browser-filtered boards, and JavaScript-rendered ATS pages must be checked through browser MCP. Follow each source'sRequired accesspolicy; LinkedIn and Djinni require Playwright MCP with the user's logged-in account/session. If login is required, log in manually in the opened browser and let the agent continue. Do not replace these checks with plain web-search snippets.
Porting To Another Candidate
To reuse this workspace for another person:
- Replace
candidate/candidate.md. - Adjust
config/language.md. - Use
job-tracker:profileto create, validate, or switch the active search profile. - Adjust
strategy/sources.md. - Update
config/paths.mdonly if the repository layout differs. - Replace
candidate/cv/cv-base.mdand company-specific CVs. - Run
node scripts/install.js all. - Run
job-tracker:setup.
The skill files should stay generic.
Using With Other LLMs
For tools that do not support SKILL.md discovery, point the agent at:
config/agent-instructions.mdfor durable project behavior;skills/[skill]/SKILL.mdfor workflow steps;config/*.md,candidate/*.md,strategy/*.md, andstyle/*.mdfor candidate configuration;data/tracker.mdanddata/companies/*/prep-notes.mdfor current state.
The workflow is plain Markdown, readable by Claude, Codex, or any other agent.
