job-pro 0.7.0 → 0.7.2

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.
@@ -0,0 +1,17 @@
1
+ // Canonical contract every company adapter must satisfy.
2
+ //
3
+ // Previously the dispatcher leaned on `type CompanyAdapter = typeof tencent`
4
+ // plus `as unknown as CompanyAdapter` casts on every entry in the ADAPTERS
5
+ // map. That silenced every shape mismatch — if an adapter's return value
6
+ // drifted, TypeScript was happy and the bug surfaced at runtime.
7
+ //
8
+ // This module defines an explicit method-signature interface so adapters
9
+ // can be wired with `satisfies Record<string, CompanyAdapter>` and any
10
+ // future drift becomes a compile error.
11
+ //
12
+ // The result types are intentionally permissive (`Promise<unknown>`-shaped):
13
+ // adapter-specific success payloads have rich, per-company keys (Tencent has
14
+ // recruitment fields Feishu doesn't, etc.) that we don't want to flatten here.
15
+ // The contract is "this method exists and is async"; the per-company JSON
16
+ // shape is documented in each adapter's source.
17
+ export {};
package/dist/alibaba.js CHANGED
@@ -56,8 +56,8 @@
56
56
  // bgs ← item.circleNames?.[0] ?? "" (BU / group name)
57
57
  // work_cities ← item.workLocations.join(" / ")
58
58
  // apply_url ← https://campus-talent.alibaba.com/campus/positionDetail?positionId=<id>
59
- import { extractResumeSignals, scoreOverlap } from "./tencent.js";
60
- export { extractResumeSignals, scoreOverlap };
59
+ import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
60
+ export { extractResumeSignals, scoreOverlap, checkResume };
61
61
  const API_ROOT = "https://campus-talent.alibaba.com";
62
62
  const CAMPUS_PAGE = `${API_ROOT}/campus/position`;
63
63
  const DETAIL_PAGE = (id) => `${API_ROOT}/campus/position/${encodeURIComponent(String(id))}`;
package/dist/baichuan.js CHANGED
@@ -1,133 +1,48 @@
1
- // 百川智能 (Baichuan AI) stub adapter for `job-pro`.
1
+ // Thin client for 百川智能 (Baichuan AI) recruiting portal.
2
2
  //
3
- // STATUS: stub-only. No public unauthenticated job API was found.
3
+ // Portal: https://cq6qe6bvfr6.jobs.feishu.cn/baichuanzhaopin/
4
+ // Platform: Feishu Recruiting (ATSX) SaaS — same API surface as nio.ts / minimax.ts.
4
5
  //
5
6
  // ============================================================
6
- // RECONNAISSANCE RESULTS (probed 2026-05):
7
+ // Discovery (2026-05):
7
8
  //
8
- // www.baichuan-ai.com/careers 404 (Next.js SPA, page removed)
9
- // www.baichuan-ai.com/join → 404
10
- // www.baichuan-ai.com/ React SPA; no inline job data;
11
- // JS bundles contain no ATS references
9
+ // www.baichuan-ai.com/ → Next.js SPA with no /careers or /jobs route
10
+ // (the corporate site doesn't link to the portal)
11
+ // baichuan.jobs.feishu.cn/ 400 (no portal configured on the obvious slug)
12
+ // baichuan-ai.jobs.feishu.cn/ → 404
12
13
  //
13
- // Feishu ATSX tenants probed:
14
- // baichuan.jobs.feishu.cn → HTTP 400 empty body for all channels
15
- // (TLS cert resolves — wildcard *.jobs.feishu.cn —
16
- // but no portal is configured on the ATSX backend)
17
- // baichuan-ai.jobs.feishu.cn → HTTP 404 (DNS only, no TLS/host)
18
- // baichuan-inc.jobs.feishu.cn → HTTP 404
19
- // baichuanai.jobs.feishu.cn → HTTP 404
14
+ // The real portal lives on a randomized Feishu tenant slug:
15
+ // cq6qe6bvfr6.jobs.feishu.cn/baichuanzhaopin/
20
16
  //
