jd-intel 0.8.0 → 0.8.1
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/package.json +1 -1
- package/src/adapters/ashby.js +2 -1
- package/src/adapters/greenhouse.js +2 -1
- package/src/adapters/lever.js +2 -1
- package/src/adapters/recruitee.js +2 -1
- package/src/adapters/smartrecruiters.js +2 -1
- package/src/adapters/teamtailor.js +2 -1
- package/src/adapters/workday.js +2 -1
- package/src/errors.js +38 -0
- package/src/index.js +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jd-intel",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Fetch and normalize job descriptions across every major ATS (Greenhouse, Lever, Ashby, Workday, and more), for your AI assistant. No copy-paste.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/adapters/ashby.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
const API_URL = 'https://jobs.ashbyhq.com/api/non-user-graphql';
|
|
4
5
|
const BOARD_URL = 'https://api.ashbyhq.com/posting-api/job-board';
|
|
@@ -28,7 +29,7 @@ async function fetchAshbyRest(slug) {
|
|
|
28
29
|
|
|
29
30
|
if (!resp.ok) {
|
|
30
31
|
if (resp.status === 404) return [];
|
|
31
|
-
throw
|
|
32
|
+
throw atsErrorFromStatus(resp.status, `Ashby REST API error for ${slug}: ${resp.status}`);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const data = await resp.json();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize, stripHtml } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
const BASE_URL = 'https://boards-api.greenhouse.io/v1/boards';
|
|
4
5
|
|
|
@@ -16,7 +17,7 @@ export async function fetchGreenhouse(slug) {
|
|
|
16
17
|
|
|
17
18
|
if (!resp.ok) {
|
|
18
19
|
if (resp.status === 404) return []; // Company not found or no jobs
|
|
19
|
-
throw
|
|
20
|
+
throw atsErrorFromStatus(resp.status, `Greenhouse API error for ${slug}: ${resp.status}`);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const data = await resp.json();
|
package/src/adapters/lever.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize, stripHtml } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
const BASE_URL = 'https://api.lever.co/v0/postings';
|
|
4
5
|
|
|
@@ -15,7 +16,7 @@ export async function fetchLever(slug) {
|
|
|
15
16
|
|
|
16
17
|
if (!resp.ok) {
|
|
17
18
|
if (resp.status === 404) return [];
|
|
18
|
-
throw
|
|
19
|
+
throw atsErrorFromStatus(resp.status, `Lever API error for ${slug}: ${resp.status}`);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const jobs = await resp.json();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize, stripHtml } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Fetch jobs from a Recruitee career site.
|
|
@@ -18,7 +19,7 @@ export async function fetchRecruitee(slug) {
|
|
|
18
19
|
|
|
19
20
|
if (!resp.ok) {
|
|
20
21
|
if (resp.status === 404) return []; // No Recruitee site for this slug
|
|
21
|
-
throw
|
|
22
|
+
throw atsErrorFromStatus(resp.status, `Recruitee API error for ${slug}: ${resp.status}`);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const data = await resp.json();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize, stripHtml } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
const BASE_URL = 'https://api.smartrecruiters.com/v1/companies';
|
|
4
5
|
const PAGE_SIZE = 100;
|
|
@@ -30,7 +31,7 @@ export async function fetchSmartrecruiters(slug) {
|
|
|
30
31
|
|
|
31
32
|
if (!resp.ok) {
|
|
32
33
|
if (resp.status === 404) return []; // Company not found
|
|
33
|
-
throw
|
|
34
|
+
throw atsErrorFromStatus(resp.status, `SmartRecruiters API error for ${slug}: ${resp.status}`);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const data = await resp.json();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize, stripHtml } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Fetch jobs from a TeamTailor career site via its public RSS feed.
|
|
@@ -43,7 +44,7 @@ async function resolveFeed(slug, method = 'GET') {
|
|
|
43
44
|
});
|
|
44
45
|
if (resp.ok) return resp;
|
|
45
46
|
if (resp.status !== 404) {
|
|
46
|
-
throw
|
|
47
|
+
throw atsErrorFromStatus(resp.status, `TeamTailor RSS error for ${slug}: ${resp.status}`);
|
|
47
48
|
}
|
|
48
49
|
// 404 on this host — try the next region.
|
|
49
50
|
}
|
package/src/adapters/workday.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalize, stripHtml } from '../normalizer.js';
|
|
2
|
+
import { atsErrorFromStatus } from '../errors.js';
|
|
2
3
|
|
|
3
4
|
const MAX_DETAIL_FETCHES = 100;
|
|
4
5
|
const LIST_PAGE_SIZE = 20;
|
|
@@ -49,7 +50,7 @@ export async function fetchWorkday(slug, ctx = {}) {
|
|
|
49
50
|
if (!resp.ok) {
|
|
50
51
|
if (resp.status === 404) return []; // wrong site / no such board
|
|
51
52
|
if (offset === 0) {
|
|
52
|
-
throw
|
|
53
|
+
throw atsErrorFromStatus(resp.status, `Workday API error for ${slug} (${tenant}/${env}/${site}): ${resp.status}`);
|
|
53
54
|
}
|
|
54
55
|
break; // mid-paging failure: keep what we have
|
|
55
56
|
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy + typed error for the jd-intel toolkit.
|
|
3
|
+
*
|
|
4
|
+
* Codes are short and stable; the MCP tool descriptions teach the AI what each
|
|
5
|
+
* one means. Adapters throw AtsError with a code so callers map failures by
|
|
6
|
+
* `err.code` instead of parsing the message.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const ERROR_CODES = {
|
|
10
|
+
COMPANY_NOT_FOUND: 'company_not_found', // Slug not in registry and not detected
|
|
11
|
+
ATS_UNREACHABLE: 'ats_unreachable', // Known ATS failed (500, timeout)
|
|
12
|
+
PARTIAL_FAILURE: 'partial_failure', // Discovery mode; some adapters failed
|
|
13
|
+
INVALID_ARGS: 'invalid_args', // Missing required, wrong type, bad pattern
|
|
14
|
+
NO_RESULTS: 'no_results', // Query succeeded, filters returned nothing
|
|
15
|
+
RATE_LIMITED: 'rate_limited', // Upstream returned 429
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Thrown by adapters on an upstream HTTP failure. Carries a stable `code`
|
|
20
|
+
* (ats_unreachable / rate_limited) so the MCP layer maps it without parsing
|
|
21
|
+
* the message. Extends Error, so the message, stack, and `instanceof Error`
|
|
22
|
+
* all keep working for existing library consumers.
|
|
23
|
+
*/
|
|
24
|
+
export class AtsError extends Error {
|
|
25
|
+
constructor(code, message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'AtsError';
|
|
28
|
+
this.code = code;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Helper for adapters: build an AtsError from an HTTP status (429 => rate
|
|
34
|
+
* limited, anything else => unreachable) with the given message.
|
|
35
|
+
*/
|
|
36
|
+
export function atsErrorFromStatus(status, message) {
|
|
37
|
+
return new AtsError(status === 429 ? ERROR_CODES.RATE_LIMITED : ERROR_CODES.ATS_UNREACHABLE, message);
|
|
38
|
+
}
|
package/src/index.js
CHANGED
|
@@ -139,3 +139,8 @@ export { applyFilters } from './filters.js';
|
|
|
139
139
|
// Re-export the list of supported ATS names (e.g. so the MCP layer can report
|
|
140
140
|
// the full set detectAts probes, instead of hardcoding a stale subset).
|
|
141
141
|
export { ATS_NAMES };
|
|
142
|
+
|
|
143
|
+
// Error taxonomy + typed error. Adapters throw AtsError with a stable .code
|
|
144
|
+
// (ats_unreachable / rate_limited) so the MCP layer maps failures without
|
|
145
|
+
// parsing messages. ERROR_CODES is the single source of truth for both.
|
|
146
|
+
export { ERROR_CODES, AtsError } from './errors.js';
|