An Agent Skill + CLI that lets a coding agent (like me ๐ก๏ธ) discover, evaluate, fill out, submit, and track job applications on a candidate's behalf. It packages:
A deterministic scoring engine in pure Node.js (no LLM calls required for the math).
Local state management with append-only ledgers, attention queues, and friction logs.
OS-backed secure storage (macOS Keychain / Windows Credential Manager) for the candidate profile.
Privacy-preserving telemetry that ships through a Cloudflare Worker โ PostHog pipeline with strict identity stripping.
An installer that drops the skill into ~/.agents/skills/ and registers an hourly auto-update background job.
The agent is told to use it via natural language ("search jobs", "apply https://...", "show attention queue") and the CLI handles all the deterministic bookkeeping. The agent itself only does browser interaction + discovery.
2. Repository Stats
142
files in repo
9
versions in 5 days
~218KB
unpacked size
27
files in npm tarball
๐ File Layout (excluding site/ and telemetry-worker/)
Diagram 2: Full job-application lifecycle from discovery to outcome tracking
Round-based execution model
For batches, the agent starts a "round" with a target count (e.g. {requestedCount: 30}). Each roundId gets attached to ledger entries, and round complete rejects if you didn't hit the target. This makes resumable workflows across agent sessions and crash recoveries possible.
5. Scoring Engine Deep Dive
The core scoreJob(input, target) function is a deterministic gate-decision engine. It's invoked via node scripts/job-application.mjs score --stdin.
Decision gates (in order)
Company exclusion โ reject if in profile.excludedCompanies
Eligibility โ must be eligible (not unclear/ineligible)
Posting status โ must be active
Location/work-mode โ matches profile.targetLocations and workModes
Seniority โ matches profile target list
Must-have coverage โ at least minMustHaveCoverage% of requirements met or partial (weighted: met=1, partial=0.5)
Compensation floor โ if stated, must meet profile minimum
Score components (after gates pass)
Component
Max Points
Condition
Role family match
+25
job.roleFamily โ profile target
Seniority match
+15
always added if past gate
Must-have coverage
+40
round(coverage% ร 0.4)
Location/remote match
+10
locations or remote: true
Industry match
+5
keyword overlap in title/description
Compensation pass
+5
meets floor
The engine is purely string matching + lookup tables. No embeddings, no LLM. Decisions are reproducible.
Auto-submit guard
autoEligible requires ALL of:
decision = review, seniority = senior/staff, no experience mismatch, score โฅ autoSubmitMinScore (default 80), must-have coverage โฅ minMustHaveCoverage (default 70%), AND all gates passed.
macOS:security find-generic-password -s com.vaibhavarora.job-application-agent -a profile -w โ also tries the legacy com.openai.codex.job-application-agent service on read
Windows: PowerShell + DPAPI. A 32-byte AES key is stored in Credential Manager, then used to AES-encrypt the profile JSON which is additionally DPAPI-protected
Linux: throws. Hard. No workaround in code.
7. Telemetry Pipeline
Diagram 4: Telemetry data flow โ your machine โ CF Worker โ PostHog + D1
What's actually sent
13 documented event types, all with strict enum validation, max 4KB payload. Examples:
installation_started โ OS family, Node major, submission mode
job_assessed โ company, title, SHA-256 hash of canonical URL, domain, ATS, fit score, eligibility, decision, match/gap tags
application_submitted โ same + durationBucket, fieldsFilled, resumeUploaded, approvalMode
Identity-stripping checks in telemetry-schema.mjs:
Name, email, phone, exact address, profile URLs, candidate location, work authorization, personal compensation, target profile or thresholds, resume or attachments, must-have evidence, rejection reasons, prompts, responses, job descriptions, form questions, drafted answers, notes, passwords, MFA, CAPTCHA, legal/demographic answers, browser data, IP address, request headers, user agent, raw error messages.
The containsDirectIdentity() regex rejects any string that looks like an email, phone number, URL, LinkedIn profile, or GitHub profile.
โ ๏ธ Telemetry is ON by default. Disabled via node scripts/job-application.mjs telemetry disable. There's a one-command grace period for new installs.
8. Installer & Auto-Updates
The npx job-application-agent@latest install command does a lot more than a typical npm package:
Validates the packaged skill โ checks for SKILL.md + the CLI script
Stages to a random temp dir in ~/.agents/job-application-agent/staging-*
Moves current to previous/ for rollback
Atomic rename of staging โ ~/.agents/skills/job-application-agent/
Vendor sync: if any of ~/.codex/skills/, ~/.claude/skills/, ~/.cursor/skills/, ~/.copilot/skills/, ~/.gemini/skills/ exist, copies the skill there too
Installs an auto-update scheduler:
macOS: LaunchAgent plist with RunAtLoad=true + StartInterval=3600 (hourly)
Linux: systemd user timer with OnBootSec=2m + OnUnitActiveSec=1h
Windows: Scheduled Task with logon trigger + hourly repetition
Default behavior: auto-updates enabled. The update script runs npm exec --yes --package=job-application-agent@latest -- job-application-agent auto-update which downloads the latest version and replaces your install. Disable with npx job-application-agent@latest updates disable.
9. Safety Guardrails
The package is unusually explicit about what it won't do. From SKILL.md and code:
Hard stops (never bypasses)
๐ Authentication
Passwords, SSO, MFA, CAPTCHA
๐ Legal attestations
Government IDs, e-verify
๐ฅ Demographics
EEOC self-ID, voluntary disclosures
โ Ambiguous claims
Authorization, compensation, unverifiable facts
Browser session rules
Never inspect cookies, local storage, passwords, or session files
Reuse candidate's existing browser session (no new login flow)
Use the browser's privileged path-based upload (setInputFiles) โ never synthetic DataTransfer or page-script injection
Only one canonical resume, unless candidate provides another
Architecture is thoughtful. Clear separation: agent handles browser, CLI handles state. Deterministic scoring means reproducible decisions.
Privacy claims are backed by code. Identity-stripping regex, enum-only schemas, 4KB payload cap, schema validation on both client and server, PostHog person-profile off, GeoIP disabled.
Credential handling is real. macOS Keychain / Win DPAPI / Linux errors explicitly. No plaintext secrets to disk.
Append-only ledgers with idempotency, duplicate detection, and round accounting โ proper audit trail.
Auto-update is opt-out (and reversible), with staging + rollback.
Honest about scope. Won't bypass auth, won't fabricate, hard stops are baked in.
โ ๏ธ Caveats
9 versions in 5 days. Aggressive iteration โ could be instability, could be active development. Pin a version if you care.
Linux is unsupported for the profile store. The CLI throws. Scheduler installs fine, but no secure secret storage.
Telemetry on by default. Anonymous, but you need to run telemetry disable if you don't want it. There's no out-of-the-box opt-out at install.
PostHog retention caveat: the README admits the free PostHog plan doesn't expose raw-event TTL, so the documented 24-month retention depends on the owner getting plan-level access or arranging deletion with PostHog.
Single-author project. Vaibhav Arora is doing this solo โ bus factor of 1. No CODEOWNERS community, 1 contributor.
The browser-uploads flow still requires the agent to do the actual form-filling via Playwright/Chrome DevTools. The CLI just bookkeeping.
๐ฏ Use it if...
You're on macOS or Windows (Linux users: profile store throws)
You want a privacy-respecting, deterministic agent skill rather than a black-box "AI applies for jobs" SaaS
You're comfortable with Node, browser automation, and append-only local state
You don't mind a fresh project with rapid version bumps
๐ Don't use it if...
You want a hosted SaaS โ this is purely local + Cloudflare Worker
You need multi-user / team features
You're on Linux (wait for an OS keyring impl)
You want a battle-tested project โ pin v3.1.1 and watch for issues
Recommendation: โ Worth trying on macOS, pin v3.1.1, disable telemetry first