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
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
-- mcp-jsa initial schema.
|
|
2
|
+
-- Collapses the JSA Postgres schema (study guide §3) + career-ops side-tables (eval_reports,
|
|
3
|
+
-- story_bank, negotiation_notes) into a single SQLite file. Shapes match JSA columns so SQL
|
|
4
|
+
-- recipes from the study guide (§10, §12.9) port over with minimal edits.
|
|
5
|
+
--
|
|
6
|
+
-- Conventions:
|
|
7
|
+
-- - id: TEXT, lowercase UUIDv4 generated in app code via crypto.randomUUID()
|
|
8
|
+
-- - timestamps: TEXT, ISO-8601 with timezone (CURRENT_TIMESTAMP yields 'YYYY-MM-DD HH:MM:SS')
|
|
9
|
+
-- - JSONB → TEXT (parsed in app); accessed via SQLite json_*() functions for views
|
|
10
|
+
-- - booleans → INTEGER 0/1
|
|
11
|
+
-- Status columns use CHECK constraints (canonical states from the brief + JSA + career-ops).
|
|
12
|
+
|
|
13
|
+
PRAGMA foreign_keys = ON;
|
|
14
|
+
|
|
15
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
-- companies
|
|
17
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
CREATE TABLE companies (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
name TEXT NOT NULL,
|
|
21
|
+
name_normalized TEXT NOT NULL UNIQUE,
|
|
22
|
+
website TEXT,
|
|
23
|
+
linkedin_url TEXT,
|
|
24
|
+
hq_city TEXT,
|
|
25
|
+
hq_country TEXT,
|
|
26
|
+
headcount_range TEXT,
|
|
27
|
+
funding_stage TEXT,
|
|
28
|
+
notes TEXT,
|
|
29
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
30
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE INDEX idx_companies_name_normalized ON companies(name_normalized);
|
|
34
|
+
|
|
35
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
-- company_aliases — alternate spellings (LinkedIn / DOL / etc.)
|
|
37
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
CREATE TABLE company_aliases (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
41
|
+
alias TEXT NOT NULL,
|
|
42
|
+
alias_normalized TEXT NOT NULL,
|
|
43
|
+
source TEXT,
|
|
44
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
45
|
+
UNIQUE (alias_normalized, source)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE INDEX idx_company_aliases_alias_norm ON company_aliases(alias_normalized);
|
|
49
|
+
CREATE INDEX idx_company_aliases_company_id ON company_aliases(company_id);
|
|
50
|
+
|
|
51
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
52
|
+
-- target_companies — what the pollers actually scrape
|
|
53
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
CREATE TABLE target_companies (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
57
|
+
greenhouse_slug TEXT,
|
|
58
|
+
ashby_slug TEXT,
|
|
59
|
+
lever_slug TEXT,
|
|
60
|
+
workday_url TEXT,
|
|
61
|
+
careers_url TEXT,
|
|
62
|
+
priority INTEGER NOT NULL DEFAULT 2,
|
|
63
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
64
|
+
last_polled_at TEXT,
|
|
65
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
66
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX idx_target_companies_active ON target_companies(is_active);
|
|
70
|
+
|
|
71
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
-- jobs — main table; status state machine matches the brief exactly
|
|
73
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
CREATE TABLE jobs (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
source TEXT NOT NULL, -- greenhouse | ashby | lever | workday | manual | paste | ...
|
|
77
|
+
source_job_id TEXT,
|
|
78
|
+
source_url TEXT NOT NULL,
|
|
79
|
+
content_hash TEXT UNIQUE,
|
|
80
|
+
company_id TEXT REFERENCES companies(id),
|
|
81
|
+
company_name_raw TEXT NOT NULL,
|
|
82
|
+
title TEXT NOT NULL,
|
|
83
|
+
role_category TEXT, -- pm | ml_eng | data_eng | analytics_eng | swe | forward_deployed | other
|
|
84
|
+
seniority TEXT, -- intern | junior | mid | senior | staff | principal | lead | unclear
|
|
85
|
+
location_raw TEXT,
|
|
86
|
+
location_city TEXT,
|
|
87
|
+
location_region TEXT,
|
|
88
|
+
location_country TEXT,
|
|
89
|
+
is_remote INTEGER,
|
|
90
|
+
is_hybrid INTEGER,
|
|
91
|
+
comp_min_usd INTEGER,
|
|
92
|
+
comp_max_usd INTEGER,
|
|
93
|
+
comp_currency TEXT DEFAULT 'USD',
|
|
94
|
+
description TEXT,
|
|
95
|
+
description_html TEXT,
|
|
96
|
+
requirements TEXT,
|
|
97
|
+
sponsors_visa INTEGER,
|
|
98
|
+
visa_signal_source TEXT,
|
|
99
|
+
visa_notes TEXT,
|
|
100
|
+
status TEXT NOT NULL DEFAULT 'sourced'
|
|
101
|
+
CHECK (status IN (
|
|
102
|
+
'sourced','ready_to_apply','materials_drafted','ready_to_review',
|
|
103
|
+
'applied','screen','onsite','offer','rejected','discarded','skip'
|
|
104
|
+
)),
|
|
105
|
+
declared_archetype TEXT, -- optional user override of role_category for scoring
|
|
106
|
+
score_total INTEGER, -- 0..100
|
|
107
|
+
score_resume_fit INTEGER,
|
|
108
|
+
score_taste_fit INTEGER,
|
|
109
|
+
score_visa_fit INTEGER,
|
|
110
|
+
score_detail TEXT, -- JSON: reasoning, concerns, parse_error, raw
|
|
111
|
+
scored_at TEXT,
|
|
112
|
+
materials_generated_at TEXT,
|
|
113
|
+
applied_at TEXT,
|
|
114
|
+
posted_at TEXT,
|
|
115
|
+
discovered_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
116
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
117
|
+
UNIQUE (source, source_job_id)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE INDEX idx_jobs_company_id ON jobs(company_id);
|
|
121
|
+
CREATE INDEX idx_jobs_status ON jobs(status);
|
|
122
|
+
CREATE INDEX idx_jobs_score_total ON jobs(score_total DESC);
|
|
123
|
+
CREATE INDEX idx_jobs_discovered_at ON jobs(discovered_at DESC);
|
|
124
|
+
CREATE INDEX idx_jobs_role_category ON jobs(role_category);
|
|
125
|
+
CREATE INDEX idx_jobs_location_ctry ON jobs(location_country);
|
|
126
|
+
|
|
127
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
128
|
+
-- linkedin_connections — imported network
|
|
129
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
130
|
+
CREATE TABLE linkedin_connections (
|
|
131
|
+
id TEXT PRIMARY KEY,
|
|
132
|
+
first_name TEXT,
|
|
133
|
+
last_name TEXT,
|
|
134
|
+
full_name TEXT,
|
|
135
|
+
email TEXT,
|
|
136
|
+
linkedin_url TEXT UNIQUE,
|
|
137
|
+
twitter_url TEXT,
|
|
138
|
+
company_id TEXT REFERENCES companies(id),
|
|
139
|
+
company_raw TEXT,
|
|
140
|
+
position TEXT,
|
|
141
|
+
seniority_inferred TEXT,
|
|
142
|
+
is_recruiter INTEGER NOT NULL DEFAULT 0,
|
|
143
|
+
is_engineering INTEGER NOT NULL DEFAULT 0,
|
|
144
|
+
is_leadership INTEGER NOT NULL DEFAULT 0,
|
|
145
|
+
preferred_channel TEXT DEFAULT 'linkedin',
|
|
146
|
+
outreach_notes TEXT,
|
|
147
|
+
notes TEXT,
|
|
148
|
+
connected_on TEXT,
|
|
149
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
150
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
CREATE INDEX idx_li_company_id ON linkedin_connections(company_id);
|
|
154
|
+
CREATE INDEX idx_li_full_name ON linkedin_connections(full_name);
|
|
155
|
+
CREATE INDEX idx_li_position ON linkedin_connections(position);
|
|
156
|
+
CREATE INDEX idx_li_is_recruiter ON linkedin_connections(is_recruiter);
|
|
157
|
+
|
|
158
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
159
|
+
-- h1b_filings — DOL OFLC quarterly data
|
|
160
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
161
|
+
CREATE TABLE h1b_filings (
|
|
162
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
163
|
+
case_number TEXT NOT NULL UNIQUE,
|
|
164
|
+
case_status TEXT NOT NULL,
|
|
165
|
+
visa_class TEXT,
|
|
166
|
+
employer_id TEXT REFERENCES companies(id),
|
|
167
|
+
employer_name_raw TEXT NOT NULL,
|
|
168
|
+
job_title TEXT,
|
|
169
|
+
soc_code TEXT,
|
|
170
|
+
soc_title TEXT,
|
|
171
|
+
work_city TEXT,
|
|
172
|
+
work_state TEXT,
|
|
173
|
+
work_postal_code TEXT,
|
|
174
|
+
wage_rate_from REAL,
|
|
175
|
+
wage_rate_to REAL,
|
|
176
|
+
wage_unit TEXT,
|
|
177
|
+
prevailing_wage REAL,
|
|
178
|
+
received_date TEXT,
|
|
179
|
+
decision_date TEXT,
|
|
180
|
+
employment_start TEXT,
|
|
181
|
+
employment_end TEXT,
|
|
182
|
+
full_time INTEGER,
|
|
183
|
+
new_employment INTEGER,
|
|
184
|
+
fiscal_year INTEGER NOT NULL,
|
|
185
|
+
raw_json TEXT,
|
|
186
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
CREATE INDEX idx_h1b_employer_id ON h1b_filings(employer_id);
|
|
190
|
+
CREATE INDEX idx_h1b_employer_raw ON h1b_filings(employer_name_raw);
|
|
191
|
+
CREATE INDEX idx_h1b_soc_code ON h1b_filings(soc_code);
|
|
192
|
+
CREATE INDEX idx_h1b_decision_date ON h1b_filings(decision_date DESC);
|
|
193
|
+
CREATE INDEX idx_h1b_fiscal_year ON h1b_filings(fiscal_year);
|
|
194
|
+
CREATE INDEX idx_h1b_case_status ON h1b_filings(case_status);
|
|
195
|
+
|
|
196
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
197
|
+
-- outreach — every warm-intro / founder DM lives here
|
|
198
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
199
|
+
CREATE TABLE outreach (
|
|
200
|
+
id TEXT PRIMARY KEY,
|
|
201
|
+
connection_id TEXT REFERENCES linkedin_connections(id) ON DELETE SET NULL,
|
|
202
|
+
company_id TEXT REFERENCES companies(id),
|
|
203
|
+
related_job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
|
|
204
|
+
outreach_type TEXT NOT NULL
|
|
205
|
+
CHECK (outreach_type IN ('warm_intro_request','founder_dm','recruiter_followup','generic','followup')),
|
|
206
|
+
channel TEXT NOT NULL DEFAULT 'linkedin',
|
|
207
|
+
status TEXT NOT NULL DEFAULT 'queued'
|
|
208
|
+
CHECK (status IN ('queued','drafted','edited','sent','replied','dead','success')),
|
|
209
|
+
draft_message TEXT,
|
|
210
|
+
edited_message TEXT,
|
|
211
|
+
subject_line TEXT,
|
|
212
|
+
reply_text TEXT,
|
|
213
|
+
notes TEXT,
|
|
214
|
+
sent_at TEXT,
|
|
215
|
+
replied_at TEXT,
|
|
216
|
+
followup_due_at TEXT,
|
|
217
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
218
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
CREATE INDEX idx_outreach_status ON outreach(status);
|
|
222
|
+
CREATE INDEX idx_outreach_connection ON outreach(connection_id);
|
|
223
|
+
CREATE INDEX idx_outreach_followup_due ON outreach(followup_due_at);
|
|
224
|
+
CREATE INDEX idx_outreach_company ON outreach(company_id);
|
|
225
|
+
|
|
226
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
227
|
+
-- applications — one per (job × materials version)
|
|
228
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
CREATE TABLE applications (
|
|
230
|
+
id TEXT PRIMARY KEY,
|
|
231
|
+
job_id TEXT NOT NULL UNIQUE REFERENCES jobs(id) ON DELETE CASCADE,
|
|
232
|
+
status TEXT NOT NULL DEFAULT 'materials_drafted'
|
|
233
|
+
CHECK (status IN (
|
|
234
|
+
'materials_drafted','render_error','ready_to_review','applied','screen',
|
|
235
|
+
'onsite','offer','rejected','discarded'
|
|
236
|
+
)),
|
|
237
|
+
tailored_bullets TEXT, -- JSON: { tagline, experience_bullets: {<employer_slug>: [...]}, projects_section, skills_section }
|
|
238
|
+
cover_letter_draft TEXT, -- plain prose, 250-350 words
|
|
239
|
+
tailoring_notes TEXT,
|
|
240
|
+
resume_path TEXT, -- path under output/ (returned as localhost link)
|
|
241
|
+
cover_path TEXT,
|
|
242
|
+
resume_tex TEXT,
|
|
243
|
+
cover_letter_tex TEXT,
|
|
244
|
+
materials_v INTEGER NOT NULL DEFAULT 1,
|
|
245
|
+
last_status_change_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
246
|
+
notes TEXT,
|
|
247
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
248
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
252
|
+
-- enrichment — Brave / web research summaries with 30-day TTL
|
|
253
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
254
|
+
CREATE TABLE enrichment (
|
|
255
|
+
id TEXT PRIMARY KEY,
|
|
256
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
257
|
+
kind TEXT NOT NULL CHECK (kind IN ('comp','culture','recent_news')),
|
|
258
|
+
raw_search_results TEXT, -- JSON
|
|
259
|
+
summary TEXT,
|
|
260
|
+
confidence_score INTEGER, -- 0..100
|
|
261
|
+
signal_quality TEXT, -- strong | mixed | weak | none
|
|
262
|
+
flags TEXT,
|
|
263
|
+
source_urls TEXT, -- JSON array
|
|
264
|
+
expires_at TEXT NOT NULL,
|
|
265
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
266
|
+
UNIQUE (company_id, kind)
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
CREATE INDEX idx_enrichment_expires_at ON enrichment(expires_at);
|
|
270
|
+
|
|
271
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
272
|
+
-- career_packet — single is_active row; full version history retained
|
|
273
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
274
|
+
CREATE TABLE career_packet (
|
|
275
|
+
id TEXT PRIMARY KEY,
|
|
276
|
+
version INTEGER NOT NULL,
|
|
277
|
+
content TEXT NOT NULL, -- markdown source-of-truth packet
|
|
278
|
+
taglines TEXT, -- JSON array of alternates
|
|
279
|
+
is_active INTEGER NOT NULL DEFAULT 0,
|
|
280
|
+
source_cv_hash TEXT,
|
|
281
|
+
notes TEXT,
|
|
282
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
CREATE INDEX idx_career_packet_active ON career_packet(is_active);
|
|
286
|
+
|
|
287
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
288
|
+
-- contacts — company-level contact discovery (people without LinkedIn rows)
|
|
289
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
290
|
+
CREATE TABLE contacts (
|
|
291
|
+
id TEXT PRIMARY KEY,
|
|
292
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
293
|
+
full_name TEXT,
|
|
294
|
+
email TEXT,
|
|
295
|
+
role TEXT,
|
|
296
|
+
source TEXT,
|
|
297
|
+
notes TEXT,
|
|
298
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
302
|
+
-- eval_reports — career-ops 6-block report (A–F) persisted; G is in score_detail
|
|
303
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
304
|
+
CREATE TABLE eval_reports (
|
|
305
|
+
id TEXT PRIMARY KEY,
|
|
306
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
307
|
+
mode TEXT NOT NULL CHECK (mode IN ('chat','api')),
|
|
308
|
+
archetype_detected TEXT,
|
|
309
|
+
block_role_summary TEXT, -- A
|
|
310
|
+
block_cv_match TEXT, -- B
|
|
311
|
+
block_level TEXT, -- C
|
|
312
|
+
block_comp TEXT, -- D
|
|
313
|
+
block_personalize TEXT, -- E
|
|
314
|
+
block_interview TEXT, -- F
|
|
315
|
+
block_legitimacy TEXT, -- G (career-ops) — optional, populated when chat fills it
|
|
316
|
+
keywords TEXT, -- JSON array
|
|
317
|
+
raw_input TEXT, -- normalized JD text passed to chat
|
|
318
|
+
html_path TEXT, -- under output/, served via /files/
|
|
319
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
CREATE INDEX idx_eval_reports_job ON eval_reports(job_id);
|
|
323
|
+
|
|
324
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
325
|
+
-- story_bank — STAR + Reflection stories distilled across evaluations
|
|
326
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
327
|
+
CREATE TABLE story_bank (
|
|
328
|
+
id TEXT PRIMARY KEY,
|
|
329
|
+
job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
|
|
330
|
+
story_text TEXT NOT NULL,
|
|
331
|
+
reflection TEXT,
|
|
332
|
+
competency_tags TEXT, -- JSON array
|
|
333
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
CREATE INDEX idx_story_bank_job ON story_bank(job_id);
|
|
337
|
+
|
|
338
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
339
|
+
-- negotiation_notes — per-offer negotiation worksheet
|
|
340
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
341
|
+
CREATE TABLE negotiation_notes (
|
|
342
|
+
id TEXT PRIMARY KEY,
|
|
343
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
344
|
+
framework TEXT,
|
|
345
|
+
leverage TEXT,
|
|
346
|
+
geo_pushback TEXT,
|
|
347
|
+
comp_target TEXT,
|
|
348
|
+
notes TEXT,
|
|
349
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
350
|
+
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
354
|
+
-- scheduler_state — single-row table for opt-in cron job enable/disable
|
|
355
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
356
|
+
CREATE TABLE scheduler_state (
|
|
357
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
358
|
+
enabled_jobs TEXT NOT NULL DEFAULT '[]', -- JSON array of job names
|
|
359
|
+
last_run_at TEXT,
|
|
360
|
+
notes TEXT
|
|
361
|
+
);
|
|
362
|
+
INSERT INTO scheduler_state (id, enabled_jobs) VALUES (1, '[]');
|
|
363
|
+
|
|
364
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
365
|
+
-- Views — JSA views recreated against SQLite (study guide §3.3)
|
|
366
|
+
-- ──────────────────────────────────────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
CREATE VIEW v_rated_jobs AS
|
|
369
|
+
SELECT
|
|
370
|
+
j.id AS job_id,
|
|
371
|
+
j.title,
|
|
372
|
+
c.name AS company_name,
|
|
373
|
+
j.location_raw AS location,
|
|
374
|
+
j.role_category,
|
|
375
|
+
j.seniority,
|
|
376
|
+
j.score_total,
|
|
377
|
+
j.score_resume_fit,
|
|
378
|
+
j.score_taste_fit,
|
|
379
|
+
j.score_visa_fit,
|
|
380
|
+
j.status,
|
|
381
|
+
j.source_url,
|
|
382
|
+
j.discovered_at,
|
|
383
|
+
j.scored_at
|
|
384
|
+
FROM jobs j
|
|
385
|
+
LEFT JOIN companies c ON c.id = j.company_id
|
|
386
|
+
WHERE j.score_total IS NOT NULL;
|
|
387
|
+
|
|
388
|
+
CREATE VIEW v_top_jobs AS
|
|
389
|
+
SELECT * FROM v_rated_jobs
|
|
390
|
+
WHERE score_total >= 75
|
|
391
|
+
ORDER BY score_total DESC, discovered_at DESC;
|
|
392
|
+
|
|
393
|
+
CREATE VIEW v_apply_ready AS
|
|
394
|
+
SELECT
|
|
395
|
+
j.id AS job_id,
|
|
396
|
+
j.title,
|
|
397
|
+
c.name AS company_name,
|
|
398
|
+
j.score_total,
|
|
399
|
+
j.role_category,
|
|
400
|
+
j.location_raw AS location,
|
|
401
|
+
j.source_url,
|
|
402
|
+
j.status,
|
|
403
|
+
a.id AS application_id,
|
|
404
|
+
a.resume_path,
|
|
405
|
+
a.cover_path,
|
|
406
|
+
a.materials_v,
|
|
407
|
+
a.last_status_change_at
|
|
408
|
+
FROM jobs j
|
|
409
|
+
LEFT JOIN companies c ON c.id = j.company_id
|
|
410
|
+
LEFT JOIN applications a ON a.job_id = j.id
|
|
411
|
+
WHERE j.status IN ('ready_to_apply','materials_drafted','ready_to_review');
|
|
412
|
+
|
|
413
|
+
CREATE VIEW v_active_pipeline AS
|
|
414
|
+
SELECT
|
|
415
|
+
j.id AS job_id,
|
|
416
|
+
c.name AS company_name,
|
|
417
|
+
j.title,
|
|
418
|
+
j.status,
|
|
419
|
+
j.score_total,
|
|
420
|
+
j.applied_at,
|
|
421
|
+
a.last_status_change_at
|
|
422
|
+
FROM jobs j
|
|
423
|
+
LEFT JOIN companies c ON c.id = j.company_id
|
|
424
|
+
LEFT JOIN applications a ON a.job_id = j.id
|
|
425
|
+
WHERE j.status IN ('applied','screen','onsite','offer')
|
|
426
|
+
ORDER BY a.last_status_change_at DESC NULLS LAST, j.applied_at DESC;
|
|
427
|
+
|
|
428
|
+
CREATE VIEW v_company_h1b_signal AS
|
|
429
|
+
SELECT
|
|
430
|
+
c.id AS company_id,
|
|
431
|
+
c.name AS company_name,
|
|
432
|
+
COUNT(h.id) AS total_filings,
|
|
433
|
+
SUM(CASE WHEN h.case_status = 'Certified' THEN 1 ELSE 0 END) AS certified_count,
|
|
434
|
+
MAX(h.decision_date) AS last_decision_date,
|
|
435
|
+
MAX(h.fiscal_year) AS most_recent_fy
|
|
436
|
+
FROM companies c
|
|
437
|
+
LEFT JOIN h1b_filings h ON h.employer_id = c.id
|
|
438
|
+
GROUP BY c.id, c.name;
|
|
439
|
+
|
|
440
|
+
-- Warm-intro pairing — job × non-recruiter connection at the same company.
|
|
441
|
+
-- contact_priority: 1=engineering peer, 2=leadership, 3=other; recruiters routed
|
|
442
|
+
-- separately so they're not in this view.
|
|
443
|
+
CREATE VIEW v_jobs_with_warm_intros AS
|
|
444
|
+
SELECT
|
|
445
|
+
j.id AS job_id,
|
|
446
|
+
c.id AS company_id,
|
|
447
|
+
c.name AS company_name,
|
|
448
|
+
j.title AS job_title,
|
|
449
|
+
j.score_total,
|
|
450
|
+
lc.id AS connection_id,
|
|
451
|
+
lc.full_name AS connection_name,
|
|
452
|
+
lc.position AS connection_position,
|
|
453
|
+
CASE
|
|
454
|
+
WHEN lc.is_engineering = 1 THEN 1
|
|
455
|
+
WHEN lc.is_leadership = 1 THEN 2
|
|
456
|
+
ELSE 3
|
|
457
|
+
END AS contact_priority
|
|
458
|
+
FROM jobs j
|
|
459
|
+
JOIN companies c ON c.id = j.company_id
|
|
460
|
+
JOIN linkedin_connections lc ON lc.company_id = c.id
|
|
461
|
+
WHERE lc.is_recruiter = 0
|
|
462
|
+
ORDER BY j.score_total DESC, contact_priority ASC;
|
|
463
|
+
|
|
464
|
+
-- Founder network — derives founder_kind via simple position substring match.
|
|
465
|
+
-- is_stealth: 1 when company_raw mentions stealth (we don't drop, we flag).
|
|
466
|
+
CREATE VIEW v_founder_network AS
|
|
467
|
+
SELECT
|
|
468
|
+
lc.id AS connection_id,
|
|
469
|
+
lc.full_name,
|
|
470
|
+
lc.position,
|
|
471
|
+
lc.company_raw,
|
|
472
|
+
lc.company_id,
|
|
473
|
+
CASE
|
|
474
|
+
WHEN LOWER(lc.position) LIKE '%founder%' THEN 'founder'
|
|
475
|
+
WHEN LOWER(lc.position) LIKE '%ceo%' THEN 'ceo'
|
|
476
|
+
WHEN LOWER(lc.position) LIKE '%cto%' THEN 'cto'
|
|
477
|
+
WHEN LOWER(lc.position) LIKE '%chief%' THEN 'c_suite'
|
|
478
|
+
ELSE 'other'
|
|
479
|
+
END AS founder_kind,
|
|
480
|
+
CASE
|
|
481
|
+
WHEN LOWER(COALESCE(lc.company_raw,'')) LIKE '%stealth%' THEN 1 ELSE 0
|
|
482
|
+
END AS is_stealth
|
|
483
|
+
FROM linkedin_connections lc
|
|
484
|
+
WHERE lc.is_leadership = 1
|
|
485
|
+
OR LOWER(lc.position) LIKE '%founder%'
|
|
486
|
+
OR LOWER(lc.position) LIKE '%ceo%'
|
|
487
|
+
OR LOWER(lc.position) LIKE '%cto%'
|
|
488
|
+
OR LOWER(lc.position) LIKE '%chief%';
|
|
489
|
+
|
|
490
|
+
CREATE VIEW v_followups_due AS
|
|
491
|
+
SELECT
|
|
492
|
+
o.id AS outreach_id,
|
|
493
|
+
o.connection_id,
|
|
494
|
+
lc.full_name AS connection_name,
|
|
495
|
+
c.name AS company_name,
|
|
496
|
+
o.outreach_type,
|
|
497
|
+
o.status,
|
|
498
|
+
o.sent_at,
|
|
499
|
+
o.followup_due_at
|
|
500
|
+
FROM outreach o
|
|
501
|
+
LEFT JOIN linkedin_connections lc ON lc.id = o.connection_id
|
|
502
|
+
LEFT JOIN companies c ON c.id = o.company_id
|
|
503
|
+
WHERE o.status = 'sent'
|
|
504
|
+
AND o.followup_due_at IS NOT NULL
|
|
505
|
+
AND o.followup_due_at <= CURRENT_TIMESTAMP;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- M2 additions:
|
|
2
|
+
-- - llm_calls: per-call telemetry for cost_estimate() + parse-error visibility
|
|
3
|
+
-- - digest_state: tracks the cutoff timestamp of the last daily_digest run
|
|
4
|
+
-- - scan_runs: per scan_portals invocation summary (added in G2)
|
|
5
|
+
|
|
6
|
+
CREATE TABLE llm_calls (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
tool TEXT NOT NULL,
|
|
9
|
+
provider TEXT NOT NULL,
|
|
10
|
+
model TEXT NOT NULL,
|
|
11
|
+
job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
|
|
12
|
+
input_chars INTEGER NOT NULL,
|
|
13
|
+
output_chars INTEGER NOT NULL,
|
|
14
|
+
parse_ok INTEGER NOT NULL DEFAULT 1,
|
|
15
|
+
parse_error TEXT,
|
|
16
|
+
duration_ms INTEGER NOT NULL,
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
-- The only reader (cost_estimate) is a time-window scan grouped by (provider, model, tool).
|
|
21
|
+
-- A single descending btree on created_at supports that scan; per-column ones add write
|
|
22
|
+
-- cost without speeding up the GROUP BY.
|
|
23
|
+
CREATE INDEX idx_llm_calls_created_at ON llm_calls(created_at DESC);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE digest_state (
|
|
26
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
27
|
+
last_digest_at TEXT
|
|
28
|
+
);
|
|
29
|
+
INSERT INTO digest_state (id) VALUES (1);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE scan_runs (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
34
|
+
finished_at TEXT,
|
|
35
|
+
sources TEXT, -- JSON array of provider ids hit
|
|
36
|
+
companies_n INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
jobs_found INTEGER NOT NULL DEFAULT 0,
|
|
38
|
+
jobs_new INTEGER NOT NULL DEFAULT 0,
|
|
39
|
+
jobs_dupes INTEGER NOT NULL DEFAULT 0,
|
|
40
|
+
errors_json TEXT, -- JSON array of { company, error }
|
|
41
|
+
triggered_by TEXT NOT NULL DEFAULT 'manual'
|
|
42
|
+
);
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Entrypoint. One process: SQLite migrations, Express + file server, MCP HTTP transport.
|
|
2
|
+
import { buildHttpApp } from './http/app.js';
|
|
3
|
+
import { mountMcp } from './mcp/server.js';
|
|
4
|
+
import { config } from './config.js';
|
|
5
|
+
import { getDb } from './db.js';
|
|
6
|
+
import { ensureActiveCareerPacket, loadProjectFiles } from './core/profile.js';
|
|
7
|
+
import { closeBrowser } from './core/render.js';
|
|
8
|
+
import { shutdownScanResources } from './core/scan_engine.js';
|
|
9
|
+
import { applyState as applySchedulerState, readEnabledJobs } from './core/scheduler.js';
|
|
10
|
+
async function main() {
|
|
11
|
+
// Trigger migrations (side effect of getDb()).
|
|
12
|
+
getDb();
|
|
13
|
+
const seed = ensureActiveCareerPacket();
|
|
14
|
+
const files = loadProjectFiles();
|
|
15
|
+
const app = buildHttpApp();
|
|
16
|
+
mountMcp(app, '/mcp');
|
|
17
|
+
applySchedulerState();
|
|
18
|
+
const enabled = readEnabledJobs();
|
|
19
|
+
const server = app.listen(config.port, config.host, () => {
|
|
20
|
+
const baseUrl = config.baseUrl;
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.error([
|
|
23
|
+
'',
|
|
24
|
+
`▷ mcp-jsa listening on ${baseUrl}`,
|
|
25
|
+
` · MCP endpoint: ${baseUrl}/mcp`,
|
|
26
|
+
` · Tracker UI: ${baseUrl}/`,
|
|
27
|
+
` · File server: ${baseUrl}/files/*`,
|
|
28
|
+
` · DB: ${config.dbPath}`,
|
|
29
|
+
` · Project root: ${config.projectRoot}`,
|
|
30
|
+
` · cv.md present: ${!!files.cvMd}`,
|
|
31
|
+
` · profile.yml: ${!!files.profile}`,
|
|
32
|
+
` · portals.yml: ${!!files.portalsYml}`,
|
|
33
|
+
` · career_packet: ${seed.created ? `seeded v${seed.version}` : `existing v${seed.version}`}`,
|
|
34
|
+
` · scheduler: ${enabled.length ? enabled.join(', ') : 'off'}`,
|
|
35
|
+
` · visa scoring: ${config.visaScoringEnabled ? 'on (0.5/0.3/0.2)' : 'off (0.6/0.4, visa tools hidden)'}`,
|
|
36
|
+
'',
|
|
37
|
+
].join('\n'));
|
|
38
|
+
});
|
|
39
|
+
const shutdown = async (signal) => {
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.error(`\n[shutdown] ${signal} received`);
|
|
42
|
+
server.close();
|
|
43
|
+
await closeBrowser();
|
|
44
|
+
await shutdownScanResources();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
};
|
|
47
|
+
process.on('SIGINT', () => { void shutdown('SIGINT'); });
|
|
48
|
+
process.on('SIGTERM', () => { void shutdown('SIGTERM'); });
|
|
49
|
+
}
|
|
50
|
+
main().catch((err) => {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.error('[fatal]', err);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|