@joohw/boss-cli 0.1.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.
Files changed (68) hide show
  1. package/AGENTS.md +25 -0
  2. package/LICENSE +674 -0
  3. package/README.md +81 -0
  4. package/dist/browser/auth.d.ts +34 -0
  5. package/dist/browser/auth.d.ts.map +1 -0
  6. package/dist/browser/auth.js +153 -0
  7. package/dist/browser/auth.js.map +1 -0
  8. package/dist/browser/browser_session.d.ts +17 -0
  9. package/dist/browser/browser_session.d.ts.map +1 -0
  10. package/dist/browser/browser_session.js +156 -0
  11. package/dist/browser/browser_session.js.map +1 -0
  12. package/dist/browser/cdp_browser.d.ts +34 -0
  13. package/dist/browser/cdp_browser.d.ts.map +1 -0
  14. package/dist/browser/cdp_browser.js +95 -0
  15. package/dist/browser/cdp_browser.js.map +1 -0
  16. package/dist/browser/chat.d.ts +7 -0
  17. package/dist/browser/chat.d.ts.map +1 -0
  18. package/dist/browser/chat.js +63 -0
  19. package/dist/browser/chat.js.map +1 -0
  20. package/dist/browser/index.d.ts +9 -0
  21. package/dist/browser/index.d.ts.map +1 -0
  22. package/dist/browser/index.js +9 -0
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/withLoggedInPage.d.ts +7 -0
  25. package/dist/browser/withLoggedInPage.d.ts.map +1 -0
  26. package/dist/browser/withLoggedInPage.js +19 -0
  27. package/dist/browser/withLoggedInPage.js.map +1 -0
  28. package/dist/cli/banner.d.ts +2 -0
  29. package/dist/cli/banner.d.ts.map +1 -0
  30. package/dist/cli/banner.js +27 -0
  31. package/dist/cli/banner.js.map +1 -0
  32. package/dist/cli/cliRouter.d.ts +9 -0
  33. package/dist/cli/cliRouter.d.ts.map +1 -0
  34. package/dist/cli/cliRouter.js +254 -0
  35. package/dist/cli/cliRouter.js.map +1 -0
  36. package/dist/cli/index.d.ts +3 -0
  37. package/dist/cli/index.d.ts.map +1 -0
  38. package/dist/cli/index.js +21 -0
  39. package/dist/cli/index.js.map +1 -0
  40. package/dist/config.d.ts +11 -0
  41. package/dist/config.d.ts.map +1 -0
  42. package/dist/config.js +27 -0
  43. package/dist/config.js.map +1 -0
  44. package/dist/toolset/index.d.ts +7 -0
  45. package/dist/toolset/index.d.ts.map +1 -0
  46. package/dist/toolset/index.js +26 -0
  47. package/dist/toolset/index.js.map +1 -0
  48. package/dist/toolset/list_candidates.d.ts +4 -0
  49. package/dist/toolset/list_candidates.d.ts.map +1 -0
  50. package/dist/toolset/list_candidates.js +127 -0
  51. package/dist/toolset/list_candidates.js.map +1 -0
  52. package/dist/toolset/list_positions.d.ts +9 -0
  53. package/dist/toolset/list_positions.d.ts.map +1 -0
  54. package/dist/toolset/list_positions.js +41 -0
  55. package/dist/toolset/list_positions.js.map +1 -0
  56. package/dist/toolset/login.d.ts +11 -0
  57. package/dist/toolset/login.d.ts.map +1 -0
  58. package/dist/toolset/login.js +91 -0
  59. package/dist/toolset/login.js.map +1 -0
  60. package/dist/toolset/open_chat.d.ts +3 -0
  61. package/dist/toolset/open_chat.d.ts.map +1 -0
  62. package/dist/toolset/open_chat.js +181 -0
  63. package/dist/toolset/open_chat.js.map +1 -0
  64. package/dist/toolset/send_message.d.ts +2 -0
  65. package/dist/toolset/send_message.d.ts.map +1 -0
  66. package/dist/toolset/send_message.js +115 -0
  67. package/dist/toolset/send_message.js.map +1 -0
  68. package/package.json +30 -0