21
- // HTTP 400 + empty body is Feishu's "site not exist / tenant has no portal"
22
- // response (documented in feishu.ts discovery notes). The subdomain is
23
- // registered at the CDN level but has no active recruiting portal behind it.
17
+ // Tenant: 百川智能 / "【百川智能】社会招聘官方网站-欢迎你的加入!"
18
+ // Channel slug: "baichuanzhaopin" (the company PATH on the tenant)
24
19
  //
25
- // Moka ATS:
26
- // app.mokahr.com/campus-recruitment/baichuan → "您访问的页面不存在" (error page)
27
- // app.mokahr.com/social-recruitment/baichuan → same error
28
- // Moka orgId probes (numeric range) — all return the Moka SPA error shell;
29
- // no slug "baichuan" maps to a live Moka org.
30
- //
31
- // Greenhouse:
32
- // boards.greenhouse.io/baichuan → 404 "job board no longer active"
33
- // (Greenhouse board existed historically but has been deactivated.)
34
- //
35
- // Lever, BOSS Zhipin, Lagou, Liepin — no unauthenticated public API found.
36
- //
37
- // Conclusion: Baichuan currently posts positions through internal/gated channels
38
- // only. When a public JSON endpoint becomes available this adapter can be
39
- // upgraded to a thin wrapper around feishu.ts (createAdapter) or a bespoke
40
- // client in a single pass.
20
+ // This is the same multi-tenant ATSX pattern that MiniMax (vrfi1sk8a0)
21
+ // uses the company path is the portal-channel header.
41
22
  //
42
23
  // ============================================================
43
- // PositionSummary field mapping (canonical matches all other adapters):
44
- // post_id position identifier
45
- // title position title
46
- // project job category / department
47
- // recruit_label recruit type (e.g. "实习" / "社招")
48
- // bgs business group (Baichuan has no public BG dimension)
49
- // work_cities work location(s)
50
- // apply_url deep-link to the position detail page
24
+ // PositionSummary field mapping (Feishu canonical):
25
+ // post_id String(item.id)
26
+ // title item.title
27
+ // project item.job_category?.name ?? item.job_function?.name
28
+ // recruit_label item.recruit_type?.name
29
+ // bgs "" (not exposed in public search)
30
+ // work_cities city_list joined " / " (city_info used as fallback)
31
+ // apply_url https://cq6qe6bvfr6.jobs.feishu.cn/baichuanzhaopin/position/${id}/detail
32
+ import { createAdapter } from "./feishu.js";
51
33
  import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
