@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
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # boss-cli
2
+
3
+ 在终端里使用的 Boss 直聘沟通页自动化工具:**登录**、**候选人列表**、**打开会话**、**发送消息**,以及读取本地 `jd/` 岗位说明(纯 CLI,无 Agent 运行时)。
4
+
5
+ ---
6
+
7
+ ## 依赖
8
+
9
+ - Node.js **≥ 20**
10
+ - 本机 **Chrome/Chromium**(不随包下载;由 Puppeteer 连接本机浏览器)
11
+
12
+ ---
13
+
14
+ ## 安装与本地运行
15
+
16
+ 全局安装(推荐):
17
+
18
+ ```bash
19
+ npm install -g @joohw/boss-cli
20
+ ```
21
+
22
+ 安装后使用:
23
+
24
+ ```bash
25
+ boss help
26
+ ```
27
+
28
+ 从源码:
29
+
30
+ ```bash
31
+ npm install
32
+ npm run build
33
+ ```
34
+
35
+ 构建产物入口为 `dist/cli/index.js`,等价于命令 `boss`(见 `package.json` 的 `bin`)。
36
+
37
+ ---
38
+
39
+ ## 命令
40
+
41
+ 与 `boss help` 一致:
42
+
43
+
44
+ | 用法 | 说明 |
45
+ | ------------------------------------------------------------ | --------------------------------- |
46
+ | `boss` | 交互模式 |
47
+ | `boss help` | 打印帮助 |
48
+ | `boss login` | 打开登录页并等待你完成登录 |
49
+ | `boss list-candidates [--unread]` | 读取「全部」聊天列表候选人;`--unread` 仅显示未读 |
50
+ | `boss open-chat <姓名> [--fuzzy]` | 打开指定联系人会话;默认精确匹配,`--fuzzy` 为包含匹配 |
51
+ | `boss send-message --text <内容> [--also-request-resume]` | 发送消息;可选在发送后触发「求简历」 |
52
+ | `boss list-positions` | 读取本地 `jd/` 目录下的岗位 Markdown |
53
+
54
+
55
+ **交互模式**
56
+
57
+ - 支持单引号/双引号包裹含空格的参数。
58
+ - 输入 `help` 查看帮助,`exit` / `quit` 退出;**Ctrl+C** 正常退出,不当作错误。
59
+
60
+ ---
61
+
62
+ ## 数据目录
63
+
64
+ 默认数据在 `~/.boss-cli/.cache/`(Cookie、缓存、浏览器用户数据目录等),细节见 `src/config.ts`。仓库内 **[AGENTS.md](./AGENTS.md)** 供自动化/协作参考。
65
+
66
+ ---
67
+
68
+ ## 开发脚本
69
+
70
+
71
+ | 命令 | 作用 |
72
+ | --------------- | ------------------------------ |
73
+ | `npm run build` | `tsc` 编译到 `dist/` |
74
+ | `npm run dev` | 先 `build` 再执行无参 `boss`(进入交互模式) |
75
+
76
+
77
+ ---
78
+
79
+ ## 许可
80
+
81
+ 本项目以 **GNU General Public License v3.0**(**GPL-3.0**)发布,全文见仓库根目录 [LICENSE](./LICENSE)。
@@ -0,0 +1,34 @@
1
+ import type { Page } from 'puppeteer-core';
2
+ /** Boss 直聘首页 */
3
+ export declare const BOSS_ZHIPIN_HOME = "https://www.zhipin.com/";
4
+ /** 默认落地页(杭州 SEO 首页);CLI 等未配置环境变量时使用 */
5
+ export declare const BOSS_DEFAULT_LANDING_URL = "https://www.zhipin.com/hangzhou/?seoRefer=index";
6
+ /** 沟通页(登录成功后的典型落地页之一) */
7
+ export declare const BOSS_CHAT_INDEX_URL = "https://www.zhipin.com/web/chat/index";
8
+ /** 尚未有可用的浏览器会话时的提示文本(供工具抛错复用)。 */
9
+ export declare function createWaitManualLoginRequiredText(action: string): string;
10
+ /** 当前 URL 是否为沟通页 `/web/chat/index`(允许带 query) */
11
+ export declare function isBossChatIndexUrl(url: string): boolean;
12
+ /** 未登录时常见跳转:如 `https://www.zhipin.com/web/user/?ka=bticket` */
13
+ export declare function isWebUserLoginUrl(url: string): boolean;
14
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
15
+ export type ProbeLoggedInSignals = {
16
+ hasNickname: boolean;
17
+ navLoginCta: boolean;
18
+ hasLogoutHint: boolean;
19
+ };
20
+ /**
21
+ * 根据当前页判断是否已登录(不导航)。
22
+ *
23
+ * **已登录(true)**:检测到顶栏昵称、或「退出登录」/logout 等信号。
24
+ * 会短轮询等待 SPA 渲染,避免 `goto` 后立即读静态 HTML 误判。
25
+ *
26
+ * **未登录(false)**:`/web/user/` 登录流 URL、顶栏出现「我要登录」入口、且轮询结束仍无昵称/退出类信号。
27
+ */
28
+ export declare function probeLoggedInFromPage(page: Page): Promise<{
29
+ loggedIn: boolean;
30
+ url: string;
31
+ }>;
32
+ /** 沟通页且已登录(与 {@link probeLoggedInFromPage} 一致)。 */
33
+ export declare function probeBossChatIndexLoggedIn(page: Page): Promise<boolean>;
34
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/browser/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE3C,gBAAgB;AAChB,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,uCAAuC;AACvC,eAAO,MAAM,wBAAwB,oDACc,CAAC;AAEpD,yBAAyB;AACzB,eAAO,MAAM,mBAAmB,0CAA0C,CAAC;AAE3E,kCAAkC;AAClC,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUtD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrE;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AA6DF;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB7C;AAED,mDAAmD;AACnD,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E"}
@@ -0,0 +1,153 @@
1
+ /** Boss 直聘首页 */
2
+ export const BOSS_ZHIPIN_HOME = 'https://www.zhipin.com/';
3
+ /** 默认落地页(杭州 SEO 首页);CLI 等未配置环境变量时使用 */
4
+ export const BOSS_DEFAULT_LANDING_URL = 'https://www.zhipin.com/hangzhou/?seoRefer=index';
5
+ /** 沟通页(登录成功后的典型落地页之一) */
6
+ export const BOSS_CHAT_INDEX_URL = 'https://www.zhipin.com/web/chat/index';
7
+ /** 尚未有可用的浏览器会话时的提示文本(供工具抛错复用)。 */
8
+ export function createWaitManualLoginRequiredText(action) {
9
+ return `浏览器尚未初始化,无法${action}。请先运行 boss login 并在浏览器中完成登录。`;
10
+ }
11
+ /** 当前 URL 是否为沟通页 `/web/chat/index`(允许带 query) */
12
+ export function isBossChatIndexUrl(url) {
13
+ try {
14
+ const u = new URL(url);
15
+ if (!u.hostname.includes('zhipin.com')) {
16
+ return false;
17
+ }
18
+ const p = u.pathname.replace(/\/+$/, '') || '/';
19
+ return p === '/web/chat/index';
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ /** 未登录时常见跳转:如 `https://www.zhipin.com/web/user/?ka=bticket` */
26
+ export function isWebUserLoginUrl(url) {
27
+ try {
28
+ const u = new URL(url);
29
+ if (!u.hostname.includes('zhipin.com')) {
30
+ return false;
31
+ }
32
+ return u.pathname.includes('/web/user/');
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ export function sleep(ms, signal) {
39
+ return new Promise((resolve, reject) => {
40
+ if (signal?.aborted) {
41
+ reject(new Error('Aborted'));
42
+ return;
43
+ }
44
+ const id = setTimeout(() => {
45
+ signal?.removeEventListener('abort', onAbort);
46
+ resolve();
47
+ }, ms);
48
+ const onAbort = () => {
49
+ clearTimeout(id);
50
+ reject(new Error('Aborted'));
51
+ };
52
+ signal?.addEventListener('abort', onAbort, { once: true });
53
+ });
54
+ }
55
+ /**
56
+ * 必须在页面里执行的探测脚本(纯字符串)。
57
+ * `tsx`/esbuild 转译 `page.evaluate(() => { ... })` 时可能注入 `__name(...)`,序列化到浏览器后会报 `__name is not defined`。
58
+ */
59
+ const PROBE_LOGGED_IN_SIGNALS_SCRIPT = `(() => {
60
+ function isNick(t) {
61
+ const s = t.replace(/\\s+/g, " ").trim();
62
+ if (s.length < 2 || s.length > 64) return false;
63
+ return !/^(我要登录|登录|注册|登录\\/注册)$/u.test(s);
64
+ }
65
+ var hasNickname = false;
66
+ var nickSelectors = [
67
+ ".user-name",
68
+ "span.user-name",
69
+ "[class*='user-name']",
70
+ ".label-name",
71
+ ".nav-user .name",
72
+ ".nav-user-name",
73
+ "a.nav-user",
74
+ ".header-nav [class*='name']",
75
+ ];
76
+ var si, i, els, el, t;
77
+ for (si = 0; si < nickSelectors.length; si++) {
78
+ els = document.querySelectorAll(nickSelectors[si]);
79
+ for (i = 0; i < els.length; i++) {
80
+ el = els[i];
81
+ t = (el.textContent || "").trim();
82
+ if (isNick(t)) {
83
+ hasNickname = true;
84
+ break;
85
+ }
86
+ }
87
+ if (hasNickname) break;
88
+ }
89
+ var navRoots = [];
90
+ var navSels = ["header", ".nav-header", ".nav-wrap", ".top-header", "#header", ".header", ".navbar"];
91
+ var j, ne;
92
+ for (j = 0; j < navSels.length; j++) {
93
+ ne = document.querySelector(navSels[j]);
94
+ if (ne instanceof HTMLElement) navRoots.push(ne);
95
+ }
96
+ var navLoginCta = false;
97
+ for (i = 0; i < navRoots.length; i++) {
98
+ if (/\\b我要登录\\b/u.test(navRoots[i].innerText || "")) {
99
+ navLoginCta = true;
100
+ break;
101
+ }
102
+ }
103
+ var bodyText = document.body instanceof HTMLElement ? document.body.innerText || "" : "";
104
+ var hasLogoutHint =
105
+ /\\b退出登录\\b/u.test(bodyText) ||
106
+ !!document.querySelector("a[href*='logout'], a[href*='signout'], [data-url*='logout']");
107
+ return { hasNickname: hasNickname, navLoginCta: navLoginCta, hasLogoutHint: hasLogoutHint };
108
+ })()`;
109
+ async function probeLoggedInSignals(page) {
110
+ return (await page.evaluate(PROBE_LOGGED_IN_SIGNALS_SCRIPT));
111
+ }
112
+ /**
113
+ * 根据当前页判断是否已登录(不导航)。
114
+ *
115
+ * **已登录(true)**:检测到顶栏昵称、或「退出登录」/logout 等信号。
116
+ * 会短轮询等待 SPA 渲染,避免 `goto` 后立即读静态 HTML 误判。
117
+ *
118
+ * **未登录(false)**:`/web/user/` 登录流 URL、顶栏出现「我要登录」入口、且轮询结束仍无昵称/退出类信号。
119
+ */
120
+ export async function probeLoggedInFromPage(page) {
121
+ const url = page.url();
122
+ if (!url || url === 'about:blank') {
123
+ return { loggedIn: false, url: url || '' };
124
+ }
125
+ if (isWebUserLoginUrl(url)) {
126
+ return { loggedIn: false, url };
127
+ }
128
+ const maxAttempts = 25;
129
+ const delayMs = 400;
130
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
131
+ const s = await probeLoggedInSignals(page);
132
+ if (s.hasNickname || s.hasLogoutHint) {
133
+ return { loggedIn: true, url };
134
+ }
135
+ if (s.navLoginCta) {
136
+ return { loggedIn: false, url };
137
+ }
138
+ if (attempt < maxAttempts - 1) {
139
+ await sleep(delayMs);
140
+ }
141
+ }
142
+ return { loggedIn: false, url };
143
+ }
144
+ /** 沟通页且已登录(与 {@link probeLoggedInFromPage} 一致)。 */
145
+ export async function probeBossChatIndexLoggedIn(page) {
146
+ const url = page.url();
147
+ if (!isBossChatIndexUrl(url)) {
148
+ return false;
149
+ }
150
+ const { loggedIn } = await probeLoggedInFromPage(page);
151
+ return loggedIn;
152
+ }
153
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/browser/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Browser, Page } from 'puppeteer-core';
2
+ /**
3
+ * 在 {@link ensureBrowserSession} 之后返回当前已连接的 Browser;
4
+ * 用于工具内单次获取句柄,避免与异步 ensure 不同步的 `getBrowser()` 竞态。
5
+ */
6
+ export declare function ensureAndGetBrowser(): Promise<Browser | null>;
7
+ export declare function ensureBrowserSession(): Promise<void>;
8
+ export declare function getBrowserRef(): Browser | null;
9
+ export declare function getPageRef(): Page | null;
10
+ /**
11
+ * 将当前会话的主操作页设为 `page`(须属于已连接的 `browserRef`)。
12
+ * 供“导航/打开页面”类流程在新建或选中标签后同步,便于其它工具通过 `getPageRef` 复用。
13
+ */
14
+ export declare function setSessionPage(page: Page): void;
15
+ /** 进程退出时断开 CDP,避免残留子进程 */
16
+ export declare function disconnectBrowserSession(): Promise<void>;
17
+ //# sourceMappingURL=browser_session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser_session.d.ts","sourceRoot":"","sources":["../../src/browser/browser_session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAiFpD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAGnE;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmC1D;AAED,wBAAgB,aAAa,IAAI,OAAO,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,GAAG,IAAI,CAIxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAS/C;AAED,0BAA0B;AAC1B,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAW9D"}
@@ -0,0 +1,156 @@
1
+ import { connectBrowser } from './cdp_browser.js';
2
+ let browserRef = null;
3
+ let pageRef = null;
4
+ let connectPromise = null;
5
+ function attachDisconnectedHandler(b) {
6
+ b.once('disconnected', () => {
7
+ if (browserRef === b) {
8
+ browserRef = null;
9
+ pageRef = null;
10
+ console.error('[boss-cli] 与浏览器断开连接(窗口关闭或进程退出);下次使用工具时会自动重连。');
11
+ }
12
+ });
13
+ }
14
+ /**
15
+ * 选一个「主」标签:避免始终把 `pages()[0]` 当主页——用户常在第二个及以后的 Boss 标签上操作,
16
+ * 而第一个是 `about:blank` 或残留空页时,错误地读到 blank 会让登录/页面检查类操作误判。
17
+ */
18
+ async function pickOrCreatePage(b) {
19
+ const pages = (await b.pages()).filter((p) => !p.isClosed());
20
+ if (pages.length === 0) {
21
+ return b.newPage();
22
+ }
23
+ const urls = await Promise.all(pages.map((p) => {
24
+ try {
25
+ return p.url();
26
+ }
27
+ catch {
28
+ return '';
29
+ }
30
+ }));
31
+ const zhipin = pages.find((p, i) => {
32
+ const u = urls[i] ?? '';
33
+ return u.length > 0 && u !== 'about:blank' && u.includes('zhipin.com');
34
+ });
35
+ if (zhipin) {
36
+ return zhipin;
37
+ }
38
+ const nonBlank = pages.find((p, i) => {
39
+ const u = urls[i] ?? '';
40
+ return u.length > 0 && u !== 'about:blank';
41
+ });
42
+ if (nonBlank) {
43
+ return nonBlank;
44
+ }
45
+ return pages[0];
46
+ }
47
+ function isSessionHealthy() {
48
+ return !!(browserRef?.connected && pageRef && !pageRef.isClosed());
49
+ }
50
+ async function establishSession() {
51
+ const prev = browserRef;
52
+ if (prev) {
53
+ try {
54
+ prev.removeAllListeners('disconnected');
55
+ await prev.close();
56
+ }
57
+ catch {
58
+ /* 已断开时忽略 */
59
+ }
60
+ browserRef = null;
61
+ pageRef = null;
62
+ }
63
+ const b = await connectBrowser();
64
+ browserRef = b;
65
+ attachDisconnectedHandler(b);
66
+ pageRef = await pickOrCreatePage(b);
67
+ console.error('[boss-cli] 已连接浏览器,当前页:', pageRef.url());
68
+ }
69
+ /**
70
+ * 在 {@link ensureBrowserSession} 之后返回当前已连接的 Browser;
71
+ * 用于工具内单次获取句柄,避免与异步 ensure 不同步的 `getBrowser()` 竞态。
72
+ */
73
+ export async function ensureAndGetBrowser() {
74
+ await ensureBrowserSession();
75
+ return getBrowserRef();
76
+ }
77
+ export async function ensureBrowserSession() {
78
+ if (browserRef?.connected) {
79
+ if (pageRef && !pageRef.isClosed()) {
80
+ try {
81
+ const u = pageRef.url();
82
+ if (u === 'about:blank' || u === '') {
83
+ const preferred = await pickOrCreatePage(browserRef);
84
+ if (preferred !== pageRef && !(preferred.url() === 'about:blank')) {
85
+ pageRef = preferred;
86
+ }
87
+ }
88
+ }
89
+ catch {
90
+ /* ignore */
91
+ }
92
+ return;
93
+ }
94
+ pageRef = await pickOrCreatePage(browserRef);
95
+ return;
96
+ }
97
+ if (connectPromise) {
98
+ await connectPromise;
99
+ return;
100
+ }
101
+ connectPromise = (async () => {
102
+ if (isSessionHealthy())
103
+ return;
104
+ await establishSession();
105
+ })();
106
+ try {
107
+ await connectPromise;
108
+ }
109
+ finally {
110
+ connectPromise = null;
111
+ }
112
+ }
113
+ export function getBrowserRef() {
114
+ return browserRef?.connected ? browserRef : null;
115
+ }
116
+ export function getPageRef() {
117
+ if (!pageRef || pageRef.isClosed())
118
+ return null;
119
+ if (!browserRef?.connected)
120
+ return null;
121
+ return pageRef;
122
+ }
123
+ /**
124
+ * 将当前会话的主操作页设为 `page`(须属于已连接的 `browserRef`)。
125
+ * 供“导航/打开页面”类流程在新建或选中标签后同步,便于其它工具通过 `getPageRef` 复用。
126
+ */
127
+ export function setSessionPage(page) {
128
+ if (!browserRef?.connected)
129
+ return;
130
+ try {
131
+ if (page.browser() !== browserRef)
132
+ return;
133
+ }
134
+ catch {
135
+ return;
136
+ }
137
+ if (page.isClosed())
138
+ return;
139
+ pageRef = page;
140
+ }
141
+ /** 进程退出时断开 CDP,避免残留子进程 */
142
+ export async function disconnectBrowserSession() {
143
+ const b = browserRef;
144
+ if (!b)
145
+ return;
146
+ try {
147
+ b.removeAllListeners('disconnected');
148
+ await b.close();
149
+ }
150
+ catch {
151
+ /* ignore */
152
+ }
153
+ browserRef = null;
154
+ pageRef = null;
155
+ }
156
+ //# sourceMappingURL=browser_session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser_session.js","sourceRoot":"","sources":["../../src/browser/browser_session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { type Browser, type CDPSession, type Page } from 'puppeteer-core';
2
+ /** 减轻「正受到自动测试软件的控制」提示与常见自动化特征(非万能,站点仍可能用其它方式检测)。手动开 Chrome 并接 CDP 时可复用。 */
3
+ export declare const LAUNCH_ARGS_LESS_AUTOMATION: readonly ["--disable-blink-features=AutomationControlled", "--disable-infobars"];
4
+ /** 仅用于本地调试:尽量放宽同源/CORS 限制,便于跨域 iframe/canvas 处理。 */
5
+ export declare const LAUNCH_ARGS_ALLOW_ALL_CORS: readonly ["--disable-web-security", "--allow-running-insecure-content"];
6
+ export type ConnectBrowserOptions = {
7
+ /** 用于启动本机 Chrome/Edge */
8
+ executablePath?: string;
9
+ /** 启动浏览器时复用的用户数据目录(登录态/缓存等) */
10
+ userDataDir?: string;
11
+ /** 启动浏览器时指定 profile(如 `Default` / `Profile 1`) */
12
+ profileDirectory?: string;
13
+ /** 默认 `false`(有界面)。也可用环境变量 `BOSS_BROWSER_HEADLESS=true` 开无头。 */
14
+ headless?: boolean;
15
+ /** 仅本地调试用:放宽同源/CORS 策略(高风险,默认关闭)。 */
16
+ allowAllCors?: boolean;
17
+ };
18
+ /**
19
+ * 启动本机浏览器(puppeteer-core 底层为 Chrome DevTools Protocol)。
20
+ *
21
+ * 环境变量(可选):
22
+ * - `CHROME_PATH` / `PUPPETEER_EXECUTABLE_PATH` — 启动本机浏览器可执行文件路径(高于自动探测)
23
+ * - `BOSS_BROWSER_USER_DATA_DIR` — 启动浏览器时复用的用户数据目录;未设置时默认 `~/.boss-cli/.cache/browser-data`
24
+ * - `BOSS_BROWSER_PROFILE_DIRECTORY` — 启动浏览器时指定 profile(如 `Default`)
25
+ * - `BOSS_BROWSER_ALLOW_ALL_CORS` — 设为 `true` 时附加放宽同源/CORS 的启动参数(仅调试)
26
+ * - `BOSS_BROWSER_DISABLE_GPU` — 设为 `true` 时附加 `--disable-gpu`
27
+ *
28
+ * 若以上均未设置,会按系统尝试常见 Chrome / Edge / Chromium 安装路径。
29
+ * - `BOSS_BROWSER_HEADLESS` — 设为 `true` 时启用无头;默认**有界面**。
30
+ */
31
+ export declare function connectBrowser(options?: ConnectBrowserOptions): Promise<Browser>;
32
+ /** 对某一页创建原生 CDP Session(需要低层域如 `Network.*`、`Fetch.*` 时使用)。 */
33
+ export declare function createPageCDPSession(page: Page): Promise<CDPSession>;
34
+ //# sourceMappingURL=cdp_browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp_browser.d.ts","sourceRoot":"","sources":["../../src/browser/cdp_browser.ts"],"names":[],"mappings":"AAEA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA4CrF,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B,kFAG9B,CAAC;AAEX,oDAAoD;AACpD,eAAO,MAAM,0BAA0B,yEAG7B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG;IAClC,yBAAyB;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CAyC1F;AAED,8DAA8D;AAC9D,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAE1E"}
@@ -0,0 +1,95 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import puppeteer from 'puppeteer-core';
4
+ import { BROWSER_USER_DATA_DIR, ensureAppDataLayout } from '../config.js';
5
+ /** 在未配置路径时,尝试常见安装位置(Chrome / Edge / Chromium)。 */
6
+ function findLocalChromiumExecutable() {
7
+ const candidates = [];
8
+ if (process.platform === 'win32') {
9
+ const local = process.env.LOCALAPPDATA;
10
+ const pf = process.env.PROGRAMFILES;
11
+ const pf86 = process.env['PROGRAMFILES(X86)'];
12
+ if (local) {
13
+ candidates.push(path.join(local, 'Google', 'Chrome', 'Application', 'chrome.exe'));
14
+ }
15
+ if (pf) {
16
+ candidates.push(path.join(pf, 'Google', 'Chrome', 'Application', 'chrome.exe'));
17
+ candidates.push(path.join(pf, 'Microsoft', 'Edge', 'Application', 'msedge.exe'));
18
+ }
19
+ if (pf86) {
20
+ candidates.push(path.join(pf86, 'Google', 'Chrome', 'Application', 'chrome.exe'));
21
+ candidates.push(path.join(pf86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'));
22
+ }
23
+ }
24
+ else if (process.platform === 'darwin') {
25
+ candidates.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', '/Applications/Chromium.app/Contents/MacOS/Chromium');
26
+ }
27
+ else {
28
+ candidates.push('/usr/bin/google-chrome-stable', '/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/microsoft-edge-stable', '/usr/bin/microsoft-edge');
29
+ }
30
+ for (const p of candidates) {
31
+ if (existsSync(p))
32
+ return p;
33
+ }
34
+ return undefined;
35
+ }
36
+ /** 减轻「正受到自动测试软件的控制」提示与常见自动化特征(非万能,站点仍可能用其它方式检测)。手动开 Chrome 并接 CDP 时可复用。 */
37
+ export const LAUNCH_ARGS_LESS_AUTOMATION = [
38
+ '--disable-blink-features=AutomationControlled',
39
+ '--disable-infobars',
40
+ ];
41
+ /** 仅用于本地调试:尽量放宽同源/CORS 限制,便于跨域 iframe/canvas 处理。 */
42
+ export const LAUNCH_ARGS_ALLOW_ALL_CORS = [
43
+ '--disable-web-security',
44
+ '--allow-running-insecure-content',
45
+ ];
46
+ /**
47
+ * 启动本机浏览器(puppeteer-core 底层为 Chrome DevTools Protocol)。
48
+ *
49
+ * 环境变量(可选):
50
+ * - `CHROME_PATH` / `PUPPETEER_EXECUTABLE_PATH` — 启动本机浏览器可执行文件路径(高于自动探测)
51
+ * - `BOSS_BROWSER_USER_DATA_DIR` — 启动浏览器时复用的用户数据目录;未设置时默认 `~/.boss-cli/.cache/browser-data`
52
+ * - `BOSS_BROWSER_PROFILE_DIRECTORY` — 启动浏览器时指定 profile(如 `Default`)
53
+ * - `BOSS_BROWSER_ALLOW_ALL_CORS` — 设为 `true` 时附加放宽同源/CORS 的启动参数(仅调试)
54
+ * - `BOSS_BROWSER_DISABLE_GPU` — 设为 `true` 时附加 `--disable-gpu`
55
+ *
56
+ * 若以上均未设置,会按系统尝试常见 Chrome / Edge / Chromium 安装路径。
57
+ * - `BOSS_BROWSER_HEADLESS` — 设为 `true` 时启用无头;默认**有界面**。
58
+ */
59
+ export async function connectBrowser(options = {}) {
60
+ const executablePath = options.executablePath?.trim() ||
61
+ process.env.CHROME_PATH?.trim() ||
62
+ process.env.PUPPETEER_EXECUTABLE_PATH?.trim() ||
63
+ findLocalChromiumExecutable();
64
+ const envUserData = process.env.BOSS_BROWSER_USER_DATA_DIR?.trim();
65
+ if (!envUserData) {
66
+ ensureAppDataLayout();
67
+ }
68
+ const userDataDir = options.userDataDir?.trim() || envUserData || BROWSER_USER_DATA_DIR;
69
+ const profileDirectory = options.profileDirectory?.trim() || process.env.BOSS_BROWSER_PROFILE_DIRECTORY?.trim();
70
+ if (!executablePath) {
71
+ throw new Error('未找到本机 Chrome/Edge:请设置 CHROME_PATH / PUPPETEER_EXECUTABLE_PATH(可执行文件路径)。');
72
+ }
73
+ const headless = options.headless ?? process.env.BOSS_BROWSER_HEADLESS === 'true';
74
+ const allowAllCors = options.allowAllCors ?? process.env.BOSS_BROWSER_ALLOW_ALL_CORS === 'true';
75
+ const disableGpu = process.env.BOSS_BROWSER_DISABLE_GPU === 'true';
76
+ return puppeteer.launch({
77
+ executablePath,
78
+ userDataDir,
79
+ headless,
80
+ /** 去掉 Chrome 默认的 `--enable-automation`,可消除顶部「正受到自动测试软件的控制」提示 */
81
+ ignoreDefaultArgs: ['--enable-automation'],
82
+ defaultViewport: headless ? { width: 1280, height: 800 } : null,
83
+ args: [
84
+ ...LAUNCH_ARGS_LESS_AUTOMATION,
85
+ ...(disableGpu ? ['--disable-gpu'] : []),
86
+ ...(allowAllCors ? LAUNCH_ARGS_ALLOW_ALL_CORS : []),
87
+ ...(profileDirectory ? [`--profile-directory=${profileDirectory}`] : []),
88
+ ],
89
+ });
90
+ }
91
+ /** 对某一页创建原生 CDP Session(需要低层域如 `Network.*`、`Fetch.*` 时使用)。 */
92
+ export async function createPageCDPSession(page) {
93
+ return page.createCDPSession();
94
+ }
95
+ //# sourceMappingURL=cdp_browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp_browser.js","sourceRoot":"","sources":["../../src/browser/cdp_browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,SAAuD,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAE1E,kDAAkD;AAClD,SAAS,2BAA2B;IAClC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QACvC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QACpC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QAC9C,IAAI,KAAK,EAAE,CAAC;YACV,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,EAAE,EAAE,CAAC;YACP,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;YAChF,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;YAClF,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACzC,UAAU,CAAC,IAAI,CACb,8DAA8D,EAC9D,gEAAgE,EAChE,oDAAoD,CACrD,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,UAAU,CAAC,IAAI,CACb,+BAA+B,EAC/B,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,gCAAgC,EAChC,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,+CAA+C;IAC/C,oBAAoB;CACZ,CAAC;AAEX,oDAAoD;AACpD,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,wBAAwB;IACxB,kCAAkC;CAC1B,CAAC;AAeX;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAiC,EAAE;IACtE,MAAM,cAAc,GAClB,OAAO,CAAC,cAAc,EAAE,IAAI,EAAE;QAC9B,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE;QAC/B,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,IAAI,EAAE;QAC7C,2BAA2B,EAAE,CAAC;IAEhC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,IAAI,EAAE,CAAC;IACnE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,mBAAmB,EAAE,CAAC;IACxB,CAAC;IACD,MAAM,WAAW,GACf,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,WAAW,IAAI,qBAAqB,CAAC;IAEtE,MAAM,gBAAgB,GACpB,OAAO,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,IAAI,EAAE,CAAC;IAEzF,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,MAAM,CAAC;IAClF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,MAAM,CAAC;IAChG,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,MAAM,CAAC;IAEnE,OAAO,SAAS,CAAC,MAAM,CAAC;QACtB,cAAc;QACd,WAAW;QACX,QAAQ;QACR,gEAAgE;QAChE,iBAAiB,EAAE,CAAC,qBAAqB,CAAC;QAC1C,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI;QAC/D,IAAI,EAAE;YACJ,GAAG,2BAA2B;YAC9B,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,uBAAuB,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACzE;KACF,CAAC,CAAC;AACL,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAU;IACnD,OAAO,IAAI,CAAC,gBAAgB,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Page } from 'puppeteer-core';
2
+ /**
3
+ * 获取 chat page 来执行回调:确保已启动浏览器并导航到 Boss 沟通列表页(/web/chat/index)。
4
+ * 仅负责“浏览器状态/导航”,不在这里做登录成功与否的业务判断。
5
+ */
6
+ export declare function withChatPage<T>(callback: (page: Page) => Promise<T>): Promise<T>;
7
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/browser/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAkCpD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA+BtF"}