@@ -0,0 +1,127 @@
1
+ import { createWaitManualLoginRequiredText, isBossChatIndexUrl, withChatPage } from '../browser/index.js';
2
+ function delay(ms) {
3
+ return new Promise((resolve) => setTimeout(resolve, ms));
4
+ }
5
+ async function waitForCandidateListSettled(page, opts) {
6
+ const start = Date.now();
7
+ let prev = -1;
8
+ let stable = 0;
9
+ while (Date.now() - start < opts.timeoutMs) {
10
+ const n = (await page.evaluate(`(() => document.querySelectorAll(".geek-item").length)()`));
11
+ const elapsed = Date.now() - start;
12
+ if (n === prev) {
13
+ stable++;
14
+ }
15
+ else {
16
+ prev = n;
17
+ stable = 1;
18
+ }
19
+ if (stable >= 2) {
20
+ if (n > 0) {
21
+ return;
22
+ }
23
+ if (n === 0 && elapsed >= opts.minMsBeforeEmptyOk) {
24
+ return;
25
+ }
26
+ }
27
+ await new Promise((r) => setTimeout(r, opts.pollMs));
28
+ }
29
+ }
30
+ async function clickChatFilterTabAll(page) {
31
+ await page.evaluate(`(() => {
32
+ const targetText = "全部";
33
+ const container = document.querySelector(".chat-message-filter-left");
34
+ if (!container) return;
35
+ const spans = Array.from(container.querySelectorAll("span"));
36
+ const norm = (v) => (v ?? "").replace(/\\s+/g, "");
37
+ const target = spans.find((el) => norm(el.textContent).includes(targetText));
38
+ if (!target) return;
39
+ target.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
40
+ target.click();
41
+ })()`);
42
+ }
43
+ export async function runGetCandidateList(opts = {}) {
44
+ const unreadOnly = opts.unreadOnly === true;
45
+ try {
46
+ return await withChatPage(async (page) => {
47
+ const currentUrl = page.url();
48
+ if (!isBossChatIndexUrl(currentUrl)) {
49
+ console.error(`[boss-cli] candidate_list not_chat_list url=${currentUrl}`);
50
+ throw new Error('请先进入聊天列表页(/web/chat/index)再获取候选人列表。');
51
+ }
52
+ await page.waitForFunction(`(() => {
53
+ const filter = document.querySelector(".chat-message-filter-left");
54
+ if (!filter) return false;
55
+ const tabs = Array.from(filter.querySelectorAll("span"));
56
+ if (tabs.length < 2) return false;
57
+ const list = document.querySelector(".chat-list, .chat-item-list, .geek-list");
58
+ const hasItems = document.querySelectorAll(".geek-item").length > 0;
59
+ return !!list || hasItems;
60
+ })()`, { timeout: 8_000 });
61
+ await clickChatFilterTabAll(page);
62
+ await delay(520);
63
+ await delay(450);
64
+ await clickChatFilterTabAll(page);
65
+ await delay(520);
66
+ await waitForCandidateListSettled(page, {
67
+ timeoutMs: 10_000,
68
+ pollMs: 220,
69
+ minMsBeforeEmptyOk: 2_800,
70
+ });
71
+ const items = (await page.evaluate(`(() => {
72
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
73
+ return Array.from(document.querySelectorAll(".geek-item")).map((el) => {
74
+ const name = norm(el.querySelector(".geek-name")?.textContent);
75
+ const job = norm(el.querySelector(".source-job")?.textContent);
76
+ const time = norm(el.querySelector(".time")?.textContent);
77
+ const message = norm(el.querySelector(".push-text")?.textContent);
78
+ const badge = el.querySelector(".badge-count");
79
+ let unreadCount = 0;
80
+ if (badge) {
81
+ const digits = norm(badge.textContent).replace(/\\D/g, "");
82
+ if (digits) unreadCount = parseInt(digits, 10) || 0;
83
+ }
84
+ return { name, job, time, message, unreadCount };
85
+ });
86
+ })()`));
87
+ const candidates = items.filter((it) => it.name);
88
+ const withUnread = candidates.filter((it) => it.unreadCount > 0).length;
89
+ const visible = unreadOnly ? candidates.filter((it) => it.unreadCount > 0) : candidates;
90
+ console.error(`[boss-cli] candidate_list extracted=${candidates.length} withUnreadBadge=${withUnread}`);
91
+ const lines = visible.map((it, idx) => {
92
+ const base = `${idx + 1}. ${it.name}${it.job ? `|${it.job}` : ''}`;
93
+ const meta = [
94
+ it.unreadCount > 0 ? `未读:${it.unreadCount}` : '',
95
+ it.time ? `时间:${it.time}` : '',
96
+ it.message ? `消息:${it.message}` : '',
97
+ ]
98
+ .filter(Boolean)
99
+ .join('|');
100
+ return meta ? `${base}|${meta}` : base;
101
+ });
102
+ const logText = lines.length > 0 ? lines.join('\n') : '(empty)';
103
+ console.error(`[boss-cli] candidate_list count=${visible.length}\n${logText}`);
104
+ const previewText = lines.length > 0 ? `候选人明细:\n${lines.join('\n')}` : '候选人明细:暂无。';
105
+ return [
106
+ unreadOnly
107
+ ? `已获取未读候选人(全部),共 ${visible.length} 条;原始列表共 ${candidates.length} 条;其中 ${withUnread} 人有未读角标。`
108
+ : `已获取候选人列表(全部),共 ${candidates.length} 条;其中 ${withUnread} 人有未读角标。`,
109
+ previewText,
110
+ ]
111
+ .filter(Boolean)
112
+ .join('\n');
113
+ });
114
+ }
115
+ catch (e) {
116
+ const message = e instanceof Error ? e.message : String(e);
117
+ console.error(`[boss-cli] candidate_list error: ${message}`);
118
+ if (e instanceof Error) {
119
+ if (e.message.includes('浏览器会话尚未初始化')) {
120
+ throw new Error(createWaitManualLoginRequiredText('获取候选人列表'));
121
+ }
122
+ throw e;
123
+ }
124
+ throw new Error(`获取候选人列表失败:${message}`);
125
+ }
126
+ }
127
+ //# sourceMappingURL=list_candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_candidates.js","sourceRoot":"","sources":["../../src/toolset/list_candidates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type ListOpenPositionsDeps = {
2
+ /**
3
+ * 存放岗位 JD 的目录(每个岗位一个 .md/.MD 文件)。
4
+ * 默认:本包根目录下的 `jd/`(相对 `src/toolset` 或 `dist/toolset` 解析)。
5
+ */
6
+ jdDir?: string;
7
+ };
8
+ export declare function runListOpenPositions(deps?: ListOpenPositionsDeps): Promise<string>;
9
+ //# sourceMappingURL=list_positions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_positions.d.ts","sourceRoot":"","sources":["../../src/toolset/list_positions.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAWF,wBAAsB,oBAAoB,CACxC,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,MAAM,CAAC,CAiCjB"}
@@ -0,0 +1,41 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ function defaultJdDir() {
5
+ const here = path.dirname(fileURLToPath(import.meta.url));
6
+ return path.join(here, '..', '..', 'jd');
7
+ }
8
+ function isMdFile(name) {
9
+ return name.toLowerCase().endsWith('.md');
10
+ }
11
+ export async function runListOpenPositions(deps = {}) {
12
+ const jdDir = deps.jdDir ?? defaultJdDir();
13
+ console.error(`[boss-cli] list_open_positions jdDir=${jdDir}`);
14
+ try {
15
+ const entries = await readdir(jdDir, { withFileTypes: true });
16
+ const files = entries
17
+ .filter((e) => e.isFile() && isMdFile(e.name))
18
+ .map((e) => e.name)
19
+ .sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'));
20
+ const positions = [];
21
+ for (const filename of files) {
22
+ const full = path.join(jdDir, filename);
23
+ const content = await readFile(full, 'utf8');
24
+ const base = path.basename(filename, path.extname(filename));
25
+ positions.push({ title: base, filename, content });
26
+ }
27
+ const header = `已读取 ${positions.length} 个岗位 JD(目录:${jdDir})`;
28
+ const blocks = positions.map((p, i) => {
29
+ const sep = '---';
30
+ return [`## ${i + 1}. ${p.title}(${p.filename})`, sep, p.content.trimEnd()].join('\n');
31
+ });
32
+ const textBody = blocks.length > 0 ? blocks.join('\n\n') : '(该目录下暂无 .md/.MD 岗位文件)';
33
+ return [header, textBody].filter(Boolean).join('\n');
34
+ }
35
+ catch (e) {
36
+ const message = e instanceof Error ? e.message : String(e);
37
+ console.error(`[boss-cli] list_open_positions error: ${message}`);
38
+ throw new Error(`读取岗位 JD 失败:${message}`);
39
+ }
40
+ }
41
+ //# sourceMappingURL=list_positions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_positions.js","sourceRoot":"","sources":["../../src/toolset/list_positions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ type WaitForLoginOptions = {
2
+ timeoutMs?: number;
3
+ pollMs?: number;
4
+ };
5
+ /**
6
+ * 登录(手动):打开 Boss 登录页,并等待用户在浏览器中完成登录。
7
+ * 成功返回纯文本;失败抛错(由 CLI 统一写入 stderr 并设置退出码)。
8
+ */
9
+ export declare function runLogin(options?: WaitForLoginOptions): Promise<string>;
10
+ export {};
11
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/toolset/login.ts"],"names":[],"mappings":"AAWA,KAAK,mBAAmB,GAAG;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AA2DF;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqCjF"}
@@ -0,0 +1,91 @@
1
+ import { ensureAndGetBrowser, ensureBrowserSession, getBrowserRef, getPageRef, probeLoggedInFromPage, setSessionPage, sleep, } from '../browser/index.js';
2
+ const BOSS_LOGIN_URL = 'https://www.zhipin.com/web/user/?ka=header-login';
3
+ async function pickExistingPage(browser) {
4
+ const pages = (await browser.pages()).filter((p) => !p.isClosed());
5
+ if (pages.length === 0)
6
+ return null;
7
+ const urls = await Promise.all(pages.map((p) => {
8
+ try {
9
+ return p.url();
10
+ }
11
+ catch {
12
+ return '';
13
+ }
14
+ }));
15
+ const zhipin = pages.find((p, i) => {
16
+ const u = urls[i] ?? '';
17
+ return u.length > 0 && u !== 'about:blank' && u.includes('zhipin.com');
18
+ });
19
+ if (zhipin)
20
+ return zhipin;
21
+ const nonBlank = pages.find((p, i) => {
22
+ const u = urls[i] ?? '';
23
+ return u.length > 0 && u !== 'about:blank';
24
+ });
25
+ return nonBlank ?? null;
26
+ }
27
+ async function waitForBossLogin(opts) {
28
+ const start = Date.now();
29
+ for (;;) {
30
+ const page = getPageRef();
31
+ if (!page) {
32
+ throw new Error('浏览器页面已不存在,登录中断。');
33
+ }
34
+ if (page.isClosed?.()) {
35
+ throw new Error('浏览器页面已关闭,登录中断。');
36
+ }
37
+ const browser = page.browser?.();
38
+ if (browser && !browser.isConnected()) {
39
+ throw new Error('浏览器已断开连接,登录中断。');
40
+ }
41
+ const { loggedIn, url } = await probeLoggedInFromPage(page);
42
+ if (loggedIn) {
43
+ console.error(`[boss-cli] login ok url=${url}`);
44
+ return;
45
+ }
46
+ if (Date.now() - start >= opts.timeoutMs) {
47
+ throw new Error(`登录超时(${Math.round(opts.timeoutMs / 1000)}s)。请在浏览器中完成登录后重试。`);
48
+ }
49
+ await sleep(opts.pollMs);
50
+ }
51
+ }
52
+ /**
53
+ * 登录(手动):打开 Boss 登录页,并等待用户在浏览器中完成登录。
54
+ * 成功返回纯文本;失败抛错(由 CLI 统一写入 stderr 并设置退出码)。
55
+ */
56
+ export async function runLogin(options = {}) {
57
+ const timeoutMs = options.timeoutMs ?? 120_000;
58
+ const pollMs = options.pollMs ?? 2_000;
59
+ let browser = null;
60
+ try {
61
+ browser = (await ensureAndGetBrowser()) ?? (getBrowserRef() ?? null);
62
+ if (!browser) {
63
+ await ensureBrowserSession();
64
+ browser = getBrowserRef() ?? null;
65
+ }
66
+ }
67
+ catch (e) {
68
+ const msg = e instanceof Error ? e.message : String(e);
69
+ throw new Error(`连接浏览器失败:${msg}`);
70
+ }
71
+ if (!browser) {
72
+ throw new Error('无法获取浏览器实例,登录失败。');
73
+ }
74
+ let page = getPageRef() ?? null;
75
+ if (!page || page.isClosed()) {
76
+ page = (await pickExistingPage(browser)) ?? (await browser.newPage());
77
+ }
78
+ setSessionPage(page);
79
+ await page.bringToFront();
80
+ await page.goto(BOSS_LOGIN_URL, { waitUntil: 'load', timeout: 60_000 });
81
+ console.error(`⏰ 请在浏览器中完成登录(超时 ${Math.round(timeoutMs / 1000)} 秒)`);
82
+ await waitForBossLogin({ timeoutMs, pollMs });
83
+ const after = await probeLoggedInFromPage(page);
84
+ return [
85
+ '✅ 登录成功',
86
+ `当前页:${after.url}`,
87
+ ]
88
+ .filter(Boolean)
89
+ .join('\n');
90
+ }
91
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/toolset/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Page } from 'puppeteer-core';
2
+ export declare function runOpenCandidateChat(page: Page, candidateName: string, exact?: boolean): Promise<string>;
3
+ //# sourceMappingURL=open_chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open_chat.d.ts","sourceRoot":"","sources":["../../src/toolset/open_chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAG3C,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,UAAO,GACX,OAAO,CAAC,MAAM,CAAC,CAgNjB"}
@@ -0,0 +1,181 @@
1
+ import { isBossChatIndexUrl } from '../browser/index.js';
2
+ export async function runOpenCandidateChat(page, candidateName, exact = true) {
3
+ const targetName = candidateName.trim();
4
+ try {
5
+ const currentUrl = page.url();
6
+ if (!isBossChatIndexUrl(currentUrl)) {
7
+ throw new Error('请先进入聊天列表页(/web/chat/index)再打开候选人聊天。');
8
+ }
9
+ const norm = (v) => (v ?? '').replace(/\s+/g, ' ').trim();
10
+ const matcher = (value) => exact ? value === targetName : value.includes(targetName);
11
+ let targetWrap = null;
12
+ let foundName = '';
13
+ const maxScrollRounds = 40;
14
+ for (let round = 0; round < maxScrollRounds && !targetWrap; round++) {
15
+ const wraps = await page.$$('.geek-item-wrap');
16
+ for (const wrap of wraps) {
17
+ const nameText = await wrap
18
+ .$eval('.geek-name', (el) => (el.textContent ?? '').trim())
19
+ .catch(() => '');
20
+ const candidate = norm(nameText);
21
+ if (!candidate)
22
+ continue;
23
+ if (matcher(candidate)) {
24
+ targetWrap = wrap;
25
+ foundName = candidate;
26
+ break;
27
+ }
28
+ }
29
+ if (targetWrap)
30
+ break;
31
+ const scrollState = (await page.evaluate(`(() => {
32
+ const first = document.querySelector(".geek-item-wrap");
33
+ if (!first) return { moved: false, atEnd: true };
34
+ let node = first.parentElement;
35
+ let scroller = null;
36
+ while (node) {
37
+ const style = window.getComputedStyle(node);
38
+ const overflowY = style.overflowY;
39
+ const canScroll =
40
+ (overflowY === "auto" || overflowY === "scroll") &&
41
+ node.scrollHeight > node.clientHeight;
42
+ if (canScroll) {
43
+ scroller = node;
44
+ break;
45
+ }
46
+ node = node.parentElement;
47
+ }
48
+ if (!scroller) return { moved: false, atEnd: true };
49
+ const prev = scroller.scrollTop;
50
+ const step = Math.max(160, Math.floor(scroller.clientHeight * 0.8));
51
+ scroller.scrollTop = Math.min(scroller.scrollTop + step, scroller.scrollHeight);
52
+ const moved = scroller.scrollTop !== prev;
53
+ const atEnd = scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2;
54
+ return { moved, atEnd };
55
+ })()`));
56
+ if (!scrollState.moved || scrollState.atEnd) {
57
+ break;
58
+ }
59
+ await new Promise((resolve) => setTimeout(resolve, 120));
60
+ }
61
+ if (!targetWrap) {
62
+ throw new Error(`未在聊天列表中找到候选人:${targetName}`);
63
+ }
64
+ try {
65
+ await targetWrap.evaluate((el) => {
66
+ const row = el.querySelector('.geek-item') ?? el;
67
+ row.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
68
+ row.click();
69
+ });
70
+ }
71
+ catch {
72
+ const box = await targetWrap.boundingBox();
73
+ if (!box) {
74
+ throw new Error(`已定位候选人 ${foundName},但元素不可点击。`);
75
+ }
76
+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { delay: 30 });
77
+ }
78
+ const selected = await targetWrap
79
+ .$eval('.geek-item', (el) => el.classList.contains('selected'))
80
+ .catch(() => false);
81
+ try {
82
+ await page.waitForSelector('.base-info-single-container', { timeout: 8_000 });
83
+ await page.waitForFunction(`((name) => {
84
+ const text = document.querySelector(".base-info-single-container .name-box")?.textContent ?? "";
85
+ return text.replace(/\\s+/g, " ").trim().includes(name);
86
+ })`, { timeout: 8_000 }, foundName || targetName);
87
+ await page.waitForFunction(`(() => {
88
+ const list = document.querySelector(".chat-message-list");
89
+ if (!list) return false;
90
+ const items = list.querySelectorAll(".message-item");
91
+ if (!items || items.length === 0) return false;
92
+ const hasText = Array.from(items).some((item) => {
93
+ const txt =
94
+ item.querySelector(".item-friend .text span")?.textContent ??
95
+ item.querySelector(".item-myself .text span")?.textContent ??
96
+ item.querySelector(".item-system .message-card-top-title")?.textContent ??
97
+ "";
98
+ return txt.replace(/\\s+/g, " ").trim().length > 0;
99
+ });
100
+ return hasText;
101
+ })()`, { timeout: 10_000 });
102
+ }
103
+ catch {
104
+ throw new Error(`已尝试点击 ${foundName}(selected=${String(selected)}),但未检测到对应聊天详情面板。`);
105
+ }
106
+ const url = page.url();
107
+ let fullMessages = [];
108
+ let hasFriendResumeAttachment = false;
109
+ try {
110
+ const scraped = (await page.evaluate(`(() => {
111
+ const norm = (v) => (v ?? "").replace(/\\s+/g, " ").trim();
112
+ const items = Array.from(document.querySelectorAll(".chat-message-list .message-item"));
113
+ let currentTime = "";
114
+ const messages = [];
115
+ let hasFriendResumeAttachment = false;
116
+ for (const item of items) {
117
+ const timeNode = item.querySelector(".message-time .time");
118
+ if (timeNode) {
119
+ const t = norm(timeNode.textContent);
120
+ if (t) currentTime = t;
121
+ }
122
+ const friendRoot = item.querySelector(".item-friend");
123
+ let friendText = "";
124
+ if (friendRoot) {
125
+ friendText = norm(friendRoot.querySelector(".text > span")?.textContent);
126
+ if (!friendText) {
127
+ const resumeIcon = friendRoot.querySelector(".resume-icon");
128
+ const title = norm(friendRoot.querySelector(".message-card-top-title")?.textContent);
129
+ const cardBtn = norm(friendRoot.querySelector(".message-card-buttons .card-btn")?.textContent);
130
+ if (resumeIcon) hasFriendResumeAttachment = true;
131
+ if (title || cardBtn) {
132
+ const parts = [];
133
+ if (title) parts.push(title);
134
+ if (cardBtn) parts.push(cardBtn);
135
+ friendText = parts.length ? parts.join(" · ") : "";
136
+ }
137
+ if (!friendText) friendText = norm(friendRoot.querySelector(".text")?.textContent);
138
+ }
139
+ }
140
+ const myselfText = norm(item.querySelector(".item-myself .text span")?.textContent);
141
+ const systemText =
142
+ norm(item.querySelector(".item-system .message-card-top-title")?.textContent) ||
143
+ norm(item.querySelector(".item-system .text span")?.textContent);
144
+ if (friendText) {
145
+ messages.push({ text: friendText, time: currentTime, from: "friend" });
146
+ } else if (myselfText) {
147
+ messages.push({ text: myselfText, time: currentTime, from: "myself" });
148
+ } else if (systemText) {
149
+ messages.push({ text: systemText, time: currentTime, from: "system" });
150
+ }
151
+ }
152
+ return { messages, hasFriendResumeAttachment };
153
+ })()`));
154
+ fullMessages = scraped.messages;
155
+ hasFriendResumeAttachment = scraped.hasFriendResumeAttachment;
156
+ const lines = fullMessages.map((m, i) => `${i + 1}. [${m.from}]${m.time ? `(${m.time})` : ''} ${m.text}`);
157
+ console.error(`[boss-cli] open_candidate_chat full_messages count=${fullMessages.length} hasFriendResumeAttachment=${String(hasFriendResumeAttachment)}\n${lines.length > 0 ? lines.join('\n') : '(empty)'}`);
158
+ }
159
+ catch (e) {
160
+ const message = e instanceof Error ? e.message : String(e);
161
+ console.error(`[boss-cli] open_candidate_chat full_messages error: ${message}`);
162
+ }
163
+ console.error(`[boss-cli] open_candidate_chat ok name=${foundName} url=${url}`);
164
+ return [
165
+ `已进入候选人聊天:${foundName}`,
166
+ `已读取完整聊天消息:${fullMessages.length} 条。`,
167
+ hasFriendResumeAttachment ? '历史记录中已有候选人发来的附件简历卡片,一般无需再发起「求简历」。' : '',
168
+ ]
169
+ .filter(Boolean)
170
+ .join('\n');
171
+ }
172
+ catch (e) {
173
+ const message = e instanceof Error ? e.message : String(e);
174
+ console.error(`[boss-cli] open_candidate_chat error: ${message}`);
175
+ if (e instanceof Error) {
176
+ throw e;
177
+ }
178
+ throw new Error(`打开候选人聊天失败:${message}`);
179
+ }
180
+ }
181
+ //# sourceMappingURL=open_chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open_chat.js","sourceRoot":"","sources":["../../src/toolset/open_chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runSendChatMessage(text: string, alsoRequestResume?: boolean, signal?: AbortSignal): Promise<string>;
2
+ //# sourceMappingURL=send_message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send_message.d.ts","sourceRoot":"","sources":["../../src/toolset/send_message.ts"],"names":[],"mappings":"AA4FA,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,iBAAiB,UAAQ,EACzB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CA6CjB"}
@@ -0,0 +1,115 @@
1
+ import { createWaitManualLoginRequiredText, isBossChatIndexUrl, sleep, withChatPage, } from '../browser/index.js';
2
+ async function runRequestOfflineResume(page) {
3
+ try {
4
+ const currentUrl = page.url();
5
+ if (!isBossChatIndexUrl(currentUrl)) {
6
+ throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
7
+ }
8
+ const inCandidateChat = await page.$('.base-info-single-container');
9
+ if (!inCandidateChat) {
10
+ throw new Error('请先打开候选人聊天详情页,再执行索取离线简历。');
11
+ }
12
+ const hasFriendResumeAttachment = (await page.evaluate(`(() => {
13
+ const items = Array.from(document.querySelectorAll(".chat-message-list .message-item"));
14
+ return items.some((item) => !!item.querySelector(".item-friend .resume-icon"));
15
+ })()`));
16
+ if (hasFriendResumeAttachment) {
17
+ return '历史消息中已有候选人发来的附件简历,已跳过索取离线简历。';
18
+ }
19
+ const availability = (await page.evaluate(`(() => {
20
+ const items = Array.from(document.querySelectorAll(".operate-icon-item"));
21
+ const target = items.find((el) => {
22
+ const text = (el.querySelector(".operate-btn")?.textContent ?? "").replace(/\\s+/g, "");
23
+ return text.includes("求简历");
24
+ });
25
+ if (!target) return { found: false, available: false };
26
+ const btn = target.querySelector(".operate-btn");
27
+ const className = [target.className ?? "", btn?.className ?? ""].join(" ");
28
+ const disabled = /disabled|forbid|ban/.test(className);
29
+ return { found: true, available: !disabled };
30
+ })()`));
31
+ if (!availability.found || !availability.available) {
32
+ throw new Error('当前不可索取离线简历。通常需要双方至少各发送过一条消息后,才会变为可索取状态。');
33
+ }
34
+ const clicked = (await page.evaluate(`(() => {
35
+ const items = Array.from(document.querySelectorAll(".operate-icon-item"));
36
+ const target = items.find((el) => {
37
+ const text = (el.querySelector(".operate-btn")?.textContent ?? "").replace(/\\s+/g, "");
38
+ return text.includes("求简历");
39
+ });
40
+ if (!target) return false;
41
+ const host = target;
42
+ host.scrollIntoView({ block: "center", inline: "nearest" });
43
+ const btn = target.querySelector(".operate-btn");
44
+ (btn || host).click();
45
+ return true;
46
+ })()`));
47
+ if (!clicked) {
48
+ throw new Error('未找到“求简历”按钮,无法执行索取。');
49
+ }
50
+ const confirmed = (await page.evaluate(`(() => {
51
+ const confirms = Array.from(document.querySelectorAll(".exchange-tooltip .boss-btn-primary.boss-btn"));
52
+ const visible = confirms.find((el) => {
53
+ const style = window.getComputedStyle(el);
54
+ return style.display !== "none" && style.visibility !== "hidden";
55
+ });
56
+ if (!visible) return false;
57
+ visible.click();
58
+ return true;
59
+ })()`));
60
+ if (!confirmed) {
61
+ throw new Error('已点击“求简历”,但未找到确认按钮(确定)。');
62
+ }
63
+ return '已发起索取离线简历请求。';
64
+ }
65
+ catch (e) {
66
+ const message = e instanceof Error ? e.message : String(e);
67
+ if (e instanceof Error) {
68
+ throw e;
69
+ }
70
+ throw new Error(`索取离线简历失败:${message}`);
71
+ }
72
+ }
73
+ export async function runSendChatMessage(text, alsoRequestResume = false, signal) {
74
+ const messageText = text.trim();
75
+ if (!messageText) {
76
+ throw new Error('消息内容为空,未发送。');
77
+ }
78
+ try {
79
+ return await withChatPage(async (page) => {
80
+ const currentUrl = page.url();
81
+ if (!isBossChatIndexUrl(currentUrl)) {
82
+ throw new Error('请先进入聊天列表页(/web/chat/index)并打开候选人聊天。');
83
+ }
84
+ const input = await page.$('#boss-chat-editor-input');
85
+ if (!input) {
86
+ throw new Error('未找到聊天输入框(#boss-chat-editor-input)。');
87
+ }
88
+ await input.click({ delay: 20 });
89
+ await page.keyboard.down('Control');
90
+ await page.keyboard.press('KeyA');
91
+ await page.keyboard.up('Control');
92
+ await page.keyboard.press('Backspace');
93
+ await page.keyboard.type(messageText, { delay: 15 });
94
+ await page.keyboard.press('Enter');
95
+ if (!alsoRequestResume) {
96
+ return `已发送消息:${messageText}`;
97
+ }
98
+ await sleep(1_500, signal);
99
+ const resumeLine = await runRequestOfflineResume(page);
100
+ return [`已发送消息:${messageText}`, resumeLine].filter(Boolean).join('\n\n');
101
+ });
102
+ }
103
+ catch (e) {
104
+ const message = e instanceof Error ? e.message : String(e);
105
+ console.error(`[boss-cli] send_chat_message error: ${message}`);
106
+ if (e instanceof Error) {
107
+ if (e.message.includes('浏览器会话尚未初始化')) {
108
+ throw new Error(createWaitManualLoginRequiredText('发送消息'));
109
+ }
110
+ throw e;
111
+ }
112
+ throw new Error(`发送消息失败:${message}`);
113
+ }
114
+ }
115
+ //# sourceMappingURL=send_message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send_message.js","sourceRoot":"","sources":["../../src/toolset/send_message.ts"],"names":[],"mappings":"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"}