@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 +2 -0
- package/package.json +1 -1
- package/src/adapters.js +40 -24
- package/src/cli.js +27 -6
- package/src/pipeline.js +73 -33
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
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
|
265
|
+
? `2) 若端口不可连接,请用远程调试方式启动 Chrome(示例:${launchExample})。`
|
|
226
266
|
: "2) 确认端口可连接且浏览器窗口保持打开。",
|
|
227
267
|
needsLogin
|
|
228
268
|
? `3) 当前检测到 Boss 未登录,请先打开并完成登录:${loginUrl}`
|