52
- export { checkResume };
53
- const SOURCE = "www.baichuan-ai.com";
54
- const STUB_MESSAGE = "Baichuan (百川智能): no public job API — Feishu ATSX subdomain " +
55
- "baichuan.jobs.feishu.cn is registered but has no portal configured " +
56
- "(HTTP 400 empty body); Moka slug 'baichuan' returns a page-not-found " +
57
- "error; Greenhouse board has been deactivated. " +
58
- "Visit https://www.baichuan-ai.com/ for any current career links.";
59
- // ---------- stub functions ----------
60
- export async function searchPositions(_opts = {}) {
61
- return {
62
- ok: false,
63
- source: SOURCE,
64
- message: STUB_MESSAGE,
65
- query: {},
66
- positions: [],
67
- };
68
- }
69
- export async function fetchAllPositions(_opts = {}) {
70
- return {
71
- ok: false,
72
- source: SOURCE,
73
- message: STUB_MESSAGE,
74
- total: 0,
75
- fetched: 0,
76
- positions: [],
77
- };
78
- }
79
- export async function fetchPositionDetail(postId) {
80
- return {
81
- ok: false,
82
- source: SOURCE,
83
- message: STUB_MESSAGE,
84
- post_id: postId,
85
- };
86
- }
87
- export async function fetchDictionaries() {
88
- return {
89
- ok: false,
90
- source: SOURCE,
91
- message: STUB_MESSAGE,
92
- };
93
- }
94
- export async function listNotices() {
95
- return {
96
- ok: false,
97
- source: SOURCE,
98
- message: STUB_MESSAGE,
99
- notices: [],
100
- };
101
- }
102
- export async function getNotice(noticeId) {
103
- return {
104
- ok: false,
105
- source: SOURCE,
106
- message: STUB_MESSAGE,
107
- notice_id: noticeId,
108
- };
109
- }
110
- export async function findNoticesByQuestion(question, _opts = {}) {
111
- return {
112
- ok: false,
113
- source: SOURCE,
114
- question,
115
- message: STUB_MESSAGE,
116
- matches: [],
117
- };
118
- }
119
- // ---------- matchResume ----------
120
- // Extracts signals so callers can see what terms were parsed, even though
121
- // no live positions are available to score against.
122
- export async function matchResume(text, _opts = {}) {
123
- const { terms, cities } = extractResumeSignals(text ?? "");
124
- return {
125
- ok: false,
126
- source: SOURCE,
127
- extracted_terms: terms,
128
- city_preferences: cities,
129
- matches: [],
130
- message: STUB_MESSAGE,
131
- };
132
- }
133
- export { extractResumeSignals, scoreOverlap };
34
+ export { extractResumeSignals, scoreOverlap, checkResume };
35
+ const _adapter = createAdapter({
36
+ host: "cq6qe6bvfr6.jobs.feishu.cn",
37
+ channel: "baichuanzhaopin",
38
+ label: "Baichuan (百川智能)",
39
+ applyUrlPrefix: "https://cq6qe6bvfr6.jobs.feishu.cn/baichuanzhaopin/position",
40
+ });
41
+ export const searchPositions = _adapter.searchPositions;
42
+ export const fetchAllPositions = _adapter.fetchAllPositions;
43
+ export const fetchPositionDetail = _adapter.fetchPositionDetail;
44
+ export const fetchDictionaries = _adapter.fetchDictionaries;
45
+ export const listNotices = _adapter.listNotices;
46
+ export const getNotice = _adapter.getNotice;
47
+ export const findNoticesByQuestion = _adapter.findNoticesByQuestion;
48
+ export const matchResume = _adapter.matchResume;
package/dist/bilibili.js CHANGED
@@ -295,6 +295,49 @@ export async function fetchAllPositions(opts = {}) {
295
295
  positions: bucket,
296
296
  };
297
297
  }
298
+ // ---------- fetchPositionDetail ----------
299
+ // Bilibili's public search response carries description + requirement inline,
300
+ // so "detail" is a paginated scan-and-filter (same pattern as feishu.ts).
301
+ export async function fetchPositionDetail(postId) {
302
+ const id = (postId ?? "").trim();
303
+ if (!id)
304
+ return { ok: false, source: "jobs.bilibili.com", message: "post_id is required" };
305
+ const pageSize = 100;
306
+ const maxPages = 4;
307
+ for (let page = 1; page <= maxPages; page++) {
308
+ const result = await searchPositions({ page, pageSize });
309
+ if (!result.ok) {
310
+ return {
311
+ ok: false,
312
+ source: "jobs.bilibili.com",
313
+ post_id: id,
314
+ message: result.message,
315
+ };
316
+ }
317
+ const found = result.positions.find((p) => p.post_id === id);
318
+ if (found) {
319
+ return {
320
+ ok: true,
321
+ source: "jobs.bilibili.com",
322
+ post_id: id,
323
+ title: found.title,
324
+ project: found.project,
325
+ recruit_label: found.recruit_label,
326
+ bgs: found.bgs,
327
+ work_cities: found.work_cities,
328
+ apply_url: found.apply_url,
329
+ };
330
+ }
331
+ if (result.positions.length < pageSize)
332
+ break;
333
+ }
334
+ return {
335
+ ok: false,
336
+ source: "jobs.bilibili.com",
337
+ post_id: id,
338
+ message: `post ${id} not found in public search results (scanned up to ${maxPages * pageSize} posts)`,
339
+ };
340
+ }
298
341
  // ---------- matchResume ----------
299
342
  export async function matchResume(text, opts = {}) {
300
343
  const topN = Math.max(1, opts.topN ?? 5);