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
@@ -0,0 +1,201 @@
1
+ // Parse cv.md into the structured pieces the HTML CV template expects.
2
+ // We keep this simple and resilient: section headers in cv.md drive what we extract.
3
+ // Anything we can't confidently extract falls back to an empty section so the renderer
4
+ // still produces a valid PDF.
5
+ import { loadProjectFiles } from './profile.js';
6
+ const SECTION_HEADERS = [
7
+ 'professional summary',
8
+ 'work experience',
9
+ 'projects',
10
+ 'projects & open source',
11
+ 'education',
12
+ 'certifications',
13
+ 'skills',
14
+ ];
15
+ export function parseCV() {
16
+ const { cvMd, profile } = loadProjectFiles();
17
+ const identity = identityFromProfile(profile);
18
+ const empty = {
19
+ ...identity,
20
+ summary: '',
21
+ competencies: [],
22
+ experiences: [],
23
+ projects: [],
24
+ education: [],
25
+ certifications: [],
26
+ skills: [],
27
+ };
28
+ if (!cvMd)
29
+ return empty;
30
+ const sections = splitSections(cvMd);
31
+ return {
32
+ ...identity,
33
+ summary: parseSummary(sections),
34
+ competencies: parseCompetencies(profile),
35
+ experiences: parseExperiences(sections),
36
+ projects: parseProjects(sections),
37
+ education: parseEducation(sections),
38
+ certifications: parseCertifications(sections),
39
+ skills: parseSkills(sections),
40
+ };
41
+ }
42
+ // ── Identity (profile.yml wins; cv.md header fills gaps) ─────────────────────
43
+ function identityFromProfile(profile) {
44
+ const c = profile?.candidate ?? {};
45
+ const linkedin = c.linkedin ?? '';
46
+ const portfolio = c.portfolio_url ?? c.github ?? '';
47
+ return {
48
+ name: c.full_name ?? 'Candidate',
49
+ phone: c.phone ?? '',
50
+ email: c.email ?? '',
51
+ location: c.location ?? '',
52
+ linkedin_url: linkedin ? (linkedin.startsWith('http') ? linkedin : `https://${linkedin}`) : '',
53
+ linkedin_display: linkedin.replace(/^https?:\/\//, ''),
54
+ portfolio_url: portfolio ? (portfolio.startsWith('http') ? portfolio : `https://${portfolio}`) : '',
55
+ portfolio_display: portfolio.replace(/^https?:\/\//, ''),
56
+ };
57
+ }
58
+ // ── Section splitter ─────────────────────────────────────────────────────────
59
+ function splitSections(md) {
60
+ const m = new Map();
61
+ // Use ## headers as section boundaries; case-insensitive title matching.
62
+ const re = /^##\s+(.+)$/gm;
63
+ const indices = [];
64
+ let match;
65
+ while ((match = re.exec(md))) {
66
+ indices.push({ name: match[1].trim().toLowerCase(), start: match.index + match[0].length });
67
+ }
68
+ for (let i = 0; i < indices.length; i++) {
69
+ const end = i + 1 < indices.length ? indices[i + 1].start - (`## ${indices[i + 1].name}`.length + 1) : md.length;
70
+ m.set(indices[i].name, md.slice(indices[i].start, end).trim());
71
+ }
72
+ return m;
73
+ }
74
+ function getSection(sections, ...names) {
75
+ for (const n of names) {
76
+ const v = sections.get(n.toLowerCase());
77
+ if (v)
78
+ return v;
79
+ }
80
+ return '';
81
+ }
82
+ function parseSummary(sections) {
83
+ const body = getSection(sections, 'professional summary', 'summary');
84
+ return body.replace(/\n+/g, ' ').trim();
85
+ }
86
+ function parseCompetencies(profile) {
87
+ const sp = profile?.narrative?.superpowers;
88
+ if (Array.isArray(sp))
89
+ return sp.slice(0, 8);
90
+ return [];
91
+ }
92
+ function parseExperiences(sections) {
93
+ const body = getSection(sections, 'work experience', 'experience');
94
+ if (!body)
95
+ return [];
96
+ // Each ### header = one job. Pattern: "### Company — Role"; next line: period + optional location.
97
+ const lines = body.split('\n');
98
+ const out = [];
99
+ let cur = null;
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = lines[i];
102
+ const h = line.match(/^###\s+(.+)$/);
103
+ if (h) {
104
+ if (cur)
105
+ out.push(cur);
106
+ const [company, role] = splitCompanyRole(h[1]);
107
+ // Next non-empty line is usually "<period> · <location>"
108
+ const next = lines.slice(i + 1).find(l => l.trim() !== '') ?? '';
109
+ const { period, location } = splitPeriodLocation(next);
110
+ cur = { company, role, period, location, bullets: [] };
111
+ continue;
112
+ }
113
+ if (cur && /^\s*-\s+/.test(line)) {
114
+ cur.bullets.push(line.replace(/^\s*-\s+/, '').trim());
115
+ }
116
+ }
117
+ if (cur)
118
+ out.push(cur);
119
+ return out;
120
+ }
121
+ function splitCompanyRole(s) {
122
+ // Accepts em-dash, en-dash, hyphen, or pipe between Company and Role.
123
+ const m = s.match(/^(.+?)\s+[—–\-|]\s+(.+)$/);
124
+ return m ? [m[1].trim(), m[2].trim()] : [s.trim(), ''];
125
+ }
126
+ function splitPeriodLocation(s) {
127
+ // Examples we see: "Remote / India · Apr 2022 – Jan 2025"
128
+ // "India · Sept 2021 – Apr 2022"
129
+ // Some users use ` | ` instead of ` · `. Try both.
130
+ const sep = s.includes('·') ? '·' : (s.includes('|') ? '|' : null);
131
+ if (sep) {
132
+ const parts = s.split(sep).map(p => p.trim());
133
+ if (parts.length >= 2) {
134
+ // Heuristic: whichever part looks like a date range is the period.
135
+ const isPeriod = (t) => /\d{4}/.test(t) || /present/i.test(t);
136
+ if (isPeriod(parts[0]) && !isPeriod(parts[1])) {
137
+ return { period: parts[0], location: parts.slice(1).join(' / ') };
138
+ }
139
+ if (!isPeriod(parts[0]) && isPeriod(parts[1])) {
140
+ return { period: parts[1], location: parts[0] };
141
+ }
142
+ return { period: parts.find(isPeriod) ?? parts[1], location: parts.find(p => !isPeriod(p)) ?? parts[0] };
143
+ }
144
+ }
145
+ return { period: s.trim(), location: '' };
146
+ }
147
+ function parseProjects(sections) {
148
+ const body = getSection(sections, 'projects & open source', 'projects');
149
+ if (!body)
150
+ return [];
151
+ const items = [];
152
+ for (const line of body.split('\n')) {
153
+ const m = line.match(/^-\s+\*\*([^*]+)\*\*\s*(?:\(([^)]+)\))?\s*[—–-]\s*(.+)$/);
154
+ if (!m)
155
+ continue;
156
+ items.push({
157
+ title: m[1].trim(),
158
+ badge: m[2]?.trim() ?? null,
159
+ description: m[3].trim(),
160
+ tech: null,
161
+ });
162
+ }
163
+ return items;
164
+ }
165
+ function parseEducation(sections) {
166
+ const body = getSection(sections, 'education');
167
+ if (!body)
168
+ return [];
169
+ const items = [];
170
+ for (const line of body.split('\n')) {
171
+ const m = line.match(/^-\s+\*\*([^*]+)\*\*,?\s*(.*)$/);
172
+ if (!m)
173
+ continue;
174
+ const title = m[1].trim();
175
+ const rest = m[2].trim();
176
+ const yearMatch = rest.match(/(\d{4}(?:[\-–]\d{4})?)/);
177
+ const year = yearMatch ? yearMatch[1] : '';
178
+ const restNoYear = year ? rest.replace(year, '').trim() : rest;
179
+ const [org, ...descBits] = restNoYear.split('—').map(s => s.trim());
180
+ items.push({ title, org: org ?? '', year, desc: descBits.join(' — ') });
181
+ }
182
+ return items;
183
+ }
184
+ function parseCertifications(_sections) {
185
+ // Not present in this user's CV; return empty so the section renders blank.
186
+ return [];
187
+ }
188
+ function parseSkills(sections) {
189
+ const body = getSection(sections, 'skills');
190
+ if (!body)
191
+ return [];
192
+ const cats = [];
193
+ for (const line of body.split('\n')) {
194
+ const m = line.match(/^-\s+\*\*([^*:]+):\*\*\s*(.+)$/);
195
+ if (!m)
196
+ continue;
197
+ cats.push({ category: m[1].trim(), items: m[2].trim() });
198
+ }
199
+ return cats;
200
+ }
201
+ //# sourceMappingURL=cv_parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cv_parse.js","sourceRoot":"","sources":["../../src/core/cv_parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ // Shared HTML escape. Used by report renderer, tracker dashboard, and PDF builder.
2
+ export function escapeHtml(s) {
3
+ return String(s ?? '')
4
+ .replace(/&/g, '&amp;')
5
+ .replace(/</g, '&lt;')
6
+ .replace(/>/g, '&gt;')
7
+ .replace(/"/g, '&quot;')
8
+ .replace(/'/g, '&#39;');
9
+ }
10
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/core/html.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,CAA4B;IACrD,OAAO,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;SACnB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,99 @@
1
+ // Job-description fetcher / normalizer.
2
+ //
3
+ // chat-mode evaluate_job accepts either:
4
+ // - a URL → we fetch HTML, strip tags, return plain-text JD
5
+ // - pasted JD text → we just trim + cap length
6
+ //
7
+ // We intentionally keep this dumb: the chat client is the reasoning layer. We hand it
8
+ // a clean text blob, not a parsed JD object. Greenhouse / Ashby fetchers in milestone 2
9
+ // will reuse the same shape for `scan_portals`.
10
+ const FETCH_TIMEOUT_MS = 12_000;
11
+ const MAX_JD_CHARS = 18_000;
12
+ const USER_AGENT = 'Mozilla/5.0 (compatible; mcp-jsa/0.1)';
13
+ export async function normalizeJD(input) {
14
+ const trimmed = input.trim();
15
+ if (looksLikeUrl(trimmed)) {
16
+ return fetchFromUrl(trimmed);
17
+ }
18
+ return {
19
+ source: 'paste',
20
+ source_url: null,
21
+ title_guess: null,
22
+ company_guess: null,
23
+ text: trimmed.slice(0, MAX_JD_CHARS),
24
+ raw_html: null,
25
+ fetched_at: new Date().toISOString(),
26
+ };
27
+ }
28
+ function looksLikeUrl(s) {
29
+ return /^https?:\/\//i.test(s) && s.length < 4096 && !s.includes('\n');
30
+ }
31
+ async function fetchFromUrl(url) {
32
+ const controller = new AbortController();
33
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
34
+ try {
35
+ const res = await fetch(url, {
36
+ headers: { 'user-agent': USER_AGENT, 'accept': 'text/html,application/xhtml+xml' },
37
+ signal: controller.signal,
38
+ redirect: 'follow',
39
+ });
40
+ const html = await res.text();
41
+ const title = extractTag(html, 'title');
42
+ const ogSite = extractMeta(html, 'og:site_name');
43
+ const ogTitle = extractMeta(html, 'og:title');
44
+ const text = htmlToPlainText(html);
45
+ return {
46
+ source: 'url',
47
+ source_url: url,
48
+ title_guess: ogTitle ?? title ?? null,
49
+ company_guess: ogSite ?? null,
50
+ text: text.slice(0, MAX_JD_CHARS),
51
+ raw_html: html.slice(0, MAX_JD_CHARS * 2),
52
+ fetched_at: new Date().toISOString(),
53
+ };
54
+ }
55
+ finally {
56
+ clearTimeout(timer);
57
+ }
58
+ }
59
+ function extractTag(html, tag) {
60
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i');
61
+ const m = html.match(re);
62
+ return m ? decodeEntities(stripTags(m[1])).trim() : null;
63
+ }
64
+ function extractMeta(html, property) {
65
+ // Match BOTH <meta property="X" content="Y"> and <meta name="X" content="Y"> in either order.
66
+ const re = new RegExp(`<meta\\b[^>]*?(?:property|name)=["']${property.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}["'][^>]*?content=["']([^"']*)["']`, 'i');
67
+ const m = html.match(re);
68
+ return m ? decodeEntities(m[1]).trim() : null;
69
+ }
70
+ function htmlToPlainText(html) {
71
+ // Drop script/style entirely; convert block-ish tags to newlines; strip the rest.
72
+ return decodeEntities(html
73
+ .replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
74
+ .replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
75
+ .replace(/<noscript\b[\s\S]*?<\/noscript>/gi, ' ')
76
+ .replace(/<br\s*\/?>/gi, '\n')
77
+ .replace(/<\/(p|div|li|section|article|header|footer|h[1-6])>/gi, '\n')
78
+ .replace(/<[^>]+>/g, ' ')
79
+ .replace(/[ \t]+/g, ' ')
80
+ .replace(/\n[ \t]+/g, '\n')
81
+ .replace(/\n{3,}/g, '\n\n')
82
+ .trim());
83
+ }
84
+ function stripTags(s) {
85
+ return s.replace(/<[^>]+>/g, '');
86
+ }
87
+ function decodeEntities(s) {
88
+ return s
89
+ .replace(/&nbsp;/g, ' ')
90
+ .replace(/&amp;/g, '&')
91
+ .replace(/&lt;/g, '<')
92
+ .replace(/&gt;/g, '>')
93
+ .replace(/&quot;/g, '"')
94
+ .replace(/&#39;/g, "'")
95
+ .replace(/&#x27;/g, "'")
96
+ .replace(/&#x2F;/g, '/')
97
+ .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(parseInt(n, 10)));
98
+ }
99
+ //# sourceMappingURL=jd_normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jd_normalize.js","sourceRoot":"","sources":["../../src/core/jd_normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ // Thin repo around jobs + companies. Used by every tool that touches a job row.
2
+ import { randomUUID } from 'node:crypto';
3
+ import { getDb, runInWriteLock } from '../db.js';
4
+ import { contentHash } from './content_hash.js';
5
+ // ── Companies ────────────────────────────────────────────────────────────────
6
+ function normalizeCompanyName(name) {
7
+ return name.toLowerCase().replace(/\s+/g, ' ').trim();
8
+ }
9
+ export function upsertCompany(name) {
10
+ const normalized = normalizeCompanyName(name);
11
+ if (!normalized)
12
+ throw new Error('upsertCompany: empty company name');
13
+ const db = getDb();
14
+ const existing = db
15
+ .prepare('SELECT id FROM companies WHERE name_normalized = ?')
16
+ .get(normalized);
17
+ if (existing)
18
+ return existing.id;
19
+ const id = randomUUID();
20
+ db.prepare(`
21
+ INSERT INTO companies (id, name, name_normalized)
22
+ VALUES (?, ?, ?)
23
+ `).run(id, name.trim(), normalized);
24
+ return id;
25
+ }
26
+ export async function upsertJob(input) {
27
+ return runInWriteLock(() => {
28
+ const db = getDb();
29
+ const company_id = upsertCompany(input.company_name);
30
+ const hash = contentHash({
31
+ company: input.company_name,
32
+ title: input.title,
33
+ location: input.location ?? null,
34
+ });
35
+ // Three-way dedup: source+source_job_id, source_url, content_hash.
36
+ let existing = db.prepare(`
37
+ SELECT id FROM jobs WHERE content_hash = ? OR source_url = ?
38
+ `).get(hash, input.source_url);
39
+ if (!existing && input.source_job_id) {
40
+ existing = db.prepare(`
41
+ SELECT id FROM jobs WHERE source = ? AND source_job_id = ?
42
+ `).get(input.source, input.source_job_id);
43
+ }
44
+ if (existing) {
45
+ db.prepare(`
46
+ UPDATE jobs SET
47
+ description = COALESCE(?, description),
48
+ requirements = COALESCE(?, requirements),
49
+ updated_at = CURRENT_TIMESTAMP
50
+ WHERE id = ?
51
+ `).run(input.description ?? null, input.requirements ?? null, existing.id);
52
+ return { id: existing.id, created: false };
53
+ }
54
+ const id = randomUUID();
55
+ db.prepare(`
56
+ INSERT INTO jobs (
57
+ id, source, source_job_id, source_url, content_hash,
58
+ company_id, company_name_raw, title, location_raw,
59
+ description, requirements, status
60
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'sourced')
61
+ `).run(id, input.source, input.source_job_id ?? null, input.source_url, hash, company_id, input.company_name, input.title, input.location ?? null, input.description ?? null, input.requirements ?? null);
62
+ return { id, created: true };
63
+ });
64
+ }
65
+ export function getJob(id) {
66
+ return getDb()
67
+ .prepare('SELECT * FROM jobs WHERE id = ?')
68
+ .get(id) ?? null;
69
+ }
70
+ export function getJobWithCompany(id) {
71
+ return getDb().prepare(`
72
+ SELECT j.*, COALESCE(c.name, j.company_name_raw) AS company_name, c.id AS resolved_company_id
73
+ FROM jobs j LEFT JOIN companies c ON c.id = j.company_id WHERE j.id = ?
74
+ `).get(id) ?? null;
75
+ }
76
+ // Company lookup that tolerates name variations — normalized exact match, then LIKE.
77
+ export function findCompanyByName(query) {
78
+ const normalized = query.toLowerCase().trim();
79
+ return getDb().prepare(`
80
+ SELECT id, name FROM companies
81
+ WHERE name_normalized = ? OR LOWER(name) LIKE ?
82
+ ORDER BY (name_normalized = ?) DESC
83
+ LIMIT 1
84
+ `).get(normalized, `%${normalized}%`, normalized) ?? null;
85
+ }
86
+ export function getJobCompanyName(id) {
87
+ const row = getDb().prepare(`
88
+ SELECT COALESCE(c.name, j.company_name_raw) AS name
89
+ FROM jobs j LEFT JOIN companies c ON c.id = j.company_id
90
+ WHERE j.id = ?
91
+ `).get(id);
92
+ return row?.name ?? null;
93
+ }
94
+ export async function adoptJobFromJD(input) {
95
+ const title = (input.title ?? input.jd.title_guess ?? 'Untitled role').trim();
96
+ const company = (input.company ?? input.jd.company_guess ?? 'Unknown company').trim();
97
+ return upsertJob({
98
+ source: input.jd.source === 'url' ? 'url' : 'paste',
99
+ source_url: input.jd.source_url ?? `paste://${Date.now()}`,
100
+ company_name: company,
101
+ title,
102
+ location: input.location ?? null,
103
+ description: input.jd.text,
104
+ });
105
+ }
106
+ //# sourceMappingURL=jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/core/jobs.ts"],"names":[],"mappings":"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"}