jobwatch 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. jobwatch-0.1.0/.github/workflows/ci.yml +26 -0
  2. jobwatch-0.1.0/.github/workflows/release.yml +67 -0
  3. jobwatch-0.1.0/.gitignore +10 -0
  4. jobwatch-0.1.0/LICENSE +21 -0
  5. jobwatch-0.1.0/PKG-INFO +415 -0
  6. jobwatch-0.1.0/README.md +383 -0
  7. jobwatch-0.1.0/jobwatch/__init__.py +3 -0
  8. jobwatch-0.1.0/jobwatch/chat.py +200 -0
  9. jobwatch-0.1.0/jobwatch/cli.py +365 -0
  10. jobwatch-0.1.0/jobwatch/config.py +209 -0
  11. jobwatch-0.1.0/jobwatch/contacts.py +197 -0
  12. jobwatch-0.1.0/jobwatch/data/learning.yaml +212 -0
  13. jobwatch-0.1.0/jobwatch/filters.py +97 -0
  14. jobwatch-0.1.0/jobwatch/learn.py +264 -0
  15. jobwatch-0.1.0/jobwatch/mcp_server.py +248 -0
  16. jobwatch-0.1.0/jobwatch/models.py +69 -0
  17. jobwatch-0.1.0/jobwatch/package.py +158 -0
  18. jobwatch-0.1.0/jobwatch/prep.py +245 -0
  19. jobwatch-0.1.0/jobwatch/report.py +119 -0
  20. jobwatch-0.1.0/jobwatch/resume.py +31 -0
  21. jobwatch-0.1.0/jobwatch/score.py +71 -0
  22. jobwatch-0.1.0/jobwatch/service.py +109 -0
  23. jobwatch-0.1.0/jobwatch/sources.py +524 -0
  24. jobwatch-0.1.0/jobwatch/static/index.html +1858 -0
  25. jobwatch-0.1.0/jobwatch/store.py +298 -0
  26. jobwatch-0.1.0/jobwatch/text.py +136 -0
  27. jobwatch-0.1.0/jobwatch/watch.py +201 -0
  28. jobwatch-0.1.0/jobwatch/web.py +483 -0
  29. jobwatch-0.1.0/pyproject.toml +56 -0
  30. jobwatch-0.1.0/scripts/screenshots.py +138 -0
  31. jobwatch-0.1.0/tests/__init__.py +0 -0
  32. jobwatch-0.1.0/tests/conftest.py +180 -0
  33. jobwatch-0.1.0/tests/test_add_job.py +116 -0
  34. jobwatch-0.1.0/tests/test_big_boards.py +218 -0
  35. jobwatch-0.1.0/tests/test_chat.py +118 -0
  36. jobwatch-0.1.0/tests/test_cli.py +97 -0
  37. jobwatch-0.1.0/tests/test_contacts.py +194 -0
  38. jobwatch-0.1.0/tests/test_filters.py +105 -0
  39. jobwatch-0.1.0/tests/test_learn.py +134 -0
  40. jobwatch-0.1.0/tests/test_package.py +162 -0
  41. jobwatch-0.1.0/tests/test_prep.py +141 -0
  42. jobwatch-0.1.0/tests/test_service.py +50 -0
  43. jobwatch-0.1.0/tests/test_sources.py +76 -0
  44. jobwatch-0.1.0/tests/test_store.py +152 -0
  45. jobwatch-0.1.0/tests/test_text.py +64 -0
  46. jobwatch-0.1.0/tests/test_watch.py +117 -0
  47. jobwatch-0.1.0/tests/test_web.py +145 -0
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ name: Test (py${{ matrix.python-version }})
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ python-version: ["3.10", "3.13"]
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ - uses: actions/setup-python@v7
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ - run: python -m pip install --upgrade pip
24
+ - run: pip install -e ".[dev,mcp]"
25
+ - run: ruff check .
26
+ - run: python -m pytest -q
@@ -0,0 +1,67 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag (e.g. v0.1.0) is pushed.
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API token is stored anywhere.
5
+ on:
6
+ push:
7
+ tags:
8
+ - "v*"
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ name: Test (py${{ matrix.python-version }})
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ matrix:
19
+ # Floor and ceiling of the supported range, so a tag can't publish
20
+ # something that breaks on the requires-python lower bound.
21
+ python-version: ["3.10", "3.13"]
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ - uses: actions/setup-python@v7
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ - run: python -m pip install --upgrade pip
28
+ - run: pip install -e ".[dev,mcp]"
29
+ - run: ruff check .
30
+ - run: python -m pytest -q
31
+
32
+ build:
33
+ name: Build distribution
34
+ needs: test
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v7
38
+ - uses: actions/setup-python@v7
39
+ with:
40
+ python-version: "3.x"
41
+ - name: Tag matches the package version
42
+ run: |
43
+ version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
44
+ test "v$version" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != version $version"; exit 1; }
45
+ - run: python -m pip install --upgrade build
46
+ - run: python -m build
47
+ - run: python -m pip install --upgrade twine && python -m twine check dist/*
48
+ - uses: actions/upload-artifact@v7
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+
53
+ publish:
54
+ name: Publish to PyPI
55
+ needs: build
56
+ runs-on: ubuntu-latest
57
+ environment:
58
+ name: pypi
59
+ url: https://pypi.org/project/jobwatch/
60
+ permissions:
61
+ id-token: write # required for trusted publishing (OIDC)
62
+ steps:
63
+ - uses: actions/download-artifact@v8
64
+ with:
65
+ name: dist
66
+ path: dist/
67
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ # Your own watchlist and state stay out of the repo.
9
+ jobwatch.yaml
10
+ *.db
jobwatch-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jobwatch contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,415 @@
1
+ Metadata-Version: 2.5
2
+ Name: jobwatch
3
+ Version: 0.1.0
4
+ Summary: Watch company job boards (Greenhouse, Lever, Ashby, Workable, Workday, Eightfold), get a ranked daily digest of new matches (optionally fit-scored against your resume), and track every application. Never auto-applies.
5
+ Project-URL: Homepage, https://github.com/vinayvobbili/jobwatch
6
+ Project-URL: Repository, https://github.com/vinayvobbili/jobwatch
7
+ Project-URL: Issues, https://github.com/vinayvobbili/jobwatch/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ashby,eightfold,greenhouse,job-search,job-tracker,jobs,lever,llm,mcp,resume,workable,workday
11
+ Classifier: Environment :: Console
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Office/Business
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: pyyaml>=6
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8; extra == 'dev'
24
+ Requires-Dist: ruff>=0.14; extra == 'dev'
25
+ Provides-Extra: local
26
+ Requires-Dist: shortlist-ai[local]>=0.1.1; extra == 'local'
27
+ Provides-Extra: mcp
28
+ Requires-Dist: mcp>=2.2; extra == 'mcp'
29
+ Provides-Extra: score
30
+ Requires-Dist: shortlist-ai>=0.1.1; extra == 'score'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # jobwatch
34
+
35
+ Watch the job boards of the companies you care about and get a short, ranked digest of **new** roles that
36
+ match you, optionally fit-scored against your resume. jobwatch finds and ranks. It never applies for you.
37
+
38
+ ```
39
+ $ jobwatch run
40
+ Checked 48 board(s): 9,412 open roles, 37 new.
41
+
42
+ # jobwatch digest
43
+
44
+ 6 matching job(s). Filtered out: 22 by location, 4 by pay, 5 by title.
45
+
46
+ ### [Staff AI Engineer](https://jobs.ashbyhq.com/initech/c1)
47
+ Initech · Denver, CO, United States; Remote · $230K–$300K · posted today
48
+ **Fit 81/100**, must-haves 6/7. Gaps: 3+ years with Kubernetes in production
49
+ Keywords: Python, RAG, agents
50
+ `ashby:initech:c1`
51
+ ...
52
+ ```
53
+
54
+ ## Why company boards
55
+
56
+ Most tech companies post jobs through Greenhouse, Lever, Ashby or Workable, and most large employers through
57
+ Workday (some through Eightfold). Each publishes open roles as public JSON so anyone can build a careers page, with
58
+ no API key or scraping. jobwatch reads those feeds for the companies on your watchlist:
59
+
60
+ - **Complete and fresh:** a role appears as soon as the company posts it, not when an aggregator picks it up.
61
+ - **Pay ranges:** read from the board's structured fields where they exist (Lever, Ashby), otherwise from
62
+ the posting text.
63
+ - **Polite:** one request per company per run on Greenhouse, Lever, Ashby and Workable. A Workday or Eightfold board can
64
+ list thousands of roles, most of them nothing like yours, so jobwatch searches it for your
65
+ `filters.titles` words and reads a posting in full only when its title passes your title filters, once:
66
+ after that, a run costs a few searches per company. Titles written as regexes can't be searched for, so
67
+ keep a plain word or two ("engineer", "forward deployed") among them.
68
+ - **Within the rules:** it reads public APIs meant for this. It doesn't scrape LinkedIn or Indeed, which
69
+ forbid it.
70
+
71
+ ## Install
72
+
73
+ ```
74
+ pip install jobwatch # discovery, filters, digest
75
+ pip install 'jobwatch[score]' # + fit scores with shortlist-ai (Claude API)
76
+ pip install 'jobwatch[local]' # + fit scores on-device (Apple Silicon, MLX)
77
+ pip install 'jobwatch[mcp]' # + MCP server for Claude and other assistants
78
+ ```
79
+
80
+ ## Quick start
81
+
82
+ ### In your browser
83
+
84
+ ```
85
+ pipx install jobwatch # or: pip install jobwatch
86
+ jobwatch ui
87
+ ```
88
+
89
+ A page opens on your computer. Add the companies you want to watch (type a name or paste a link to one of
90
+ their jobs), say what you're looking for, and press **Check for new jobs**. From there:
91
+
92
+ - **Today:** new matching jobs, with pay, how long ago they were posted, keywords, and who you know there.
93
+ **Queue** the ones worth applying to, **Skip** the rest.
94
+ - **Queue:** your short list. Apply on the company's site, then press **I applied**.
95
+ - Long lists come in pages (12, 24, 48 or 96 at a time, kept per browser).
96
+ - **Applications:** every job you applied to and where it stands (applied, screening, interviewing, offer,
97
+ rejected, withdrawn), with the next step and a follow-up day. Those due come first. Filter by status with
98
+ the chips over the list (or click the pipeline bar), and switch between **Cards** and a sortable **Table**.
99
+ **Add an application** covers jobs you found elsewhere, such as a referral or a recruiter.
100
+ - **Settings:** companies, filters, keywords, your resume (for fit scores), your LinkedIn connections, and the
101
+ page's theme (system, light or dark) and width (standard, wide or full, for a big monitor).
102
+ - **Ask jobwatch:** a chat on Today (about all of today's jobs and your applications) and beside each job's
103
+ details (about that posting). See [Chat](#chat).
104
+
105
+ The page only talks to jobwatch on your own machine. The one exception is a model you choose: with
106
+ `scoring.backend: claude`, fit scores and chat send the posting and your resume to Anthropic. It's the same
107
+ watchlist file and history as the command line, so you can switch between the two.
108
+
109
+ To have the page always there, even after a restart, on a Mac:
110
+
111
+ ```
112
+ jobwatch service install # starts jobwatch ui when you log in, and again if it stops
113
+ jobwatch service status # running? where's the log?
114
+ jobwatch service uninstall
115
+ ```
116
+
117
+ A login item doesn't see variables set in your shell profile, so with `scoring.backend: claude` the page's
118
+ chat and scores need `ANTHROPIC_API_KEY` given to launchd (`launchctl setenv ANTHROPIC_API_KEY ...`), or
119
+ run `jobwatch ui` from a terminal instead. The local backend needs nothing.
120
+
121
+ ### On the command line
122
+
123
+ ```
124
+ jobwatch init # writes an example jobwatch.yaml
125
+ jobwatch find "Anthropic" "Scale AI" # find each company's board
126
+ jobwatch find https://jobs.lever.co/spotify/4f1c2a9e-... # or paste any job link
127
+ ```
128
+
129
+ `find` prints a line like `Anthropic: greenhouse:anthropic (627 open roles)`. For a Workday company it tries
130
+ the usual site names; if that finds nothing, paste a job link from their careers site (it has
131
+ `myworkdayjobs.com` in it) and `find` reads the board from it: `workday:nvidia.wd5/NVIDIAExternalCareerSite`. Add those
132
+ entries under `companies:`, adjust the filters, then:
133
+
134
+ ```
135
+ jobwatch run # fetch every board, then show the digest
136
+ ```
137
+
138
+ Run it daily, for example from cron: `0 8 * * * jobwatch run -o ~/jobs-today.md`.
139
+
140
+ ## The watchlist
141
+
142
+ ```yaml
143
+ companies:
144
+ - greenhouse:anthropic
145
+ - lever:spotify
146
+ - {source: ashby, board: openai, name: OpenAI}
147
+ - workable:acme # apply.workable.com/acme
148
+ - {source: workday, board: nvidia.wd5/NVIDIAExternalCareerSite, name: NVIDIA} # tenant.wdN/site
149
+ - {source: eightfold, board: acme, name: Acme} # tenant (or tenant/domain), from a link
150
+
151
+ filters:
152
+ titles: ["engineer", "architect"] # regexes; the title must match one
153
+ exclude_titles: ["intern", "manager"]
154
+ exclude_departments: ["sales"]
155
+ locations: ["remote", "Denver", "Boulder, CO"]
156
+ remote_country: US # remote roles must be open in the US ("any" to allow all)
157
+ min_salary: 200000 # the top of a listed range must reach this
158
+ require_salary: false # true: drop postings without pay
159
+ max_age_days: 30
160
+
161
+ keywords: # relevance: title hits count double
162
+ LLM: 3
163
+ RAG: 3
164
+ Python: 2
165
+ "re:agent(s|ic)?": 2 # "re:" prefix = regex
166
+
167
+ resume: ~/Documents/resume.pdf # for fit scores
168
+ connections: ~/Downloads/linkedin.zip # who you know at each company (see below)
169
+ scoring:
170
+ backend: claude # or local
171
+ top: 5 # score the 5 most relevant new jobs per digest
172
+ display: # the browser page
173
+ theme: system # system, light or dark
174
+ width: standard # standard, wide or full
175
+ ```
176
+
177
+ Relative paths are resolved from the watchlist's folder. State (which jobs you've seen, applied to or
178
+ skipped, and their scores) lives in one SQLite file, by default `~/.local/share/jobwatch/state.db`.
179
+
180
+ ### How locations match
181
+
182
+ A posting can list several places (`London, UK; Remote-Friendly, United States; Austin, TX`), and each one
183
+ is checked:
184
+
185
+ - `remote` matches a place that says remote and is in `remote_country`, or says only "Remote". It also
186
+ matches a posting whose own remote flag is set and that lists a US location.
187
+ - Any other entry matches as text, so `Denver` matches `Denver, CO, United States`.
188
+
189
+ ## Fit scores
190
+
191
+ Keyword relevance is fast and explainable, but it can't tell "uses Kubernetes" from "5+ years running
192
+ Kubernetes in production". With `resume:` set and `scoring.top` (or `--score N`), the most relevant new
193
+ jobs are scored with [shortlist-ai](https://github.com/vinayvobbili/shortlist-ai):
194
+
195
+ 1. It turns the posting into must-have and nice-to-have requirements.
196
+ 2. It judges each requirement against your resume, with quotes it checks against the resume text.
197
+ 3. It lists the must-haves the resume doesn't show.
198
+
199
+ Scores are stored per resume file content, so each job is scored once, and again only after you edit your
200
+ resume. The local backend takes minutes per job, so keep `top` small.
201
+
202
+ ## Chat
203
+
204
+ Ask questions in plain words: "which three should I apply to first?", "what follow-ups are due?", "how well
205
+ do I fit this one, honestly?", "what will they ask in interviews?". The chat uses the model you set for fit
206
+ scores:
207
+
208
+ - `scoring.backend: local`: the same on-device model as scoring (`pip install 'jobwatch[local]'`). Nothing
209
+ leaves your computer. The first answer waits for the model to load.
210
+ - `scoring.backend: claude`: Claude Sonnet via Anthropic's API (`ANTHROPIC_API_KEY`). Your question, your resume
211
+ and the jobs it's about are sent to Anthropic.
212
+
213
+ It reads today's matching jobs and your applications, or one posting with its fit score and
214
+ [what you sent](#what-you-sent), plus your resume (the one you sent for that job, when it's kept).
215
+ It's told to use only your resume for facts about you and to treat posting text as data, not instructions.
216
+ It has no tools, so it can't change, apply for or send anything. The same thing works from the terminal:
217
+
218
+ ```
219
+ jobwatch ask "What follow-ups are due this week?"
220
+ jobwatch ask --job c1 "What should my resume lead with for this one?"
221
+ ```
222
+
223
+ ## Skills to build
224
+
225
+ Which skills do the jobs you're going after ask for that your resume doesn't show, and where can you learn
226
+ them in the time you have?
227
+
228
+ ```
229
+ jobwatch skills # gaps across today's matches and your applications
230
+ jobwatch skills --timeline month # week, month, quarter (default) or any
231
+ jobwatch skills --all # also the skills your resume already shows
232
+ ```
233
+
234
+ Skills are ranked by demand: how many of your matching jobs mention one, with a must-have that a fit score
235
+ found missing counting three times. Each comes with:
236
+ - curated courses and certifications from the official pages (AWS, Linux Foundation, DeepLearning.AI,
237
+ Hugging Face, OWASP...), each with a rough time and marked if it's longer than your timeline;
238
+ - searches on LinkedIn Learning, Coursera, edX and, for AI skills, DeepLearning.AI;
239
+ - with a timeline of a quarter or more, certificate programs at colleges near you.
240
+
241
+ Fit-score gaps no course closes (a clearance, citizenship, a degree, travel) are listed apart, and so are
242
+ must-haves asking for years of something: a course gives you something concrete to point to, not the years.
243
+
244
+ ```yaml
245
+ learning:
246
+ timeline: quarter # week, month, quarter or any
247
+ near: Denver # for colleges nearby; default: the first city in filters.locations
248
+ ```
249
+
250
+ The chat knows the top gaps too ("what should I learn this month?"), and a job's details list the skills
251
+ that posting asks for that your resume doesn't show.
252
+
253
+ ## The apply queue
254
+
255
+ Pick the roles worth a tailored application from the digest and queue them. Work through the queue when
256
+ you have time:
257
+
258
+ ```
259
+ jobwatch queue c1 aaaa-1111 --note "ask for a referral first" # add (the posting id is enough)
260
+ jobwatch queue # what to apply to next, oldest first
261
+ jobwatch show aaaa-1111 # full posting text: for tailoring a resume
262
+ jobwatch mark applied aaaa-1111 --note "referred by a friend" # after you submit
263
+ jobwatch mark skipped greenhouse:acme:102
264
+ jobwatch list --status applied
265
+ ```
266
+
267
+ Queued jobs leave the digest. The queue flags any posting that has since closed.
268
+
269
+ ### Jobs you found somewhere else
270
+
271
+ A job from LinkedIn, a job-alert email or a friend goes in the queue with its link. When the link is to a
272
+ posting on Greenhouse, Lever, Ashby, Workable or Workday, jobwatch reads the posting from there, so it can be
273
+ fit-scored and prepped for like any other, even if you don't watch that company. For anything else (LinkedIn,
274
+ a company's own site), give the company and title and paste the posting's text:
275
+
276
+ ```
277
+ jobwatch add https://apply.workable.com/acme/j/A1B2C3D4E5/ --status queued
278
+ jobwatch add "Umbrella" "Staff Engineer" --url https://www.linkedin.com/jobs/view/... --text posting.txt --status queued
279
+ pbpaste | jobwatch add "Umbrella" "Staff Engineer" --text - --status queued # the posting from the clipboard
280
+ ```
281
+
282
+ In the browser, press **Add a job** on the Queue page. Many job sites (LinkedIn's "Apply on company
283
+ website", job-alert emails) link through to the company's own board: that link is the one to use.
284
+
285
+ ## Tracking applications
286
+
287
+ An application moves through stages: `applied`, `screening`, `interviewing`, `offer`, and then `rejected`
288
+ or `withdrawn`. Each can have a next step and a day to follow up. Jobs you found somewhere jobwatch doesn't
289
+ watch (a referral, a recruiter, LinkedIn) go in with `add`, so every application is in one list:
290
+
291
+ ```
292
+ jobwatch add "Umbrella" "Principal Engineer" --url https://... --on 2026-09-14 --note "via a recruiter"
293
+ jobwatch add https://job-boards.greenhouse.io/acme/jobs/101 --on 2026-09-20 # read from the link
294
+ jobwatch mark screening c1 --next "technical round" --follow-up +7 # or a date: 2026-10-05
295
+ jobwatch mark rejected principal-engineer
296
+ jobwatch mark withdrawn c1 --add-note "recruiter says onsite only" # adds a dated line; --note replaces
297
+ jobwatch applications # every application, follow-ups due first (alias: apps)
298
+ jobwatch applications --due # only the ones to act on today
299
+ ```
300
+
301
+ The day you applied is kept as an application moves through the stages. `--next ""` or `--follow-up ""`
302
+ clears a field.
303
+
304
+ Every company on Workday has its own careers site with its own sign-in, so after a few applications it's hard
305
+ to remember where each one lives. For a Workday application, `jobwatch show`, `jobwatch applications` and
306
+ the job's **Details** link that company's candidate page (`.../userHome`), where you sign in to see its
307
+ status. jobwatch keeps only the link, never a login or password: your password manager saves each company's
308
+ login under that company's own address. For one you added by hand whose posting has since come down, give
309
+ it the company's careers site (`jobwatch mark <stage> <key> --url https://acme.wd1.myworkdayjobs.com/Careers`)
310
+ and the page link follows.
311
+
312
+ ### What you sent
313
+
314
+ Each application keeps what you submitted, as copies:
315
+ - the resume and cover letter exactly as uploaded;
316
+ - the answers you gave on the form (salary expectation, why this company, notice period...);
317
+ - the posting as it read the day you applied.
318
+
319
+ Tailored resumes get rebuilt and postings change or come down. When a recruiter calls three weeks later,
320
+ this is what they're looking at. The chat reads it too, so "prep me for the recruiter call" works from
321
+ what you actually told them.
322
+
323
+ In the browser, open an application's **Details** and drop files into **What you sent**. From the command
324
+ line:
325
+
326
+ ```
327
+ jobwatch mark applied c1 --attach ~/Downloads/Resume_Initech.pdf
328
+ jobwatch attach c1 cover-letter.pdf --answers answers.yaml # answers.yaml: "Why Initech?: ..." pairs
329
+ jobwatch package c1 # show it
330
+ ```
331
+
332
+ Packages live in `packages/<job>/` next to the state file. Removing a file moves it to `.removed/` there
333
+ rather than deleting it.
334
+
335
+ ### Prep sheets
336
+
337
+ Before a recruiter call or an interview, open the application's **Details** and press **Prep sheet** (or run
338
+ `jobwatch prep c1`). One page, ready to print, with:
339
+ - the stage, next step and your notes;
340
+ - each requirement and responsibility in the posting, next to the line on your resume closest to it (the
341
+ resume you sent, if you kept it), or a plain "nothing close" so you can prepare a story or an honest answer;
342
+ - skills they ask for that your resume doesn't show, with the fit score's missing must-haves;
343
+ - what you sent;
344
+ - questions to expect and questions to ask, for a screen or for interviews.
345
+
346
+ Nothing on it is written for you: it quotes the posting and your resume. For an application you added by hand,
347
+ jobwatch looks for the posting on your watched boards by the requisition id in its title (`R0123456`,
348
+ `REQ-4711`, `Job 20769`), so a Workday posting fetched later fills in the sheet.
349
+
350
+ ## Who you know there
351
+
352
+ A referral gets read before an application does. Download your LinkedIn data (Settings → Data privacy →
353
+ Get a copy of your data) and upload the archive in `jobwatch ui`, or point `connections:` at the .zip or
354
+ at its Connections.csv. Each digest and queue entry then lists your connections who work there:
355
+
356
+ ```
357
+ You know: Ana Li (Staff Engineer) [messaged 14×, last 2025-03-02]; Bo Chen (Recruiter)
358
+ ```
359
+
360
+ With the full archive, people you actually talk to come first. jobwatch counts the messages you exchanged,
361
+ recommendations and endorsements, so a close colleague ranks above someone who only accepted a connection
362
+ request. Only those counts are kept, never your messages. When you know nobody at a company, the page links
363
+ to a LinkedIn search of your 2nd-degree network there, to find someone who can introduce you.
364
+
365
+ Companies are matched by name, ignoring suffixes like "Inc." and "Corporation". Your LinkedIn data is only
366
+ read on your machine.
367
+
368
+ A digest lists each job once. Use `digest --all` to include jobs already shown, or `--peek` to look without
369
+ marking them shown. Roles that disappear from a board are marked closed.
370
+
371
+ ## MCP server
372
+
373
+ `jobwatch-mcp` offers `find_board`, `fetch_jobs`, `digest`, `job_details`, `mark_job`, `apply_queue`,
374
+ `add_application`, `applications`, `save_application_package`, `application_package`, `interview_prep`,
375
+ `skill_gaps` and `list_jobs` to Claude Code or any MCP client. The watchlist comes
376
+ from `JOBWATCH_CONFIG`:
377
+
378
+ ```
379
+ claude mcp add jobwatch -s user -e JOBWATCH_CONFIG=~/jobwatch.yaml -- jobwatch-mcp
380
+ ```
381
+
382
+ With a resume tool alongside it (for example [resume-kit](https://github.com/vinayvobbili/resume-kit), whose
383
+ `resume draft` starts a tailored version from a posting), an assistant can work through your queue: read
384
+ the posting, tailor the resume from facts you've confirmed, check for a referral, and fill in the
385
+ application for you to review. You still press Submit.
386
+
387
+ ## Why it doesn't auto-apply
388
+
389
+ Tools that auto-apply to hundreds of jobs make the process worse for everyone and rarely work for the
390
+ person using them:
391
+
392
+ - Recruiters recognize mass applications.
393
+ - Some companies cap how many roles one person can apply to.
394
+ - Application forms ask legal questions (work authorization, export control, signatures) that you answer
395
+ yourself.
396
+
397
+ jobwatch's job is to make sure you never miss a role worth applying to, and to spend your time on those.
398
+
399
+ ## Development
400
+
401
+ ```
402
+ python -m venv .venv && .venv/bin/pip install -e '.[dev,mcp]'
403
+ .venv/bin/ruff check . && .venv/bin/python -m pytest -q
404
+ ```
405
+
406
+ Tests use canned board responses and never touch the network, except the check that every curated course
407
+ link still resolves: `JOBWATCH_LINK_TESTS=1 pytest tests/test_learn.py`. To see how `jobwatch ui` looks after a change,
408
+ `scripts/screenshots.py` captures every tab in light and dark, at wide, desktop and phone widths, plus the
409
+ chat with a canned reply (it needs
410
+ `pip install playwright && python -m playwright install chromium`).
411
+
412
+ Releases publish to PyPI through Trusted
413
+ Publishing when a `v*` tag is pushed.
414
+
415
+ MIT licensed.