@viyzhu/boss-cli-fork 0.7.2-rc.1 → 0.7.3

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.
Files changed (37) hide show
  1. package/AGENTS.md +85 -56
  2. package/LICENSE +674 -674
  3. package/README.md +284 -276
  4. package/dist/browser/cdp_browser.d.ts +170 -88
  5. package/dist/browser/cdp_browser.d.ts.map +1 -1
  6. package/dist/browser/cdp_browser.js +674 -400
  7. package/dist/browser/cdp_browser.js.map +1 -1
  8. package/dist/browser/index.d.ts +1 -1
  9. package/dist/browser/index.d.ts.map +1 -1
  10. package/dist/browser/index.js +1 -1
  11. package/dist/browser/index.js.map +1 -1
  12. package/dist/cli/cliRouter.js +61 -61
  13. package/dist/common/auth.js +49 -49
  14. package/dist/common/boss_modal.js +42 -42
  15. package/dist/common/boss_page_guards.js +247 -247
  16. package/dist/common/boss_paywall_popup.js +87 -87
  17. package/dist/common/boss_session_page.d.ts.map +1 -1
  18. package/dist/common/boss_session_page.js +16 -15
  19. package/dist/common/boss_session_page.js.map +1 -1
  20. package/dist/common/boss_sidebar_nav.js +24 -24
  21. package/dist/common/c_resume_capture.js +61 -61
  22. package/dist/toolset/action.d.ts.map +1 -1
  23. package/dist/toolset/action.js +237 -233
  24. package/dist/toolset/action.js.map +1 -1
  25. package/dist/toolset/chat.js +437 -437
  26. package/dist/toolset/deep-search.js +570 -570
  27. package/dist/toolset/jd.js +132 -132
  28. package/dist/toolset/list.js +48 -48
  29. package/dist/toolset/normal-search.js +158 -158
  30. package/dist/toolset/preview.d.ts.map +1 -1
  31. package/dist/toolset/preview.js +3 -3
  32. package/dist/toolset/preview.js.map +1 -1
  33. package/dist/toolset/recommend.js +207 -207
  34. package/package.json +69 -69
  35. package/skills/boss-frontend-analysis/SKILL.md +58 -58
  36. package/skills/boss-frontend-analysis/agents/openai.yaml +4 -4
  37. package/skills/boss-frontend-analysis/scripts/capture_boss_frontend.mjs +604 -604
@@ -43,48 +43,48 @@ async function getRecommendFrame(page) {
43
43
  throw new Error(`已检测到推荐 iframe,但无法获取其页面上下文。iframe src:${iframeSrc || 'unknown'};frames:${frameUrls || 'empty'}`);
44
44
  }
