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.
- package/.env.example +33 -0
- package/LICENSE +21 -0
- package/README.md +400 -0
- package/config/profile.example.yml +67 -0
- package/cv.example.md +53 -0
- package/dist/cli.js +385 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +63 -0
- package/dist/config.js.map +1 -0
- package/dist/core/browser.js +27 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/content_hash.js +11 -0
- package/dist/core/content_hash.js.map +1 -0
- package/dist/core/csv.js +107 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/cv_parse.js +201 -0
- package/dist/core/cv_parse.js.map +1 -0
- package/dist/core/html.js +10 -0
- package/dist/core/html.js.map +1 -0
- package/dist/core/jd_normalize.js +99 -0
- package/dist/core/jd_normalize.js.map +1 -0
- package/dist/core/jobs.js +106 -0
- package/dist/core/jobs.js.map +1 -0
- package/dist/core/llm.js +227 -0
- package/dist/core/llm.js.map +1 -0
- package/dist/core/modes.js +55 -0
- package/dist/core/modes.js.map +1 -0
- package/dist/core/outreach_safety.js +77 -0
- package/dist/core/outreach_safety.js.map +1 -0
- package/dist/core/profile.js +88 -0
- package/dist/core/profile.js.map +1 -0
- package/dist/core/providers/amazon.js +36 -0
- package/dist/core/providers/amazon.js.map +1 -0
- package/dist/core/providers/ashby.js +31 -0
- package/dist/core/providers/ashby.js.map +1 -0
- package/dist/core/providers/google.js +46 -0
- package/dist/core/providers/google.js.map +1 -0
- package/dist/core/providers/greenhouse.js +55 -0
- package/dist/core/providers/greenhouse.js.map +1 -0
- package/dist/core/providers/http.js +36 -0
- package/dist/core/providers/http.js.map +1 -0
- package/dist/core/providers/index.js +53 -0
- package/dist/core/providers/index.js.map +1 -0
- package/dist/core/providers/lever.js +32 -0
- package/dist/core/providers/lever.js.map +1 -0
- package/dist/core/providers/playwright_generic.js +53 -0
- package/dist/core/providers/playwright_generic.js.map +1 -0
- package/dist/core/providers/types.js +2 -0
- package/dist/core/providers/types.js.map +1 -0
- package/dist/core/providers/workday.js +44 -0
- package/dist/core/providers/workday.js.map +1 -0
- package/dist/core/render.js +253 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/reports.js +257 -0
- package/dist/core/reports.js.map +1 -0
- package/dist/core/resources.js +40 -0
- package/dist/core/resources.js.map +1 -0
- package/dist/core/scan_engine.js +164 -0
- package/dist/core/scan_engine.js.map +1 -0
- package/dist/core/scheduler.js +117 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/db.js +60 -0
- package/dist/db.js.map +1 -0
- package/dist/http/app.js +35 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/dashboard.js +131 -0
- package/dist/http/dashboard.js.map +1 -0
- package/dist/mcp/define.js +35 -0
- package/dist/mcp/define.js.map +1 -0
- package/dist/mcp/server.js +103 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/apply_prefill.js +167 -0
- package/dist/mcp/tools/apply_prefill.js.map +1 -0
- package/dist/mcp/tools/batch_evaluate.js +143 -0
- package/dist/mcp/tools/batch_evaluate.js.map +1 -0
- package/dist/mcp/tools/evaluate_job.js +181 -0
- package/dist/mcp/tools/evaluate_job.js.map +1 -0
- package/dist/mcp/tools/generate_materials.js +126 -0
- package/dist/mcp/tools/generate_materials.js.map +1 -0
- package/dist/mcp/tools/get_report.js +24 -0
- package/dist/mcp/tools/get_report.js.map +1 -0
- package/dist/mcp/tools/ops.js +321 -0
- package/dist/mcp/tools/ops.js.map +1 -0
- package/dist/mcp/tools/outreach.js +481 -0
- package/dist/mcp/tools/outreach.js.map +1 -0
- package/dist/mcp/tools/render_pdf.js +27 -0
- package/dist/mcp/tools/render_pdf.js.map +1 -0
- package/dist/mcp/tools/scan_portals.js +35 -0
- package/dist/mcp/tools/scan_portals.js.map +1 -0
- package/dist/mcp/tools/scheduler.js +32 -0
- package/dist/mcp/tools/scheduler.js.map +1 -0
- package/dist/mcp/tools/stories.js +172 -0
- package/dist/mcp/tools/stories.js.map +1 -0
- package/dist/mcp/tools/tracker.js +183 -0
- package/dist/mcp/tools/tracker.js.map +1 -0
- package/dist/mcp/tools/visa.js +219 -0
- package/dist/mcp/tools/visa.js.map +1 -0
- package/dist/migrations/001_initial.sql +505 -0
- package/dist/migrations/002_llm_and_digest.sql +42 -0
- package/dist/server.js +55 -0
- package/dist/server.js.map +1 -0
- package/fonts/dm-sans-latin-ext.woff2 +0 -0
- package/fonts/dm-sans-latin.woff2 +0 -0
- package/fonts/space-grotesk-latin-ext.woff2 +0 -0
- package/fonts/space-grotesk-latin.woff2 +0 -0
- package/modes/career_packet.md +91 -0
- package/modes/negotiation_playbook.md +64 -0
- package/modes/outreach_tone.md +80 -0
- package/modes/report_format.md +83 -0
- package/modes/rubric.md +119 -0
- package/modes/tailoring_rules.md +102 -0
- package/package.json +67 -0
- package/portals.example.yml +95 -0
- package/templates/cover-template.html +64 -0
- package/templates/cv-template.html +421 -0
- package/templates/cv-template.tex +123 -0
package/dist/db.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// SQLite handle. WAL mode + a single migration on first open.
|
|
2
|
+
//
|
|
3
|
+
// Writes from MCP tools must go through `runInWriteLock(fn)` (defined here) so concurrent
|
|
4
|
+
// tracker / outreach / application mutations don't interleave. better-sqlite3 is sync, so
|
|
5
|
+
// the lock is just a JS promise queue.
|
|
6
|
+
import Database from 'better-sqlite3';
|
|
7
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { resolve, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { config } from './config.js';
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const MIGRATIONS_DIR = resolve(here, 'migrations');
|
|
13
|
+
let _db = null;
|
|
14
|
+
export function getDb() {
|
|
15
|
+
if (_db)
|
|
16
|
+
return _db;
|
|
17
|
+
const db = new Database(config.dbPath);
|
|
18
|
+
db.pragma('journal_mode = WAL');
|
|
19
|
+
db.pragma('foreign_keys = ON');
|
|
20
|
+
db.pragma('synchronous = NORMAL');
|
|
21
|
+
ensureMigrationsTable(db);
|
|
22
|
+
applyPendingMigrations(db);
|
|
23
|
+
_db = db;
|
|
24
|
+
return db;
|
|
25
|
+
}
|
|
26
|
+
function ensureMigrationsTable(db) {
|
|
27
|
+
db.exec(`
|
|
28
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
29
|
+
name TEXT PRIMARY KEY,
|
|
30
|
+
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
31
|
+
);
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
function applyPendingMigrations(db) {
|
|
35
|
+
const files = readdirSync(MIGRATIONS_DIR).filter(f => f.endsWith('.sql')).sort();
|
|
36
|
+
const applied = new Set(db.prepare('SELECT name FROM _migrations').all().map((r) => r.name));
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
if (applied.has(file))
|
|
39
|
+
continue;
|
|
40
|
+
const sql = readFileSync(resolve(MIGRATIONS_DIR, file), 'utf-8');
|
|
41
|
+
const tx = db.transaction(() => {
|
|
42
|
+
db.exec(sql);
|
|
43
|
+
db.prepare('INSERT INTO _migrations (name) VALUES (?)').run(file);
|
|
44
|
+
});
|
|
45
|
+
tx();
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.error(`[db] applied migration ${file}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
// Single write lock. tracker / application / outreach writes wait their turn.
|
|
52
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
let writeQueue = Promise.resolve();
|
|
54
|
+
export function runInWriteLock(fn) {
|
|
55
|
+
const next = writeQueue.then(() => fn());
|
|
56
|
+
// Keep the chain alive even if a task throws — failures shouldn't deadlock the queue.
|
|
57
|
+
writeQueue = next.catch(() => undefined);
|
|
58
|
+
return next;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,0FAA0F;AAC1F,0FAA0F;AAC1F,uCAAuC;AACvC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;AAEnD,IAAI,GAAG,GAA6B,IAAI,CAAC;AAEzC,MAAM,UAAU,KAAK;IACnB,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IACpB,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAClC,qBAAqB,CAAC,EAAE,CAAC,CAAC;IAC1B,sBAAsB,CAAC,EAAE,CAAC,CAAC;IAC3B,GAAG,GAAG,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,qBAAqB,CAAC,EAAqB;IAClD,EAAE,CAAC,IAAI,CAAC;;;;;GAKP,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAAC,EAAqB;IACnD,MAAM,KAAK,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,EAAE,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAc,CAAC,CACnF,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAChC,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QACjE,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAC7B,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACb,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;QACH,EAAE,EAAE,CAAC;QACL,sCAAsC;QACtC,OAAO,CAAC,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,8EAA8E;AAC9E,iFAAiF;AAEjF,IAAI,UAAU,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;AAErD,MAAM,UAAU,cAAc,CAAI,EAAwB;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,sFAAsF;IACtF,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/http/app.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Express app: tracker dashboard, /files static server, /healthz, and the /mcp transport.
|
|
2
|
+
// The MCP HTTP transport is mounted from src/mcp/server.ts onto the same Express instance
|
|
3
|
+
// so users only need to expose one port to their chat client.
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { resolve, normalize, relative, sep } from 'node:path';
|
|
6
|
+
import { config } from '../config.js';
|
|
7
|
+
import { renderDashboard } from './dashboard.js';
|
|
8
|
+
export function buildHttpApp() {
|
|
9
|
+
const app = express();
|
|
10
|
+
// Body parser — MCP transport uses JSON-RPC; raise the limit slightly so chat clients
|
|
11
|
+
// can POST report bodies on step 2 of evaluate_job.
|
|
12
|
+
app.use(express.json({ limit: '4mb' }));
|
|
13
|
+
app.get('/healthz', (_req, res) => {
|
|
14
|
+
res.json({ ok: true, baseUrl: config.baseUrl });
|
|
15
|
+
});
|
|
16
|
+
app.get('/', (_req, res) => {
|
|
17
|
+
res.type('html').send(renderDashboard());
|
|
18
|
+
});
|
|
19
|
+
// /files/* — serve anything under outputDir. Path traversal guarded.
|
|
20
|
+
app.get('/files/*', (req, res) => {
|
|
21
|
+
const raw = req.params[0];
|
|
22
|
+
const abs = normalize(resolve(config.outputDir, raw));
|
|
23
|
+
const rel = relative(config.outputDir, abs);
|
|
24
|
+
if (rel.startsWith('..') || rel.includes(`${sep}..${sep}`) || abs === config.outputDir) {
|
|
25
|
+
res.status(403).send('forbidden');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
res.sendFile(abs, (err) => {
|
|
29
|
+
if (err)
|
|
30
|
+
res.status(404).send('not found');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return app;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,0FAA0F;AAC1F,8DAA8D;AAC9D,OAAO,OAAsD,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE9D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IAEtB,sFAAsF;IACtF,oDAAoD;IACpD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAExC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;QACnD,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;QAC5C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,qEAAqE;IACrE,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAClD,MAAM,GAAG,GAAI,GAAG,CAAC,MAAc,CAAC,CAAC,CAAW,CAAC;QAC7C,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;QACtD,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC5C,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,KAAK,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;YACvF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAClC,OAAO;QACT,CAAC;QACD,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,IAAI,GAAG;gBAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Single-page HTML tracker. Read-only for milestone 1; later milestones add status-change
|
|
2
|
+
// links that hit MCP tools via the same server.
|
|
3
|
+
import { getDb } from '../db.js';
|
|
4
|
+
import { escapeHtml } from '../core/html.js';
|
|
5
|
+
export function renderDashboard() {
|
|
6
|
+
const db = getDb();
|
|
7
|
+
const counts = db.prepare(`
|
|
8
|
+
SELECT
|
|
9
|
+
SUM(CASE WHEN status = 'sourced' THEN 1 ELSE 0 END) AS sourced,
|
|
10
|
+
SUM(CASE WHEN status = 'ready_to_apply' THEN 1 ELSE 0 END) AS ready_to_apply,
|
|
11
|
+
SUM(CASE WHEN status = 'materials_drafted' THEN 1 ELSE 0 END) AS materials_drafted,
|
|
12
|
+
SUM(CASE WHEN status = 'ready_to_review' THEN 1 ELSE 0 END) AS ready_to_review,
|
|
13
|
+
SUM(CASE WHEN status = 'applied' THEN 1 ELSE 0 END) AS applied,
|
|
14
|
+
SUM(CASE WHEN status = 'screen' THEN 1 ELSE 0 END) AS screen,
|
|
15
|
+
SUM(CASE WHEN status = 'onsite' THEN 1 ELSE 0 END) AS onsite,
|
|
16
|
+
SUM(CASE WHEN status = 'offer' THEN 1 ELSE 0 END) AS offer,
|
|
17
|
+
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) AS rejected,
|
|
18
|
+
COUNT(*) AS total
|
|
19
|
+
FROM jobs
|
|
20
|
+
`).get();
|
|
21
|
+
// Latest 30 evaluated/scored jobs, newest first
|
|
22
|
+
const rows = db.prepare(`
|
|
23
|
+
SELECT
|
|
24
|
+
j.id, j.title, j.score_total, j.status, j.role_category, j.seniority,
|
|
25
|
+
COALESCE(c.name, j.company_name_raw) AS company_name,
|
|
26
|
+
j.location_raw AS location,
|
|
27
|
+
j.source_url, j.discovered_at, j.scored_at,
|
|
28
|
+
(SELECT er.id FROM eval_reports er WHERE er.job_id = j.id ORDER BY er.created_at DESC LIMIT 1) AS report_id,
|
|
29
|
+
(SELECT er.html_path FROM eval_reports er WHERE er.job_id = j.id ORDER BY er.created_at DESC LIMIT 1) AS report_html
|
|
30
|
+
FROM jobs j
|
|
31
|
+
LEFT JOIN companies c ON c.id = j.company_id
|
|
32
|
+
ORDER BY datetime(j.discovered_at) DESC
|
|
33
|
+
LIMIT 30
|
|
34
|
+
`).all();
|
|
35
|
+
const card = (label, n) => `<div class="card"><div class="n">${n ?? 0}</div><div class="lbl">${label}</div></div>`;
|
|
36
|
+
const tier = (s) => {
|
|
37
|
+
if (s == null)
|
|
38
|
+
return '<span class="tier muted">—</span>';
|
|
39
|
+
if (s >= 85)
|
|
40
|
+
return `<span class="tier a">${s}</span>`;
|
|
41
|
+
if (s >= 75)
|
|
42
|
+
return `<span class="tier b">${s}</span>`;
|
|
43
|
+
if (s >= 60)
|
|
44
|
+
return `<span class="tier c">${s}</span>`;
|
|
45
|
+
return `<span class="tier d">${s}</span>`;
|
|
46
|
+
};
|
|
47
|
+
const tbody = rows.map((r) => `
|
|
48
|
+
<tr>
|
|
49
|
+
<td>${tier(r.score_total)}</td>
|
|
50
|
+
<td>${escapeHtml(r.company_name)}</td>
|
|
51
|
+
<td><a href="${escapeHtml(r.source_url)}" target="_blank" rel="noopener">${escapeHtml(r.title)}</a></td>
|
|
52
|
+
<td>${escapeHtml(r.role_category ?? '')}</td>
|
|
53
|
+
<td>${escapeHtml(r.seniority ?? '')}</td>
|
|
54
|
+
<td><code>${escapeHtml(r.status)}</code></td>
|
|
55
|
+
<td>${escapeHtml(r.location ?? '')}</td>
|
|
56
|
+
<td>${r.report_html ? `<a href="/files/${escapeHtml(r.report_html)}">report</a>` : '<span class="muted">—</span>'}</td>
|
|
57
|
+
<td class="meta">${escapeHtml((r.discovered_at ?? '').slice(0, 16))}</td>
|
|
58
|
+
</tr>
|
|
59
|
+
`).join('');
|
|
60
|
+
return `<!DOCTYPE html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8" />
|
|
64
|
+
<title>mcp-jsa tracker</title>
|
|
65
|
+
<style>
|
|
66
|
+
:root { color-scheme: light dark; }
|
|
67
|
+
body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; background: #fafbfc; color: #1a1a2e; }
|
|
68
|
+
header { padding: 1.4rem 1.6rem; border-bottom: 1px solid #e2e2e2; background: #fff; display: flex; justify-content: space-between; align-items: baseline; }
|
|
69
|
+
header h1 { margin: 0; font-size: 1.05rem; letter-spacing: 0.04em; text-transform: uppercase; color: hsl(187, 74%, 32%); }
|
|
70
|
+
header .meta { color: #777; font-size: 0.85rem; }
|
|
71
|
+
main { padding: 1.4rem 1.6rem; max-width: 1180px; margin: 0 auto; }
|
|
72
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.75rem; margin-bottom: 1.4rem; }
|
|
73
|
+
.card { background: #fff; border: 1px solid #e2e2e2; border-radius: 4px; padding: 0.75rem 0.9rem; }
|
|
74
|
+
.card .n { font-size: 1.4rem; font-weight: 700; }
|
|
75
|
+
.card .lbl { color: #555; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
76
|
+
table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #e2e2e2; }
|
|
77
|
+
th, td { text-align: left; padding: 0.55rem 0.7rem; border-bottom: 1px solid #f0f0f0; font-size: 0.88rem; vertical-align: top; }
|
|
78
|
+
th { background: #f6f6f8; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.72rem; color: #555; }
|
|
79
|
+
td.meta { color: #777; font-size: 0.8rem; }
|
|
80
|
+
.tier { font-weight: 700; padding: 0.1rem 0.5rem; border-radius: 3px; color: #fff; }
|
|
81
|
+
.tier.a { background: hsl(140, 60%, 38%); }
|
|
82
|
+
.tier.b { background: hsl(195, 60%, 42%); }
|
|
83
|
+
.tier.c { background: hsl(36, 80%, 45%); }
|
|
84
|
+
.tier.d { background: hsl(8, 55%, 50%); }
|
|
85
|
+
.muted { color: #999; }
|
|
86
|
+
code { background: #f0f0f3; padding: 0.05rem 0.4rem; border-radius: 2px; font-size: 0.78em; }
|
|
87
|
+
a { color: hsl(270, 70%, 45%); text-decoration: none; }
|
|
88
|
+
a:hover { text-decoration: underline; }
|
|
89
|
+
.empty { padding: 3rem; text-align: center; color: #777; }
|
|
90
|
+
</style>
|
|
91
|
+
</head>
|
|
92
|
+
<body>
|
|
93
|
+
<header>
|
|
94
|
+
<h1>mcp-jsa tracker</h1>
|
|
95
|
+
<span class="meta">${counts.total ?? 0} jobs in pipeline · /files served from output/</span>
|
|
96
|
+
</header>
|
|
97
|
+
<main>
|
|
98
|
+
<div class="cards">
|
|
99
|
+
${card('Sourced', counts.sourced)}
|
|
100
|
+
${card('Ready to apply', counts.ready_to_apply)}
|
|
101
|
+
${card('Materials drafted', counts.materials_drafted)}
|
|
102
|
+
${card('Applied', counts.applied)}
|
|
103
|
+
${card('Screen', counts.screen)}
|
|
104
|
+
${card('Onsite', counts.onsite)}
|
|
105
|
+
${card('Offer', counts.offer)}
|
|
106
|
+
${card('Rejected', counts.rejected)}
|
|
107
|
+
</div>
|
|
108
|
+
${rows.length === 0 ? `
|
|
109
|
+
<div class="empty">
|
|
110
|
+
No jobs yet. Wire your chat client to <code>evaluate_job</code> and paste a JD or URL to get started.
|
|
111
|
+
</div>` : `
|
|
112
|
+
<table>
|
|
113
|
+
<thead>
|
|
114
|
+
<tr>
|
|
115
|
+
<th>Score</th>
|
|
116
|
+
<th>Company</th>
|
|
117
|
+
<th>Title</th>
|
|
118
|
+
<th>Role</th>
|
|
119
|
+
<th>Level</th>
|
|
120
|
+
<th>Status</th>
|
|
121
|
+
<th>Location</th>
|
|
122
|
+
<th>Report</th>
|
|
123
|
+
<th>Discovered</th>
|
|
124
|
+
</tr>
|
|
125
|
+
</thead>
|
|
126
|
+
<tbody>${tbody}</tbody>
|
|
127
|
+
</table>`}
|
|
128
|
+
</main>
|
|
129
|
+
</body></html>`;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../src/http/dashboard.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,gDAAgD;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,UAAU,eAAe;IAC7B,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC;;;;;;;;;;;;;GAazB,CAAC,CAAC,GAAG,EAA4B,CAAC;IAEnC,gDAAgD;IAChD,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC;;;;;;;;;;;;GAYvB,CAAC,CAAC,GAAG,EAAW,CAAC;IAElB,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,CAAS,EAAE,EAAE,CACxC,oCAAoC,CAAC,IAAI,CAAC,0BAA0B,KAAK,cAAc,CAAC;IAE1F,MAAM,IAAI,GAAG,CAAC,CAAgB,EAAE,EAAE;QAChC,IAAI,CAAC,IAAI,IAAI;YAAE,OAAO,mCAAmC,CAAC;QAC1D,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,wBAAwB,CAAC,SAAS,CAAC;QACvD,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,wBAAwB,CAAC,SAAS,CAAC;QACvD,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,wBAAwB,CAAC,SAAS,CAAC;QACvD,OAAO,wBAAwB,CAAC,SAAS,CAAC;IAC5C,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;;YAEpB,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;YACnB,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;qBACjB,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,oCAAoC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;YACxF,UAAU,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC;YACjC,UAAU,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;kBACvB,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1B,UAAU,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;YAC5B,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,mBAAmB,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,8BAA8B;yBAC9F,UAAU,CAAC,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;;GAEtE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAmCc,MAAM,CAAC,KAAK,IAAI,CAAC;;;;MAIlC,IAAI,CAAC,SAAS,EAAY,MAAM,CAAC,OAAO,CAAC;MACzC,IAAI,CAAC,gBAAgB,EAAK,MAAM,CAAC,cAAc,CAAC;MAChD,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,iBAAiB,CAAC;MACnD,IAAI,CAAC,SAAS,EAAY,MAAM,CAAC,OAAO,CAAC;MACzC,IAAI,CAAC,QAAQ,EAAa,MAAM,CAAC,MAAM,CAAC;MACxC,IAAI,CAAC,QAAQ,EAAa,MAAM,CAAC,MAAM,CAAC;MACxC,IAAI,CAAC,OAAO,EAAc,MAAM,CAAC,KAAK,CAAC;MACvC,IAAI,CAAC,UAAU,EAAW,MAAM,CAAC,QAAQ,CAAC;;IAE5C,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;;;WAGb,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;eAeC,KAAK;aACP;;eAEE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Tool-registration pattern.
|
|
2
|
+
//
|
|
3
|
+
// Each tool file exports a single `ToolDef` from `defineTool({...})`. The MCP server
|
|
4
|
+
// registers them all in one loop (see src/mcp/server.ts). Benefits:
|
|
5
|
+
// - one place to standardize result shape (okResult / errResult)
|
|
6
|
+
// - tools/list comes from a single typed array; adding a new tool is one import + one
|
|
7
|
+
// line in the registry
|
|
8
|
+
// - tests can call `tool.handler(args)` directly without spinning up the transport
|
|
9
|
+
//
|
|
10
|
+
// Conventions:
|
|
11
|
+
// - inputSchema is a `ZodRawShape` (object map of zod types), NOT a full ZodObject
|
|
12
|
+
// - handler returns `ToolResult` — okResult(payload) for success, errResult(msg) for failure
|
|
13
|
+
// - any tool that does writes must wrap the write in `runInWriteLock(...)`
|
|
14
|
+
export function defineTool(d) {
|
|
15
|
+
return d;
|
|
16
|
+
}
|
|
17
|
+
export function registerTools(server, tools) {
|
|
18
|
+
for (const t of tools) {
|
|
19
|
+
server.registerTool(t.name, { title: t.title, description: t.description, inputSchema: t.inputSchema }, t.handler);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ── Shared result helpers ────────────────────────────────────────────────────
|
|
23
|
+
export function okResult(payload) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
26
|
+
structuredContent: payload,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function errResult(message) {
|
|
30
|
+
return {
|
|
31
|
+
isError: true,
|
|
32
|
+
content: [{ type: 'text', text: message }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=define.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.js","sourceRoot":"","sources":["../../src/mcp/define.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,qFAAqF;AACrF,oEAAoE;AACpE,mEAAmE;AACnE,wFAAwF;AACxF,2BAA2B;AAC3B,qFAAqF;AACrF,EAAE;AACF,eAAe;AACf,qFAAqF;AACrF,+FAA+F;AAC/F,6EAA6E;AAmB7E,MAAM,UAAU,UAAU,CAA0B,CAAa;IAC/D,OAAO,CAAC,CAAC;AACX,CAAC;AAOD,MAAM,UAAU,aAAa,CAAC,MAAiB,EAAE,KAAgC;IAC/E,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,YAAY,CACjB,CAAC,CAAC,IAAI,EACN,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,EAC1E,CAAC,CAAC,OAAc,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,gFAAgF;AAEhF,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO;QACL,OAAO,EAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAC7E,iBAAiB,EAAE,OAAO;KAC3B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;KAC3C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// MCP server wiring.
|
|
2
|
+
//
|
|
3
|
+
// Stateless mode: one fresh McpServer per HTTP request. For a local single-user process
|
|
4
|
+
// the per-request setup is microseconds and the lifecycle is dramatically simpler than
|
|
5
|
+
// session-based mode (no Map<sessionId, transport>, no GC).
|
|
6
|
+
//
|
|
7
|
+
// Tool registration follows src/mcp/define.ts — every tool exports a single ToolDef.
|
|
8
|
+
// To add a tool: write src/mcp/tools/<name>.ts that exports `<name>Tool`, import it
|
|
9
|
+
// below, and add it to ALL_TOOLS. resources/list comes from src/core/resources.ts.
|
|
10
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
12
|
+
import { config } from '../config.js';
|
|
13
|
+
import { listResources } from '../core/resources.js';
|
|
14
|
+
import { registerTools } from './define.js';
|
|
15
|
+
// ── Tool imports ─────────────────────────────────────────────────────────────
|
|
16
|
+
// Keep this block ordered by the brief's grouping for at-a-glance navigation.
|
|
17
|
+
// M1 — eval & PDFs
|
|
18
|
+
import { evaluateJobTool } from './tools/evaluate_job.js';
|
|
19
|
+
import { renderPdfTool } from './tools/render_pdf.js';
|
|
20
|
+
import { getReportTool } from './tools/get_report.js';
|
|
21
|
+
// G1 — tracker queries + mutators
|
|
22
|
+
import { getTopJobsTool, getTrackerTool, updateStatusTool, markReadyToApplyTool } from './tools/tracker.js';
|
|
23
|
+
// G2 — portal scanner
|
|
24
|
+
import { scanPortalsTool } from './tools/scan_portals.js';
|
|
25
|
+
// G3 — outreach
|
|
26
|
+
import { findWarmIntrosTool, findFoundersTool, draftOutreachTool, getOutreachQueueTool, updateOutreachTool, getFollowupsDueTool, draftFollowupTool, draftReplyTool, } from './tools/outreach.js';
|
|
27
|
+
// G4 — visa
|
|
28
|
+
import { visaSignalTool, importH1bTool, importLinkedinTool } from './tools/visa.js';
|
|
29
|
+
// G5 — stories + negotiation
|
|
30
|
+
import { extractStoriesTool, getStoryBankTool, negotiationBriefTool } from './tools/stories.js';
|
|
31
|
+
// G6 — batch + materials
|
|
32
|
+
import { batchEvaluateTool } from './tools/batch_evaluate.js';
|
|
33
|
+
import { generateMaterialsTool } from './tools/generate_materials.js';
|
|
34
|
+
// G7 — training/project/research/digest + profile ops
|
|
35
|
+
import { evaluateTrainingTool, evaluateProjectTool, deepResearchTool, dailyDigestTool, getCareerPacketTool, updateCareerPacketTool, enrichCompanyTool, costEstimateTool, } from './tools/ops.js';
|
|
36
|
+
// G8 — apply prefill
|
|
37
|
+
import { applyPrefillTool } from './tools/apply_prefill.js';
|
|
38
|
+
// G9 — scheduler
|
|
39
|
+
import { schedulerStatusTool, schedulerEnableTool, schedulerDisableTool } from './tools/scheduler.js';
|
|
40
|
+
// Tools tagged "visa" are hidden from tools/list when MCP_JSA_VISA_SCORING=false.
|
|
41
|
+
const VISA_TOOL_NAMES = new Set([
|
|
42
|
+
'visa_signal', 'import_h1b', 'import_linkedin',
|
|
43
|
+
]);
|
|
44
|
+
const FULL_TOOLSET = [
|
|
45
|
+
evaluateJobTool, renderPdfTool, getReportTool,
|
|
46
|
+
getTopJobsTool, getTrackerTool, updateStatusTool, markReadyToApplyTool,
|
|
47
|
+
scanPortalsTool,
|
|
48
|
+
findWarmIntrosTool, findFoundersTool, draftOutreachTool, getOutreachQueueTool,
|
|
49
|
+
updateOutreachTool, getFollowupsDueTool, draftFollowupTool, draftReplyTool,
|
|
50
|
+
visaSignalTool, importH1bTool, importLinkedinTool,
|
|
51
|
+
extractStoriesTool, getStoryBankTool, negotiationBriefTool,
|
|
52
|
+
batchEvaluateTool, generateMaterialsTool,
|
|
53
|
+
evaluateTrainingTool, evaluateProjectTool, deepResearchTool, dailyDigestTool,
|
|
54
|
+
getCareerPacketTool, updateCareerPacketTool, enrichCompanyTool, costEstimateTool,
|
|
55
|
+
applyPrefillTool,
|
|
56
|
+
schedulerStatusTool, schedulerEnableTool, schedulerDisableTool,
|
|
57
|
+
];
|
|
58
|
+
const ALL_TOOLS = config.visaScoringEnabled
|
|
59
|
+
? FULL_TOOLSET
|
|
60
|
+
: FULL_TOOLSET.filter(t => !VISA_TOOL_NAMES.has(t.name));
|
|
61
|
+
// Tools and resources are static — one McpServer instance is reused across all requests.
|
|
62
|
+
// Only the transport is per-request in stateless mode.
|
|
63
|
+
let _server = null;
|
|
64
|
+
function getMcpServer() {
|
|
65
|
+
if (_server)
|
|
66
|
+
return _server;
|
|
67
|
+
_server = new McpServer({ name: 'mcp-jsa', version: '0.2.0' });
|
|
68
|
+
registerTools(_server, ALL_TOOLS);
|
|
69
|
+
for (const r of listResources()) {
|
|
70
|
+
_server.registerResource(r.name, r.uri, { title: r.title, description: r.description, mimeType: r.mimeType }, async () => ({ contents: [{ uri: r.uri, mimeType: r.mimeType, text: r.body() }] }));
|
|
71
|
+
}
|
|
72
|
+
return _server;
|
|
73
|
+
}
|
|
74
|
+
export function mountMcp(app, path = '/mcp') {
|
|
75
|
+
const server = getMcpServer();
|
|
76
|
+
const handle = async (req, res) => {
|
|
77
|
+
const transport = new StreamableHTTPServerTransport({
|
|
78
|
+
sessionIdGenerator: undefined,
|
|
79
|
+
enableJsonResponse: true,
|
|
80
|
+
});
|
|
81
|
+
res.on('close', () => {
|
|
82
|
+
transport.close().catch(() => undefined);
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
await server.connect(transport);
|
|
86
|
+
await transport.handleRequest(req, res, req.body);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (!res.headersSent) {
|
|
90
|
+
res.status(500).json({
|
|
91
|
+
jsonrpc: '2.0',
|
|
92
|
+
error: { code: -32603, message: err?.message ?? 'internal error' },
|
|
93
|
+
id: null,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
app.post(path, handle);
|
|
99
|
+
app.get(path, (_req, res) => res.status(405).end());
|
|
100
|
+
app.delete(path, (_req, res) => res.status(405).end());
|
|
101
|
+
}
|
|
102
|
+
export function listAllTools() { return ALL_TOOLS; }
|
|
103
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// apply_prefill — Playwright opens the application form, reads visible fields, drafts
|
|
2
|
+
// values from career_packet + the tailored materials, takes a screenshot, returns a
|
|
3
|
+
// preview. NEVER auto-submits. Brief is explicit: human-in-the-loop everywhere.
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { config } from '../../config.js';
|
|
9
|
+
import { getDb } from '../../db.js';
|
|
10
|
+
import { defineTool, okResult, errResult } from '../define.js';
|
|
11
|
+
import { getActiveCareerPacket, loadProjectFiles } from '../../core/profile.js';
|
|
12
|
+
import { getJobWithCompany } from '../../core/jobs.js';
|
|
13
|
+
import { getSharedBrowser } from '../../core/browser.js';
|
|
14
|
+
import { safeJson } from '../../core/llm.js';
|
|
15
|
+
const PREVIEW_SUBDIR = 'previews';
|
|
16
|
+
export const applyPrefillTool = defineTool({
|
|
17
|
+
name: 'apply_prefill',
|
|
18
|
+
title: 'Apply prefill (preview only — never submits)',
|
|
19
|
+
description: 'Opens the application URL in Playwright, detects common form fields, drafts values from your profile + ' +
|
|
20
|
+
'tailored materials, and saves a screenshot. Returns a preview + localhost links to the rendered PDFs. ' +
|
|
21
|
+
'NEVER submits, NEVER fills file inputs (resume/cover upload stays manual).',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
job_id: z.string().min(1),
|
|
24
|
+
url: z.string().url().optional().describe('Override the job source_url (e.g. a different ATS apply URL).'),
|
|
25
|
+
},
|
|
26
|
+
handler: async (args) => {
|
|
27
|
+
const job = getJobWithCompany(args.job_id);
|
|
28
|
+
if (!job)
|
|
29
|
+
return errResult(`No job ${args.job_id}`);
|
|
30
|
+
const url = args.url ?? job.source_url;
|
|
31
|
+
if (!url || !/^https?:\/\//.test(url))
|
|
32
|
+
return errResult(`No usable application URL for job ${args.job_id}`);
|
|
33
|
+
const app = getDb().prepare(`SELECT * FROM applications WHERE job_id = ?`).get(args.job_id);
|
|
34
|
+
const { profile } = loadProjectFiles();
|
|
35
|
+
const packet = getActiveCareerPacket()?.content ?? '';
|
|
36
|
+
// Build a lookup of values we can confidently draft.
|
|
37
|
+
const identity = (profile?.candidate ?? {});
|
|
38
|
+
const tailored = safeJson(app?.tailored_bullets, null);
|
|
39
|
+
const coverDraft = app?.cover_letter_draft ?? null;
|
|
40
|
+
try {
|
|
41
|
+
const browser = await getSharedBrowser();
|
|
42
|
+
const page = await browser.newPage();
|
|
43
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 30_000 });
|
|
44
|
+
await page.waitForTimeout(1_500);
|
|
45
|
+
// Detect form inputs. We deliberately stay vanilla: input + textarea + select; skip
|
|
46
|
+
// file/password/hidden; honor <label for="..."> AND aria-label AND placeholder.
|
|
47
|
+
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
|
+
const lookupLabel = (el) => {
|
|
49
|
+
if (el.getAttribute('aria-label'))
|
|
50
|
+
return el.getAttribute('aria-label');
|
|
51
|
+
const id = el.getAttribute('id');
|
|
52
|
+
if (id) {
|
|
53
|
+
const lab = document.querySelector(`label[for="${window.CSS.escape(id)}"]`);
|
|
54
|
+
if (lab)
|
|
55
|
+
return lab.innerText.trim();
|
|
56
|
+
}
|
|
57
|
+
const parentLab = el.closest('label');
|
|
58
|
+
if (parentLab)
|
|
59
|
+
return parentLab.innerText.trim();
|
|
60
|
+
return el.getAttribute('placeholder') || el.getAttribute('name') || '';
|
|
61
|
+
};
|
|
62
|
+
const sel = (el) => {
|
|
63
|
+
const id = el.getAttribute('id');
|
|
64
|
+
if (id)
|
|
65
|
+
return `#${id}`;
|
|
66
|
+
const name = el.getAttribute('name');
|
|
67
|
+
if (name)
|
|
68
|
+
return `[name="${name}"]`;
|
|
69
|
+
return el.tagName.toLowerCase();
|
|
70
|
+
};
|
|
71
|
+
return els.slice(0, 60).map((el) => ({
|
|
72
|
+
selector: sel(el),
|
|
73
|
+
label: lookupLabel(el),
|
|
74
|
+
type: (el.getAttribute('type') || el.tagName).toLowerCase(),
|
|
75
|
+
required: !!el.required || el.getAttribute('aria-required') === 'true',
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
// Screenshot
|
|
79
|
+
mkdirSync(resolve(config.outputDir, PREVIEW_SUBDIR), { recursive: true });
|
|
80
|
+
const shotPath = `${PREVIEW_SUBDIR}/apply-${args.job_id.slice(0, 8)}-${Date.now()}.png`;
|
|
81
|
+
const absShot = resolve(config.outputDir, shotPath);
|
|
82
|
+
await page.screenshot({ path: absShot, fullPage: true });
|
|
83
|
+
const fields = detected.map((d) => draftValue(d, identity, packet, tailored, coverDraft));
|
|
84
|
+
const previewId = randomUUID();
|
|
85
|
+
const previewJson = {
|
|
86
|
+
preview_id: previewId,
|
|
87
|
+
job_id: args.job_id,
|
|
88
|
+
url,
|
|
89
|
+
company: job.company_name,
|
|
90
|
+
title: job.title,
|
|
91
|
+
warning: 'preview only — this tool never submits. Upload resume/cover manually using the localhost links below.',
|
|
92
|
+
fields,
|
|
93
|
+
resume_url: app?.resume_path ? `${config.baseUrl}/files/${app.resume_path}` : null,
|
|
94
|
+
cover_url: app?.cover_path ? `${config.baseUrl}/files/${app.cover_path}` : null,
|
|
95
|
+
screenshot_url: `${config.baseUrl}/files/${shotPath}`,
|
|
96
|
+
};
|
|
97
|
+
// Persist a JSON copy alongside the screenshot for the chat to re-read.
|
|
98
|
+
writeFileSync(absShot.replace(/\.png$/, '.json'), JSON.stringify(previewJson, null, 2), 'utf-8');
|
|
99
|
+
await page.close();
|
|
100
|
+
return okResult(previewJson);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
return errResult(`apply_prefill failed: ${e?.message ?? String(e)}`);
|
|
104
|
+
}
|
|
105
|
+
// Shared browser stays alive for the next caller — closed at server shutdown.
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
function draftValue(d, identity, _packet, tailored, coverDraft) {
|
|
109
|
+
const key = (d.label + ' ' + d.selector).toLowerCase();
|
|
110
|
+
const profileMatch = (...needles) => needles.some(n => key.includes(n));
|
|
111
|
+
let value = '';
|
|
112
|
+
let source = 'user_must_provide';
|
|
113
|
+
if (profileMatch('first name', 'firstname', 'given')) {
|
|
114
|
+
value = (identity.full_name ?? '').split(' ')[0] ?? '';
|
|
115
|
+
source = 'profile';
|
|
116
|
+
}
|
|
117
|
+
else if (profileMatch('last name', 'lastname', 'surname', 'family')) {
|
|
118
|
+
value = (identity.full_name ?? '').split(' ').slice(1).join(' ').trim();
|
|
119
|
+
source = 'profile';
|
|
120
|
+
}
|
|
121
|
+
else if (profileMatch('full name', 'name')) {
|
|
122
|
+
value = identity.full_name ?? '';
|
|
123
|
+
source = 'profile';
|
|
124
|
+
}
|
|
125
|
+
else if (profileMatch('email')) {
|
|
126
|
+
value = identity.email ?? '';
|
|
127
|
+
source = 'profile';
|
|
128
|
+
}
|
|
129
|
+
else if (profileMatch('phone', 'telephone', 'mobile')) {
|
|
130
|
+
value = identity.phone ?? '';
|
|
131
|
+
source = 'profile';
|
|
132
|
+
}
|
|
133
|
+
else if (profileMatch('linkedin')) {
|
|
134
|
+
value = identity.linkedin ?? '';
|
|
135
|
+
source = 'profile';
|
|
136
|
+
}
|
|
137
|
+
else if (profileMatch('github')) {
|
|
138
|
+
value = identity.github ?? '';
|
|
139
|
+
source = 'profile';
|
|
140
|
+
}
|
|
141
|
+
else if (profileMatch('portfolio', 'website', 'personal site', 'url')) {
|
|
142
|
+
value = identity.portfolio_url ?? identity.github ?? '';
|
|
143
|
+
source = 'profile';
|
|
144
|
+
}
|
|
145
|
+
else if (profileMatch('city', 'location')) {
|
|
146
|
+
value = identity.location ?? '';
|
|
147
|
+
source = 'profile';
|
|
148
|
+
}
|
|
149
|
+
else if (profileMatch('cover letter', 'why', 'interest', 'message', 'tell us')) {
|
|
150
|
+
value = coverDraft ?? '';
|
|
151
|
+
source = 'materials';
|
|
152
|
+
}
|
|
153
|
+
else if (profileMatch('summary', 'tagline', 'bio', 'about')) {
|
|
154
|
+
value = (tailored?.tagline ?? '') || '';
|
|
155
|
+
source = 'materials';
|
|
156
|
+
}
|
|
157
|
+
// Honour the visa rail explicitly — refuse to draft anything for these.
|
|
158
|
+
if (profileMatch('visa', 'sponsor', 'authoriz', 'authoris', 'citizen', 'work permit', 'h1b', 'opt')) {
|
|
159
|
+
value = '';
|
|
160
|
+
source = 'user_must_provide';
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
selector: d.selector, label: d.label, type: d.type, required: !!d.required,
|
|
164
|
+
draft_value: value, source,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=apply_prefill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply_prefill.js","sourceRoot":"","sources":["../../../src/mcp/tools/apply_prefill.ts"],"names":[],"mappings":"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"}
|