job_ops-mcp 0.3.0

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 (116) hide show
  1. package/.env.example +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +400 -0
  4. package/config/profile.example.yml +67 -0
  5. package/cv.example.md +53 -0
  6. package/dist/cli.js +385 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config.js +63 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/browser.js +27 -0
  11. package/dist/core/browser.js.map +1 -0
  12. package/dist/core/content_hash.js +11 -0
  13. package/dist/core/content_hash.js.map +1 -0
  14. package/dist/core/csv.js +107 -0
  15. package/dist/core/csv.js.map +1 -0
  16. package/dist/core/cv_parse.js +201 -0
  17. package/dist/core/cv_parse.js.map +1 -0
  18. package/dist/core/html.js +10 -0
  19. package/dist/core/html.js.map +1 -0
  20. package/dist/core/jd_normalize.js +99 -0
  21. package/dist/core/jd_normalize.js.map +1 -0
  22. package/dist/core/jobs.js +106 -0
  23. package/dist/core/jobs.js.map +1 -0
  24. package/dist/core/llm.js +227 -0
  25. package/dist/core/llm.js.map +1 -0
  26. package/dist/core/modes.js +55 -0
  27. package/dist/core/modes.js.map +1 -0
  28. package/dist/core/outreach_safety.js +77 -0
  29. package/dist/core/outreach_safety.js.map +1 -0
  30. package/dist/core/profile.js +88 -0
  31. package/dist/core/profile.js.map +1 -0
  32. package/dist/core/providers/amazon.js +36 -0
  33. package/dist/core/providers/amazon.js.map +1 -0
  34. package/dist/core/providers/ashby.js +31 -0
  35. package/dist/core/providers/ashby.js.map +1 -0
  36. package/dist/core/providers/google.js +46 -0
  37. package/dist/core/providers/google.js.map +1 -0
  38. package/dist/core/providers/greenhouse.js +55 -0
  39. package/dist/core/providers/greenhouse.js.map +1 -0
  40. package/dist/core/providers/http.js +36 -0
  41. package/dist/core/providers/http.js.map +1 -0
  42. package/dist/core/providers/index.js +53 -0
  43. package/dist/core/providers/index.js.map +1 -0
  44. package/dist/core/providers/lever.js +32 -0
  45. package/dist/core/providers/lever.js.map +1 -0
  46. package/dist/core/providers/playwright_generic.js +53 -0
  47. package/dist/core/providers/playwright_generic.js.map +1 -0
  48. package/dist/core/providers/types.js +2 -0
  49. package/dist/core/providers/types.js.map +1 -0
  50. package/dist/core/providers/workday.js +44 -0
  51. package/dist/core/providers/workday.js.map +1 -0
  52. package/dist/core/render.js +253 -0
  53. package/dist/core/render.js.map +1 -0
  54. package/dist/core/reports.js +257 -0
  55. package/dist/core/reports.js.map +1 -0
  56. package/dist/core/resources.js +40 -0
  57. package/dist/core/resources.js.map +1 -0
  58. package/dist/core/scan_engine.js +164 -0
  59. package/dist/core/scan_engine.js.map +1 -0
  60. package/dist/core/scheduler.js +117 -0
  61. package/dist/core/scheduler.js.map +1 -0
  62. package/dist/db.js +60 -0
  63. package/dist/db.js.map +1 -0
  64. package/dist/http/app.js +35 -0
  65. package/dist/http/app.js.map +1 -0
  66. package/dist/http/dashboard.js +131 -0
  67. package/dist/http/dashboard.js.map +1 -0
  68. package/dist/mcp/define.js +35 -0
  69. package/dist/mcp/define.js.map +1 -0
  70. package/dist/mcp/server.js +103 -0
  71. package/dist/mcp/server.js.map +1 -0
  72. package/dist/mcp/tools/apply_prefill.js +167 -0
  73. package/dist/mcp/tools/apply_prefill.js.map +1 -0
  74. package/dist/mcp/tools/batch_evaluate.js +143 -0
  75. package/dist/mcp/tools/batch_evaluate.js.map +1 -0
  76. package/dist/mcp/tools/evaluate_job.js +181 -0
  77. package/dist/mcp/tools/evaluate_job.js.map +1 -0
  78. package/dist/mcp/tools/generate_materials.js +126 -0
  79. package/dist/mcp/tools/generate_materials.js.map +1 -0
  80. package/dist/mcp/tools/get_report.js +24 -0
  81. package/dist/mcp/tools/get_report.js.map +1 -0
  82. package/dist/mcp/tools/ops.js +321 -0
  83. package/dist/mcp/tools/ops.js.map +1 -0
  84. package/dist/mcp/tools/outreach.js +481 -0
  85. package/dist/mcp/tools/outreach.js.map +1 -0
  86. package/dist/mcp/tools/render_pdf.js +27 -0
  87. package/dist/mcp/tools/render_pdf.js.map +1 -0
  88. package/dist/mcp/tools/scan_portals.js +35 -0
  89. package/dist/mcp/tools/scan_portals.js.map +1 -0
  90. package/dist/mcp/tools/scheduler.js +32 -0
  91. package/dist/mcp/tools/scheduler.js.map +1 -0
  92. package/dist/mcp/tools/stories.js +172 -0
  93. package/dist/mcp/tools/stories.js.map +1 -0
  94. package/dist/mcp/tools/tracker.js +183 -0
  95. package/dist/mcp/tools/tracker.js.map +1 -0
  96. package/dist/mcp/tools/visa.js +219 -0
  97. package/dist/mcp/tools/visa.js.map +1 -0
  98. package/dist/migrations/001_initial.sql +505 -0
  99. package/dist/migrations/002_llm_and_digest.sql +42 -0
  100. package/dist/server.js +55 -0
  101. package/dist/server.js.map +1 -0
  102. package/fonts/dm-sans-latin-ext.woff2 +0 -0
  103. package/fonts/dm-sans-latin.woff2 +0 -0
  104. package/fonts/space-grotesk-latin-ext.woff2 +0 -0
  105. package/fonts/space-grotesk-latin.woff2 +0 -0
  106. package/modes/career_packet.md +91 -0
  107. package/modes/negotiation_playbook.md +64 -0
  108. package/modes/outreach_tone.md +80 -0
  109. package/modes/report_format.md +83 -0
  110. package/modes/rubric.md +119 -0
  111. package/modes/tailoring_rules.md +102 -0
  112. package/package.json +67 -0
  113. package/portals.example.yml +95 -0
  114. package/templates/cover-template.html +64 -0
  115. package/templates/cv-template.html +421 -0
  116. package/templates/cv-template.tex +123 -0
