@reconcrap/boss-recommend-mcp 0.1.10 → 0.1.12

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.
package/README.md CHANGED
@@ -34,6 +34,7 @@ MCP 工具名:`run_recommend_pipeline`
34
34
  - 简历提取采用“分段滚动截图 + 拼成长图”的方式,再交给多模态模型判断
35
35
  - 运行前会自动做依赖体检(Node.js、Python、Pillow、`chrome-remote-interface`、`ws`),缺失时会在 `doctor` 与流水线失败诊断中明确提示
36
36
  - 若 preflight 失败,返回 `diagnostics.recovery`(含有序修复步骤与 `agent_prompt`),可直接交给 AI agent 自动按顺序安装依赖
37
+ - 不依赖 PowerShell;Windows / macOS 均可运行(命令提示会按平台给出)
37
38
 
38
39
  ## 安装
39
40
 
@@ -82,6 +83,7 @@ node src/cli.js run --instruction "推荐页筛选985男生,近14天没有,
82
83
 
83
84
  - `install` 阶段不会自动创建 `screening-config.json`
84
85
  - 首次运行若缺配置,会由 doctor / pipeline 明确提示用户填写 `baseUrl`、`apiKey`、`model`
86
+ - 在 `npx` 临时目录(如 `AppData\\Local\\npm-cache\\_npx\\...`)执行时,不会再把该临时目录当作 `screening-config.json` 目标路径
85
87
 
86
88
  配置样例见:
87
89
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
package/src/adapters.js CHANGED
@@ -60,6 +60,9 @@ function parsePositiveInteger(raw) {
60
60
 
61
61
  function resolveWorkspaceConfigCandidates(workspaceRoot) {
62
62
  const root = path.resolve(String(workspaceRoot || process.cwd()));
63
+ if (isEphemeralNpxWorkspaceRoot(root)) {
64
+ return [];
65
+ }
63
66
  const directPath = path.join(root, "config", "screening-config.json");
64
67
  const nestedPath = path.join(root, "boss-recommend-mcp", "config", "screening-config.json");
65
68
  const candidates = [directPath];
@@ -93,16 +96,13 @@ function serializeSchoolTagSelection(value) {
93
96
  return normalized || "不限";
94
97
  }
95
98
 
96
- function canWriteConfigPath(filePath) {
97
- if (!filePath) return false;
98
- try {
99
- const dirPath = path.dirname(filePath);
100
- ensureDir(dirPath);
101
- fs.accessSync(dirPath, fs.constants.W_OK);
102
- return true;
103
- } catch {
104
- return false;
105
- }
99
+ function isEphemeralNpxWorkspaceRoot(workspaceRoot) {
100
+ const root = path.resolve(String(workspaceRoot || ""));
101
+ const normalized = root.replace(/\\/g, "/").toLowerCase();
102
+ return (
103
+ normalized.includes("/appdata/local/npm-cache/_npx/")
104
+ || normalized.includes("/node_modules/@reconcrap/boss-recommend-mcp")
105
+ );
106
106
  }
107
107
 
108
108
  function buildScreenConfigCandidateMap(workspaceRoot) {
@@ -135,14 +135,7 @@ function resolveScreenConfigPath(workspaceRoot) {
135
135
  if (existingWorkspacePath) {
136
136
  return existingWorkspacePath;
137
137
  }
138
- if (canWriteConfigPath(candidateMap.user_path)) {
139
- return candidateMap.user_path;
140
- }
141
- const writableWorkspacePath = candidateMap.workspace_paths.find((item) => canWriteConfigPath(item));
142
- if (writableWorkspacePath) {
143
- return writableWorkspacePath;
144
- }
145
- // 默认固定写入/读取 Agent 无关路径,避免意外落到 .codex 旧路径。
138
+ // 默认固定写入/读取 Agent 无关路径,避免落到 npx 临时目录或 .codex 旧路径。
146
139
  if (candidateMap.user_path) {
147
140
  return candidateMap.user_path;
148
141
  }
@@ -156,6 +149,8 @@ export function getScreenConfigResolution(workspaceRoot) {
156
149
  return {
157
150
  resolved_path,
158
151
  candidate_paths,
152
+ workspace_root: path.resolve(String(workspaceRoot || process.cwd())),
153
+ workspace_ephemeral: isEphemeralNpxWorkspaceRoot(workspaceRoot),
159
154
  writable_path: candidateMap.user_path,
160
155
  legacy_path: candidateMap.legacy_path
161
156
  };
@@ -217,13 +212,34 @@ function resolveWorkspaceDebugPort(workspaceRoot) {
217
212
  return parsePositiveInteger(config?.debugPort) || 9222;
218
213
  }
219
214
 
215
+ function getDefaultChromeExecutableCandidates() {
216
+ const candidates = [process.env.BOSS_RECOMMEND_CHROME_PATH].filter(Boolean);
217
+ if (process.platform === "win32") {
218
+ candidates.push(
219
+ path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
220
+ path.join(process.env.ProgramFiles || "", "Google", "Chrome", "Application", "chrome.exe"),
221
+ path.join(process.env["ProgramFiles(x86)"] || "", "Google", "Chrome", "Application", "chrome.exe")
222
+ );
223
+ } else if (process.platform === "darwin") {
224
+ candidates.push(
225
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
226
+ path.join(os.homedir(), "Applications", "Google Chrome.app", "Contents", "MacOS", "Google Chrome"),
227
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
228
+ );
229
+ } else {
230
+ candidates.push(
231
+ "/usr/bin/google-chrome",
232
+ "/usr/bin/google-chrome-stable",
233
+ "/usr/bin/chromium-browser",
234
+ "/usr/bin/chromium",
235
+ "/snap/bin/chromium"
236
+ );
237
+ }
238
+ return Array.from(new Set(candidates.filter(Boolean)));
239
+ }
240
+
220
241
  function getChromeExecutable() {
221
- const candidates = [
222
- process.env.BOSS_RECOMMEND_CHROME_PATH,
223
- path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
224
- path.join(process.env.ProgramFiles || "", "Google", "Chrome", "Application", "chrome.exe"),
225
- path.join(process.env["ProgramFiles(x86)"] || "", "Google", "Chrome", "Application", "chrome.exe")
226
- ].filter(Boolean);
242
+ const candidates = getDefaultChromeExecutableCandidates();
227
243
  return candidates.find((candidate) => pathExists(candidate)) || null;
228
244
  }
229
245
 
package/src/cli.js CHANGED
@@ -557,13 +557,34 @@ function findChromeOnboardingUrl(tabs) {
557
557
  return null;
558
558
  }
559
559
 
560
+ function getDefaultChromeExecutableCandidates() {
561
+ const candidates = [process.env.BOSS_RECOMMEND_CHROME_PATH].filter(Boolean);
562
+ if (process.platform === "win32") {
563
+ candidates.push(
564
+ path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
565
+ path.join(process.env.ProgramFiles || "", "Google", "Chrome", "Application", "chrome.exe"),
566
+ path.join(process.env["ProgramFiles(x86)"] || "", "Google", "Chrome", "Application", "chrome.exe")
567
+ );
568
+ } else if (process.platform === "darwin") {
569
+ candidates.push(
570
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
571
+ path.join(os.homedir(), "Applications", "Google Chrome.app", "Contents", "MacOS", "Google Chrome"),
572
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
573
+ );
574
+ } else {
575
+ candidates.push(
576
+ "/usr/bin/google-chrome",
577
+ "/usr/bin/google-chrome-stable",
578
+ "/usr/bin/chromium-browser",
579
+ "/usr/bin/chromium",
580
+ "/snap/bin/chromium"
581
+ );
582
+ }
583
+ return Array.from(new Set(candidates.filter(Boolean)));
584
+ }
585
+
560
586
  function getChromeExecutable() {
561
- const candidates = [
562
- process.env.BOSS_RECOMMEND_CHROME_PATH,
563
- path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
564
- path.join(process.env.ProgramFiles || "", "Google", "Chrome", "Application", "chrome.exe"),
565
- path.join(process.env["ProgramFiles(x86)"] || "", "Google", "Chrome", "Application", "chrome.exe")
566
- ].filter(Boolean);
587
+ const candidates = getDefaultChromeExecutableCandidates();
567
588
  return candidates.find((candidate) => fs.existsSync(candidate)) || null;
568
589
  }
569
590
 
package/src/pipeline.js CHANGED
@@ -18,7 +18,7 @@ function failedCheckSet(checks = []) {
18
18
  return new Set(failed);
19
19
  }
20
20
 
21
- function collectNpmInstallDirs(checks = [], workspaceRoot) {
21
+ function collectNpmInstallDirs(checks = [], workspaceRoot) {
22
22
  const npmCheckKeys = new Set([
23
23
  "npm_dep_chrome_remote_interface_search",
24
24
  "npm_dep_chrome_remote_interface_screen",
@@ -28,20 +28,61 @@ function collectNpmInstallDirs(checks = [], workspaceRoot) {
28
28
  .filter((item) => item && item.ok === false && npmCheckKeys.has(item.key))
29
29
  .map((item) => item.install_cwd)
30
30
  .filter((value) => typeof value === "string" && value.trim());
31
- if (dirs.length > 0) return dedupe(dirs);
32
- return workspaceRoot ? [workspaceRoot] : [];
33
- }
34
-
35
- function buildNpmInstallCommands(checks = [], workspaceRoot) {
36
- const dirs = collectNpmInstallDirs(checks, workspaceRoot);
37
- const commands = [];
38
- for (const dir of dirs) {
39
- const escaped = String(dir).replace(/'/g, "''");
40
- commands.push(`Set-Location '${escaped}'`);
41
- commands.push("npm install");
42
- }
43
- return commands;
44
- }
31
+ if (dirs.length > 0) return dedupe(dirs);
32
+ return workspaceRoot ? [workspaceRoot] : [];
33
+ }
34
+
35
+ function quoteForCommand(value) {
36
+ return JSON.stringify(String(value));
37
+ }
38
+
39
+ function buildNpmInstallCommands(checks = [], workspaceRoot) {
40
+ const dirs = collectNpmInstallDirs(checks, workspaceRoot);
41
+ const commands = [];
42
+ for (const dir of dirs) {
43
+ commands.push(`npm install --prefix ${quoteForCommand(dir)}`);
44
+ }
45
+ return commands;
46
+ }
47
+
48
+ function getNodeInstallCommands() {
49
+ if (process.platform === "win32") {
50
+ return [
51
+ "winget install OpenJS.NodeJS.LTS",
52
+ "node --version"
53
+ ];
54
+ }
55
+ if (process.platform === "darwin") {
56
+ return [
57
+ "brew install node",
58
+ "node --version"
59
+ ];
60
+ }
61
+ return [
62
+ "使用系统包管理器安装 Node.js >= 18(例如 apt / yum / brew)",
63
+ "node --version"
64
+ ];
65
+ }
66
+
67
+ function getPythonInstallCommands() {
68
+ if (process.platform === "win32") {
69
+ return [
70
+ "winget install Python.Python.3.12",
71
+ "python --version"
72
+ ];
73
+ }
74
+ if (process.platform === "darwin") {
75
+ return [
76
+ "brew install python",
77
+ "python3 --version",
78
+ "若系统无 python 命令,请在当前终端建立 python -> python3 别名后重试。"
79
+ ];
80
+ }
81
+ return [
82
+ "使用系统包管理器安装 Python(例如 apt / yum / brew)",
83
+ "python --version"
84
+ ];
85
+ }
45
86
 
46
87
  function formatCommandBlock(commands = []) {
47
88
  return commands.map((command) => `- ${command}`).join("\n");
@@ -79,12 +120,9 @@ function buildPreflightRecovery(checks = [], workspaceRoot) {
79
120
  id: "install_nodejs",
80
121
  title: "安装 Node.js >= 18",
81
122
  blocked_by: [],
82
- commands: [
83
- "winget install OpenJS.NodeJS.LTS",
84
- "node --version"
85
- ]
86
- });
87
- }
123
+ commands: getNodeInstallCommands()
124
+ });
125
+ }
88
126
  if (needNpm) {
89
127
  ordered_steps.push({
90
128
  id: "install_npm_dependencies",
@@ -93,17 +131,14 @@ function buildPreflightRecovery(checks = [], workspaceRoot) {
93
131
  commands: buildNpmInstallCommands(checks, workspaceRoot)
94
132
  });
95
133
  }
96
- if (needPython) {
97
- ordered_steps.push({
98
- id: "install_python",
99
- title: "安装 Python(确保 python 命令可用)",
100
- blocked_by: [],
101
- commands: [
102
- "winget install Python.Python.3.12",
103
- "python --version"
104
- ]
105
- });
106
- }
134
+ if (needPython) {
135
+ ordered_steps.push({
136
+ id: "install_python",
137
+ title: "安装 Python(确保 python 命令可用)",
138
+ blocked_by: [],
139
+ commands: getPythonInstallCommands()
140
+ });
141
+ }
107
142
  if (needPillow) {
108
143
  ordered_steps.push({
109
144
  id: "install_pillow",
@@ -217,12 +252,17 @@ function buildChromeSetupGuidance({ debugPort, pageState }) {
217
252
  const launchLine = launchAttempt?.ok
218
253
  ? `已自动启动 Chrome(--remote-debugging-port=${debugPort},--user-data-dir=${launchAttempt.user_data_dir || "auto"})。`
219
254
  : null;
255
+ const launchExample = process.platform === "win32"
256
+ ? `chrome.exe --remote-debugging-port=${debugPort} --user-data-dir=<profile-dir>`
257
+ : process.platform === "darwin"
258
+ ? `'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' --remote-debugging-port=${debugPort} --user-data-dir=<profile-dir>`
259
+ : `google-chrome --remote-debugging-port=${debugPort} --user-data-dir=<profile-dir>`;
220
260
  const steps = [
221
261
  `请先在可连接到 DevTools 端口 ${debugPort} 的 Chrome 实例中完成以下操作:`,
222
262
  ...(launchLine ? [launchLine] : []),
223
263
  "1) 确认当前 Chrome 与本次运行使用同一个远程调试端口。",
224
264
  isPortIssue
225
- ? `2) 若端口不可连接,请用远程调试方式启动 Chrome(示例:chrome.exe --remote-debugging-port=${debugPort})。`
265
+ ? `2) 若端口不可连接,请用远程调试方式启动 Chrome(示例:${launchExample})。`
226
266
  : "2) 确认端口可连接且浏览器窗口保持打开。",
227
267
  needsLogin
228
268
  ? `3) 当前检测到 Boss 未登录,请先打开并完成登录:${loginUrl}`