45
45
  async function ensureRecommendFrameReady(frame) {
46
- await frame.waitForFunction(`(() => {
47
- const sel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
48
- if (document.querySelector(sel)) return true;
49
- const root = document.querySelector(".card-list, .geek-list-wrap .geek-list");
50
- return !!root;
46
+ await frame.waitForFunction(`(() => {
47
+ const sel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
48
+ if (document.querySelector(sel)) return true;
49
+ const root = document.querySelector(".card-list, .geek-list-wrap .geek-list");
50
+ return !!root;
51
51
  })()`, { timeout: 18_000 });
52
52
  }
53
53
  async function readCurrentRecommendJobLabel(frame) {
54
- return (await frame.evaluate(`(() => {
55
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
56
- return norm(document.querySelector(".job-selecter-wrap .ui-dropmenu-label")?.textContent);
54
+ return (await frame.evaluate(`(() => {
55
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
56
+ return norm(document.querySelector(".job-selecter-wrap .ui-dropmenu-label")?.textContent);
57
57
  })()`));
58
58
  }
59
59
  async function waitForRecommendJobDropdownReady(frame) {
60
- await frame.waitForFunction(`(() => {
61
- const options = document.querySelector(".job-selecter-options");
62
- if (!(options instanceof HTMLElement)) return false;
63
- const rect = options.getBoundingClientRect();
64
- const style = window.getComputedStyle(options);
65
- if (rect.width <= 0 || rect.height <= 0 || style.display === "none" || style.visibility === "hidden") {
66
- return false;
67
- }
68
- return !!options.querySelector(".top-chat-search .chat-job-search");
60
+ await frame.waitForFunction(`(() => {
61
+ const options = document.querySelector(".job-selecter-options");
62
+ if (!(options instanceof HTMLElement)) return false;
63
+ const rect = options.getBoundingClientRect();
64
+ const style = window.getComputedStyle(options);
65
+ if (rect.width <= 0 || rect.height <= 0 || style.display === "none" || style.visibility === "hidden") {
66
+ return false;
67
+ }
68
+ return !!options.querySelector(".top-chat-search .chat-job-search");
69
69
  })()`, { timeout: 8_000 });
70
70
  }
71
71
  async function waitForRecommendJobSearchResults(frame, keyword) {
72
- await frame.waitForFunction(`((kw) => {
73
- const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
74
- const rows = Array.from(document.querySelectorAll(".job-selecter-options .job-list .job-item"));
75
- if (rows.length === 0) return false;
76
- if (!kw) return true;
77
- return rows.some((el) => {
78
- const label = norm(el.querySelector(".label")?.textContent || el.textContent || "");
79
- return label.includes(norm(kw));
80
- });
72
+ await frame.waitForFunction(`((kw) => {
73
+ const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
74
+ const rows = Array.from(document.querySelectorAll(".job-selecter-options .job-list .job-item"));
75
+ if (rows.length === 0) return false;
76
+ if (!kw) return true;
77
+ return rows.some((el) => {
78
+ const label = norm(el.querySelector(".label")?.textContent || el.textContent || "");
79
+ return label.includes(norm(kw));
80
+ });
81
81
  })`, { timeout: 10_000 }, keyword);
82
82
  }
83
83
  async function waitForRecommendJobSelected(frame, expectedLabel) {
84
- await frame.waitForFunction(`((label) => {
85
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
86
- const current = norm(document.querySelector(".job-selecter-wrap .ui-dropmenu-label")?.textContent);
87
- return !!current && current === label;
84
+ await frame.waitForFunction(`((label) => {
85
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
86
+ const current = norm(document.querySelector(".job-selecter-wrap .ui-dropmenu-label")?.textContent);
87
+ return !!current && current === label;
88
88
  })`, { timeout: 10_000 }, expectedLabel);
89
89
  await ensureRecommendFrameReady(frame);
90
90
  }
@@ -94,49 +94,49 @@ export async function selectRecommendJob(frame, keyword) {
94
94
  return readCurrentRecommendJobLabel(frame);
95
95
  }
96
96
  const kwLiteral = JSON.stringify(kw);
97
- const opened = (await frame.evaluate(`(() => {
98
- const host = document.querySelector(".job-selecter-wrap .ui-dropmenu-label");
99
- if (!(host instanceof HTMLElement)) return false;
100
- host.scrollIntoView({ block: "center", inline: "nearest" });
101
- host.click();
102
- return true;
97
+ const opened = (await frame.evaluate(`(() => {
98
+ const host = document.querySelector(".job-selecter-wrap .ui-dropmenu-label");
99
+ if (!(host instanceof HTMLElement)) return false;
100
+ host.scrollIntoView({ block: "center", inline: "nearest" });
101
+ host.click();
102
+ return true;
103
103
  })()`));
104
104
  if (!opened) {
105
105
  throw new Error('未找到岗位下拉入口(.job-selecter-wrap .ui-dropmenu-label)。');
106
106
  }
107
107
  await sleepRandom(JOB_SELECT_ACTION_GAP_MS.min, JOB_SELECT_ACTION_GAP_MS.max);
108
108
  await waitForRecommendJobDropdownReady(frame);
109
- const searched = (await frame.evaluate(`(() => {
110
- const kw = ${kwLiteral};
111
- const input = document.querySelector(".job-selecter-options .top-chat-search .chat-job-search");
112
- if (!(input instanceof HTMLInputElement)) return false;
113
- input.focus();
114
- input.value = kw;
115
- input.dispatchEvent(new Event("input", { bubbles: true }));
116
- input.dispatchEvent(new Event("change", { bubbles: true }));
117
- return true;
109
+ const searched = (await frame.evaluate(`(() => {
110
+ const kw = ${kwLiteral};
111
+ const input = document.querySelector(".job-selecter-options .top-chat-search .chat-job-search");
112
+ if (!(input instanceof HTMLInputElement)) return false;
113
+ input.focus();
114
+ input.value = kw;
115
+ input.dispatchEvent(new Event("input", { bubbles: true }));
116
+ input.dispatchEvent(new Event("change", { bubbles: true }));
117
+ return true;
118
118
  })()`));
119
119
  if (!searched) {
120
120
  throw new Error('已打开岗位下拉,但未找到职位搜索框(.chat-job-search)。');
121
121
  }
122
122
  await sleepRandom(JOB_SEARCH_ACTION_GAP_MS.min, JOB_SEARCH_ACTION_GAP_MS.max);
123
123
  await waitForRecommendJobSearchResults(frame, kw);
124
- const picked = (await frame.evaluate(`(() => {
125
- const kw = ${kwLiteral};
126
- const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
127
- const rows = Array.from(document.querySelectorAll(".job-selecter-options .job-list .job-item"));
128
- if (rows.length === 0) return { ok: false, reason: "empty" };
129
- const target = rows.find((el) => {
130
- const label = norm(el.querySelector(".label")?.textContent || el.textContent || "");
131
- return label.includes(norm(kw));
132
- });
133
- if (!(target instanceof HTMLElement)) return { ok: false, reason: "not_found" };
134
- const label = (target.querySelector(".label")?.textContent ?? target.textContent ?? "")
135
- .replace(/\\s+/g, " ")
136
- .trim();
137
- target.scrollIntoView({ block: "center", inline: "nearest" });
138
- target.click();
139
- return { ok: true, label };
124
+ const picked = (await frame.evaluate(`(() => {
125
+ const kw = ${kwLiteral};
126
+ const norm = (v) => (v ?? "").replace(/\\s+/g, "").trim().toLowerCase();
127
+ const rows = Array.from(document.querySelectorAll(".job-selecter-options .job-list .job-item"));
128
+ if (rows.length === 0) return { ok: false, reason: "empty" };
129
+ const target = rows.find((el) => {
130
+ const label = norm(el.querySelector(".label")?.textContent || el.textContent || "");
131
+ return label.includes(norm(kw));
132
+ });
133
+ if (!(target instanceof HTMLElement)) return { ok: false, reason: "not_found" };
134
+ const label = (target.querySelector(".label")?.textContent ?? target.textContent ?? "")
135
+ .replace(/\\s+/g, " ")
136
+ .trim();
137
+ target.scrollIntoView({ block: "center", inline: "nearest" });
138
+ target.click();
139
+ return { ok: true, label };
140
140
  })()`));
141
141
  if (!picked.ok) {
142
142
  throw new Error(`未找到匹配岗位“${kw}”。`);
@@ -171,71 +171,71 @@ export async function assertRecommendPageReadyForPreview(page) {
171
171
  return assertRecommendPageReady(page, '预览候选人');
172
172
  }
173
173
  export async function readRecommendList(frame) {
174
- return (await frame.evaluate(`(() => {
175
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
176
- const cardSel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
177
- const cards = Array.from(document.querySelectorAll(cardSel));
178
- return cards.map((item) => {
179
- const inner = item.querySelector(".card-inner") || item;
180
- const wrap = item.matches(".candidate-card-wrap")
181
- ? item
182
- : item.querySelector(".candidate-card-wrap");
183
- const hasViewed = Boolean(
184
- (wrap && wrap.classList.contains("has-viewed")) ||
185
- (inner && inner.classList.contains("has-viewed")),
186
- );
187
- const geekId =
188
- inner?.getAttribute("data-geekid") ??
189
- inner?.getAttribute("data-geek") ??
190
- "";
191
- const name =
192
- norm(item.querySelector(".name-wrap .name")?.textContent) ||
193
- norm(item.querySelector(".name")?.textContent);
194
- const salary = norm(item.querySelector(".salary-wrap span")?.textContent);
195
- const baseInfo = Array.from(item.querySelectorAll(".base-info span"))
196
- .map((el) => norm(el.textContent))
197
- .filter(Boolean)
198
- .join(" / ");
199
- const expect =
200
- norm(item.querySelector(".expect-wrap .content")?.textContent) ||
201
- norm(item.querySelector(".expect-wrap .join-text-wrap")?.textContent);
202
- const experience = norm(item.querySelector(".experience-wrap .join-text-wrap")?.textContent);
203
- const advantage = norm(item.querySelector(".geek-desc .content")?.textContent);
204
- const highlightLabels = [
205
- ...Array.from(item.querySelectorAll(".operate .labels .label")),
206
- ...Array.from(item.querySelectorAll(".tags-wrap .tag-item")),
207
- ]
208
- .map((el) => norm(el.textContent))
209
- .filter(Boolean);
210
- const highlights = [...new Set(highlightLabels)];
211
- const greetBtn = item.querySelector(".button-chat-wrap .btn.btn-greet");
212
- const btnCls = greetBtn?.className ?? "";
213
- const disabled =
214
- !greetBtn ||
215
- /disabled|forbid|ban/i.test(btnCls) ||
216
- greetBtn.getAttribute("disabled") !== null;
217
- const hasHistoryChat = (() => {
218
- if (item.querySelector(".tooltip-wrap.chat-history .icon-chat-history")) return true;
219
- const uses = Array.from(item.querySelectorAll("use"));
220
- return uses.some((u) => {
221
- const href = u.getAttribute("href") ?? u.getAttributeNS("http://www.w3.org/1999/xlink", "href") ?? "";
222
- return href.includes("icon-chat-history");
223
- });
224
- })();
225
- return {
226
- geekId,
227
- name,
228
- salary,
229
- baseInfo,
230
- expect,
231
- experience,
232
- advantage,
233
- highlights,
234
- canGreet: !disabled,
235
- hasHistoryChat,
236
- hasViewed,
237
- };
238
- }).filter((x) => x.name);
174
+ return (await frame.evaluate(`(() => {
175
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
176
+ const cardSel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
177
+ const cards = Array.from(document.querySelectorAll(cardSel));
178
+ return cards.map((item) => {
179
+ const inner = item.querySelector(".card-inner") || item;
180
+ const wrap = item.matches(".candidate-card-wrap")
181
+ ? item
182
+ : item.querySelector(".candidate-card-wrap");
183
+ const hasViewed = Boolean(
184
+ (wrap && wrap.classList.contains("has-viewed")) ||
185
+ (inner && inner.classList.contains("has-viewed")),
186
+ );
187
+ const geekId =
188
+ inner?.getAttribute("data-geekid") ??
189
+ inner?.getAttribute("data-geek") ??
190
+ "";
191
+ const name =
192
+ norm(item.querySelector(".name-wrap .name")?.textContent) ||
193
+ norm(item.querySelector(".name")?.textContent);
194
+ const salary = norm(item.querySelector(".salary-wrap span")?.textContent);
195
+ const baseInfo = Array.from(item.querySelectorAll(".base-info span"))
196
+ .map((el) => norm(el.textContent))
197
+ .filter(Boolean)
198
+ .join(" / ");
199
+ const expect =
200
+ norm(item.querySelector(".expect-wrap .content")?.textContent) ||
201
+ norm(item.querySelector(".expect-wrap .join-text-wrap")?.textContent);
202
+ const experience = norm(item.querySelector(".experience-wrap .join-text-wrap")?.textContent);
203
+ const advantage = norm(item.querySelector(".geek-desc .content")?.textContent);
204
+ const highlightLabels = [
205
+ ...Array.from(item.querySelectorAll(".operate .labels .label")),
206
+ ...Array.from(item.querySelectorAll(".tags-wrap .tag-item")),
207
+ ]
208
+ .map((el) => norm(el.textContent))
209
+ .filter(Boolean);
210
+ const highlights = [...new Set(highlightLabels)];
211
+ const greetBtn = item.querySelector(".button-chat-wrap .btn.btn-greet");
212
+ const btnCls = greetBtn?.className ?? "";
213
+ const disabled =
214
+ !greetBtn ||
215
+ /disabled|forbid|ban/i.test(btnCls) ||
216
+ greetBtn.getAttribute("disabled") !== null;
217
+ const hasHistoryChat = (() => {
218
+ if (item.querySelector(".tooltip-wrap.chat-history .icon-chat-history")) return true;
219
+ const uses = Array.from(item.querySelectorAll("use"));
220
+ return uses.some((u) => {
221
+ const href = u.getAttribute("href") ?? u.getAttributeNS("http://www.w3.org/1999/xlink", "href") ?? "";
222
+ return href.includes("icon-chat-history");
223
+ });
224
+ })();
225
+ return {
226
+ geekId,
227
+ name,
228
+ salary,
229
+ baseInfo,
230
+ expect,
231
+ experience,
232
+ advantage,
233
+ highlights,
234
+ canGreet: !disabled,
235
+ hasHistoryChat,
236
+ hasViewed,
237
+ };
238
+ }).filter((x) => x.name);
239
239
  })()`));
240
240
  }
241
241
  export function renderRecommendList(candidates) {
@@ -288,44 +288,44 @@ export function renderRecommendList(candidates) {
288
288
  }
289
289
  export async function clickGreet(frame, target) {
290
290
  const targetLiteral = JSON.stringify(target.trim());
291
- const result = (await frame.evaluate(`(() => {
292
- const raw = ${targetLiteral};
293
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
294
- const cardSel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
295
- const cards = Array.from(document.querySelectorAll(cardSel));
296
- if (cards.length === 0) {
297
- return { kind: "empty" };
298
- }
299
- const targetCard = cards.find((item) => {
300
- const name =
301
- norm(item.querySelector(".name-wrap .name")?.textContent) ||
302
- norm(item.querySelector(".name")?.textContent);
303
- return name === raw || name.includes(raw);
304
- }) ?? null;
305
- if (!targetCard) {
306
- return { kind: "not_found", target: raw };
307
- }
308
-
309
- const name =
310
- norm(targetCard.querySelector(".name-wrap .name")?.textContent) ||
311
- norm(targetCard.querySelector(".name")?.textContent);
312
- const inner = targetCard.querySelector(".card-inner") || targetCard;
313
- const geekId =
314
- inner?.getAttribute("data-geekid") ??
315
- inner?.getAttribute("data-geek") ??
316
- "";
317
- const btn = targetCard.querySelector(".button-chat-wrap .btn.btn-greet");
318
- if (!(btn instanceof HTMLElement)) {
319
- return { kind: "no_btn", name };
320
- }
321
- const cls = btn.className ?? "";
322
- const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
323
- if (disabled) {
324
- return { kind: "disabled", name };
325
- }
326
- btn.scrollIntoView({ block: "center", inline: "nearest" });
327
- btn.click();
328
- return { kind: "clicked", name, geekId };
291
+ const result = (await frame.evaluate(`(() => {
292
+ const raw = ${targetLiteral};
293
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
294
+ const cardSel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
295
+ const cards = Array.from(document.querySelectorAll(cardSel));
296
+ if (cards.length === 0) {
297
+ return { kind: "empty" };
298
+ }
299
+ const targetCard = cards.find((item) => {
300
+ const name =
301
+ norm(item.querySelector(".name-wrap .name")?.textContent) ||
302
+ norm(item.querySelector(".name")?.textContent);
303
+ return name === raw || name.includes(raw);
304
+ }) ?? null;
305
+ if (!targetCard) {
306
+ return { kind: "not_found", target: raw };
307
+ }
308
+
309
+ const name =
310
+ norm(targetCard.querySelector(".name-wrap .name")?.textContent) ||
311
+ norm(targetCard.querySelector(".name")?.textContent);
312
+ const inner = targetCard.querySelector(".card-inner") || targetCard;
313
+ const geekId =
314
+ inner?.getAttribute("data-geekid") ??
315
+ inner?.getAttribute("data-geek") ??
316
+ "";
317
+ const btn = targetCard.querySelector(".button-chat-wrap .btn.btn-greet");
318
+ if (!(btn instanceof HTMLElement)) {
319
+ return { kind: "no_btn", name };
320
+ }
321
+ const cls = btn.className ?? "";
322
+ const disabled = /disabled|forbid|ban/i.test(cls) || btn.getAttribute("disabled") !== null;
323
+ if (disabled) {
324
+ return { kind: "disabled", name };
325
+ }
326
+ btn.scrollIntoView({ block: "center", inline: "nearest" });
327
+ btn.click();
328
+ return { kind: "clicked", name, geekId };
329
329
  })()`));
330
330
  switch (result.kind) {
331
331
  case 'empty':
@@ -361,49 +361,49 @@ export function markGreetProduced(before, after) {
361
361
  export async function openRecommendResumePreview(frame, target) {
362
362
  const raw = target.trim();
363
363
  const targetLiteral = JSON.stringify(raw);
364
- const opened = (await frame.evaluate(`(() => {
365
- const raw = ${targetLiteral};
366
- const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
367
- const cardSel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
368
- const cards = Array.from(document.querySelectorAll(cardSel));
369
- if (cards.length === 0) return false;
370
- const targetCard = cards.find((item) => {
371
- const name =
372
- norm(item.querySelector(".name-wrap .name")?.textContent) ||
373
- norm(item.querySelector(".name")?.textContent);
374
- return name === raw || name.includes(raw);
375
- }) ?? null;
376
- if (!targetCard) return false;
377
-
378
- function tryOpen(el) {
379
- if (!(el instanceof HTMLElement)) return false;
380
- if (el.classList.contains("disabled")) return false;
381
- const st = window.getComputedStyle(el);
382
- if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
383
- el.scrollIntoView({ block: "center", inline: "nearest" });
384
- el.click();
385
- return true;
386
- }
387
-
388
- const inner = targetCard.querySelector(".card-inner");
389
- if (inner instanceof HTMLElement) {
390
- inner.scrollIntoView({ block: "center", inline: "nearest" });
391
- inner.click();
392
- return true;
393
- }
394
-
395
- const resumeOnline = targetCard.querySelector("a.resume-btn-online");
396
- if (tryOpen(resumeOnline)) return true;
397
- const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
398
- if (tryOpen(hrefResume)) return true;
399
-
400
- const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
401
- const t = norm(node.textContent);
402
- return /在线简历|查看简历|简历预览|预览/.test(t);
403
- });
404
- if (links.length > 0 && tryOpen(links[0])) return true;
405
-
406
- return false;
364
+ const opened = (await frame.evaluate(`(() => {
365
+ const raw = ${targetLiteral};
366
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
367
+ const cardSel = ${JSON.stringify(RECOMMEND_CARD_ROOT_SELECTOR)};
368
+ const cards = Array.from(document.querySelectorAll(cardSel));
369
+ if (cards.length === 0) return false;
370
+ const targetCard = cards.find((item) => {
371
+ const name =
372
+ norm(item.querySelector(".name-wrap .name")?.textContent) ||
373
+ norm(item.querySelector(".name")?.textContent);
374
+ return name === raw || name.includes(raw);
375
+ }) ?? null;
376
+ if (!targetCard) return false;
377
+
378
+ function tryOpen(el) {
379
+ if (!(el instanceof HTMLElement)) return false;
380
+ if (el.classList.contains("disabled")) return false;
381
+ const st = window.getComputedStyle(el);
382
+ if (st.pointerEvents === "none" || Number(st.opacity) < 0.3) return false;
383
+ el.scrollIntoView({ block: "center", inline: "nearest" });
384
+ el.click();
385
+ return true;
386
+ }
387
+
388
+ const inner = targetCard.querySelector(".card-inner");
389
+ if (inner instanceof HTMLElement) {
390
+ inner.scrollIntoView({ block: "center", inline: "nearest" });
391
+ inner.click();
392
+ return true;
393
+ }
394
+
395
+ const resumeOnline = targetCard.querySelector("a.resume-btn-online");
396
+ if (tryOpen(resumeOnline)) return true;
397
+ const hrefResume = targetCard.querySelector('a[href*="c-resume"], a[href*="frame/c-resume"]');
398
+ if (tryOpen(hrefResume)) return true;
399
+
400
+ const links = Array.from(targetCard.querySelectorAll("a, button, .btn")).filter((node) => {
401
+ const t = norm(node.textContent);
402
+ return /在线简历|查看简历|简历预览|预览/.test(t);
403
+ });
404
+ if (links.length > 0 && tryOpen(links[0])) return true;
405
+
406
+ return false;
407
407
  })()`));
408
408
  if (opened) {
409
409
  await sleepRandom(RESUME_PREVIEW_OPEN_GAP_MS.min, RESUME_PREVIEW_OPEN_GAP_MS.max);
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "@viyzhu/boss-cli-fork",
3
- "version": "0.7.2-rc.1",
4
- "description": "Boss直聘自动化 CLI(@joohw/boss-cli 的 fork,含风控页反弹熔断等修复):批量发消息、自动打招呼、候选人管理、深度搜索。基于 Puppeteer/CDP 驱动本机 Chrome,支持 AI Agent 编排",
5
- "keywords": [
6
- "boss",
7
- "boss-cli",
8
- "boss直聘",
9
- "boss-zhipin",
10
- "zhipin",
11
- "bosszp",
12
- "招聘自动化",
13
- "hr-automation",
14
- "recruitment-automation",
15
- "puppeteer",
16
- "cdp",
17
- "chrome-devtools-protocol",
18
- "cli",
19
- "agent",
20
- "ai-agent",
21
- "自动打招呼",
22
- "批量发消息",
23
- "候选人管理",
24
- "hr-tools",
25
- "boss-direct"
26
- ],
27
- "homepage": "https://github.com/Viy1204/boss-cli#readme",
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/Viy1204/boss-cli.git"
31
- },
32
- "bugs": {
33
- "url": "https://github.com/Viy1204/boss-cli/issues"
34
- },
35
- "type": "module",
36
- "main": "dist/cli/index.js",
37
- "types": "dist/cli/index.d.ts",
38
- "bin": {
39
- "boss": "dist/cli/index.js"
40
- },
41
- "files": [
42
- "dist",
43
- "skills",
44
- "README.md",
45
- "AGENTS.md",
46
- "LICENSE"
47
- ],
48
- "scripts": {
49
- "build": "tsc",
50
- "dev": "npm run build && node dist/cli/index.js",
51
- "deploy": "node scripts/deploy.mjs",
52
- "test": "npm run build && node --test tests/*.test.mjs",
53
- "test:baidu": "node --env-file=.env scripts/test-baidu-ocr.mjs"
54
- },
55
- "dependencies": {
56
- "dotenv": "^17.4.2",
57
- "puppeteer-core": "^24.29.1"
58
- },
59
- "devDependencies": {
60
- "@types/node": "^24.10.0",
61
- "ssh2": "^1.17.0",
62
- "typescript": "^5.3.3"
63
- },
64
- "author": "Joo <1546635571@qq.com>",
65
- "license": "GPL-3.0",
66
- "publishConfig": {
67
- "access": "public"
68
- }
69
- }
1
+ {
2
+ "name": "@viyzhu/boss-cli-fork",
3
+ "version": "0.7.3",
4
+ "description": "Boss直聘自动化 CLI(@joohw/boss-cli 的 fork,含风控页反弹熔断等修复):批量发消息、自动打招呼、候选人管理、深度搜索。基于 Puppeteer/CDP 驱动本机 Chrome,支持 AI Agent 编排",
5
+ "keywords": [
6
+ "boss",
7
+ "boss-cli",
8
+ "boss直聘",
9
+ "boss-zhipin",
10
+ "zhipin",
11
+ "bosszp",
12
+ "招聘自动化",
13
+ "hr-automation",
14
+ "recruitment-automation",
15
+ "puppeteer",
16
+ "cdp",
17
+ "chrome-devtools-protocol",
18
+ "cli",
19
+ "agent",
20
+ "ai-agent",
21
+ "自动打招呼",
22
+ "批量发消息",
23
+ "候选人管理",
24
+ "hr-tools",
25
+ "boss-direct"
26
+ ],
27
+ "homepage": "https://github.com/Viy1204/boss-cli#readme",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Viy1204/boss-cli.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/Viy1204/boss-cli/issues"
34
+ },
35
+ "type": "module",
36
+ "main": "dist/cli/index.js",
37
+ "types": "dist/cli/index.d.ts",
38
+ "bin": {
39
+ "boss": "dist/cli/index.js"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "skills",
44
+ "README.md",
45
+ "AGENTS.md",
46
+ "LICENSE"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc",
50
+ "dev": "npm run build && node dist/cli/index.js",
51
+ "deploy": "node scripts/deploy.mjs",
52
+ "test": "npm run build && node --test tests/*.test.mjs",
53
+ "test:baidu": "node --env-file=.env scripts/test-baidu-ocr.mjs"
54
+ },
55
+ "dependencies": {
56
+ "dotenv": "^17.4.2",
57
+ "puppeteer-core": "^24.29.1"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^24.10.0",
61
+ "ssh2": "^1.17.0",
62
+ "typescript": "^5.3.3"
63
+ },
64
+ "author": "Joo <1546635571@qq.com>",
65
+ "license": "GPL-3.0",
66
+ "publishConfig": {
67
+ "access": "public"
68
+ }
69
+ }