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,481 @@
1
+ // G3 — outreach suite (8 tools).
2
+ //
3
+ // All draft_* tools default to mode='chat': they return the active career packet, the
4
+ // outreach_tone.md rules, and the connection/job context, so the chat client drafts the
5
+ // message. With mode='api' we call the configured LLM with the same context inline and
6
+ // validate the result against the safety rails (char cap + never-refer-me + no-visa +
7
+ // no-emojis + no-exclamations). Validation failures are surfaced — never silently saved.
8
+ import { z } from 'zod';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { getDb, runInWriteLock } from '../../db.js';
11
+ import { defineTool, okResult, errResult } from '../define.js';
12
+ import { getActiveCareerPacket } from '../../core/profile.js';
13
+ import { chatLogged } from '../../core/llm.js';
14
+ import { getMode } from '../../core/modes.js';
15
+ import { getOutreachLimits, validateOutreach } from '../../core/outreach_safety.js';
16
+ const OUTREACH_TYPES = ['warm_intro_request', 'founder_dm', 'recruiter_followup', 'generic', 'followup'];
17
+ const OUTREACH_STATUSES = ['queued', 'drafted', 'edited', 'sent', 'replied', 'dead', 'success'];
18
+ const readOutreachTone = () => getMode('outreach_tone.md');
19
+ // Maps an outreach_type to the validation profile in outreach_safety.ts.
20
+ // `warm_intro_request` and `recruiter_followup` both fall under "warm" rails per modes/outreach_tone.md.
21
+ const TYPE_TO_LIMITS = {
22
+ warm_intro_request: 'warm',
23
+ founder_dm: 'founder',
24
+ followup: 'followup',
25
+ recruiter_followup: 'warm',
26
+ };
27
+ function typeForLimits(t) {
28
+ return TYPE_TO_LIMITS[t] ?? 'generic';
29
+ }
30
+ // ── find_warm_intros ─────────────────────────────────────────────────────────
31
+ export const findWarmIntrosTool = defineTool({
32
+ name: 'find_warm_intros',
33
+ title: 'Find warm intros at a company',
34
+ description: 'Joins jobs × non-recruiter LinkedIn connections at the same company. Filterable by company name substring. Sorted by score then contact priority.',
35
+ inputSchema: {
36
+ company: z.string().optional(),
37
+ min_score: z.number().int().min(0).max(100).default(80),
38
+ limit: z.number().int().min(1).max(200).default(50),
39
+ },
40
+ handler: async (args) => {
41
+ const where = ['v.score_total IS NOT NULL', 'v.score_total >= ?'];
42
+ const params = [args.min_score];
43
+ if (args.company) {
44
+ where.push('LOWER(v.company_name) LIKE ?');
45
+ params.push(`%${args.company.toLowerCase()}%`);
46
+ }
47
+ const rows = getDb().prepare(`
48
+ SELECT v.*, lc.linkedin_url, lc.preferred_channel
49
+ FROM v_jobs_with_warm_intros v
50
+ JOIN linkedin_connections lc ON lc.id = v.connection_id
51
+ WHERE ${where.join(' AND ')}
52
+ LIMIT ?
53
+ `).all(...params, args.limit);
54
+ return okResult({ count: rows.length, items: rows });
55
+ },
56
+ });
57
+ // ── find_founders ────────────────────────────────────────────────────────────
58
+ export const findFoundersTool = defineTool({
59
+ name: 'find_founders',
60
+ title: 'Find founders/CEO/CTO/c-suite in your network',
61
+ description: 'Returns non-stealth founders / c-suite from linkedin_connections, derived in v_founder_network.',
62
+ inputSchema: {
63
+ kind: z.enum(['founder', 'ceo', 'cto', 'c_suite', 'any']).default('any'),
64
+ limit: z.number().int().min(1).max(500).default(100),
65
+ },
66
+ handler: async (args) => {
67
+ const where = ['is_stealth = 0'];
68
+ const params = [];
69
+ if (args.kind !== 'any') {
70
+ where.push('founder_kind = ?');
71
+ params.push(args.kind);
72
+ }
73
+ const rows = getDb().prepare(`
74
+ SELECT v.*, lc.linkedin_url, lc.preferred_channel
75
+ FROM v_founder_network v
76
+ JOIN linkedin_connections lc ON lc.id = v.connection_id
77
+ WHERE ${where.join(' AND ')}
78
+ ORDER BY
79
+ CASE founder_kind WHEN 'founder' THEN 1 WHEN 'ceo' THEN 2 WHEN 'cto' THEN 3 ELSE 4 END,
80
+ lc.full_name
81
+ LIMIT ?
82
+ `).all(...params, args.limit);
83
+ return okResult({ count: rows.length, items: rows });
84
+ },
85
+ });
86
+ // ── draft_outreach ───────────────────────────────────────────────────────────
87
+ export const draftOutreachTool = defineTool({
88
+ name: 'draft_outreach',
89
+ title: 'Draft a warm-intro or founder DM',
90
+ description: 'mode=chat: returns connection + (optional) job context + career packet + outreach_tone.md so the chat client drafts the DM. ' +
91
+ 'mode=api: calls the configured LLM, validates char cap / no visa / no refer-me / no emojis, persists draft. ' +
92
+ 'Always inserts a queued or drafted outreach row tied to the connection.',
93
+ inputSchema: {
94
+ connection_id: z.string().min(1),
95
+ job_id: z.string().optional(),
96
+ type: z.enum(OUTREACH_TYPES).default('warm_intro_request'),
97
+ mode: z.enum(['chat', 'api']).default('chat'),
98
+ message: z.string().optional().describe('Provide on a second chat-mode call to persist a drafted message.'),
99
+ },
100
+ handler: async (args) => {
101
+ const db = getDb();
102
+ const conn = db.prepare(`SELECT * FROM linkedin_connections WHERE id = ?`).get(args.connection_id);
103
+ if (!conn)
104
+ return errResult(`No connection ${args.connection_id}`);
105
+ const job = args.job_id
106
+ ? db.prepare(`SELECT id, title, company_id, company_name_raw, source_url FROM jobs WHERE id = ?`).get(args.job_id)
107
+ : null;
108
+ const limits = getOutreachLimits(typeForLimits(args.type));
109
+ // Persisting a chat-drafted message on the round-trip.
110
+ if (args.mode === 'chat' && args.message) {
111
+ const validation = validateOutreach(args.message, typeForLimits(args.type));
112
+ if (!validation.ok) {
113
+ return errResult(`outreach validation failed: ${JSON.stringify(validation)}`);
114
+ }
115
+ const id = await persistDraft({
116
+ connection_id: conn.id,
117
+ company_id: conn.company_id ?? job?.company_id ?? null,
118
+ related_job_id: job?.id ?? null,
119
+ outreach_type: args.type,
120
+ channel: conn.preferred_channel ?? 'linkedin',
121
+ draft_message: args.message,
122
+ });
123
+ return okResult({
124
+ outreach_id: id, status: 'drafted',
125
+ validation, char_cap: limits.maxChars, length: args.message.length,
126
+ });
127
+ }
128
+ if (args.mode === 'chat') {
129
+ // Step 1: hand the chat all the context it needs to draft.
130
+ return okResult({
131
+ instructions: 'Draft the message per modes/outreach_tone.md (under the char cap; no emojis; no exclamation marks; ' +
132
+ 'never "refer me"; NEVER mention visa / OPT / sponsorship). Then call draft_outreach AGAIN with the ' +
133
+ '`message` argument to persist.',
134
+ char_cap: limits.maxChars,
135
+ type: args.type,
136
+ connection: {
137
+ id: conn.id, full_name: conn.full_name, position: conn.position,
138
+ company_raw: conn.company_raw, is_recruiter: !!conn.is_recruiter,
139
+ is_engineering: !!conn.is_engineering, is_leadership: !!conn.is_leadership,
140
+ linkedin_url: conn.linkedin_url, preferred_channel: conn.preferred_channel,
141
+ },
142
+ job: job ? {
143
+ id: job.id, title: job.title, company: job.company_name_raw, source_url: job.source_url,
144
+ } : null,
145
+ career_packet: getActiveCareerPacket()?.content ?? '',
146
+ outreach_tone: readOutreachTone(),
147
+ });
148
+ }
149
+ // mode=api — call LLM, validate, persist.
150
+ try {
151
+ const systemTone = readOutreachTone();
152
+ const packet = getActiveCareerPacket()?.content ?? '';
153
+ const system = systemTone + '\n\n== CAREER PACKET ==\n' + packet +
154
+ '\n\n== OUTPUT CONTRACT ==\nReturn STRICT JSON: { "message": "...", "opening_hook": "...", "primary_ask": "...", "strategy_note": "...", "subject_line": "..." }.';
155
+ const user = JSON.stringify({
156
+ type: args.type, char_cap: limits.maxChars,
157
+ connection: {
158
+ full_name: conn.full_name, position: conn.position, company_raw: conn.company_raw,
159
+ is_recruiter: !!conn.is_recruiter, is_engineering: !!conn.is_engineering, is_leadership: !!conn.is_leadership,
160
+ },
161
+ job: job ? { title: job.title, company: job.company_name_raw } : null,
162
+ });
163
+ const call = await chatLogged('draft_outreach.api', [
164
+ { role: 'system', content: system },
165
+ { role: 'user', content: user },
166
+ ], { responseFormat: 'json_object', temperature: 0.6, maxTokens: 1200 });
167
+ if (!call.parseOk || typeof call.parsed?.message !== 'string') {
168
+ return errResult(`LLM produced unparseable output: ${call.parseError ?? 'no message field'}`);
169
+ }
170
+ const parsed = call.parsed;
171
+ const validation = validateOutreach(parsed.message, typeForLimits(args.type));
172
+ if (!validation.ok) {
173
+ return errResult(`LLM draft failed safety rails — not persisted. ${JSON.stringify(validation)}`);
174
+ }
175
+ const id = await persistDraft({
176
+ connection_id: conn.id,
177
+ company_id: conn.company_id ?? job?.company_id ?? null,
178
+ related_job_id: job?.id ?? null,
179
+ outreach_type: args.type,
180
+ channel: conn.preferred_channel ?? 'linkedin',
181
+ draft_message: parsed.message,
182
+ subject_line: parsed.subject_line ?? null,
183
+ notes: [parsed.opening_hook && `hook: ${parsed.opening_hook}`,
184
+ parsed.primary_ask && `ask: ${parsed.primary_ask}`,
185
+ parsed.strategy_note && `strategy: ${parsed.strategy_note}`]
186
+ .filter(Boolean).join(' | ') || null,
187
+ });
188
+ return okResult({ outreach_id: id, status: 'drafted', validation, parsed });
189
+ }
190
+ catch (e) {
191
+ return errResult(`api draft_outreach failed: ${e?.message ?? String(e)}`);
192
+ }
193
+ },
194
+ });
195
+ // ── get_outreach_queue ───────────────────────────────────────────────────────
196
+ export const getOutreachQueueTool = defineTool({
197
+ name: 'get_outreach_queue',
198
+ title: 'Outreach queue',
199
+ description: 'Returns outreach rows filtered by status (defaults: queued + drafted + edited). Newest first.',
200
+ inputSchema: {
201
+ status: z.array(z.enum(OUTREACH_STATUSES)).optional(),
202
+ limit: z.number().int().min(1).max(500).default(100),
203
+ },
204
+ handler: async (args) => {
205
+ const statuses = args.status?.length ? args.status : ['queued', 'drafted', 'edited'];
206
+ const placeholders = statuses.map(() => '?').join(',');
207
+ const rows = getDb().prepare(`
208
+ SELECT o.*, lc.full_name AS connection_name, lc.linkedin_url,
209
+ c.name AS company_name,
210
+ j.title AS job_title, j.source_url
211
+ FROM outreach o
212
+ LEFT JOIN linkedin_connections lc ON lc.id = o.connection_id
213
+ LEFT JOIN companies c ON c.id = o.company_id
214
+ LEFT JOIN jobs j ON j.id = o.related_job_id
215
+ WHERE o.status IN (${placeholders})
216
+ ORDER BY datetime(o.updated_at) DESC
217
+ LIMIT ?
218
+ `).all(...statuses, args.limit);
219
+ return okResult({ count: rows.length, items: rows });
220
+ },
221
+ });
222
+ // ── update_outreach ──────────────────────────────────────────────────────────
223
+ export const updateOutreachTool = defineTool({
224
+ name: 'update_outreach',
225
+ title: 'Update outreach status',
226
+ description: 'Move an outreach row to a new status. Stamps sent_at on "sent" and replied_at on "replied". Optional reply text + followup_due_at.',
227
+ inputSchema: {
228
+ id: z.string().min(1),
229
+ status: z.enum(OUTREACH_STATUSES),
230
+ reply: z.string().optional(),
231
+ followup_in_days: z.number().int().min(1).max(365).optional()
232
+ .describe('Sets followup_due_at to now + N days. Defaults to 5 when status=sent and no override.'),
233
+ edited_message: z.string().optional()
234
+ .describe('If set, replaces the edited_message and re-validates safety rails.'),
235
+ },
236
+ handler: async (args) => {
237
+ const result = await runInWriteLock(() => {
238
+ const db = getDb();
239
+ const row = db.prepare(`SELECT * FROM outreach WHERE id = ?`).get(args.id);
240
+ if (!row)
241
+ return { ok: false, message: `no outreach ${args.id}` };
242
+ // Validate edited_message if provided.
243
+ if (args.edited_message) {
244
+ const validation = validateOutreach(args.edited_message, typeForLimits(row.outreach_type));
245
+ if (!validation.ok)
246
+ return { ok: false, message: `edited_message failed: ${JSON.stringify(validation)}` };
247
+ }
248
+ const sets = ['status = ?', 'updated_at = CURRENT_TIMESTAMP'];
249
+ const params = [args.status];
250
+ if (args.status === 'sent') {
251
+ sets.push('sent_at = CURRENT_TIMESTAMP');
252
+ const days = args.followup_in_days ?? 5;
253
+ sets.push(`followup_due_at = datetime('now', '+${days} days')`);
254
+ }
255
+ if (args.status === 'replied') {
256
+ sets.push('replied_at = CURRENT_TIMESTAMP');
257
+ if (args.reply) {
258
+ sets.push('reply_text = ?');
259
+ params.push(args.reply);
260
+ }
261
+ }
262
+ if (args.edited_message) {
263
+ sets.push('edited_message = ?');
264
+ params.push(args.edited_message);
265
+ }
266
+ db.prepare(`UPDATE outreach SET ${sets.join(', ')} WHERE id = ?`).run(...params, args.id);
267
+ return { ok: true, from: row.status, to: args.status };
268
+ });
269
+ if (!result.ok)
270
+ return errResult(result.message);
271
+ return okResult({ id: args.id, from: result.from, to: result.to });
272
+ },
273
+ });
274
+ // ── get_followups_due ────────────────────────────────────────────────────────
275
+ export const getFollowupsDueTool = defineTool({
276
+ name: 'get_followups_due',
277
+ title: 'Follow-ups due',
278
+ description: 'Returns outreach rows in status=sent with followup_due_at <= now. View v_followups_due.',
279
+ inputSchema: { limit: z.number().int().min(1).max(500).default(100) },
280
+ handler: async (args) => {
281
+ const rows = getDb().prepare(`
282
+ SELECT v.*, lc.linkedin_url, lc.preferred_channel
283
+ FROM v_followups_due v
284
+ LEFT JOIN linkedin_connections lc ON lc.id = v.connection_id
285
+ ORDER BY datetime(v.followup_due_at) ASC
286
+ LIMIT ?
287
+ `).all(args.limit);
288
+ return okResult({ count: rows.length, items: rows });
289
+ },
290
+ });
291
+ // ── draft_followup ───────────────────────────────────────────────────────────
292
+ export const draftFollowupTool = defineTool({
293
+ name: 'draft_followup',
294
+ title: 'Draft a 1-2 line follow-up nudge',
295
+ description: 'For outreach in status=sent that has not been replied to. mode=chat returns context + tone rules. ' +
296
+ 'mode=api calls the LLM and validates safety rails before persisting.',
297
+ inputSchema: {
298
+ outreach_id: z.string().min(1),
299
+ mode: z.enum(['chat', 'api']).default('chat'),
300
+ message: z.string().optional().describe('Provide on a second chat-mode call to persist.'),
301
+ },
302
+ handler: async (args) => {
303
+ const db = getDb();
304
+ const original = db.prepare(`SELECT * FROM outreach WHERE id = ?`).get(args.outreach_id);
305
+ if (!original)
306
+ return errResult(`No outreach ${args.outreach_id}`);
307
+ const conn = db.prepare(`SELECT * FROM linkedin_connections WHERE id = ?`).get(original.connection_id);
308
+ const limits = getOutreachLimits('followup');
309
+ if (args.mode === 'chat' && args.message) {
310
+ const validation = validateOutreach(args.message, 'followup');
311
+ if (!validation.ok)
312
+ return errResult(`followup validation failed: ${JSON.stringify(validation)}`);
313
+ const id = await persistDraft({
314
+ connection_id: original.connection_id,
315
+ company_id: original.company_id,
316
+ related_job_id: original.related_job_id,
317
+ outreach_type: 'followup',
318
+ channel: original.channel ?? 'linkedin',
319
+ draft_message: args.message,
320
+ notes: `followup to outreach ${original.id}`,
321
+ });
322
+ return okResult({ outreach_id: id, status: 'drafted', validation });
323
+ }
324
+ if (args.mode === 'chat') {
325
+ return okResult({
326
+ instructions: 'Draft a 1-2 line nudge. Tone: still curious about X, drop me a line if it ever makes sense. Soft close again. NO new ask. NO visa mention. Then call draft_followup with `message` to persist.',
327
+ char_cap: limits.maxChars,
328
+ original: {
329
+ id: original.id, sent_at: original.sent_at, draft_message: original.edited_message ?? original.draft_message,
330
+ },
331
+ connection: conn ? { full_name: conn.full_name, position: conn.position } : null,
332
+ outreach_tone: readOutreachTone(),
333
+ });
334
+ }
335
+ // api mode
336
+ try {
337
+ const system = readOutreachTone() +
338
+ '\n\n== TASK ==\nDraft a 1-2 line follow-up nudge. NO new ask. NO visa mention. Output STRICT JSON: { "message": "..." }.';
339
+ const user = JSON.stringify({
340
+ original_message: original.edited_message ?? original.draft_message,
341
+ connection: conn ? { full_name: conn.full_name, position: conn.position } : null,
342
+ days_since_sent: original.sent_at ? daysAgo(original.sent_at) : null,
343
+ });
344
+ const call = await chatLogged('draft_followup.api', [
345
+ { role: 'system', content: system }, { role: 'user', content: user },
346
+ ], { responseFormat: 'json_object', temperature: 0.5, maxTokens: 600 });
347
+ if (!call.parseOk || typeof call.parsed?.message !== 'string') {
348
+ return errResult(`LLM produced unparseable output: ${call.parseError ?? 'no message'}`);
349
+ }
350
+ const msg = call.parsed.message;
351
+ const validation = validateOutreach(msg, 'followup');
352
+ if (!validation.ok)
353
+ return errResult(`LLM draft failed safety rails: ${JSON.stringify(validation)}`);
354
+ const id = await persistDraft({
355
+ connection_id: original.connection_id,
356
+ company_id: original.company_id,
357
+ related_job_id: original.related_job_id,
358
+ outreach_type: 'followup',
359
+ channel: original.channel ?? 'linkedin',
360
+ draft_message: msg,
361
+ notes: `followup to outreach ${original.id}`,
362
+ });
363
+ return okResult({ outreach_id: id, status: 'drafted', validation, message: msg });
364
+ }
365
+ catch (e) {
366
+ return errResult(`api draft_followup failed: ${e?.message ?? String(e)}`);
367
+ }
368
+ },
369
+ });
370
+ // ── draft_reply ──────────────────────────────────────────────────────────────
371
+ export const draftReplyTool = defineTool({
372
+ name: 'draft_reply',
373
+ title: 'Draft a reply to a received reply',
374
+ description: 'Contextual response draft for outreach in status=replied. Human edits + sends.',
375
+ inputSchema: {
376
+ outreach_id: z.string().min(1),
377
+ received_reply: z.string().optional().describe('Override or supply the inbound reply text if not stored.'),
378
+ mode: z.enum(['chat', 'api']).default('chat'),
379
+ message: z.string().optional(),
380
+ },
381
+ handler: async (args) => {
382
+ const db = getDb();
383
+ const original = db.prepare(`SELECT * FROM outreach WHERE id = ?`).get(args.outreach_id);
384
+ if (!original)
385
+ return errResult(`No outreach ${args.outreach_id}`);
386
+ const reply = args.received_reply ?? original.reply_text ?? '';
387
+ if (!reply)
388
+ return errResult('No reply text on file. Pass `received_reply` to draft.');
389
+ const conn = db.prepare(`SELECT * FROM linkedin_connections WHERE id = ?`).get(original.connection_id);
390
+ if (args.mode === 'chat' && args.message) {
391
+ const validation = validateOutreach(args.message, 'reply');
392
+ if (!validation.ok)
393
+ return errResult(`reply validation failed: ${JSON.stringify(validation)}`);
394
+ const id = await persistDraft({
395
+ connection_id: original.connection_id,
396
+ company_id: original.company_id,
397
+ related_job_id: original.related_job_id,
398
+ outreach_type: 'generic',
399
+ channel: original.channel ?? 'linkedin',
400
+ draft_message: args.message,
401
+ notes: `reply to outreach ${original.id}`,
402
+ });
403
+ return okResult({ outreach_id: id, status: 'drafted', validation });
404
+ }
405
+ if (args.mode === 'chat') {
406
+ return okResult({
407
+ instructions: 'Match their energy. If they replied warmly with a question, answer it tightly and add one forward-moving piece. ' +
408
+ 'If they deflected, send 1-line thanks + an open door. Never beg. No visa mention. Then call draft_reply with `message` to persist.',
409
+ char_cap: 800,
410
+ original_message: original.edited_message ?? original.draft_message,
411
+ received_reply: reply,
412
+ connection: conn ? { full_name: conn.full_name, position: conn.position } : null,
413
+ career_packet: getActiveCareerPacket()?.content ?? '',
414
+ outreach_tone: readOutreachTone(),
415
+ });
416
+ }
417
+ // api mode
418
+ try {
419
+ const system = readOutreachTone() +
420
+ '\n\n== TASK ==\nDraft a reply. Match their energy. NO visa mention. Output STRICT JSON: { "message": "..." }.';
421
+ const user = JSON.stringify({
422
+ original: original.edited_message ?? original.draft_message,
423
+ received_reply: reply,
424
+ connection: conn ? { full_name: conn.full_name, position: conn.position } : null,
425
+ });
426
+ const call = await chatLogged('draft_reply.api', [
427
+ { role: 'system', content: system }, { role: 'user', content: user },
428
+ ], { responseFormat: 'json_object', temperature: 0.5, maxTokens: 1000 });
429
+ if (!call.parseOk || typeof call.parsed?.message !== 'string') {
430
+ return errResult(`LLM produced unparseable output: ${call.parseError ?? 'no message'}`);
431
+ }
432
+ const msg = call.parsed.message;
433
+ const validation = validateOutreach(msg, 'reply');
434
+ if (!validation.ok)
435
+ return errResult(`LLM draft failed safety rails: ${JSON.stringify(validation)}`);
436
+ const id = await persistDraft({
437
+ connection_id: original.connection_id,
438
+ company_id: original.company_id,
439
+ related_job_id: original.related_job_id,
440
+ outreach_type: 'generic',
441
+ channel: original.channel ?? 'linkedin',
442
+ draft_message: msg,
443
+ notes: `reply to outreach ${original.id}`,
444
+ });
445
+ return okResult({ outreach_id: id, status: 'drafted', validation, message: msg });
446
+ }
447
+ catch (e) {
448
+ return errResult(`api draft_reply failed: ${e?.message ?? String(e)}`);
449
+ }
450
+ },
451
+ });
452
+ async function persistDraft(p) {
453
+ return runInWriteLock(() => {
454
+ const db = getDb();
455
+ const existing = db.prepare(`
456
+ SELECT id FROM outreach
457
+ WHERE connection_id = ? AND COALESCE(related_job_id,'') = COALESCE(?,'') AND outreach_type = ? AND status IN ('queued','drafted','edited')
458
+ ORDER BY datetime(updated_at) DESC LIMIT 1
459
+ `).get(p.connection_id, p.related_job_id ?? '', p.outreach_type);
460
+ if (existing) {
461
+ db.prepare(`
462
+ UPDATE outreach SET draft_message = ?, subject_line = COALESCE(?, subject_line),
463
+ notes = COALESCE(?, notes), status = 'drafted', updated_at = CURRENT_TIMESTAMP
464
+ WHERE id = ?
465
+ `).run(p.draft_message, p.subject_line ?? null, p.notes ?? null, existing.id);
466
+ return existing.id;
467
+ }
468
+ const id = randomUUID();
469
+ db.prepare(`
470
+ INSERT INTO outreach (id, connection_id, company_id, related_job_id, outreach_type, channel, status,
471
+ draft_message, subject_line, notes)
472
+ VALUES (?, ?, ?, ?, ?, ?, 'drafted', ?, ?, ?)
473
+ `).run(id, p.connection_id, p.company_id, p.related_job_id, p.outreach_type, p.channel, p.draft_message, p.subject_line ?? null, p.notes ?? null);
474
+ return id;
475
+ });
476
+ }
477
+ function daysAgo(iso) {
478
+ const t = new Date(iso).getTime();
479
+ return Math.max(0, Math.floor((Date.now() - t) / 86_400_000));
480
+ }
481
+ //# sourceMappingURL=outreach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outreach.js","sourceRoot":"","sources":["../../../src/mcp/tools/outreach.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import { renderPdf } from '../../core/render.js';
3
+ import { defineTool, okResult, errResult } from '../define.js';
4
+ export const renderPdfTool = defineTool({
5
+ name: 'render_pdf',
6
+ title: 'Render resume/cover PDF',
7
+ description: 'HTML → PDF via Playwright using the career-ops template. Returns a localhost link for each file.',
8
+ inputSchema: {
9
+ job_id: z.string().min(1),
10
+ kind: z.enum(['resume', 'cover', 'both']).default('resume'),
11
+ cover_body: z.string().optional().describe('Plain-prose cover letter body (used when kind includes cover). 250-350 words.'),
12
+ page_format: z.enum(['a4', 'letter']).default('letter'),
13
+ },
14
+ handler: async (args) => {
15
+ try {
16
+ const files = await renderPdf(args);
17
+ return okResult({
18
+ job_id: args.job_id,
19
+ files: files.map(f => ({ kind: f.kind, url: f.url, bytes: f.bytes, path: f.path })),
20
+ });
21
+ }
22
+ catch (err) {
23
+ return errResult(`render_pdf failed: ${err?.message ?? String(err)}`);
24
+ }
25
+ },
26
+ });
27
+ //# sourceMappingURL=render_pdf.js.map
@@ -0,0 +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;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;IACtC,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,yBAAyB;IAChC,WAAW,EAAE,kGAAkG;IAC/G,WAAW,EAAE;QACX,MAAM,EAAO,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,IAAI,EAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;QAClE,UAAU,EAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+EAA+E,CAAC;QAC5H,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;KACxD;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YACpC,OAAO,QAAQ,CAAC;gBACd,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;aACpF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,SAAS,CAAC,sBAAsB,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ import { runScan, knownProviderIds } from '../../core/scan_engine.js';
3
+ import { defineTool, okResult, errResult } from '../define.js';
4
+ export const scanPortalsTool = defineTool({
5
+ name: 'scan_portals',
6
+ title: 'Scan job portals',
7
+ description: 'Fan out across configured ATS endpoints. Greenhouse / Ashby / Lever / Workday hit JSON APIs; ' +
8
+ 'Google + custom playwright_generic use Chromium. Content-hash dedupes across sources. ' +
9
+ 'Reads tracked_companies from portals.yml in the project root.',
10
+ inputSchema: {
11
+ sources: z.array(z.string()).optional().describe(`Restrict to a subset of providers. Known: ${knownProviderIds().join(', ')}.`),
12
+ companies: z.array(z.string()).optional().describe('Restrict to companies whose name contains any of these substrings.'),
13
+ title_positive: z.array(z.string()).optional(),
14
+ title_negative: z.array(z.string()).optional(),
15
+ location_allow: z.array(z.string()).optional(),
16
+ location_block: z.array(z.string()).optional(),
17
+ },
18
+ handler: async (args) => {
19
+ try {
20
+ const result = await runScan({
21
+ sources: args.sources,
22
+ companies: args.companies,
23
+ title_positive: args.title_positive,
24
+ title_negative: args.title_negative,
25
+ location_allow: args.location_allow,
26
+ location_block: args.location_block,
27
+ }, { triggeredBy: 'scan_portals' });
28
+ return okResult(result);
29
+ }
30
+ catch (err) {
31
+ return errResult(`scan_portals failed: ${err?.message ?? String(err)}`);
32
+ }
33
+ },
34
+ });
35
+ //# sourceMappingURL=scan_portals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan_portals.js","sourceRoot":"","sources":["../../../src/mcp/tools/scan_portals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,kBAAkB;IACzB,WAAW,EACT,+FAA+F;QAC/F,wFAAwF;QACxF,+DAA+D;IACjE,WAAW,EAAE;QACX,OAAO,EAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,gBAAgB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QACjI,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oEAAoE,CAAC;QACxH,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC9C,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC9C,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC9C,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;KAC/C;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,OAAO,EAAS,IAAI,CAAC,OAAO;gBAC5B,SAAS,EAAO,IAAI,CAAC,SAAS;gBAC9B,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,cAAc,EAAE,IAAI,CAAC,cAAc;aACpC,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;YACpC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,SAAS,CAAC,wBAAwB,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import { defineTool, okResult } from '../define.js';
3
+ import { JOB_DEFS, disableAll, setEnabledJobs, status } from '../../core/scheduler.js';
4
+ const JOB_NAMES = Object.keys(JOB_DEFS);
5
+ export const schedulerStatusTool = defineTool({
6
+ name: 'scheduler_status',
7
+ title: 'Scheduler status',
8
+ description: 'Returns currently enabled scheduler jobs, available jobs + intervals, and last fire time.',
9
+ inputSchema: {},
10
+ handler: async () => okResult(status()),
11
+ });
12
+ export const schedulerEnableTool = defineTool({
13
+ name: 'scheduler_enable',
14
+ title: 'Enable scheduler jobs (opt-in)',
15
+ description: 'Pass an array of job names to enable. Overrides the previous set. The cron is off until you call this.',
16
+ inputSchema: { jobs: z.array(z.enum(JOB_NAMES)).min(1) },
17
+ handler: async (args) => {
18
+ const enabled = await setEnabledJobs(args.jobs);
19
+ return okResult({ enabled, status: status() });
20
+ },
21
+ });
22
+ export const schedulerDisableTool = defineTool({
23
+ name: 'scheduler_disable',
24
+ title: 'Disable all scheduler jobs',
25
+ description: 'Clears the enabled-jobs list. Process keeps running.',
26
+ inputSchema: {},
27
+ handler: async () => {
28
+ disableAll();
29
+ return okResult({ enabled: [], status: status() });
30
+ },
31
+ });
32
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../src/mcp/tools/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAgB,MAAM,yBAAyB,CAAC;AAErG,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAc,CAAC;AAErD,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;IAC5C,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,kBAAkB;IACzB,WAAW,EAAE,2FAA2F;IACxG,WAAW,EAAE,EAAE;IACf,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;CACxC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;IAC5C,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,gCAAgC;IACvC,WAAW,EAAE,wGAAwG;IACrH,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAoC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;IACnF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;IAC7C,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,4BAA4B;IACnC,WAAW,EAAE,sDAAsD;IACnE,WAAW,EAAE,EAAE;IACf,OAAO,EAAE,KAAK,IAAI,EAAE;QAClB,UAAU,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC;CACF,CAAC,CAAC"}