CareerWeaver — Free, Open-Source AI-Apply Alternative
The free, local CLI alternative to AI-Apply / LazyApply ($200-$400/mo). One canonical fact graph, six persona lenses, one CLI that turns them into tailored resumes — then fills the application form for you, without inventing a thing you didn't do.
$0 · MIT · Local-first · Agent-driven — No subscription. No hosted data. Your resume, fact graph, and every tailored doc stay on your machine. Bring your own LLM (any OpenAI-compatible endpoint) or run deterministic with no key.
Free AI-Apply / LazyApply replacement · ATS auto-apply · Greenhouse / Lever / Workable / Ashby · Resume tailor · Cover letter
Everything runs on your machine. Your resume, your profile, and your fact graph never leave it except to reach the LLM provider you configure — or the job site you explicitly apply to. No server, no account, no telemetry.
weaver init # blank profile template — yours to fill
weaver seed-import # your old resumes → a canonical fact graph
weaver tailor # facts + a lens → a tailored resume (md/docx/pdf)
weaver apply # a resume + a job → a filled form (human sends)
Agent-first. Every command emits clean JSON when piped. Any agent — Claude Code, Codex, OpenCode, Hermes, GLM — can drive the full
find → tailor → applyloop from the CLI. See Agent Setup anddocs/agents.md.
Why CareerWeaver — the free AI-Apply alternative
| AI-Apply / LazyApply / Simplify / Teal / Huntr / JobCopilot / Sonara / LoopCV (hosted, $20-$400/mo) | CareerWeaver | |
|---|---|---|
| Price | $200-$400/mo subscription | $0 · MIT open-source |
| Where your data lives | Their cloud (resume, PII, docs) | Your machine only (data/ gitignored) |
| Model | Locked to their provider | Bring your own — any OpenAI-compatible endpoint, or deterministic with no key |
| Resume tailoring | Black-box rewrite | Fact graph + persona lenses — same facts, different emphasis, unverified_mentions never shipped |
| Auto-apply | Hosted browser, auto-submit | Local Playwright Chrome — verifies every field landed, parks at held for your audit, you press send |
| ATS coverage | Often limited / scraping | Greenhouse + Lever + Ashby + Workable (public APIs, no scraping) |
| Agent / automation | Web UI only | CLI-first, JSON when piped — any agent can drive find → tailor → apply |
| Telemetry | Vendor analytics | Zero — no server, no account, no telemetry |
- One resume is wrong. The same career reads differently for a Design Engineer vs. an SRE. Weaver keeps one verified fact graph and re-tells it through persona lenses — same facts, different emphasis.
- LLMs are good at re-weighting, bad at truth. The guardrail is non-negotiable: every claim in a generated resume must trace to a fact in your graph. Unbacked claims surface as
unverified_mentions, not shipped. - Applying is the grind. Weaver drives a real Chrome via Playwright, verifies every field actually landed, and parks at
heldfor your audit — you clear CAPTCHA and press send. - Bring your own model. Any OpenAI-compatible
/chat/completionsendpoint. No LLM key? Every command degrades to a deterministic local path — the test suite runs this way.
Search keywords: ai-apply alternative, lazyapply alternative, simplify alternative, teal alternative, huntr alternative, jobcopilot alternative, sonara alternative, loopcv alternative, applyhero alternative, careerflow alternative, jobright alternative — free open-source job application automation, ATS auto-apply, Greenhouse Lever Workable Ashby Workday iCIMS Taleo BambooHR, resume tailoring, cover letter generator, autofill, auto-apply bot**
Install
Requires Python 3.11+.
git clone https://github.com/idea-torx/CareerWeaver.git
cd CareerWeaver
# with uv (recommended)
uv sync
uv run playwright install chromium # apply engine
# or with pip
pip install -e .
playwright install chromium
uv run weaver --version # or `weaver` once the venv is active
# every example below also works as `uv run weaver ...`
pip install -e . installs a weaver console script; uv sync keeps it inside .venv.
60-second quickstart
# 1. Profile (blank template, nothing ships with real details)
weaver init --interactive
# or non-interactively:
weaver init --email you@example.com --location "Portland, OR" \
--links "linkedin.com/in/you,you.example" \
--target-roles "Design Engineer,Full Stack Engineer"
# env-var alternative:
WEAVER_PROFILE_EMAIL=you@example.com weaver init
# 2. Fact graph — import every resume you've ever written
weaver seed-import --dir ./seed_resumes # reads .md/.txt/.docx/.pdf
weaver stats # facts_total, verified %, lenses, jobs
# 3. Tailor — lens or job auto-picks the lens
weaver lens list
weaver tailor graph --lens fde --format docx
weaver tailor graph --job 3 # lens chosen to fit the posting
# 4. Apply — dry-run first, then fill held for audit
weaver preflight 3
weaver apply 7 --dry-run
weaver apply 7 --visible --hold --tab # one shared window, trace survives kills
weaver apps list
weaver serve # read-only ledger viewer at http://127.0.0.1:8787
Re-running weaver init never blanks a field you already set. data/ (config, DB, traces, renders) is gitignored by construction.
LLM provider
Weaver speaks any OpenAI-compatible /chat/completions endpoint. Nothing is baked in beyond a default pair you are free to replace.
| Variable | Meaning | Default |
|---|---|---|
WEAVER_API_KEY (or OPENAI_API_KEY) |
provider key | — |
WEAVER_BASE_URL (or OPENAI_BASE_URL) |
endpoint root | https://api.openai.com/v1 |
WEAVER_MODEL |
model id — required when you change the base URL | gpt-4o-mini |
WEAVER_TEMPERATURE |
sampling temperature | 0.3 |
WEAVER_TIMEOUT |
seconds per request | 60 |
WEAVER_USER_AGENT / WEAVER_HTTP_REFERER / WEAVER_APP_TITLE |
attribution headers (some relays 403 without them) | CareerWeaver/0.1 |
export WEAVER_API_KEY=sk-...
# a different provider — set both, or weaver refuses loudly:
export WEAVER_BASE_URL=https://your-provider.example/v1
export WEAVER_MODEL=their-model-id
WEAVER_BASE_URL without WEAVER_MODEL is a configuration error: weaver reports it on stderr, in weaver stats --json (llm.config_error), and in every fallback reason, then degrades to the deterministic path rather than shipping an OpenAI model id to someone else's endpoint.
No key is a supported mode. Every command falls back to deterministic local assembly — resumes still get written, just without LLM re-wording.
Your profile
weaver init writes data/config.json — the only place your personal details live:
- Name, email, phone, location, links (LinkedIn/GitHub/portfolio)
- Target roles & skills (drives
findscoring and lens ranking) - Work authorization, visa, availability / earliest start date
- ATS-consent and "how did you hear about us" defaults
- Voluntary self-ID fields (empty = "I do not wish to disclose" — exactly what the agent answers)
avoid— topics the model must never mention (e.g. an employer you don't want surfaced)
Prefer flags or env vars over prompts for scripting:
weaver init --phone "+1 (503) 555-0148" --availability "Immediately" \
--how-did-you-hear "Company careers page"
data/ is gitignored. Real resumes belong in seed_resumes/ (also gitignored); samples/ ships only synthetic fixtures (mira@halloway.example, +1 (503) 555-01xx — reserved .example names).
The three lanes
1. Find — collect the postings
weaver jobs add https://boards.example.com/acme/jobs/42
weaver jobs add ./posting.txt # file
pbpaste | weaver jobs add - # stdin
weaver jobs add https://... --fetch # also fetch the posting text now
weaver jobs list
weaver find # orgs in config.json find.orgs
weaver find --wide --limit 10 # sweep the whole board registry
weaver find --wide --max-boards 60 --delay 1.2
weaver boards list # what --wide sweeps
weaver boards add acme lever # extend it (writes <data-dir>/boards.json)
jobs add <url> records the URL and does not touch the network unless --fetch is passed. --wide walks the committed registry of public Greenhouse/Lever/Ashby/Workable boards — product companies and creative agencies — one board at a time with a delay between requests, capped at 60 boards per sweep. Postings already in the ledger are reported as N already applied, skipped.
Ashby URLs require --fetch (resolved via the public Ashby API). Auto-lens, preflight, and fit scoring are blind on a job with no posting text.
2. Adapt — build the fact graph, then the resume
weaver seed-import --dir seed_resumes # every resume you have ever written
weaver seed-import --dir seed_resumes --no-llm # deterministic, no network
weaver stats # what the graph knows
weaver lens list
weaver lens show fde
weaver lens create --name platform \
--lead-domains sre_cloud,fullstack_engineering \
--titles "Platform Engineer;Infrastructure Engineer"
weaver tailor graph --lens fde --format docx
weaver tailor graph --lens multimedia --format md --out ./out/resume.md
weaver tailor graph --job 3 # picks the lens that fits the posting
weaver tailor graph --job 3 --json # structured JSON + unverified_mentions
seed-import reads .md/.txt/.docx/.pdf, merges into one fact graph, and marks a fact verified when ≥2 seed documents agree on it. A lens re-tells those same facts for a different audience; it never adds a new one. Rendered output is diffed back against the graph — claims with no supporting fact surface as unverified_mentions.
A do-not-mention guard also runs: any fact whose topic is in profile.avoid is stripped from the prompt and refused if the model tries to mention it.
Six shipped lenses (from src/weaver/lenses.py):
| Lens | Leads | For |
|---|---|---|
fde |
agentic_engineering, ai_expertise, fullstack_engineering, sre_cloud |
Forward Deployed AI Engineer |
fdc |
ai_expertise, cgi_motion, video_multimedia, direction_pm |
Forward Deployed Creative |
design-engineer |
design_engineering, graphics_brand, fullstack_engineering, ai_expertise |
Design Engineer |
multimedia |
video_multimedia, cgi_motion, graphics_brand, direction_pm |
Multimedia Production |
creative |
graphics_brand, cgi_motion, direction_pm |
Brand / Creative Director |
sre |
sre_cloud, fullstack_engineering, agentic_engineering |
SRE / Platform |
3. Apply — fill the form
weaver preflight 3 # can the facts answer this form? (no browser)
weaver apply 7 --dry-run # build + print the request, submit nothing
weaver apply 7 --visible --hold # fill in a real browser, park for audit
weaver apply 7 --visible # fill AND submit, watched
weaver apply 7 --visible --hold --tab # tab of the shared window (recommended)
weaver apps list | weaver apps show 12
Preflight fetches a posting's public questions (Greenhouse/Lever/Ashby/Workable expose them; Workable enumerates every field with its required flag) and checks each required one against your facts. If a required question has no supporting fact, the apply is refused before a browser opens — add the fact or pass --force.
One engine, on your machine: Playwright Chromium driven by the agent loop.
--visiblewatches it work; closing the window stops that run.--holdfills everything but never submits — parks ataudit_pending/heldwith the window open for you to review and press send.--tabopens concurrent runs as tabs of one shared window (weaver tab-host, auto-started) instead of a window per run; closing a tab ends only that run.- Every real run streams its trace to
data/traces/*.jsonlas it happens — a killed window never loses evidence.
Workable is real-Chrome-only. Turnstile at submit fails even a human inside the Playwright tab-host (Aug 2026). Workable fills auto-route to a real Chrome on a dedicated profile (CDP 127.0.0.1:9223 / WEAVER_REAL_CDP_PORT, WEAVER_CHROME_BIN overrides the binary). The hold semantics are identical — you answer Turnstile and press send. If Chrome can't start, only Workable entries fail, with remediation in their reason string.
Batch & cycle:
weaver batch 12 13 14 # fill a queue as held tabs, one report at end
weaver cycle --count 5 --wide --json # one-shot: find → adapt → batch (for cron)
weaver tab-host # pre-start the shared window
The agent stops and reports rather than guessing at a required question it has no fact for.
The morning flow
weaver jobs add <url> --fetch # paste the postings worth a shot
weaver tailor graph --job <id> # a resume per posting, lens auto-picked
weaver preflight <id> # cheap gate, no browser
weaver apply <resume-id> --visible --hold --tab
weaver apps list # what went out, and what stopped
weaver serve # read-only viewer at :8787
How it works
seed resumes ──► extract ──► fact graph (sqlite) ──► lens ──► render ──► .md / .docx / .pdf
▲ ▲ │
corroboration job posting ▼
(2+ seeds = verified) preflight → apply → ledger
- Fact graph —
data/weaver.db(facts,skills,domains,lenses,jobs,resumes,applications). Facts carrykind,title,org,bullets,metrics,tags,source,verified. - Lenses — ordered
lead_domains,compress_domains,skills_order,summary_tone. Same facts, different emphasis. - Guardrail — output diffed back against the graph;
unverified_mentionsare reported. The apply agent refuses to type a value that didn't come from your profile (PII guard). - Ledger — every dry-run and real apply lands in
applications—weaver apps list,weaver apps show <id>,weaver serve(/api/*JSON endpoints).
Agent setup
Weaver is built for agents. Every command emits human text on a TTY, clean JSON when piped (--json forces JSON).
For any agent harness
# JSON mode (automatic when piped)
weaver stats --json | jq
weaver tailor graph --job 3 --json
weaver apply 7 --dry-run --json
weaver batch 12 13 --json # one JSON doc on stdout, blocks on stderr
weaver cycle --count 5 --wide --json
- Credentials without a shell: put
KEY=VALUElines in<data-dir>/env(data/envby default, gitignored) —WEAVER_API_KEY,WEAVER_BASE_URL,WEAVER_MODEL, orWEAVER_LLM_CMD(a local CLI instead of an endpoint). Real env vars always win.exportprefixes and quotes are tolerated. - Absolute binary for launchers:
<repo>/.venv/bin/weaver - One argv, no shell:
cycleis designed for harnesses that can only run a single command with a bare environment (cron, gated launchers) — nosource,&&, or pipes required. WEAVER_PROFILE_*env vars set any profile field without a flag (e.g.WEAVER_PROFILE_EMAIL).- Global
--data-diroverrides whereweaver.db/config.jsonlive (default./data). --visible --hold --tabis the safe default for agents: never auto-submits, tabs share one window, traces survive kills.
MCP — Claude, Codex, Hermes
weaver-mcp exposes the CLI over MCP stdio. It runs on your machine: nothing is
hosted, no account, and weaver.db never leaves the laptop. The fill step drives your
own Chrome with your own logged-in ATS sessions, which is why a hosted server could not
do this job.
Nine tools — weaver_stats, weaver_lens_list, weaver_jobs_add, weaver_jobs_list,
weaver_preflight, weaver_tailor, weaver_apply_hold, weaver_apps_list,
weaver_apps_show. There is no submit tool. weaver_apply_hold always passes
--hold, so it fills and parks at audit_pending for you to review and send.
Claude Code — via the plugin (bundles the server config):
claude plugin marketplace add idea-torx/CareerWeaver
claude plugin install careerweaver@careerweaver
Or wire the server directly:
claude mcp add careerweaver --env WEAVER_DATA_DIR=$PWD/data -- weaver-mcp
Codex — ~/.codex/config.toml:
[mcp_servers.careerweaver]
command = "weaver-mcp"
env = { WEAVER_DATA_DIR = "/absolute/path/to/CareerWeaver/data" }
Claude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"careerweaver": {
"command": "weaver-mcp",
"env": { "WEAVER_DATA_DIR": "/absolute/path/to/CareerWeaver/data" }
}
}
}
Hermes — hermes mcp install careerweaver once
packaging/hermes/careerweaver/manifest.yaml
is merged into its optional-mcps/ catalog.
Set WEAVER_DATA_DIR to an absolute path. The CLI defaults to ./data relative to
the working directory, and an MCP client spawns the server with whatever cwd it likes —
without it the server creates or reads an empty database wherever the client
happened to start it (or errors outright, if that directory is not writable).
Run weaver init and weaver seed-import before first use — an empty fact graph has
nothing to tailor from. If weaver-mcp is not on PATH, use the absolute path
<repo>/.venv/bin/weaver-mcp.
Full guide
See docs/agents.md for the complete agent contract:
JSON vs human output, exit codes, cycle one-shot, batch orchestration, preflight gate, single-flight lock, Workable/real-Chrome routing, harness constraints (launchd user agent vs system daemon), and the build/audit/test contributor loop.
Privacy
Weaver runs on your machine. There is no CareerWeaver server, no telemetry, and no account. The database, your config, and every generated resume live in data/, which is gitignored. The only outbound requests weaver ever makes are:
- Your LLM provider (only if
WEAVER_API_KEYis set) - A posting URL you explicitly asked it to
--fetch - On a real, non-dry-run
apply— the job site itself
Run with no key at all and it makes zero LLM calls. Tests are built from samples/ (synthetic *.example fixtures); real seeds belong in seed_resumes/ (gitignored).
CLI reference
weaver [--data-dir DIR] [--json] [--version]
init [--interactive] [--email E --phone P --location L --links A,B ...]
seed-import --dir DIR [--no-llm] [--overwrite-profile]
lens list | lens show <name> | lens create --name N --lead-domains a,b --titles "A;B"
tailor [graph|PATH] --lens <name> | --job <id> [--format md|docx|pdf] [--out PATH] [--json] [--no-llm]
alias: adapt
jobs add <url|path|-> [--fetch] | jobs list
find [--wide] [--limit N] [--max-boards N] [--delay S]
boards list | boards add <name> <type>
preflight <job-id>
apply <resume-id> [--dry-run] [--visible] [--hold] [--tab] [--force] [--max-actions N] [--resume-url URL]
batch <resume-id>... [--force] [--max-actions N] [--notify/--no-notify]
cycle [--count N] [--wide] [--force] [--format FMT] [--max-actions N] [--json]
tab-host [--port P]
apps list | apps show <id> (alias: ledger)
serve [--port 8787]
stats [--json]
weaver apply and weaver batch/weaver cycle stream their loud completion block to stderr so it survives | jq and > file. Exit codes: 0 success / held+audit_pending (human to finish), 2 failed, 3 audit-pending (needs one field).
Development
uv run pytest # full suite, no keys, no network
uv run pytest -m live # opt-in network tests
samples/is synthetic;seed_resumes/is private and must not be committed (see.gitignore).AGENTS.mdencodes the build/audit/test loop and hard rules (no invented facts, verify-before-trust, keeplocal_driver.py/local_agent.pysingle-editor).docs/batch-cycle-contract.mdanddocs/cron-and-harness-setup.mddocument orchestration and harness constraints.
Branding & scope
No web UI, no sales assets, no hosted deployment. Branding/PDF polish and D1/R2 port are later phases. Open-source from commit one — MIT.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file careerweaver-0.2.0.tar.gz.
File metadata
- Download URL: careerweaver-0.2.0.tar.gz
- Upload date:
- Size: 182.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87060180f82d7ade018ce4e3e5f3d3baa54c7e4c191a5c923c8bed6db1d10136
|
|
| MD5 |
37500bb0f63f56b48e38cafd108fc680
|
|
| BLAKE2b-256 |
e79686ef2ce2fe552d9e30a1c061ab50d79f789252f21298c50d308806e68d82
|
File details
Details for the file careerweaver-0.2.0-py3-none-any.whl.
File metadata
- Download URL: careerweaver-0.2.0-py3-none-any.whl
- Upload date:
- Size: 192.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac4298ed2d779bbde077ec2852b8eb279253f65d388cce6bdddd5d2f68a2a0a4
|
|
| MD5 |
4755493741285da0b2568f8d01c5190e
|
|
| BLAKE2b-256 |
aeaf5a6c219f2e89a7ed76d7c26b64941b3cbda365c5d93ea9af9089f738df62
|