package/.env.example ADDED
@@ -0,0 +1,33 @@
1
+ # mcp-jsa configuration
2
+
3
+ # Network
4
+ MCP_JSA_PORT=7891
5
+ MCP_JSA_HOST=127.0.0.1
6
+
7
+ # Project root — where cv.md, config/profile.yml, portals.yml live.
8
+ # Defaults to the directory this server runs from.
9
+ # To reuse the existing career-ops/ data, point it there:
10
+ # MCP_JSA_PROJECT_ROOT=/Users/you/Documents/Career Ops/career-ops
11
+ MCP_JSA_PROJECT_ROOT=
12
+
13
+ # Data + outputs (resolved relative to the server install dir, not project root)
14
+ MCP_JSA_DATA_DIR=./data
15
+ MCP_JSA_OUTPUT_DIR=./output
16
+
17
+ # LLM provider — only used on api/batch paths in later milestones.
18
+ # chat-mode (default) runs zero LLM calls server-side; the chat client is the brain.
19
+ MCP_JSA_LLM_PROVIDER=gemini # gemini | deepseek | none
20
+ MCP_JSA_LLM_MODEL=
21
+ GEMINI_API_KEY=
22
+ DEEPSEEK_API_KEY=
23
+
24
+ # Scheduler — off by default. Enable specific jobs via scheduler_enable MCP tool.
25
+ MCP_JSA_SCHEDULER_ENABLED=false
26
+
27
+ # Visa scoring — set to `false` if you don't need visa/H1B signal (US citizens, non-US
28
+ # users, etc.). When false, visa_fit is dropped from the rubric, weights renormalize to
29
+ # resume 0.6 / taste 0.4, the visa_signal / import_h1b / import_linkedin tools are hidden,
30
+ # and visa columns are stripped from tool responses. Default: true.
31
+ MCP_JSA_VISA_SCORING=true
32
+
33
+ # Playwright — usually no config needed once `npm run playwright:install` is done.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohith Kattukuttiyil Das
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.
package/README.md ADDED
@@ -0,0 +1,400 @@
1
+ # job_ops-mcp
2
+
3
+ A self-hosted **Model Context Protocol** server for the full job-search loop — portal
4
+ scanning, JD evaluation, tailored resume + cover PDFs, outreach drafting, story bank,
5
+ negotiation brief — all driven from your MCP-aware chat client (Claude Desktop, Cursor,
6
+ any client that speaks streamable-HTTP MCP).
7
+
8
+ The chat is the brain. This server executes the mechanical work and hands every artifact
9
+ back as an `http://localhost:7891/...` link.
10
+
11
+ > **Status:** early. Works. APIs may still move pre-1.0.
12
+
13
+ ---
14
+
15
+ ## Quickstart
16
+
17
+ ```bash
18
+ # 1. Scaffold your working directory (cv.md, profile.yml, portals.yml + SQLite DB)
19
+ npx job_ops-mcp init
20
+
21
+ # 2. Open cv.md, config/profile.yml, portals.yml and replace every <TODO> placeholder.
22
+
23
+ # 3. Confirm everything is wired
24
+ npx job_ops-mcp doctor
25
+
26
+ # 4. Boot the server (Chromium auto-installs on first run)
27
+ npx job_ops-mcp start
28
+ # ▷ job_ops-mcp listening on http://127.0.0.1:7891
29
+
30
+ # 5. Get a copy-paste config block for your MCP client
31
+ npx job_ops-mcp connect
32
+
33
+ # 6. Paste a job URL or pasted JD into your chat — the chat calls evaluate_job, draws on
34
+ # your rubric + career_packet + tailoring rules, and walks the rest of the workflow.
35
+ ```
36
+
37
+ That's the loop. Everything else (warm-intro finder, story bank, negotiation brief,
38
+ batch rater, scheduler, …) is wired in but optional.
39
+
40
+ ---
41
+
42
+ ## What it does
43
+
44
+ Two systems merged into one MCP server:
45
+
46
+ - **Evaluation + materials side** — port of [santifer/career-ops](https://github.com/santifer/career-ops):
47
+ 6-block A–F (+G legitimacy) report, archetype detection, ATS-friendly HTML→PDF resume
48
+ + cover generation, story bank, negotiation playbook.
49
+ - **Pipeline side** — port of a personal Postgres + n8n pipeline ("JSA"): Greenhouse /
50
+ Ashby / Lever / Workday pollers + closed-board Playwright scrapers, content-hash
51
+ dedupe, batch LLM rater with strict-JSON parsing, warm-intro / founder DM drafter,
52
+ visa signal from DOL OFLC H1B data.
53
+
54
+ Everything lives in a single Node process with a single SQLite file. No external
55
+ Postgres, no n8n, no cloud anything. Bring your own LLM key (Gemini free tier by
56
+ default, DeepSeek optional) if you want the API/batch paths; chat-mode tools work
57
+ without one.
58
+
59
+ > **Not affiliated with or endorsed by santifer's career-ops.** This is an independent
60
+ > project that ports + adapts the publicly-released MIT-licensed templates and rubric
61
+ > shape into the MCP transport surface. See the [Attribution](#attribution) section.
62
+
63
+ ---
64
+
65
+ ## Tools (36 — one MCP `tools/list` call away)
66
+
67
+ | Group | Tools |
68
+ |---|---|
69
+ | **Evaluation** | `evaluate_job`, `batch_evaluate`, `get_top_jobs`, `evaluate_training`, `evaluate_project` |
70
+ | **Materials** | `generate_materials`, `render_pdf`, `get_report` |
71
+ | **Tracker** | `get_tracker`, `update_status`, `mark_ready_to_apply` |
72
+ | **Sourcing** | `scan_portals` (Greenhouse + Ashby + Lever + Workday + Amazon + Google + generic Playwright) |
73
+ | **Outreach** | `find_warm_intros`, `find_founders`, `draft_outreach`, `draft_followup`, `draft_reply`, `get_outreach_queue`, `update_outreach`, `get_followups_due` |
74
+ | **Interview / offer** | `extract_stories`, `get_story_bank`, `negotiation_brief` |
75
+ | **Research** | `deep_research`, `enrich_company`, `daily_digest` |
76
+ | **Profile + ops** | `get_career_packet`, `update_career_packet`, `cost_estimate` |
77
+ | **Apply (preview only — never submits)** | `apply_prefill` |
78
+ | **Visa (optional, can be hidden)** | `visa_signal`, `import_h1b`, `import_linkedin` |
79
+ | **Scheduler (opt-in cron, off by default)** | `scheduler_status`, `scheduler_enable`, `scheduler_disable` |
80
+
81
+ Six MCP **resources** carry the editable behaviour — rubric, report_format,
82
+ tailoring_rules, outreach_tone, negotiation_playbook, career_packet — all loaded from
83
+ `modes/*.md` and live-reloaded on edit. Tune scoring or tone without touching code.
84
+
85
+ ---
86
+
87
+ ## Designed to be made yours
88
+
89
+ The defaults assume nothing about your location, citizenship, role, or industry. Every
90
+ behaviour-shaping piece is a markdown file you can rewrite:
91
+
92
+ | You can change… | By editing… |
93
+ |---|---|
94
+ | Scoring dimensions + weights | `modes/rubric.md` |
95
+ | 6-block report shape | `modes/report_format.md` |
96
+ | Resume/cover tailoring rules | `modes/tailoring_rules.md` |
97
+ | Outreach tone + char caps | `modes/outreach_tone.md` |
98
+ | Negotiation scripts + framework | `modes/negotiation_playbook.md` |
99
+ | Your bullet/project bank | `modes/career_packet.md` (or via `update_career_packet`) |
100
+ | Tracked companies + filters | `portals.yml` |
101
+ | Identity + target roles | `config/profile.yml` |
102
+
103
+ ### Non-US users / non-sponsorship cases
104
+
105
+ Visa scoring is **fully optional**. Set:
106
+
107
+ ```bash
108
+ export MCP_JSA_VISA_SCORING=false
109
+ ```
110
+
111
+ When off:
112
+
113
+ - `score_total = round(0.6 · resume_fit + 0.4 · taste_fit)` (server-side authoritative)
114
+ - The `visa_signal`, `import_h1b`, `import_linkedin` tools are hidden from `tools/list`
115
+ - `score_visa_fit` is stripped from `get_top_jobs` items and the eval-report HTML badge
116
+ - The rubric resource gets a "VISA SCORING DISABLED" override prefix the chat reads
117
+
118
+ Other features are unaffected. If you're a US citizen, a non-US user, or anyone scoring
119
+ roles where sponsorship is a non-issue — turn it off; the rest of the system works.
120
+
121
+ ### Non-US markets
122
+
123
+ `portals.yml` ships with example shapes for Greenhouse / Ashby / Lever / Workday / Amazon
124
+ / Google / generic Playwright. Drop in the boards relevant to your market. `modes/rubric.md`
125
+ + `modes/negotiation_playbook.md` + `config/profile.yml` are all yours to localize
126
+ (language, comp ranges, geography priors).
127
+
128
+ ---
129
+
130
+ ## Environment variables
131
+
132
+ | Var | Default | Purpose |
133
+ |---|---|---|
134
+ | `MCP_JSA_PORT` | `7891` | HTTP port (MCP + file server + tracker dashboard) |
135
+ | `MCP_JSA_HOST` | `127.0.0.1` | Bind host |
136
+ | `MCP_JSA_PROJECT_ROOT` | cwd | Where `cv.md` / `config/profile.yml` / `portals.yml` live |
137
+ | `MCP_JSA_DATA_DIR` | `<install>/data` | SQLite + WAL location |
138
+ | `MCP_JSA_OUTPUT_DIR` | `<install>/output` | Rendered artifacts (PDFs, report HTML) |
139
+ | `MCP_JSA_VISA_SCORING` | `true` | Set `false` to drop visa surface entirely (see above) |
140
+ | `MCP_JSA_LLM_PROVIDER` | `gemini` | Used only by `api`/batch paths: `gemini`, `deepseek`, `none` |
141
+ | `MCP_JSA_LLM_MODEL` | _empty_ | Provider-specific model id |
142
+ | `GEMINI_API_KEY` / `DEEPSEEK_API_KEY` | _empty_ | Provider credentials |
143
+ | `MCP_JSA_SCHEDULER_ENABLED` | `false` | Whether opt-in cron runs at all |
144
+
145
+ A working starter is at `.env.example`.
146
+
147
+ ---
148
+
149
+ ## Wiring it to Claude Desktop
150
+
151
+ `npx job_ops-mcp connect` prints the exact config block. The short version:
152
+
153
+ ```jsonc
154
+ {
155
+ "mcpServers": {
156
+ "job_ops-mcp": {
157
+ "command": "npx",
158
+ "args": ["-y", "job_ops-mcp", "start"],
159
+ "env": {
160
+ "MCP_JSA_PORT": "7891",
161
+ "MCP_JSA_PROJECT_ROOT": "/absolute/path/to/your/job-search/dir"
162
+ }
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ (Drop into `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
169
+ `%APPDATA%/Claude/claude_desktop_config.json` on Windows. Restart Claude Desktop.)
170
+
171
+ Generic MCP clients that take a streamable-HTTP URL: point them at
172
+ `http://127.0.0.1:7891/mcp` after `npx job_ops-mcp start` is running.
173
+
174
+ ### LibreChat
175
+
176
+ `npx job_ops-mcp connect` also prints a `librechat.yaml` block. Two shapes:
177
+
178
+ - **LibreChat as a host process:** `type: streamable-http`, `url: http://127.0.0.1:7891/mcp`.
179
+ - **LibreChat in Docker:** swap to `http://host.docker.internal:7891/mcp` AND allowlist
180
+ the address under `mcpSettings.allowedAddresses` (LibreChat blocks private/internal
181
+ addresses by default as SSRF protection). On Linux, also add
182
+ `extra_hosts: ["host.docker.internal:host-gateway"]` to the LibreChat service in your
183
+ `docker-compose.yml`.
184
+
185
+ (Refs:
186
+ [librechat.ai/docs/.../mcp_servers](https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/mcp_servers),
187
+ [features/mcp](https://www.librechat.ai/docs/features/mcp).)
188
+
189
+ ---
190
+
191
+ ## Working `evaluate_job` payloads
192
+
193
+ ### Step 1 — paste a JD or URL
194
+
195
+ ```jsonc
196
+ {
197
+ "method": "tools/call",
198
+ "params": {
199
+ "name": "evaluate_job",
200
+ "arguments": {
201
+ "input": "https://jobs.ashbyhq.com/example/123",
202
+ "mode": "chat",
203
+ "title": "Builder PM",
204
+ "company": "Frontier AI Tools Co"
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ Returns the rubric, the report format, the active career packet, and a `job_id`. The
211
+ chat client uses those to score + draft the 6 blocks.
212
+
213
+ ### Step 2 — finalize
214
+
215
+ ```jsonc
216
+ {
217
+ "method": "tools/call",
218
+ "params": {
219
+ "name": "evaluate_job",
220
+ "arguments": {
221
+ "job_id": "<from step 1>",
222
+ "mode": "chat",
223
+ "report": {
224
+ "archetype_detected": "Agentic / LLMOps hybrid",
225
+ "block_role_summary": "…",
226
+ "block_cv_match": "…",
227
+ "block_level": "…",
228
+ "block_comp": "…",
229
+ "block_personalize": "…",
230
+ "block_interview": "…",
231
+ "block_legitimacy": "…",
232
+ "keywords": ["builder pm", "agentic workflows", "…"]
233
+ },
234
+ "scores": {
235
+ "resume_fit": 86, "taste_fit": 92, "visa_fit": 88, "score_total": 88,
236
+ "reasoning": "Strong match on agentic workflows + PRDs + SQL/Python.",
237
+ "concerns": "Evals experience is adjacent rather than LLM-eval-specific.",
238
+ "role_category": "pm",
239
+ "seniority": "senior"
240
+ }
241
+ }
242
+ }
243
+ }
244
+ ```
245
+
246
+ Server persists, renders HTML at `/files/reports/<id>.html`, returns the URL.
247
+
248
+ ---
249
+
250
+ ## Advanced / outreach features (optional)
251
+
252
+ ### Importing your LinkedIn network → warm-intro finder
253
+
254
+ Download your LinkedIn data export (Settings → Data Privacy → Get a copy of your data →
255
+ Connections), then:
256
+
257
+ ```bash
258
+ # Through your MCP chat:
259
+ import_linkedin path="/absolute/path/to/Connections.csv"
260
+ ```
261
+
262
+ Now `find_warm_intros(company="…")` returns the people you actually know who work there
263
+ (filtered to non-recruiters, sorted by engineering / leadership weight).
264
+
265
+ ### Importing DOL OFLC H1B data → visa-friendliness signal
266
+
267
+ Download a quarterly LCA disclosure CSV from <https://www.dol.gov/agencies/eta/foreign-labor/performance>,
268
+ then:
269
+
270
+ ```bash
271
+ # Through your MCP chat:
272
+ import_h1b path="/absolute/path/to/LCA_Disclosure_Data_FY2025_Q1.csv"
273
+ ```
274
+
275
+ `visa_signal(company="…")` then returns a friendliness band (`strong | mixed | weak |
276
+ none`) computed from filings count + recency. **Internal only** — never surfaced in any
277
+ resume, cover letter, or outreach (see the visa hard rule).
278
+
279
+ If you disabled visa scoring (`MCP_JSA_VISA_SCORING=false`), these tools don't appear in
280
+ `tools/list` at all.
281
+
282
+ ### Scheduler (opt-in cron)
283
+
284
+ Off by default. To run scans + batch rates on a schedule:
285
+
286
+ ```bash
287
+ # In your MCP chat:
288
+ scheduler_enable jobs=["scan_portals_4h", "batch_evaluate_30m", "daily_digest_morning"]
289
+ ```
290
+
291
+ Job cadence is fixed (4h / 30m / hourly with an 8AM digest window). Toggle off with
292
+ `scheduler_disable`. Survives only as long as the server process is alive.
293
+
294
+ ---
295
+
296
+ ## Hard rules baked in
297
+
298
+ 1. **Never surface visa / work-auth** in any resume, cover letter, or outreach. Visa data
299
+ is internal scoring only.
300
+ 2. **Never invent claims** not in `career_packet`. The materials generator validates LLM
301
+ output against the packet before persisting.
302
+ 3. **Human-in-the-loop everywhere.** No tool auto-submits an application or auto-sends a
303
+ DM. `apply_prefill` is preview-only — it opens the form in Chromium, drafts values,
304
+ takes a screenshot, and stops. You submit manually.
305
+ 4. **Strict-JSON parsing on the api path** with a recorded `PARSE_ERROR` fallback —
306
+ never silent zeros.
307
+ 5. **Tracker / application / outreach writes are serialized** behind a single write lock.
308
+
309
+ ---
310
+
311
+ ## Layout
312
+
313
+ ```
314
+ job_ops-mcp/
315
+ ├── modes/ # MCP resources (edit me to tune the brain)
316
+ ├── templates/ # CV HTML/LaTeX templates + cover-letter template
317
+ ├── fonts/ # Space Grotesk, DM Sans (woff2 subsets)
318
+ ├── cv.example.md # → cv.md after init
319
+ ├── config/profile.example.yml # → config/profile.yml after init
320
+ ├── portals.example.yml # → portals.yml after init
321
+ ├── src/ # TypeScript source (not published)
322
+ │ ├── cli.ts # init / start / doctor / connect
323
+ │ ├── server.ts # HTTP + MCP boot
324
+ │ ├── core/ # llm, providers, jobs, reports, render, scan_engine, …
325
+ │ ├── http/ # express app + dashboard
326
+ │ ├── mcp/ # define + register + tools/
327
+ │ └── migrations/*.sql # SQLite migrations
328
+ └── data/, output/ # gitignored runtime state
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Attribution
334
+
335
+ - The HTML CV template, font set, and ATS unicode-normalization logic are ported from
336
+ [santifer/career-ops](https://github.com/santifer/career-ops) (MIT licensed). The
337
+ 6-block A–F report shape, scoring rubric framing, and outreach tone rules are also
338
+ inspired by that project. **Not affiliated with or endorsed by career-ops** — this is
339
+ an independent fork of those publicly-released ideas into the MCP transport.
340
+ - The 3-dimension scoring formula (resume / taste / visa), the schema shape
341
+ (companies / jobs / outreach / enrichment / career_packet views), and the strict-JSON
342
+ rater rubric are distilled from a personal pipeline ("JSA") that predates this
343
+ project.
344
+
345
+ ---
346
+
347
+ ## Releasing (maintainer notes)
348
+
349
+ Releases ship to npm via the GitHub Actions workflow at
350
+ [`.github/workflows/publish.yml`](.github/workflows/publish.yml). The workflow fires
351
+ **only on pushing a version tag** (`v*.*.*`) — never on a push to `main` — so merging
352
+ work never auto-publishes.
353
+
354
+ ### One-time setup
355
+
356
+ 1. **Generate an npm automation token** at [npmjs.com](https://www.npmjs.com/) →
357
+ click your avatar → **Access Tokens** → **Generate New Token** → choose
358
+ **"Automation"** (NOT *Read-Only* and NOT *Publish*; Automation tokens bypass 2FA,
359
+ which CI needs).
360
+ 2. **Add it to GitHub.** In the repo → **Settings** → **Secrets and variables** →
361
+ **Actions** → **New repository secret** → name **`NPM_TOKEN`**, value the token
362
+ you just copied (starts with `npm_`).
363
+
364
+ ### Cutting a release
365
+
366
+ ```bash
367
+ # 1. Bump the version in package.json. Either edit by hand, or:
368
+ npm version patch # 0.3.0 → 0.3.1 (also creates a git commit + tag)
369
+ # npm version minor # 0.3.0 → 0.4.0
370
+ # npm version major # 0.3.0 → 1.0.0
371
+
372
+ # 2. If you edited package.json by hand instead of `npm version`, commit it:
373
+ # git add package.json && git commit -m "release: vX.Y.Z"
374
+ # git tag vX.Y.Z
375
+
376
+ # 3. Push the commit + tag.
377
+ git push && git push origin vX.Y.Z
378
+ ```
379
+
380
+ That tag push triggers `publish.yml`, which:
381
+
382
+ 1. Checks out the tagged commit.
383
+ 2. Sets up Node 20 with the npm registry.
384
+ 3. Verifies the tag (`vX.Y.Z`) matches `package.json`'s `version` — fails fast on a typo.
385
+ 4. `npm ci` + `npm run build`.
386
+ 5. `npm publish --access public --provenance` — provenance attaches a sigstore
387
+ attestation visible on npmjs.com showing exactly which GitHub Actions run produced
388
+ the tarball.
389
+
390
+ Watch progress in the repo's **Actions** tab. On success the new version appears on
391
+ [npmjs.com/package/job_ops-mcp](https://www.npmjs.com/package/job_ops-mcp).
392
+
393
+ ## Contributing / feedback
394
+
395
+ Issues + PRs welcome. There's no contributor guide yet — open an issue first if you're
396
+ planning a large change.
397
+
398
+ ---
399
+
400
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,67 @@
1
+ # job_ops-mcp profile configuration.
2
+ #
3
+ # Copy to `config/profile.yml` (or let `npx job_ops-mcp init` do it) and fill in your
4
+ # details. This is the source of truth for your identity + targeting + comp/location
5
+ # preferences. The chat reads it via the rubric + career_packet resources.
6
+
7
+ candidate:
8
+ full_name: "<Your Full Name>"
9
+ email: "<you@example.com>"
10
+ phone: "<+country area-number>"
11
+ location: "<City, State / Country>"
12
+ linkedin: "linkedin.com/in/<your-handle>"
13
+ portfolio_url: "https://<your-portfolio>"
14
+ github: "github.com/<your-handle>"
15
+ twitter: "" # optional
16
+
17
+ target_roles:
18
+ # Your North-Star roles — what you're optimizing for.
19
+ primary:
20
+ - "<role title 1>"
21
+ - "<role title 2>"
22
+ # Archetypes drive scoring (`role_category`) + tailoring per JD.
23
+ # fit values: primary | secondary | adjacent
24
+ archetypes:
25
+ - { name: "<archetype label, e.g. AI/ML Engineer>", level: "Senior", fit: "primary" }
26
+ - { name: "<archetype label, e.g. Technical PM>", level: "Senior/Staff",fit: "secondary" }
27
+ - { name: "<archetype label, e.g. Forward-Deployed>",level: "Mid-Senior", fit: "adjacent" }
28
+
29
+ narrative:
30
+ # One-line professional headline.
31
+ headline: "<your one-line headline>"
32
+ # Two-sentence story about what makes you uniquely hireable.
33
+ exit_story: "<your story>"
34
+ # Top 3–5 differentiators (used in score reasoning + outreach hooks).
35
+ superpowers:
36
+ - "<superpower 1>"
37
+ - "<superpower 2>"
38
+ - "<superpower 3>"
39
+ # The 2–4 things that excite you (used in taste_fit scoring).
40
+ likes:
41
+ - "<like 1, e.g. 'lean teams, fast iteration, AI/data tooling'>"
42
+ - "<like 2>"
43
+ # The dislikes that should drop taste_fit below 50.
44
+ dislikes:
45
+ - "<dislike 1, e.g. 'legacy Java enterprise modernization'>"
46
+ - "<dislike 2>"
47
+ # Proof points — projects/articles/case studies with measurable impact.
48
+ proof_points:
49
+ - { name: "<project name>", url: "<url>", hero_metric: "<short hero-metric phrase>" }
50
+ - { name: "<project name>", url: "<url>", hero_metric: "<...>" }
51
+
52
+ compensation:
53
+ target_range: "<currency-min – currency-max>" # e.g. "$130K-180K"
54
+ currency: "USD"
55
+ minimum: "<walk-away number>"
56
+ location_flexibility: "<e.g. 'Remote preferred; 1 week/month on-site possible'>"
57
+
58
+ location:
59
+ country: "<country>"
60
+ city: "<city>"
61
+ timezone: "<TZ abbr, e.g. PST>"
62
+ visa_status: "<e.g. 'No sponsorship needed' or 'On OPT, need H1B sponsorship'>"
63
+ # Optional. If your target locations differ from `city`, list them here.
64
+ onsite_availability: ""
65
+
66
+ cv:
67
+ output_format: "html" # "html" (default) or "latex"
package/cv.example.md ADDED
@@ -0,0 +1,53 @@
1
+ # CV — <Your Full Name>
2
+ **Location:** <City, State / Country> (open to <relocation policy>)
3
+ **Email:** <you@example.com>
4
+ **Phone:** <+country area-number>
5
+ **LinkedIn:** linkedin.com/in/<your-handle>
6
+ **GitHub:** github.com/<your-handle>
7
+ **Portfolio:** <your-portfolio-url>
8
+
9
+ ## Professional Summary
10
+ <2–3 sentences. State your specialism, the size/shape of the work you've owned, and the
11
+ hybrid skillset that makes you hire-able for the roles you actually want. Keep it
12
+ concrete — name systems, scopes, and one or two outcomes.>
13
+
14
+ ## Work Experience
15
+
16
+ ### <Company> — <Role Title>
17
+ <Location / Remote> · <Start Mon YYYY> – <End Mon YYYY or Present>
18
+ Products owned: <comma-separated list, optional>.
19
+ - <One-sentence bullet, action verb start, ~15–25 words, real metric inline.>
20
+ - <Second bullet — different verb, different shape of impact.>
21
+ - <Third bullet — emphasise scope (people / systems / dollars).>
22
+ - <Fourth bullet — emphasise a hard-to-mimic technical/product skill.>
23
+ - <Fifth bullet — emphasise a cross-functional or leadership thread.>
24
+
25
+ ### <Earlier Company> — <Role>
26
+ <Location> · <Mon YYYY> – <Mon YYYY>
27
+ - <Bullet.>
28
+ - <Bullet.>
29
+ - <Bullet.>
30
+
31
+ ### <Earliest Company> — <Role>
32
+ <Location> · <Mon YYYY> – <Mon YYYY>
33
+ - <Bullet.>
34
+ - <Bullet.>
35
+
36
+ ## Projects & Open Source
37
+
38
+ - **<Project name>** (Open source / Personal / Academic) — <one-sentence description with
39
+ the credibility marker; stack named explicitly>. <github.com/you/repo or link>
40
+ - **<Project name>** — <description>. <link if public>
41
+ - **<Project name>** — <description>.
42
+
43
+ ## Education
44
+ - **<Degree>**, <Institution> — <year range>. <optional 1-line coursework / honors>
45
+ - **<Earlier degree>**, <Institution> — <year range>.
46
+
47
+ ## Skills
48
+ - **AI / LLM Systems:** <list>
49
+ - **Data & Analytics Engineering:** <list>
50
+ - **Infrastructure & DevOps:** <list>
51
+ - **Product:** <list>
52
+ - **Web & Tools:** <list>
53
+ - **Languages:** <primary first>