job_ops-mcp 0.4.2 → 0.4.4
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.
- package/dist/core/jd_normalize.js +60 -1
- package/dist/core/jd_normalize.js.map +1 -1
- package/dist/mcp/tools/apply_prefill.js +165 -60
- package/dist/mcp/tools/apply_prefill.js.map +1 -1
- package/dist/mcp/tools/generate_materials.js +30 -3
- package/dist/mcp/tools/generate_materials.js.map +1 -1
- package/dist/mcp/tools/render_pdf.js +60 -1
- package/dist/mcp/tools/render_pdf.js.map +1 -1
- package/package.json +2 -1
|
@@ -10,6 +10,61 @@
|
|
|
10
10
|
const FETCH_TIMEOUT_MS = 12_000;
|
|
11
11
|
const MAX_JD_CHARS = 18_000;
|
|
12
12
|
const USER_AGENT = 'Mozilla/5.0 (compatible; mcp-jsa/0.1)';
|
|
13
|
+
/**
|
|
14
|
+
* Best-effort extraction of the company name from a known ATS URL. Returns a
|
|
15
|
+
* Title-Cased company name when the URL matches a recognised host, else null.
|
|
16
|
+
* Exported for tests + direct use in adoptJobFromJD.
|
|
17
|
+
*
|
|
18
|
+
* greenhouse: https://(job-boards|boards|job-boards.eu).greenhouse.io/<slug>
|
|
19
|
+
* ashby: https://jobs.ashbyhq.com/<slug>
|
|
20
|
+
* lever: https://jobs.lever.co/<slug>
|
|
21
|
+
* workday: https://<tenant>.wd<N>.myworkdayjobs.com/<site>
|
|
22
|
+
* amazon: https://*.amazon.jobs/* → "Amazon"
|
|
23
|
+
* google careers: https://www.google.com/about/careers/* → "Google"
|
|
24
|
+
*/
|
|
25
|
+
export function extractCompanyFromUrl(url) {
|
|
26
|
+
if (!url)
|
|
27
|
+
return null;
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = new URL(url);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const host = parsed.hostname.toLowerCase();
|
|
36
|
+
const path = parsed.pathname;
|
|
37
|
+
if (/(?:^|\.)greenhouse\.io$/.test(host)) {
|
|
38
|
+
const m = path.match(/^\/([^/]+)/);
|
|
39
|
+
if (m)
|
|
40
|
+
return titleCaseSlug(m[1]);
|
|
41
|
+
}
|
|
42
|
+
if (host === 'jobs.ashbyhq.com') {
|
|
43
|
+
const m = path.match(/^\/([^/]+)/);
|
|
44
|
+
if (m)
|
|
45
|
+
return titleCaseSlug(m[1]);
|
|
46
|
+
}
|
|
47
|
+
if (host === 'jobs.lever.co') {
|
|
48
|
+
const m = path.match(/^\/([^/]+)/);
|
|
49
|
+
if (m)
|
|
50
|
+
return titleCaseSlug(m[1]);
|
|
51
|
+
}
|
|
52
|
+
const wd = host.match(/^([^.]+)\.wd\d+\.myworkdayjobs\.com$/);
|
|
53
|
+
if (wd)
|
|
54
|
+
return titleCaseSlug(wd[1]);
|
|
55
|
+
if (host === 'www.amazon.jobs' || host === 'amazon.jobs')
|
|
56
|
+
return 'Amazon';
|
|
57
|
+
if (host === 'www.google.com' && /^\/about\/careers/.test(path))
|
|
58
|
+
return 'Google';
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function titleCaseSlug(slug) {
|
|
62
|
+
return slug
|
|
63
|
+
.split(/[-_]+/)
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
66
|
+
.join(' ');
|
|
67
|
+
}
|
|
13
68
|
export async function normalizeJD(input) {
|
|
14
69
|
const trimmed = input.trim();
|
|
15
70
|
if (looksLikeUrl(trimmed)) {
|
|
@@ -42,11 +97,15 @@ async function fetchFromUrl(url) {
|
|
|
42
97
|
const ogSite = extractMeta(html, 'og:site_name');
|
|
43
98
|
const ogTitle = extractMeta(html, 'og:title');
|
|
44
99
|
const text = htmlToPlainText(html);
|
|
100
|
+
// URL-derived company is authoritative for known ATS hosts. og:site_name on those
|
|
101
|
+
// hosts is often the ATS vendor ("Greenhouse"), not the hiring company, so prefer
|
|
102
|
+
// the slug. Fall back to OG site_name for unknown hosts.
|
|
103
|
+
const company_guess = extractCompanyFromUrl(url) ?? ogSite ?? null;
|
|
45
104
|
return {
|
|
46
105
|
source: 'url',
|
|
47
106
|
source_url: url,
|
|
48
107
|
title_guess: ogTitle ?? title ?? null,
|
|
49
|
-
company_guess
|
|
108
|
+
company_guess,
|
|
50
109
|
text: text.slice(0, MAX_JD_CHARS),
|
|
51
110
|
raw_html: html.slice(0, MAX_JD_CHARS * 2),
|
|
52
111
|
fetched_at: new Date().toISOString(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jd_normalize.js","sourceRoot":"","sources":["../../src/core/jd_normalize.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,yCAAyC;AACzC,8DAA8D;AAC9D,iDAAiD;AACjD,EAAE;AACF,sFAAsF;AACtF,wFAAwF;AACxF,gDAAgD;AAEhD,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,YAAY,GAAO,MAAM,CAAC;AAChC,MAAM,UAAU,GAAS,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"jd_normalize.js","sourceRoot":"","sources":["../../src/core/jd_normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
// apply_prefill — Playwright opens the application form, reads visible fields, drafts
|
|
2
|
-
// values
|
|
3
|
-
// preview. NEVER auto-submits.
|
|
2
|
+
// values for the few fields we can confidently identify from structured profile data,
|
|
3
|
+
// takes a screenshot, returns a preview. NEVER auto-submits.
|
|
4
|
+
//
|
|
5
|
+
// Mapping policy (the heart of this file):
|
|
6
|
+
//
|
|
7
|
+
// We maintain a SMALL ALLOWLIST of field kinds we'll fill from structured profile data:
|
|
8
|
+
// first_name, last_name, preferred_name, full_name, email, phone,
|
|
9
|
+
// linkedin, github, portfolio_url, city.
|
|
10
|
+
//
|
|
11
|
+
// A field is classified into a kind ONLY when it has a strong, well-defined signal:
|
|
12
|
+
// - autocomplete attribute (e.g. autocomplete="email")
|
|
13
|
+
// - input type attribute (e.g. type="email", type="tel")
|
|
14
|
+
// - exact `name` attribute match (e.g. name="email", name="first_name")
|
|
15
|
+
// - exact / whole-word label match (e.g. label === "Email" or "First Name *")
|
|
16
|
+
//
|
|
17
|
+
// Anything else — free-text questions ("Why this company?", "Where did you hear",
|
|
18
|
+
// "Tell us about a project"), dropdowns, custom EEO/demographic fields, ambiguous
|
|
19
|
+
// labels — is left BLANK with source='user_must_provide'. We NEVER fall back to
|
|
20
|
+
// dumping tagline / cover-letter / summary prose into unmapped fields. Resume + cover
|
|
21
|
+
// are returned as localhost links for the user to download and upload manually.
|
|
22
|
+
//
|
|
23
|
+
// Visa / work-auth / citizenship / country-of-residence fields are explicitly
|
|
24
|
+
// blocked regardless of any other match.
|
|
25
|
+
//
|
|
26
|
+
// The default for an unrecognised field is ALWAYS blank/user_must_provide. We err
|
|
27
|
+
// toward under-filling so a wrong value never lands in a free-text answer.
|
|
4
28
|
import { z } from 'zod';
|
|
5
29
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
6
30
|
import { resolve } from 'node:path';
|
|
@@ -8,17 +32,22 @@ import { randomUUID } from 'node:crypto';
|
|
|
8
32
|
import { config } from '../../config.js';
|
|
9
33
|
import { getDb } from '../../db.js';
|
|
10
34
|
import { defineTool, okResult, errResult } from '../define.js';
|
|
11
|
-
import {
|
|
35
|
+
import { loadProjectFiles } from '../../core/profile.js';
|
|
12
36
|
import { getJobWithCompany } from '../../core/jobs.js';
|
|
13
37
|
import { getSharedBrowser } from '../../core/browser.js';
|
|
14
|
-
import { safeJson } from '../../core/llm.js';
|
|
15
38
|
const PREVIEW_SUBDIR = 'previews';
|
|
39
|
+
// ── Tool ─────────────────────────────────────────────────────────────────────
|
|
16
40
|
export const applyPrefillTool = defineTool({
|
|
17
41
|
name: 'apply_prefill',
|
|
18
42
|
title: 'Apply prefill (preview only — never submits)',
|
|
19
|
-
description: 'Opens the application URL in Playwright,
|
|
20
|
-
'
|
|
21
|
-
'
|
|
43
|
+
description: 'Opens the application URL in Playwright, classifies form fields by strong signals ' +
|
|
44
|
+
'(autocomplete, input type, exact name, whole-word label), and fills ONLY a small ' +
|
|
45
|
+
'allowlist of identity fields (name parts, email, phone, LinkedIn, GitHub, ' +
|
|
46
|
+
'portfolio, city) from your profile. Any free-text question, dropdown, or ' +
|
|
47
|
+
'unrecognised field is left BLANK with source=user_must_provide — never auto-filled ' +
|
|
48
|
+
'with tagline / cover-letter / summary text. Visa / work-auth fields are explicitly ' +
|
|
49
|
+
'blocked. Returns a preview + screenshot + your rendered resume/cover URLs for you ' +
|
|
50
|
+
'to download + upload manually. NEVER submits.',
|
|
22
51
|
inputSchema: {
|
|
23
52
|
job_id: z.string().min(1),
|
|
24
53
|
url: z.string().url().optional().describe('Override the job source_url (e.g. a different ATS apply URL).'),
|
|
@@ -32,18 +61,14 @@ export const applyPrefillTool = defineTool({
|
|
|
32
61
|
return errResult(`No usable application URL for job ${args.job_id}`);
|
|
33
62
|
const app = getDb().prepare(`SELECT * FROM applications WHERE job_id = ?`).get(args.job_id);
|
|
34
63
|
const { profile } = loadProjectFiles();
|
|
35
|
-
const packet = getActiveCareerPacket()?.content ?? '';
|
|
36
|
-
// Build a lookup of values we can confidently draft.
|
|
37
64
|
const identity = (profile?.candidate ?? {});
|
|
38
|
-
const tailored = safeJson(app?.tailored_bullets, null);
|
|
39
|
-
const coverDraft = app?.cover_letter_draft ?? null;
|
|
40
65
|
try {
|
|
41
66
|
const browser = await getSharedBrowser();
|
|
42
67
|
const page = await browser.newPage();
|
|
43
68
|
await page.goto(url, { waitUntil: 'networkidle', timeout: 30_000 });
|
|
44
69
|
await page.waitForTimeout(1_500);
|
|
45
|
-
// Detect form inputs.
|
|
46
|
-
//
|
|
70
|
+
// Detect form inputs. Skip file/password/hidden/submit/button. Gather every
|
|
71
|
+
// signal classification needs: autocomplete, name, type, plus the label.
|
|
47
72
|
const detected = await page.$$eval('input:not([type="hidden"]):not([type="file"]):not([type="password"]):not([type="submit"]):not([type="button"]), textarea, select', (els) => {
|
|
48
73
|
const lookupLabel = (el) => {
|
|
49
74
|
if (el.getAttribute('aria-label'))
|
|
@@ -71,8 +96,11 @@ export const applyPrefillTool = defineTool({
|
|
|
71
96
|
return els.slice(0, 60).map((el) => ({
|
|
72
97
|
selector: sel(el),
|
|
73
98
|
label: lookupLabel(el),
|
|
99
|
+
name: el.getAttribute('name') ?? '',
|
|
100
|
+
autocomplete: el.getAttribute('autocomplete') ?? '',
|
|
74
101
|
type: (el.getAttribute('type') || el.tagName).toLowerCase(),
|
|
75
102
|
required: !!el.required || el.getAttribute('aria-required') === 'true',
|
|
103
|
+
tag: el.tagName.toLowerCase(),
|
|
76
104
|
}));
|
|
77
105
|
});
|
|
78
106
|
// Screenshot
|
|
@@ -80,21 +108,24 @@ export const applyPrefillTool = defineTool({
|
|
|
80
108
|
const shotPath = `${PREVIEW_SUBDIR}/apply-${args.job_id.slice(0, 8)}-${Date.now()}.png`;
|
|
81
109
|
const absShot = resolve(config.outputDir, shotPath);
|
|
82
110
|
await page.screenshot({ path: absShot, fullPage: true });
|
|
83
|
-
const fields = detected.map(
|
|
111
|
+
const fields = detected.map(d => draftValue(d, identity));
|
|
84
112
|
const previewId = randomUUID();
|
|
113
|
+
const filled = fields.filter(f => f.source === 'profile').length;
|
|
85
114
|
const previewJson = {
|
|
86
115
|
preview_id: previewId,
|
|
87
116
|
job_id: args.job_id,
|
|
88
117
|
url,
|
|
89
118
|
company: job.company_name,
|
|
90
119
|
title: job.title,
|
|
91
|
-
warning: 'preview only — this tool never submits. Upload resume/cover manually using the localhost links below.',
|
|
120
|
+
warning: 'preview only — this tool never submits. Upload resume/cover manually using the localhost links below. Any blank field is intentionally left for you to answer.',
|
|
121
|
+
fields_total: fields.length,
|
|
122
|
+
fields_auto_filled: filled,
|
|
123
|
+
fields_user_must_provide: fields.length - filled,
|
|
92
124
|
fields,
|
|
93
125
|
resume_url: app?.resume_path ? `${config.baseUrl}/files/${app.resume_path}` : null,
|
|
94
126
|
cover_url: app?.cover_path ? `${config.baseUrl}/files/${app.cover_path}` : null,
|
|
95
127
|
screenshot_url: `${config.baseUrl}/files/${shotPath}`,
|
|
96
128
|
};
|
|
97
|
-
// Persist a JSON copy alongside the screenshot for the chat to re-read.
|
|
98
129
|
writeFileSync(absShot.replace(/\.png$/, '.json'), JSON.stringify(previewJson, null, 2), 'utf-8');
|
|
99
130
|
await page.close();
|
|
100
131
|
return okResult(previewJson);
|
|
@@ -105,63 +136,137 @@ export const applyPrefillTool = defineTool({
|
|
|
105
136
|
// Shared browser stays alive for the next caller — closed at server shutdown.
|
|
106
137
|
},
|
|
107
138
|
});
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
139
|
+
// ── Classification (pure — exported for tests) ───────────────────────────────
|
|
140
|
+
const VISA_PATTERN = /\b(visa|sponsor(ship|ed|s)?|work[\s_-]*(auth|permit|authoriz|authoris)|legally\s*authorized|citizen(ship)?|h-?1-?b|opt\b|stem\s*opt|ead\b|nationality|country[\s_-]*of[\s_-]*(residence|origin|citizenship))\b/i;
|
|
141
|
+
/** Classify a detected field by strong signals only. Returns kind=null when unsure. */
|
|
142
|
+
export function classifyField(d) {
|
|
143
|
+
const label = (d.label ?? '').toLowerCase().trim();
|
|
144
|
+
const name = (d.name ?? '').toLowerCase().trim();
|
|
145
|
+
const ac = (d.autocomplete ?? '').toLowerCase().trim();
|
|
146
|
+
const type = (d.type ?? '').toLowerCase().trim();
|
|
147
|
+
// Visa / work-auth — explicit block, runs FIRST so no other rule can override.
|
|
148
|
+
if (VISA_PATTERN.test(label) || VISA_PATTERN.test(name)) {
|
|
149
|
+
return { kind: null, reason: 'visa/work-auth field — never auto-fill' };
|
|
150
|
+
}
|
|
151
|
+
// Free-text answer surfaces (textarea, large free-form questions) are NEVER mapped,
|
|
152
|
+
// even if the label happens to contain a kind-keyword as a substring. The point of
|
|
153
|
+
// these fields is to elicit a candidate-written answer.
|
|
154
|
+
if (d.tag === 'textarea') {
|
|
155
|
+
return { kind: null, reason: 'textarea / free-text — never auto-fill' };
|
|
156
|
+
}
|
|
157
|
+
if (d.tag === 'select') {
|
|
158
|
+
return { kind: null, reason: 'select / dropdown — never auto-fill' };
|
|
116
159
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
// ── Email
|
|
161
|
+
if (type === 'email' || ac === 'email'
|
|
162
|
+
|| /^(email|email_address|emailaddress|e_mail|emailid)$/.test(name)
|
|
163
|
+
|| /^(email|e[\s\-]?mail)( address)?\*?$/.test(label)) {
|
|
164
|
+
return { kind: 'email', reason: 'strong email signal' };
|
|
120
165
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
166
|
+
// ── Phone
|
|
167
|
+
if (type === 'tel' || ac === 'tel'
|
|
168
|
+
|| /^(phone|telephone|mobile|tel|phone_number|mobile_number|cellphone)$/.test(name)
|
|
169
|
+
|| /^(phone|telephone|mobile|cell)( number)?\*?$/.test(label)) {
|
|
170
|
+
return { kind: 'phone', reason: 'strong phone signal' };
|
|
124
171
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
// ── First name
|
|
173
|
+
if (ac === 'given-name'
|
|
174
|
+
|| /^(first|first_name|firstname|givenname|given_name|fname)$/.test(name)
|
|
175
|
+
|| /^(first name|given name|first)\*?$/.test(label)) {
|
|
176
|
+
return { kind: 'first_name', reason: 'strong first-name signal' };
|
|
128
177
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
178
|
+
// ── Last name
|
|
179
|
+
if (ac === 'family-name'
|
|
180
|
+
|| /^(last|last_name|lastname|familyname|family_name|surname|lname)$/.test(name)
|
|
181
|
+
|| /^(last name|family name|surname|last)\*?$/.test(label)) {
|
|
182
|
+
return { kind: 'last_name', reason: 'strong last-name signal' };
|
|
132
183
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
// ── Preferred name / nickname
|
|
185
|
+
if (/^(preferred_name|preferredname|nickname|known_as)$/.test(name)
|
|
186
|
+
|| /^(preferred name|nickname|what should we call you|known as)\*?$/.test(label)) {
|
|
187
|
+
return { kind: 'preferred_name', reason: 'preferred-name signal' };
|
|
136
188
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
189
|
+
// ── Full name (kept narrow — "company name", "manager name" must NOT match)
|
|
190
|
+
if (ac === 'name'
|
|
191
|
+
|| /^(name|full_name|fullname|your_name|legal_name)$/.test(name)
|
|
192
|
+
|| /^(full name|your name|legal name|name)\*?$/.test(label)) {
|
|
193
|
+
return { kind: 'full_name', reason: 'strong full-name signal' };
|
|
140
194
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
195
|
+
// ── LinkedIn
|
|
196
|
+
if (/(^|[_\b])linkedin($|[_\b])/.test(name)
|
|
197
|
+
|| /\blinkedin( profile| url)?\*?$/.test(label)) {
|
|
198
|
+
return { kind: 'linkedin', reason: 'linkedin signal' };
|
|
144
199
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
200
|
+
// ── GitHub
|
|
201
|
+
if (/(^|[_\b])github($|[_\b])/.test(name)
|
|
202
|
+
|| /\bgithub( profile| url)?\*?$/.test(label)) {
|
|
203
|
+
return { kind: 'github', reason: 'github signal' };
|
|
148
204
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
205
|
+
// ── Portfolio / personal website (only when whole-word, not generic "url")
|
|
206
|
+
if (/^(website|portfolio|personal_site|personal_website|website_url|portfolio_url|portfolio_link)$/.test(name)
|
|
207
|
+
|| /^(website|portfolio|personal website|personal site)\*?$/.test(label)) {
|
|
208
|
+
return { kind: 'portfolio_url', reason: 'portfolio signal' };
|
|
152
209
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
210
|
+
// ── City / location (strict whole-word; "where are you LOCATED" is too ambiguous)
|
|
211
|
+
if (ac === 'address-level2'
|
|
212
|
+
|| /^(city|current_city|location)$/.test(name)
|
|
213
|
+
|| /^(city|current city|location)\*?$/.test(label)) {
|
|
214
|
+
return { kind: 'city', reason: 'city signal' };
|
|
156
215
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
216
|
+
return { kind: null, reason: 'no confident match — left for user' };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Map a classified field to a draft value from the candidate's profile. Pure function,
|
|
220
|
+
* exported for tests. Returns a DetectedField with source='user_must_provide' for any
|
|
221
|
+
* kind=null OR when the profile lacks the data (e.g. no linkedin in profile.yml).
|
|
222
|
+
*/
|
|
223
|
+
export function draftValue(d, identity) {
|
|
224
|
+
const classification = classifyField(d);
|
|
225
|
+
let value = '';
|
|
226
|
+
switch (classification.kind) {
|
|
227
|
+
case 'email':
|
|
228
|
+
value = identity.email ?? '';
|
|
229
|
+
break;
|
|
230
|
+
case 'phone':
|
|
231
|
+
value = identity.phone ?? '';
|
|
232
|
+
break;
|
|
233
|
+
case 'first_name':
|
|
234
|
+
value = (identity.full_name ?? '').trim().split(/\s+/)[0] ?? '';
|
|
235
|
+
break;
|
|
236
|
+
case 'last_name':
|
|
237
|
+
value = (identity.full_name ?? '').trim().split(/\s+/).slice(1).join(' ').trim();
|
|
238
|
+
break;
|
|
239
|
+
case 'preferred_name':
|
|
240
|
+
value = (identity.full_name ?? '').trim().split(/\s+/)[0] ?? '';
|
|
241
|
+
break;
|
|
242
|
+
case 'full_name':
|
|
243
|
+
value = identity.full_name ?? '';
|
|
244
|
+
break;
|
|
245
|
+
case 'linkedin':
|
|
246
|
+
value = identity.linkedin ?? '';
|
|
247
|
+
break;
|
|
248
|
+
case 'github':
|
|
249
|
+
value = identity.github ?? '';
|
|
250
|
+
break;
|
|
251
|
+
case 'portfolio_url':
|
|
252
|
+
value = identity.portfolio_url ?? identity.github ?? '';
|
|
253
|
+
break;
|
|
254
|
+
case 'city':
|
|
255
|
+
value = identity.location ?? '';
|
|
256
|
+
break;
|
|
257
|
+
case null:
|
|
258
|
+
value = '';
|
|
259
|
+
break;
|
|
161
260
|
}
|
|
261
|
+
const source = (classification.kind && value) ? 'profile' : 'user_must_provide';
|
|
162
262
|
return {
|
|
163
|
-
selector: d.selector,
|
|
164
|
-
|
|
263
|
+
selector: d.selector,
|
|
264
|
+
label: d.label,
|
|
265
|
+
type: d.type,
|
|
266
|
+
required: !!d.required,
|
|
267
|
+
draft_value: value,
|
|
268
|
+
source,
|
|
269
|
+
classification,
|
|
165
270
|
};
|
|
166
271
|
}
|
|
167
272
|
//# sourceMappingURL=apply_prefill.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply_prefill.js","sourceRoot":"","sources":["../../../src/mcp/tools/apply_prefill.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,oFAAoF;AACpF,gFAAgF;
|
|
1
|
+
{"version":3,"file":"apply_prefill.js","sourceRoot":"","sources":["../../../src/mcp/tools/apply_prefill.ts"],"names":[],"mappings":"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"}
|
|
@@ -83,10 +83,37 @@ export const generateMaterialsTool = defineTool({
|
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
});
|
|
86
|
+
/**
|
|
87
|
+
* Concatenates the materials fields that end up in the actual delivered resume + cover
|
|
88
|
+
* letter. Used by the visa-rail scan. Internal-only fields (tailoring_notes, debug
|
|
89
|
+
* scratch) are deliberately excluded — a chat note saying "I made sure not to mention
|
|
90
|
+
* visa" should NOT trip the rail. Exported for tests.
|
|
91
|
+
*/
|
|
92
|
+
export function candidateFacingMaterialsText(m) {
|
|
93
|
+
const parts = [];
|
|
94
|
+
if (typeof m?.tagline === 'string')
|
|
95
|
+
parts.push(m.tagline);
|
|
96
|
+
if (m?.experience_bullets && typeof m.experience_bullets === 'object') {
|
|
97
|
+
for (const v of Object.values(m.experience_bullets)) {
|
|
98
|
+
if (Array.isArray(v)) {
|
|
99
|
+
for (const b of v)
|
|
100
|
+
if (typeof b === 'string')
|
|
101
|
+
parts.push(b);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (typeof m?.projects_section === 'string')
|
|
106
|
+
parts.push(m.projects_section);
|
|
107
|
+
if (typeof m?.skills_section === 'string')
|
|
108
|
+
parts.push(m.skills_section);
|
|
109
|
+
if (typeof m?.cover_letter_body === 'string')
|
|
110
|
+
parts.push(m.cover_letter_body);
|
|
111
|
+
return parts.join('\n');
|
|
112
|
+
}
|
|
86
113
|
async function persistMaterials(job_id, m) {
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
const leaks = scanForVisaLeakage(
|
|
114
|
+
// Visa rail across only the candidate-facing text. tailoring_notes is internal meta
|
|
115
|
+
// and may legitimately mention visa as a deliberate-omission note — don't trip on it.
|
|
116
|
+
const leaks = scanForVisaLeakage(candidateFacingMaterialsText(m));
|
|
90
117
|
if (leaks.length) {
|
|
91
118
|
throw new Error(`materials failed visa rail: ${JSON.stringify(leaks)} — not persisted`);
|
|
92
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate_materials.js","sourceRoot":"","sources":["../../../src/mcp/tools/generate_materials.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"generate_materials.js","sourceRoot":"","sources":["../../../src/mcp/tools/generate_materials.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { renderPdf } from '../../core/render.js';
|
|
4
|
+
import { getDb, runInWriteLock } from '../../db.js';
|
|
3
5
|
import { defineTool, okResult, errResult } from '../define.js';
|
|
4
6
|
export const renderPdfTool = defineTool({
|
|
5
7
|
name: 'render_pdf',
|
|
6
8
|
title: 'Render resume/cover PDF',
|
|
7
|
-
description: 'HTML → PDF via Playwright using the career-ops template. Returns a localhost link
|
|
9
|
+
description: 'HTML → PDF via Playwright using the career-ops template. Returns a localhost link ' +
|
|
10
|
+
'per file AND writes resume_path / cover_path onto the application row so the ' +
|
|
11
|
+
'tracker, apply_prefill, and daily_digest can find the artifacts. Advances ' +
|
|
12
|
+
'materials_drafted → ready_to_review in the job lifecycle.',
|
|
8
13
|
inputSchema: {
|
|
9
14
|
job_id: z.string().min(1),
|
|
10
15
|
kind: z.enum(['resume', 'cover', 'both']).default('resume'),
|
|
@@ -14,9 +19,13 @@ export const renderPdfTool = defineTool({
|
|
|
14
19
|
handler: async (args) => {
|
|
15
20
|
try {
|
|
16
21
|
const files = await renderPdf(args);
|
|
22
|
+
const persisted = await persistRenderedFiles(args.job_id, files);
|
|
17
23
|
return okResult({
|
|
18
24
|
job_id: args.job_id,
|
|
19
25
|
files: files.map(f => ({ kind: f.kind, url: f.url, bytes: f.bytes, path: f.path })),
|
|
26
|
+
application_id: persisted.application_id,
|
|
27
|
+
application_status: persisted.status,
|
|
28
|
+
status_advanced: persisted.status_advanced,
|
|
20
29
|
});
|
|
21
30
|
}
|
|
22
31
|
catch (err) {
|
|
@@ -24,4 +33,54 @@ export const renderPdfTool = defineTool({
|
|
|
24
33
|
}
|
|
25
34
|
},
|
|
26
35
|
});
|
|
36
|
+
export async function persistRenderedFiles(job_id, files) {
|
|
37
|
+
const resume = files.find(f => f.kind === 'resume')?.path ?? null;
|
|
38
|
+
const cover = files.find(f => f.kind === 'cover')?.path ?? null;
|
|
39
|
+
return runInWriteLock(() => {
|
|
40
|
+
const db = getDb();
|
|
41
|
+
const existing = db.prepare(`
|
|
42
|
+
SELECT id, status FROM applications WHERE job_id = ?
|
|
43
|
+
`).get(job_id);
|
|
44
|
+
const PRE_RENDER = new Set(['materials_drafted', 'render_error']);
|
|
45
|
+
if (existing) {
|
|
46
|
+
const advance = PRE_RENDER.has(existing.status);
|
|
47
|
+
db.prepare(`
|
|
48
|
+
UPDATE applications SET
|
|
49
|
+
resume_path = COALESCE(?, resume_path),
|
|
50
|
+
cover_path = COALESCE(?, cover_path),
|
|
51
|
+
status = CASE WHEN status IN ('materials_drafted','render_error')
|
|
52
|
+
THEN 'ready_to_review'
|
|
53
|
+
ELSE status END,
|
|
54
|
+
last_status_change_at = CASE WHEN status IN ('materials_drafted','render_error')
|
|
55
|
+
THEN CURRENT_TIMESTAMP
|
|
56
|
+
ELSE last_status_change_at END,
|
|
57
|
+
updated_at = CURRENT_TIMESTAMP
|
|
58
|
+
WHERE id = ?
|
|
59
|
+
`).run(resume, cover, existing.id);
|
|
60
|
+
mirrorJobStatus(db, job_id);
|
|
61
|
+
const after = db.prepare(`SELECT status FROM applications WHERE id = ?`).get(existing.id);
|
|
62
|
+
return {
|
|
63
|
+
application_id: existing.id,
|
|
64
|
+
status: after.status,
|
|
65
|
+
status_advanced: advance && after.status === 'ready_to_review',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const id = randomUUID();
|
|
69
|
+
db.prepare(`
|
|
70
|
+
INSERT INTO applications (id, job_id, status, resume_path, cover_path, materials_v, last_status_change_at)
|
|
71
|
+
VALUES (?, ?, 'ready_to_review', ?, ?, 1, CURRENT_TIMESTAMP)
|
|
72
|
+
`).run(id, job_id, resume, cover);
|
|
73
|
+
mirrorJobStatus(db, job_id);
|
|
74
|
+
return { application_id: id, status: 'ready_to_review', status_advanced: true };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Mirror status onto jobs.status — only advance from earlier states. Don't push a job
|
|
78
|
+
// already in applied / screen / onsite / offer / rejected backwards.
|
|
79
|
+
function mirrorJobStatus(db, job_id) {
|
|
80
|
+
db.prepare(`
|
|
81
|
+
UPDATE jobs SET status = 'ready_to_review', updated_at = CURRENT_TIMESTAMP
|
|
82
|
+
WHERE id = ?
|
|
83
|
+
AND status IN ('sourced','ready_to_apply','materials_drafted')
|
|
84
|
+
`).run(job_id);
|
|
85
|
+
}
|
|
27
86
|
//# sourceMappingURL=render_pdf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_pdf.js","sourceRoot":"","sources":["../../../src/mcp/tools/render_pdf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"render_pdf.js","sourceRoot":"","sources":["../../../src/mcp/tools/render_pdf.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job_ops-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Self-hosted MCP server for the full job-search loop: portal scanning, JD evaluation, tailored resume + cover PDFs, outreach drafting, story bank, negotiation brief — chat-driven, human-in-the-loop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"connect": "node dist/cli.js connect",
|
|
51
51
|
"dev": "tsx watch src/cli.ts start",
|
|
52
52
|
"typecheck": "tsc --noEmit",
|
|
53
|
+
"test": "npm run build && node --test 'tests/**/*.test.mjs'",
|
|
53
54
|
"prepublishOnly": "npm run build",
|
|
54
55
|
"playwright:install": "playwright install chromium"
|
|
55
56
|
},
|