jd-intel 0.1.0 → 0.2.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/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
4
  [![Node.js 18+](https://img.shields.io/badge/node-18%2B-green.svg)](https://nodejs.org)
5
5
  [![npm](https://img.shields.io/npm/v/jd-intel.svg)](https://www.npmjs.com/package/jd-intel)
6
+ [![npm downloads](https://img.shields.io/npm/dw/jd-intel-mcp.svg)](https://www.npmjs.com/package/jd-intel-mcp)
7
+ [![GitHub stars](https://img.shields.io/github/stars/prPMDev/jd-intel.svg?style=flat)](https://github.com/prPMDev/jd-intel/stargazers)
6
8
 
7
9
  > **Stop pasting job descriptions into AI assistants. Let your AI fetch them directly.**
8
10
 
@@ -16,13 +18,17 @@ Your AI assistant already knows a lot about you. Your resume is in its memory. Y
16
18
 
17
19
  So you copy-paste.
18
20
 
19
- A JD from Stripe. Another from Mercury. Six more from your target list. Half have broken HTML. Salary info dies in translation. Links get stripped. And for every role, the dance starts over.
21
+ A JD from one company. Another from the next. A half-dozen more from your target list. Half have broken HTML. Salary info dies in translation. Links get stripped. And for every role, the dance starts over.
20
22
 
21
23
  You could wait for the job boards to ship their own MCPs. They'll get there eventually. On their timeline. Filtered through their priorities, not yours. Tied to their query abstractions.
22
24
 
23
25
  jd-intel skips that wait. Raw JDs, fetched directly by your AI, on your terms. One level below the curated layer.
24
26
 
25
- > "Claude, pull the senior PM role at Stripe and draft a cover letter based on my resume."
27
+ Try asking your AI:
28
+
29
+ > "Find AI/ML engineering jobs posted this week."
30
+ > "What product designer roles are open at fintechs right now?"
31
+ > "Pull the staff PM roles posted in the last 7 days."
26
32
 
27
33
  Done.
28
34
 
@@ -30,10 +36,10 @@ Done.
30
36
 
31
37
  ## What you can do with it
32
38
 
33
- - Draft cover letters without pasting anything
39
+ - Look up open roles at any company directly from your AI, no copy-paste
34
40
  - Tailor your resume across ten roles in one conversation
35
41
  - Rank openings by fit with your background
36
- - Scan a whole sector: "Pull PM roles at fintech companies posted this week"
42
+ - Scan a whole sector: "Pull open roles at fintech companies posted this week"
37
43
  - Research teams by reading their JDs in bulk
38
44
 
39
45
  The toolkit fetches. Your AI thinks.
@@ -42,12 +48,63 @@ The toolkit fetches. Your AI thinks.
42
48
 
43
49
  ## Install
44
50
 
45
- ### For Claude Desktop, Cursor, Windsurf users
51
+ Works with MCP-aware AI clients: Claude Desktop, Claude Code, Cursor, Windsurf. ChatGPT, Gemini, and other non-MCP clients don't support this yet. They use different tool-calling systems. (We wish they did. The protocol works the same way regardless of which AI you talk to.)
52
+
53
+ You'll need [Node.js 18 or newer](https://nodejs.org/). To check: open a terminal and run `node --version`. If it's missing or older, install from nodejs.org first.
54
+
55
+ ### For Claude Desktop (one command)
56
+
57
+ 1. **Open a terminal.** It's just a text window. Nothing destructive happens here.
58
+ - **macOS:** Spotlight (`⌘ Space`), type "Terminal", hit Enter.
59
+ - **Windows:** Start menu, type "PowerShell", hit Enter.
60
+
61
+ 2. **Paste this and hit Enter:**
62
+ ```bash
63
+ npx jd-intel-mcp install
64
+ ```
65
+
66
+ 3. **Quit and reopen Claude Desktop.** The tools appear automatically.
67
+
68
+ Try: *"Find product roles at devtools companies."*
69
+
70
+ If something goes wrong or you'd rather edit the config file directly, see [Manual install](#manual-install-fallback) below.
71
+
72
+ ### For Cursor and Windsurf
73
+
74
+ These clients have their own MCP setup flows. Follow their docs:
75
+ - Cursor: [docs.cursor.com](https://docs.cursor.com)
76
+ - Windsurf: [docs.windsurf.com](https://docs.windsurf.com)
77
+
78
+ Use this server config: `command: "npx"`, `args: ["-y", "jd-intel-mcp"]`.
46
79
 
47
- Add to your MCP config file:
80
+ ### For developers
48
81
 
49
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
50
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
82
+ ```bash
83
+ npm install jd-intel
84
+ ```
85
+
86
+ ```js
87
+ import { fetchJobs } from 'jd-intel';
88
+
89
+ const jobs = await fetchJobs({
90
+ company: '<your-target-company>',
91
+ titleFilter: 'designer',
92
+ postedWithinDays: 14,
93
+ limit: 50,
94
+ });
95
+ ```
96
+
97
+ CLI usage: `npx jd-intel fetch <company-slug> --title-filter "engineer" --posted-within-days 14`. Full filter reference [below](#filters-quick-reference).
98
+
99
+ Node.js 18+. No API keys. No configuration.
100
+
101
+ ### Manual install (fallback)
102
+
103
+ If `npx jd-intel-mcp install` fails, edit the config directly.
104
+
105
+ **Config file location:**
106
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
107
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
51
108
 
52
109
  ```json
53
110
  {
@@ -60,40 +117,26 @@ Add to your MCP config file:
60
117
  }
61
118
  ```
62
119
 
63
- Restart your AI client. The tools appear automatically. Ask your AI to fetch any role.
120
+ Restart Claude Desktop.
64
121
 
65
- **One-command install (avoids hand-editing the config):**
66
- ```bash
67
- npx jd-intel-mcp install
68
- ```
122
+ ### Updating
69
123
 
70
- ### For developers (CLI and library)
124
+ `npx -y jd-intel-mcp` auto-updates within ~24 hours via npm's cache. To force an update immediately:
71
125
 
72
126
  ```bash
73
- npm install jd-intel
127
+ npx clear-npx-cache
74
128
  ```
75
129
 
76
- Or run without installing:
130
+ Then quit and reopen Claude Desktop.
77
131
 
78
- ```bash
79
- npx jd-intel fetch stripe --title-filter "product manager"
80
- ```
132
+ If you installed the library or CLI directly:
81
133
 
82
- Or import as a library:
83
-
84
- ```js
85
- import { fetchJobs, registry } from 'jd-intel';
86
-
87
- const jobs = await fetchJobs({
88
- company: 'ramp',
89
- titleFilter: 'engineer',
90
- postedWithinDays: 14,
91
- limit: 50,
92
- });
134
+ ```bash
135
+ npm install jd-intel@latest # force latest
136
+ # or
137
+ npm update jd-intel # respect semver
93
138
  ```
94
139
 
95
- Node.js 18+. No API keys. No configuration.
96
-
97
140
  ---
98
141
 
99
142
  ## MCP tools
@@ -115,14 +158,14 @@ Every job normalizes to one schema, across every platform:
115
158
  ```json
116
159
  {
117
160
  "id": "a1b2c3d4e5f6",
118
- "company": "Stripe",
119
- "title": "Senior Product Manager, Integrations",
120
- "department": "Product",
121
- "location": "San Francisco, CA",
122
- "locationType": "hybrid",
123
- "salary": { "min": 180000, "max": 260000, "currency": "USD" },
124
- "description": "Lead strategy for Stripe's integration ecosystem...",
125
- "url": "https://boards.greenhouse.io/stripe/jobs/12345",
161
+ "company": "Example Co",
162
+ "title": "Senior Software Engineer, Platform",
163
+ "department": "Engineering",
164
+ "location": "Remote - US",
165
+ "locationType": "remote",
166
+ "salary": { "min": 180000, "max": 240000, "currency": "USD" },
167
+ "description": "Design and build the API surface our customers integrate against...",
168
+ "url": "https://boards.example.com/jobs/12345",
126
169
  "postedAt": "2026-04-10T14:30:00Z"
127
170
  }
128
171
  ```
@@ -152,6 +195,7 @@ No custom parsing per company.
152
195
  | Greenhouse | Shipped | Most widely used ATS in tech |
153
196
  | Ashby | Shipped | Growing fast with startups |
154
197
  | Lever | Shipped | Common at mid-stage companies |
198
+ | SmartRecruiters | Shipped | Enterprise and mid-market |
155
199
  | BambooHR | Planned | Mid-market companies |
156
200
  | Workday | Planned | Large enterprises |
157
201
 
@@ -178,18 +222,18 @@ All filters AND together. Deep dive on patterns and gotchas: [docs/filters.md](d
178
222
 
179
223
  **Shipped**
180
224
  - Library, CLI, and MCP server (three surfaces of one toolkit)
181
- - Greenhouse, Ashby, Lever adapters
225
+ - Greenhouse, Ashby, Lever, SmartRecruiters adapters
182
226
  - Title, topic, location, and date filters
183
227
  - Salary extraction from JD text
184
- - Verified company registry (66 companies)
228
+ - Verified company registry (100+ companies)
185
229
 
186
230
  **Next**
231
+ - TeamTailor adapter (European startup coverage)
187
232
  - Anthropic MCP marketplace submission
188
- - Setup guide with screenshots (non-technical walkthrough)
189
- - Remote MCP transport (for Claude.ai Custom Connectors)
190
233
 
191
234
  **Planned**
192
- - BambooHR and Workday adapters
235
+ - BambooHR and Workable adapters
236
+ - Workday support (scoped scraper — large enterprise universe)
193
237
  - Temporal tracking (when roles open, close, reopen)
194
238
  - Change detection
195
239
  - Resume-aware fit scoring
@@ -208,7 +252,7 @@ All filters AND together. Deep dive on patterns and gotchas: [docs/filters.md](d
208
252
 
209
253
  ## Built by
210
254
 
211
- **[Prashant R](https://prashantrana.xyz)**. PM who builds. I write about what actually happens at the layer below the AI hype.
255
+ **[Prashant R](https://prashantrana.xyz)**. PM who builds. I try out and build what really matters below the AI hype.
212
256
 
213
257
  - Portfolio and writing: [prashantrana.xyz](https://prashantrana.xyz)
214
258
  - [LinkedIn](https://www.linkedin.com/in/prashant-rana)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jd-intel",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Fetch and normalize job descriptions across every major ATS (Greenhouse, Lever, Ashby) — for your AI assistant, no copy-paste.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -28,7 +28,17 @@
28
28
  "ai",
29
29
  "claude",
30
30
  "hiring",
31
- "careers"
31
+ "careers",
32
+ "model-context-protocol",
33
+ "anthropic",
34
+ "cursor",
35
+ "windsurf",
36
+ "ai-tools",
37
+ "ai-assistant",
38
+ "job-search",
39
+ "applicant-tracking-system",
40
+ "developer-tools",
41
+ "nodejs"
32
42
  ],
33
43
  "author": "Prashant R",
34
44
  "license": "MIT",
@@ -12,5 +12,18 @@
12
12
  {"slug": "shiftkey", "name": "ShiftKey", "sector": "healthcare staffing"},
13
13
  {"slug": "gorgias", "name": "Gorgias", "sector": "customer support"},
14
14
  {"slug": "zapier", "name": "Zapier", "sector": "integration platform"},
15
- {"slug": "clickup", "name": "ClickUp", "sector": "productivity"}
15
+ {"slug": "clickup", "name": "ClickUp", "sector": "productivity"},
16
+ {"slug": "higharc", "name": "Higharc", "sector": "architecture tech"},
17
+ {"slug": "mural", "name": "Mural", "sector": "collaboration"},
18
+ {"slug": "ironcladhq", "name": "Ironclad", "sector": "legal tech"},
19
+ {"slug": "wrapbook", "name": "Wrapbook", "sector": "entertainment payroll"},
20
+ {"slug": "Tabs", "name": "Tabs", "sector": "fintech"},
21
+ {"slug": "vibe", "name": "Vibe", "sector": "saas"},
22
+ {"slug": "unicourt", "name": "UniCourt", "sector": "legal tech"},
23
+ {"slug": "meridianlink", "name": "MeridianLink", "sector": "fintech"},
24
+ {"slug": "Hippocratic AI", "name": "Hippocratic AI", "sector": "healthcare ai"},
25
+ {"slug": "scan-com", "name": "Scan.com", "sector": "healthcare imaging"},
26
+ {"slug": "hiive", "name": "Hiive", "sector": "marketplaces"},
27
+ {"slug": "virtuous", "name": "Virtuous", "sector": "nonprofit crm"},
28
+ {"slug": "jasper ai", "name": "Jasper", "sector": "ai writing"}
16
29
  ]
@@ -43,5 +43,37 @@
43
43
  {"slug": "braze", "name": "Braze", "sector": "marketing tech"},
44
44
  {"slug": "appsflyer", "name": "AppsFlyer", "sector": "marketing tech"},
45
45
  {"slug": "attentive", "name": "Attentive", "sector": "marketing tech"},
46
- {"slug": "iterable", "name": "Iterable", "sector": "marketing tech"}
46
+ {"slug": "iterable", "name": "Iterable", "sector": "marketing tech"},
47
+ {"slug": "nextroll", "name": "NextRoll", "sector": "adtech"},
48
+ {"slug": "cloudbeds", "name": "Cloudbeds", "sector": "hospitality tech"},
49
+ {"slug": "headspacesourcing", "name": "Headspace (Sourcing)", "sector": "mental health"},
50
+ {"slug": "fleetio", "name": "Fleetio", "sector": "fleet management"},
51
+ {"slug": "engine", "name": "Engine", "sector": "travel tech"},
52
+ {"slug": "axon", "name": "Axon", "sector": "public safety"},
53
+ {"slug": "sparrow", "name": "Sparrow", "sector": "hr tech"},
54
+ {"slug": "remotecom", "name": "Remote", "sector": "hr tech"},
55
+ {"slug": "weedmaps77", "name": "Weedmaps", "sector": "cannabis"},
56
+ {"slug": "patterndata", "name": "Pattern", "sector": "ecommerce data"},
57
+ {"slug": "tubitv", "name": "Tubi", "sector": "streaming"},
58
+ {"slug": "future", "name": "Future", "sector": "fitness"},
59
+ {"slug": "eltropyinc", "name": "Eltropy", "sector": "fintech"},
60
+ {"slug": "ziprecruiter", "name": "ZipRecruiter", "sector": "recruiting tech"},
61
+ {"slug": "cognitiv", "name": "Cognitiv", "sector": "adtech"},
62
+ {"slug": "maintainx", "name": "MaintainX", "sector": "industrial saas"},
63
+ {"slug": "betterhelpcom", "name": "BetterHelp", "sector": "mental health"},
64
+ {"slug": "shopmonkey", "name": "Shopmonkey", "sector": "automotive saas"},
65
+ {"slug": "ezcaterinc", "name": "ezCater", "sector": "food tech"},
66
+ {"slug": "trivelta", "name": "TriVelta", "sector": "saas"},
67
+ {"slug": "firstconnectinsurance", "name": "First Connect Insurance", "sector": "insurtech"},
68
+ {"slug": "justworks", "name": "Justworks", "sector": "hr tech"},
69
+ {"slug": "starrez", "name": "StarRez", "sector": "property management"},
70
+ {"slug": "upstart", "name": "Upstart", "sector": "fintech"},
71
+ {"slug": "affirm", "name": "Affirm", "sector": "fintech"},
72
+ {"slug": "houseaccount", "name": "House Account", "sector": "saas"},
73
+ {"slug": "appdirect", "name": "AppDirect", "sector": "cloud commerce"},
74
+ {"slug": "rubrik", "name": "Rubrik", "sector": "security"},
75
+ {"slug": "asana", "name": "Asana", "sector": "productivity"},
76
+ {"slug": "instacart", "name": "Instacart", "sector": "grocery delivery"},
77
+ {"slug": "discord", "name": "Discord", "sector": "social"},
78
+ {"slug": "hs", "name": "Headspace", "sector": "mental health"}
47
79
  ]
@@ -6,5 +6,12 @@
6
6
  {"slug": "clari", "name": "Clari", "sector": "sales tech"},
7
7
  {"slug": "netflix", "name": "Netflix", "sector": "media"},
8
8
  {"slug": "lever", "name": "Lever", "sector": "recruiting tech"},
9
- {"slug": "postman", "name": "Postman", "sector": "developer tools"}
9
+ {"slug": "postman", "name": "Postman", "sector": "developer tools"},
10
+ {"slug": "agiloft", "name": "Agiloft", "sector": "contract management"},
11
+ {"slug": "gohighlevel", "name": "HighLevel", "sector": "marketing saas"},
12
+ {"slug": "veeva", "name": "Veeva Systems", "sector": "life sciences"},
13
+ {"slug": "caremessage", "name": "CareMessage", "sector": "healthcare communications"},
14
+ {"slug": "rover", "name": "Rover", "sector": "pet care"},
15
+ {"slug": "LuminDigital", "name": "Lumin Digital", "sector": "fintech (banking)"},
16
+ {"slug": "redoxengine", "name": "Redox", "sector": "healthcare interop"}
10
17
  ]
@@ -0,0 +1,13 @@
1
+ [
2
+ {"slug": "Visa", "name": "Visa", "sector": "payments"},
3
+ {"slug": "Wise", "name": "Wise", "sector": "fintech"},
4
+ {"slug": "Wabtec", "name": "Wabtec", "sector": "rail / industrial"},
5
+ {"slug": "Sutherland", "name": "Sutherland", "sector": "bpo / cx"},
6
+ {"slug": "AveryDennison", "name": "Avery Dennison", "sector": "materials"},
7
+ {"slug": "PublicStorage", "name": "Public Storage", "sector": "real estate"},
8
+ {"slug": "Sportradar", "name": "Sportradar", "sector": "sports data"},
9
+ {"slug": "Entain", "name": "Entain", "sector": "gaming"},
10
+ {"slug": "Picnic", "name": "Picnic", "sector": "grocery delivery"},
11
+ {"slug": "Hootsuite", "name": "Hootsuite", "sector": "social media management"},
12
+ {"slug": "BusinessWire", "name": "Business Wire", "sector": "pr / newswire"}
13
+ ]
@@ -1,11 +1,13 @@
1
1
  export { fetchGreenhouse, hasGreenhouse } from './greenhouse.js';
2
2
  export { fetchLever, hasLever } from './lever.js';
3
3
  export { fetchAshby, hasAshby } from './ashby.js';
4
+ export { fetchSmartrecruiters, hasSmartrecruiters } from './smartrecruiters.js';
4
5
 
5
6
  export const ADAPTERS = {
6
7
  greenhouse: { fetch: (...args) => import('./greenhouse.js').then(m => m.fetchGreenhouse(...args)), has: (...args) => import('./greenhouse.js').then(m => m.hasGreenhouse(...args)) },
7
8
  lever: { fetch: (...args) => import('./lever.js').then(m => m.fetchLever(...args)), has: (...args) => import('./lever.js').then(m => m.hasLever(...args)) },
8
9
  ashby: { fetch: (...args) => import('./ashby.js').then(m => m.fetchAshby(...args)), has: (...args) => import('./ashby.js').then(m => m.hasAshby(...args)) },
10
+ smartrecruiters: { fetch: (...args) => import('./smartrecruiters.js').then(m => m.fetchSmartrecruiters(...args)), has: (...args) => import('./smartrecruiters.js').then(m => m.hasSmartrecruiters(...args)) },
9
11
  };
10
12
 
11
13
  export const ATS_NAMES = Object.keys(ADAPTERS);
@@ -0,0 +1,108 @@
1
+ import { normalize, stripHtml } from '../normalizer.js';
2
+
3
+ const BASE_URL = 'https://api.smartrecruiters.com/v1/companies';
4
+ const PAGE_SIZE = 100;
5
+
6
+ /**
7
+ * Fetch all postings from a SmartRecruiters company.
8
+ * Public API, no auth required.
9
+ * Docs: https://developers.smartrecruiters.com/reference/postingsget-1
10
+ *
11
+ * Two-step flow (unavoidable N+1):
12
+ * - The postings LIST endpoint omits the job description entirely.
13
+ * - jd-intel's contract is "full JD text", so we must fetch each
14
+ * posting's DETAIL endpoint to get jobAd.sections.
15
+ * Large enterprise tenants with hundreds of openings will therefore be
16
+ * slow against SmartRecruiters specifically. This is the API's shape,
17
+ * not a bug here.
18
+ *
19
+ * @param {string} slug - SmartRecruiters company identifier (e.g., 'Visa')
20
+ * @returns {Promise<Array>} Normalized job objects
21
+ */
22
+ export async function fetchSmartrecruiters(slug) {
23
+ // 1. Page through the postings list.
24
+ const postings = [];
25
+ let offset = 0;
26
+
27
+ while (true) {
28
+ const listUrl = `${BASE_URL}/${slug}/postings?limit=${PAGE_SIZE}&offset=${offset}`;
29
+ const resp = await fetch(listUrl);
30
+
31
+ if (!resp.ok) {
32
+ if (resp.status === 404) return []; // Company not found
33
+ throw new Error(`SmartRecruiters API error for ${slug}: ${resp.status}`);
34
+ }
35
+
36
+ const data = await resp.json();
37
+ const content = data.content || [];
38
+ postings.push(...content);
39
+
40
+ offset += PAGE_SIZE;
41
+ if (content.length === 0 || offset >= (data.totalFound || 0)) break;
42
+ }
43
+
44
+ // 2. Fetch detail per posting for the description.
45
+ const jobs = await Promise.all(postings.map(async (p) => {
46
+ let sections = {};
47
+ let postingUrl = '';
48
+
49
+ try {
50
+ const detailResp = await fetch(`${BASE_URL}/${slug}/postings/${p.id}`);
51
+ if (detailResp.ok) {
52
+ const detail = await detailResp.json();
53
+ sections = detail.jobAd?.sections || {};
54
+ postingUrl = detail.postingUrl || detail.applyUrl || '';
55
+ }
56
+ } catch {
57
+ // Detail fetch failed: fall back to list-only fields (no description).
58
+ }
59
+
60
+ const description = [
61
+ sections.jobDescription?.text,
62
+ sections.qualifications?.text,
63
+ sections.additionalInformation?.text,
64
+ ].filter(Boolean).join('\n\n');
65
+
66
+ const loc = p.location || {};
67
+ const place = loc.fullLocation
68
+ || [loc.city, loc.region, loc.country].filter(Boolean).join(', ');
69
+ let location = place;
70
+ if (loc.remote) location = `Remote - ${place}`.replace(/ - $/, ' ');
71
+ else if (loc.hybrid) location = `Hybrid - ${place}`.replace(/ - $/, ' ');
72
+
73
+ return normalize({
74
+ companySlug: slug,
75
+ company: p.company?.name || slug,
76
+ title: p.name || '',
77
+ department: p.department?.label || p.function?.label || '',
78
+ location,
79
+ description: stripHtml(description),
80
+ url: postingUrl,
81
+ postedAt: p.releasedDate || null,
82
+ salary: null, // SmartRecruiters has no structured salary; normalizer parses text
83
+ metadata: {
84
+ smartRecruitersId: p.id,
85
+ refNumber: p.refNumber || '',
86
+ function: p.function?.label || '',
87
+ experienceLevel: p.experienceLevel?.label || '',
88
+ typeOfEmployment: p.typeOfEmployment?.label || '',
89
+ },
90
+ }, 'smartrecruiters');
91
+ }));
92
+
93
+ return jobs;
94
+ }
95
+
96
+ /**
97
+ * Check if a company exists on SmartRecruiters.
98
+ * (HEAD isn't reliably supported on the postings endpoint, so use a
99
+ * minimal GET.)
100
+ */
101
+ export async function hasSmartrecruiters(slug) {
102
+ try {
103
+ const resp = await fetch(`${BASE_URL}/${slug}/postings?limit=1`);
104
+ return resp.ok;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }