@reconcrap/boss-recommend-mcp 1.2.10 → 1.3.0

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 (34) hide show
  1. package/README.md +82 -1
  2. package/package.json +2 -1
  3. package/skills/boss-chat/README.md +5 -0
  4. package/skills/boss-chat/SKILL.md +69 -0
  5. package/skills/boss-recommend-pipeline/SKILL.md +40 -4
  6. package/src/adapters.js +19 -5
  7. package/src/boss-chat.js +436 -0
  8. package/src/cli.js +294 -129
  9. package/src/index.js +459 -108
  10. package/src/pipeline.js +605 -8
  11. package/src/run-state.js +5 -0
  12. package/src/test-adapters-runtime.js +69 -0
  13. package/src/test-boss-chat.js +399 -0
  14. package/src/test-index-async.js +238 -4
  15. package/src/test-pipeline.js +408 -1
  16. package/vendor/boss-chat-cli/README.md +134 -0
  17. package/vendor/boss-chat-cli/package.json +53 -0
  18. package/vendor/boss-chat-cli/src/app.js +769 -0
  19. package/vendor/boss-chat-cli/src/browser/chat-page.js +2681 -0
  20. package/vendor/boss-chat-cli/src/cli.js +1350 -0
  21. package/vendor/boss-chat-cli/src/mcp/server.js +149 -0
  22. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +193 -0
  23. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +260 -0
  24. package/vendor/boss-chat-cli/src/runtime/interaction.js +102 -0
  25. package/vendor/boss-chat-cli/src/runtime/run-control.js +102 -0
  26. package/vendor/boss-chat-cli/src/services/chrome-client.js +97 -0
  27. package/vendor/boss-chat-cli/src/services/llm.js +352 -0
  28. package/vendor/boss-chat-cli/src/services/profile-store.js +157 -0
  29. package/vendor/boss-chat-cli/src/services/report-store.js +19 -0
  30. package/vendor/boss-chat-cli/src/services/resume-capture.js +554 -0
  31. package/vendor/boss-chat-cli/src/services/state-store.js +217 -0
  32. package/vendor/boss-chat-cli/src/utils/customer-key.js +82 -0
  33. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +902 -56
  34. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +387 -1
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { runRecommendPipeline } from "./pipeline.js";
2
+ import { runRecommendPipeline as runRecommendPipelineImpl } from "./pipeline.js";
3
3
 
4
4
  const DEFAULT_JOB_OPTIONS = [
5
5
  {
@@ -85,6 +85,40 @@ function createParsed(overrides = {}) {
85
85
  };
86
86
  }
87
87
 
88
+ function createBasePipelineDeps(overrides = {}) {
89
+ return {
90
+ readRecommendTabState: async () => ({
91
+ ok: true,
92
+ active_status: "0",
93
+ active_tab_status: "0"
94
+ }),
95
+ switchRecommendTab: async () => ({
96
+ ok: true,
97
+ state: "TAB_READY",
98
+ after_state: {
99
+ active_status: "0",
100
+ active_tab_status: "0"
101
+ }
102
+ }),
103
+ ...overrides
104
+ };
105
+ }
106
+
107
+ async function runRecommendPipeline(args, dependencies = {}, runtime = null) {
108
+ return runRecommendPipelineImpl(args, createBasePipelineDeps(dependencies), runtime);
109
+ }
110
+
111
+ function createFollowUpChat(overrides = {}) {
112
+ return {
113
+ chat: {
114
+ criteria: "有 AI Agent 经验",
115
+ start_from: "unread",
116
+ target_count: 5,
117
+ ...overrides
118
+ }
119
+ };
120
+ }
121
+
88
122
  async function testPauseRequestedBeforeScreenShouldReturnPaused() {
89
123
  let screenCalled = false;
90
124
  const result = await runRecommendPipeline(
@@ -1178,6 +1212,62 @@ async function testLatestPipelineShouldRunSearchThenSwitchTabThenScreen() {
1178
1212
  assert.equal(result.result.resume_source, "image_fallback");
1179
1213
  }
1180
1214
 
1215
+ async function testPipelineShouldPassInputSummaryToScreenCli() {
1216
+ let capturedInputSummary = null;
1217
+ const result = await runRecommendPipeline(
1218
+ {
1219
+ workspaceRoot: process.cwd(),
1220
+ instruction: "帮我找有 AI/LLM 项目经验的人",
1221
+ confirmation: createJobConfirmedConfirmation(),
1222
+ overrides: {}
1223
+ },
1224
+ {
1225
+ parseRecommendInstruction: () => createParsed(),
1226
+ runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1227
+ ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1228
+ listRecommendJobs: async () => createJobListResult(),
1229
+ runRecommendSearchCli: async () => ({
1230
+ ok: true,
1231
+ summary: {
1232
+ candidate_count: 3,
1233
+ applied_filters: {
1234
+ school_tag: ["985"],
1235
+ degree: ["本科"],
1236
+ gender: "男",
1237
+ recent_not_view: "近14天没有"
1238
+ }
1239
+ }
1240
+ }),
1241
+ runRecommendScreenCli: async ({ inputSummary }) => {
1242
+ capturedInputSummary = inputSummary;
1243
+ return {
1244
+ ok: true,
1245
+ summary: {
1246
+ processed_count: 3,
1247
+ passed_count: 1,
1248
+ skipped_count: 2,
1249
+ output_csv: "C:/temp/result.csv",
1250
+ active_tab_status: "0",
1251
+ resume_source: "network"
1252
+ }
1253
+ };
1254
+ }
1255
+ }
1256
+ );
1257
+
1258
+ assert.equal(result.status, "COMPLETED");
1259
+ assert.equal(Boolean(capturedInputSummary), true);
1260
+ assert.equal(capturedInputSummary.instruction, "帮我找有 AI/LLM 项目经验的人");
1261
+ assert.equal(capturedInputSummary.selected_page, "recommend");
1262
+ assert.equal(capturedInputSummary.selected_job?.title, "数据分析实习生 _ 杭州");
1263
+ assert.equal(capturedInputSummary.user_search_params?.recent_not_view, "近14天没有");
1264
+ assert.equal(capturedInputSummary.effective_search_params?.recent_not_view, "近14天没有");
1265
+ assert.equal(capturedInputSummary.screen_params?.criteria, "候选人需要有大模型平台经验");
1266
+ assert.equal(Object.prototype.hasOwnProperty.call(capturedInputSummary, "baseUrl"), false);
1267
+ assert.equal(Object.prototype.hasOwnProperty.call(capturedInputSummary, "apiKey"), false);
1268
+ assert.equal(Object.prototype.hasOwnProperty.call(capturedInputSummary, "model"), false);
1269
+ }
1270
+
1181
1271
  async function testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue() {
1182
1272
  const calls = [];
1183
1273
  let preflightCallCount = 0;
@@ -1687,6 +1777,8 @@ async function testNeedJobConfirmationGate() {
1687
1777
  runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1688
1778
  ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1689
1779
  listRecommendJobs: async () => createJobListResult(),
1780
+ readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
1781
+ switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
1690
1782
  runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1691
1783
  runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1692
1784
  }
@@ -2038,6 +2130,312 @@ async function testScreenConfigRecoveryStepShouldBeFirst() {
2038
2130
  assert.equal(result.diagnostics.recovery.ordered_steps[0].id, "fill_screening_config");
2039
2131
  }
2040
2132
 
2133
+ async function testFollowUpChatMissingCriteriaShouldNeedInput() {
2134
+ const result = await runRecommendPipeline(
2135
+ {
2136
+ workspaceRoot: process.cwd(),
2137
+ instruction: "test",
2138
+ confirmation: {},
2139
+ overrides: {},
2140
+ followUp: createFollowUpChat({ criteria: "" })
2141
+ },
2142
+ {
2143
+ parseRecommendInstruction: () => createParsed()
2144
+ }
2145
+ );
2146
+
2147
+ assert.equal(result.status, "NEED_INPUT");
2148
+ assert.equal(result.missing_fields.includes("follow_up.chat.criteria"), true);
2149
+ assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.criteria"), true);
2150
+ }
2151
+
2152
+ async function testFollowUpChatMissingFieldsShouldExposeRecommendDefaults() {
2153
+ const result = await runRecommendPipeline(
2154
+ {
2155
+ workspaceRoot: process.cwd(),
2156
+ instruction: "test",
2157
+ confirmation: {},
2158
+ overrides: {},
2159
+ followUp: createFollowUpChat({
2160
+ criteria: "",
2161
+ start_from: "",
2162
+ target_count: null
2163
+ })
2164
+ },
2165
+ {
2166
+ parseRecommendInstruction: () => createParsed({
2167
+ screenParams: {
2168
+ criteria: "默认沿用 recommend 的筛选条件",
2169
+ target_count: 18,
2170
+ post_action: "favorite",
2171
+ max_greet_count: null
2172
+ }
2173
+ })
2174
+ }
2175
+ );
2176
+
2177
+ assert.equal(result.status, "NEED_INPUT");
2178
+ assert.equal(result.missing_fields.includes("follow_up.chat.criteria"), true);
2179
+ assert.equal(result.missing_fields.includes("follow_up.chat.start_from"), true);
2180
+ assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2181
+ const criteriaQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.criteria");
2182
+ const startFromQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.start_from");
2183
+ const targetCountQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2184
+ assert.equal(criteriaQuestion?.value, "默认沿用 recommend 的筛选条件");
2185
+ assert.equal(startFromQuestion?.value, "unread");
2186
+ assert.equal(targetCountQuestion?.value, 18);
2187
+ }
2188
+
2189
+ async function testFollowUpChatMissingStartFromShouldNeedInput() {
2190
+ const result = await runRecommendPipeline(
2191
+ {
2192
+ workspaceRoot: process.cwd(),
2193
+ instruction: "test",
2194
+ confirmation: {},
2195
+ overrides: {},
2196
+ followUp: createFollowUpChat({ start_from: "" })
2197
+ },
2198
+ {
2199
+ parseRecommendInstruction: () => createParsed()
2200
+ }
2201
+ );
2202
+
2203
+ assert.equal(result.status, "NEED_INPUT");
2204
+ assert.equal(result.missing_fields.includes("follow_up.chat.start_from"), true);
2205
+ assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.start_from"), true);
2206
+ }
2207
+
2208
+ async function testFollowUpChatMissingTargetCountShouldNeedInput() {
2209
+ const result = await runRecommendPipeline(
2210
+ {
2211
+ workspaceRoot: process.cwd(),
2212
+ instruction: "test",
2213
+ confirmation: {},
2214
+ overrides: {},
2215
+ followUp: createFollowUpChat({ target_count: null })
2216
+ },
2217
+ {
2218
+ parseRecommendInstruction: () => createParsed()
2219
+ }
2220
+ );
2221
+
2222
+ assert.equal(result.status, "NEED_INPUT");
2223
+ assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2224
+ assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.target_count"), true);
2225
+ }
2226
+
2227
+ async function testFinalReviewShouldIncludeFollowUpChatSummary() {
2228
+ const result = await runRecommendPipeline(
2229
+ {
2230
+ workspaceRoot: process.cwd(),
2231
+ instruction: "test",
2232
+ confirmation: createJobConfirmedWithoutFinalConfirmation(),
2233
+ overrides: {},
2234
+ followUp: createFollowUpChat()
2235
+ },
2236
+ {
2237
+ parseRecommendInstruction: () => createParsed(),
2238
+ runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
2239
+ ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2240
+ listRecommendJobs: async () => createJobListResult(),
2241
+ runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
2242
+ runRecommendScreenCli: async () => ({ ok: true, summary: {} })
2243
+ }
2244
+ );
2245
+
2246
+ assert.equal(result.status, "NEED_CONFIRMATION");
2247
+ assert.equal(result.follow_up?.chat?.criteria, "有 AI Agent 经验");
2248
+ const finalReview = result.pending_questions.find((item) => item.field === "final_review");
2249
+ assert.equal(finalReview?.value?.follow_up?.chat?.start_from, "unread");
2250
+ assert.equal(finalReview?.value?.follow_up?.chat?.target_count, 5);
2251
+ }
2252
+
2253
+ async function testCompletedPipelineShouldRunChatFollowUp() {
2254
+ let getChatRunCalls = 0;
2255
+ const result = await runRecommendPipeline(
2256
+ {
2257
+ workspaceRoot: process.cwd(),
2258
+ instruction: "test",
2259
+ confirmation: createJobConfirmedConfirmation(),
2260
+ overrides: {},
2261
+ followUp: createFollowUpChat()
2262
+ },
2263
+ {
2264
+ parseRecommendInstruction: () => createParsed(),
2265
+ runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
2266
+ ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2267
+ listRecommendJobs: async () => createJobListResult(),
2268
+ readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2269
+ switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2270
+ runRecommendSearchCli: async () => ({
2271
+ ok: true,
2272
+ summary: {
2273
+ candidate_count: 8,
2274
+ applied_filters: { degree: ["本科"] },
2275
+ selected_job: DEFAULT_JOB_OPTIONS[0]
2276
+ }
2277
+ }),
2278
+ runRecommendScreenCli: async () => ({
2279
+ ok: true,
2280
+ summary: {
2281
+ processed_count: 6,
2282
+ passed_count: 2,
2283
+ skipped_count: 4,
2284
+ output_csv: "C:/temp/recommend.csv",
2285
+ completion_reason: "screen_completed"
2286
+ }
2287
+ }),
2288
+ startBossChatRun: async ({ input }) => {
2289
+ assert.equal(input.job, DEFAULT_JOB_OPTIONS[0].title);
2290
+ assert.equal(input.port, 9555);
2291
+ return {
2292
+ status: "ACCEPTED",
2293
+ run_id: "chat-run-1",
2294
+ message: "chat started"
2295
+ };
2296
+ },
2297
+ getBossChatRun: async () => {
2298
+ getChatRunCalls += 1;
2299
+ if (getChatRunCalls === 1) {
2300
+ return {
2301
+ status: "RUN_STATUS",
2302
+ run: {
2303
+ runId: "chat-run-1",
2304
+ state: "running",
2305
+ lastMessage: "chat running",
2306
+ progress: {
2307
+ inspected: 1,
2308
+ passed: 0,
2309
+ requested: 0,
2310
+ skipped: 1,
2311
+ errors: 0
2312
+ }
2313
+ }
2314
+ };
2315
+ }
2316
+ return {
2317
+ status: "RUN_STATUS",
2318
+ run: {
2319
+ runId: "chat-run-1",
2320
+ state: "completed",
2321
+ lastMessage: "chat completed",
2322
+ progress: {
2323
+ inspected: 3,
2324
+ passed: 1,
2325
+ requested: 1,
2326
+ skipped: 2,
2327
+ errors: 0
2328
+ },
2329
+ result: {
2330
+ requested_count: 1
2331
+ }
2332
+ }
2333
+ };
2334
+ }
2335
+ }
2336
+ );
2337
+
2338
+ assert.equal(result.status, "COMPLETED");
2339
+ assert.equal(result.result.output_csv, "C:/temp/recommend.csv");
2340
+ assert.equal(result.follow_up?.chat?.run_id, "chat-run-1");
2341
+ assert.equal(result.follow_up?.chat?.state, "completed");
2342
+ assert.equal(result.follow_up?.chat?.input?.port, 9555);
2343
+ }
2344
+
2345
+ async function testCompletedPipelineShouldFailWhenChatLaunchFails() {
2346
+ const result = await runRecommendPipeline(
2347
+ {
2348
+ workspaceRoot: process.cwd(),
2349
+ instruction: "test",
2350
+ confirmation: createJobConfirmedConfirmation(),
2351
+ overrides: {},
2352
+ followUp: createFollowUpChat()
2353
+ },
2354
+ {
2355
+ parseRecommendInstruction: () => createParsed(),
2356
+ runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
2357
+ ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2358
+ listRecommendJobs: async () => createJobListResult(),
2359
+ readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2360
+ switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2361
+ runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
2362
+ runRecommendScreenCli: async () => ({
2363
+ ok: true,
2364
+ summary: {
2365
+ processed_count: 1,
2366
+ passed_count: 1,
2367
+ skipped_count: 0,
2368
+ output_csv: "C:/temp/recommend.csv",
2369
+ completion_reason: "screen_completed"
2370
+ }
2371
+ }),
2372
+ startBossChatRun: async () => ({
2373
+ status: "FAILED",
2374
+ error: {
2375
+ code: "CHAT_START_FAILED",
2376
+ message: "cannot start chat"
2377
+ }
2378
+ })
2379
+ }
2380
+ );
2381
+
2382
+ assert.equal(result.status, "FAILED");
2383
+ assert.equal(result.error.code, "CHAT_START_FAILED");
2384
+ assert.equal(result.follow_up?.chat?.launch_result?.status, "FAILED");
2385
+ }
2386
+
2387
+ async function testCompletedPipelineShouldFailWhenChatRunFails() {
2388
+ const result = await runRecommendPipeline(
2389
+ {
2390
+ workspaceRoot: process.cwd(),
2391
+ instruction: "test",
2392
+ confirmation: createJobConfirmedConfirmation(),
2393
+ overrides: {},
2394
+ followUp: createFollowUpChat()
2395
+ },
2396
+ {
2397
+ parseRecommendInstruction: () => createParsed(),
2398
+ runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
2399
+ ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2400
+ listRecommendJobs: async () => createJobListResult(),
2401
+ readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2402
+ switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2403
+ runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
2404
+ runRecommendScreenCli: async () => ({
2405
+ ok: true,
2406
+ summary: {
2407
+ processed_count: 1,
2408
+ passed_count: 1,
2409
+ skipped_count: 0,
2410
+ output_csv: "C:/temp/recommend.csv",
2411
+ completion_reason: "screen_completed"
2412
+ }
2413
+ }),
2414
+ startBossChatRun: async () => ({
2415
+ status: "ACCEPTED",
2416
+ run_id: "chat-run-2",
2417
+ message: "chat started"
2418
+ }),
2419
+ getBossChatRun: async () => ({
2420
+ status: "RUN_STATUS",
2421
+ run: {
2422
+ runId: "chat-run-2",
2423
+ state: "failed",
2424
+ lastMessage: "chat failed",
2425
+ error: {
2426
+ code: "CHAT_RUNTIME_FAILED",
2427
+ message: "chat runtime failed"
2428
+ }
2429
+ }
2430
+ })
2431
+ }
2432
+ );
2433
+
2434
+ assert.equal(result.status, "FAILED");
2435
+ assert.equal(result.error.code, "CHAT_RUNTIME_FAILED");
2436
+ assert.equal(result.follow_up?.chat?.state, "failed");
2437
+ }
2438
+
2041
2439
  async function main() {
2042
2440
  await testPauseRequestedBeforeScreenShouldReturnPaused();
2043
2441
  await testPausedScreenResultShouldBubbleUp();
@@ -2057,6 +2455,7 @@ async function main() {
2057
2455
  await testNeedInputGate();
2058
2456
  await testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen();
2059
2457
  await testLatestPipelineShouldRunSearchThenSwitchTabThenScreen();
2458
+ await testPipelineShouldPassInputSummaryToScreenCli();
2060
2459
  await testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue();
2061
2460
  await testFeaturedCalibrationFailureShouldReturnCalibrationRequired();
2062
2461
  await testFeaturedTabSwitchFailureShouldReturnRetryableError();
@@ -2076,6 +2475,14 @@ async function main() {
2076
2475
  await testScreenConfigFailureShouldRequireUserProvidedConfig();
2077
2476
  await testScreenConfigPlaceholderShouldRequireUserConfirmationAfterUpdate();
2078
2477
  await testScreenConfigRecoveryStepShouldBeFirst();
2478
+ await testFollowUpChatMissingCriteriaShouldNeedInput();
2479
+ await testFollowUpChatMissingFieldsShouldExposeRecommendDefaults();
2480
+ await testFollowUpChatMissingStartFromShouldNeedInput();
2481
+ await testFollowUpChatMissingTargetCountShouldNeedInput();
2482
+ await testFinalReviewShouldIncludeFollowUpChatSummary();
2483
+ await testCompletedPipelineShouldRunChatFollowUp();
2484
+ await testCompletedPipelineShouldFailWhenChatLaunchFails();
2485
+ await testCompletedPipelineShouldFailWhenChatRunFails();
2079
2486
  console.log("pipeline tests passed");
2080
2487
  }
2081
2488
 
@@ -0,0 +1,134 @@
1
+ # boss-chat
2
+
3
+ 基于 Chrome DevTools Protocol 和 OpenAI 兼容 LLM 的 Boss 直聘聊天页教育筛选与自动沟通 CLI。
4
+
5
+ ## 功能
6
+
7
+ - 连接已登录、已打开聊天页的 Chrome
8
+ - 遍历聊天列表客户卡片,读取教育信息
9
+ - 用自定义 `baseUrl + apiKey + model` 调用大模型判断是否符合教育要求
10
+ - 命中时自动生成简短话术并写入聊天框,随后发送
11
+ - 默认记录已处理客户,避免重复触达
12
+ - 支持选择从`未读`或`全部`列表开始处理
13
+ - 支持 `--dry-run` 先验证页面稳定性和命中效果
14
+
15
+ ## 依赖
16
+
17
+ - Node.js(建议 18+)
18
+ - Google Chrome(需开启远程调试端口)
19
+ - npm 包依赖:
20
+ - `chrome-remote-interface@^0.33.3`
21
+
22
+ ## 使用前准备
23
+
24
+ 1. 用远程调试模式启动 Chrome:
25
+
26
+ ```powershell
27
+ chrome.exe --remote-debugging-port=9222
28
+ ```
29
+
30
+ 2. 登录 Boss 直聘并打开聊天页:
31
+
32
+ ```text
33
+ https://www.zhipin.com/web/chat/index
34
+ ```
35
+
36
+ 3. 全局安装:
37
+
38
+ ```powershell
39
+ npm install -g @reconcrap/boss-chat-cli
40
+ ```
41
+
42
+ 也可以直接用 `npx`(适合 agent 平台托管 MCP 时):
43
+
44
+ ```powershell
45
+ npx -y -p @reconcrap/boss-chat-cli@latest boss-chat-mcp
46
+ ```
47
+
48
+ ## 运行
49
+
50
+ 首次运行会交互式询问教育要求、话术样例、LLM 参数等配置;每次运行也可选择从`未读`或`全部`开始:
51
+
52
+ ```powershell
53
+ boss-chat run
54
+ ```
55
+
56
+ 建议先用 dry-run:
57
+
58
+ ```powershell
59
+ boss-chat run --dry-run --targetCount 3
60
+ ```
61
+
62
+ ## MCP Agent 集成(openclaw / codex / trae-cn)
63
+
64
+ 包内已内置 MCP stdio server,可直接被三类平台调用:
65
+
66
+ - `openclaw-boss-chat-mcp`
67
+ - `codex-boss-chat-mcp`
68
+ - `trae-cn-boss-chat-mcp`
69
+ - 通用入口:`boss-chat-mcp`
70
+
71
+ ### 工具列表
72
+
73
+ - `health_check`: 检查服务是否可用
74
+ - `start_run`: 启动异步任务,返回 `run_id`
75
+ - `get_run`: 查询任务状态
76
+ - `pause_run`: 暂停任务
77
+ - `resume_run`: 继续任务
78
+ - `cancel_run`: 取消任务
79
+
80
+ ### 平台配置示例
81
+
82
+ 项目里提供了三份可直接复制的模板:
83
+
84
+ - `configs/mcp/openclaw.json`
85
+ - `configs/mcp/codex.json`
86
+ - `configs/mcp/trae-cn.json`
87
+
88
+ 三份配置都通过 `npx` 拉取最新包并启动对应 MCP 入口,例如:
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "boss-chat": {
94
+ "command": "npx",
95
+ "args": ["-y", "-p", "@reconcrap/boss-chat-cli@latest", "boss-chat-mcp"]
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ > 不同平台的 MCP 配置文件路径可能不同,但 `command + args` 可直接复用。
102
+
103
+ ## 运行中控制
104
+
105
+ 程序运行时可以直接用键盘控制:
106
+
107
+ - `p`: 暂停;再次按 `p` 或按 `r` 继续
108
+ - `r`: 继续运行
109
+ - `q`: 请求停止,当前步骤结束后安全退出
110
+ - `Ctrl+C`: 请求停止,当前步骤结束后安全退出
111
+
112
+ 停止后仍会写入本次运行报告,已记录的客户状态也会保留。
113
+
114
+ ## 常用参数
115
+
116
+ - `--profile <name>`: 使用指定 profile
117
+ - `--dry-run`: 只检查和生成文案,不实际发送
118
+ - `--no-state`: 不记录已处理客户
119
+ - `--targetCount <n>`: 覆盖本次检查人数
120
+ - `--educationRequirement <text>`: 覆盖教育要求
121
+ - `--messageSample <text>`: 覆盖话术样例
122
+ - `--start-from <unread|all>`: 本次从未读或全部列表开始
123
+ - `--baseurl <url>`: 覆盖 LLM base URL
124
+ - `--apikey <key>`: 覆盖 LLM API key
125
+ - `--model <name>`: 覆盖 LLM 模型
126
+ - `--port <n>`: 覆盖 Chrome 远程调试端口
127
+
128
+ ## 数据目录
129
+
130
+ 运行产生的数据默认保存在项目下的 `.boss-chat/`:
131
+
132
+ - `profiles/`: 保存 profile 配置
133
+ - `state/`: 保存已处理客户状态
134
+ - `reports/`: 保存每次运行的 JSON 报告
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@reconcrap/boss-chat-cli",
3
+ "version": "1.1.0",
4
+ "description": "Boss chat education screening and outreach CLI with MCP agent bridge",
5
+ "type": "module",
6
+ "bin": {
7
+ "boss-chat": "src/cli.js",
8
+ "boss-chat-mcp": "src/mcp/server.js",
9
+ "openclaw-boss-chat-mcp": "src/mcp/server.js",
10
+ "codex-boss-chat-mcp": "src/mcp/server.js",
11
+ "trae-cn-boss-chat-mcp": "src/mcp/server.js"
12
+ },
13
+ "main": "./src/cli.js",
14
+ "files": [
15
+ "src/",
16
+ "configs/",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "start": "node ./src/cli.js run",
21
+ "start:mcp": "node ./src/mcp/server.js",
22
+ "test": "node --test"
23
+ },
24
+ "keywords": [
25
+ "boss",
26
+ "zhipin",
27
+ "chrome-devtools",
28
+ "cli",
29
+ "automation",
30
+ "mcp",
31
+ "openclaw",
32
+ "codex",
33
+ "trae-cn"
34
+ ],
35
+ "author": "",
36
+ "license": "MIT",
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": ""
46
+ },
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.18.1",
49
+ "chrome-remote-interface": "^0.33.3",
50
+ "sharp": "^0.34.5",
51
+ "zod": "^4.1.12"
52
+ }
53
+ }