@reconcrap/boss-recruit-mcp 1.0.15 → 1.0.16

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 (3) hide show
  1. package/README.md +11 -1
  2. package/package.json +1 -1
  3. package/src/cli.js +229 -40
package/README.md CHANGED
@@ -25,10 +25,12 @@ npx @reconcrap/boss-recruit-mcp install
25
25
  - 安装 Codex skill 到 `$CODEX_HOME/skills/boss-recruit-pipeline`
26
26
  - 初始化用户配置到 `$CODEX_HOME/boss-recruit-mcp/screening-config.json`
27
27
  - 生成通用 MCP 配置模板到 `$CODEX_HOME/boss-recruit-mcp/agent-mcp-configs`
28
+ - 自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
29
+ - 自动尝试把 skill 镜像到已检测到的外部 agent skills 目录
28
30
  - 包内自带 `boss-search-cli` 与 `boss-screen-cli` 运行时文件,无需额外目录结构
29
31
  - 不包含 `favorite-calibration.json`,首次使用前需要自行校准生成
30
32
 
31
- ## 跨 Agent 快速接入(Cursor / Trae / Claude Code / OpenClaw)
33
+ ## 跨 Agent 快速接入(Cursor / Trae / trae-cn / Claude Code / OpenClaw)
32
34
 
33
35
  生成 MCP 配置模板:
34
36
 
@@ -58,6 +60,7 @@ $CODEX_HOME/boss-recruit-mcp/agent-mcp-configs
58
60
  boss-recruit-mcp mcp-config --client cursor
59
61
  boss-recruit-mcp mcp-config --client claudecode
60
62
  boss-recruit-mcp mcp-config --client trae
63
+ boss-recruit-mcp mcp-config --client trae-cn
61
64
  boss-recruit-mcp mcp-config --client openclaw
62
65
  boss-recruit-mcp mcp-config --client generic
63
66
  ```
@@ -73,6 +76,13 @@ boss-recruit-mcp mcp-config --client generic
73
76
  boss-recruit-mcp mcp-config --client generic --command boss-recruit-mcp --args-json "[\"start\"]"
74
77
  ```
75
78
 
79
+ 可选环境变量(用于跨 agent 自动配置):
80
+
81
+ ```bash
82
+ BOSS_RECRUIT_MCP_CONFIG_TARGETS # JSON 数组或系统 path 分隔路径列表,指定额外 mcp.json 目标文件
83
+ BOSS_RECRUIT_EXTERNAL_SKILL_DIRS # JSON 数组或系统 path 分隔路径列表,指定额外 skills 根目录
84
+ ```
85
+
76
86
  ## 准备配置
77
87
 
78
88
  1. 初始化后编辑用户配置文件:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recruit-mcp",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Unified MCP pipeline for boss-search-cli and boss-screen-cli",
5
5
  "keywords": [
6
6
  "boss",
package/src/cli.js CHANGED
@@ -28,14 +28,16 @@ const SUPPORTED_MCP_CLIENTS = ["generic", "cursor", "trae", "claudecode", "openc
28
28
  const DEFAULT_MCP_SERVER_NAME = "boss-recruit";
29
29
  const DEFAULT_MCP_COMMAND = "npx";
30
30
  const DEFAULT_MCP_ARGS = ["-y", "@reconcrap/boss-recruit-mcp@latest", "start"];
31
- const AUTO_SYNC_SKIP_COMMANDS = new Set([
32
- "install",
33
- "install-skill",
34
- "where",
35
- "help",
36
- "--help",
37
- "-h"
38
- ]);
31
+ const AUTO_SYNC_SKIP_COMMANDS = new Set([
32
+ "install",
33
+ "install-skill",
34
+ "where",
35
+ "help",
36
+ "--help",
37
+ "-h"
38
+ ]);
39
+ const EXTERNAL_MCP_TARGETS_ENV = "BOSS_RECRUIT_MCP_CONFIG_TARGETS";
40
+ const EXTERNAL_SKILL_DIRS_ENV = "BOSS_RECRUIT_EXTERNAL_SKILL_DIRS";
39
41
 
40
42
  function getPackageVersion() {
41
43
  try {
@@ -58,13 +60,46 @@ function getCodexHome() {
58
60
  : path.join(os.homedir(), ".codex");
59
61
  }
60
62
 
61
- function ensureDir(targetPath) {
62
- fs.mkdirSync(targetPath, { recursive: true });
63
- }
64
-
65
- function getDesktopDir() {
66
- return path.join(os.homedir(), "Desktop");
67
- }
63
+ function ensureDir(targetPath) {
64
+ fs.mkdirSync(targetPath, { recursive: true });
65
+ }
66
+
67
+ function pathExists(targetPath) {
68
+ try {
69
+ return fs.existsSync(targetPath);
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ function readJsonObjectFileSafe(filePath) {
76
+ if (!pathExists(filePath)) return {};
77
+ try {
78
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
79
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
80
+ return parsed;
81
+ }
82
+ } catch {
83
+ // Fallback below.
84
+ }
85
+ return {};
86
+ }
87
+
88
+ function dedupePaths(items) {
89
+ const result = [];
90
+ const seen = new Set();
91
+ for (const item of items || []) {
92
+ const resolved = path.resolve(String(item || ""));
93
+ if (!resolved || seen.has(resolved)) continue;
94
+ seen.add(resolved);
95
+ result.push(resolved);
96
+ }
97
+ return result;
98
+ }
99
+
100
+ function getDesktopDir() {
101
+ return path.join(os.homedir(), "Desktop");
102
+ }
68
103
 
69
104
  function getUserConfigPath() {
70
105
  return path.join(getCodexHome(), "boss-recruit-mcp", "screening-config.json");
@@ -146,12 +181,13 @@ function parseStringArrayOption(value, label) {
146
181
  return parsed;
147
182
  }
148
183
 
149
- function normalizeMcpClientName(value) {
150
- const raw = String(value || "").trim().toLowerCase();
151
- if (!raw) return "";
152
- if (raw === "claude-code") return "claudecode";
153
- return raw;
154
- }
184
+ function normalizeMcpClientName(value) {
185
+ const raw = String(value || "").trim().toLowerCase();
186
+ if (!raw) return "";
187
+ if (raw === "claude-code") return "claudecode";
188
+ if (raw === "trae-cn") return "trae";
189
+ return raw;
190
+ }
155
191
 
156
192
  function parseMcpClientTargets(rawValue) {
157
193
  if (!rawValue) return SUPPORTED_MCP_CLIENTS.slice();
@@ -214,7 +250,7 @@ function buildMcpConfigFileContent(options = {}) {
214
250
  };
215
251
  }
216
252
 
217
- function writeMcpConfigFiles(options = {}) {
253
+ function writeMcpConfigFiles(options = {}) {
218
254
  const clients = parseMcpClientTargets(options.client);
219
255
  const outputDir = getAgentConfigOutputDir(options);
220
256
  ensureDir(outputDir);
@@ -227,8 +263,142 @@ function writeMcpConfigFiles(options = {}) {
227
263
  files.push({ client, file: filePath });
228
264
  }
229
265
 
230
- return { outputDir, files };
231
- }
266
+ return { outputDir, files };
267
+ }
268
+
269
+ function parsePathListFromEnv(raw) {
270
+ if (!raw) return [];
271
+ const text = String(raw).trim();
272
+ if (!text) return [];
273
+ try {
274
+ const parsed = JSON.parse(text);
275
+ if (Array.isArray(parsed)) {
276
+ return dedupePaths(parsed.filter(Boolean));
277
+ }
278
+ } catch {
279
+ // Fallback to delimiter split.
280
+ }
281
+ return dedupePaths(
282
+ text
283
+ .split(path.delimiter)
284
+ .map((item) => item.trim())
285
+ .filter(Boolean)
286
+ );
287
+ }
288
+
289
+ function getKnownExternalMcpConfigPaths() {
290
+ const home = os.homedir();
291
+ const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
292
+ return dedupePaths([
293
+ path.join(appData, "Cursor", "User", "mcp.json"),
294
+ path.join(appData, "Trae", "User", "mcp.json"),
295
+ path.join(appData, "Trae CN", "User", "mcp.json"),
296
+ path.join(home, ".trae", "mcp.json"),
297
+ path.join(home, ".trae-cn", "mcp.json"),
298
+ path.join(home, ".claude", "mcp.json"),
299
+ path.join(home, ".openclaw", "mcp.json")
300
+ ]);
301
+ }
302
+
303
+ function resolveExternalMcpConfigTargets() {
304
+ const fromEnv = parsePathListFromEnv(process.env[EXTERNAL_MCP_TARGETS_ENV]);
305
+ const known = getKnownExternalMcpConfigPaths().filter((filePath) => {
306
+ if (pathExists(filePath)) return true;
307
+ return pathExists(path.dirname(filePath));
308
+ });
309
+ return dedupePaths([...fromEnv, ...known]);
310
+ }
311
+
312
+ function mergeMcpServerConfigFile(filePath, options = {}) {
313
+ const nextConfig = buildMcpConfigFileContent(options);
314
+ const serverName = Object.keys(nextConfig.mcpServers || {})[0] || DEFAULT_MCP_SERVER_NAME;
315
+ const launchConfig = nextConfig.mcpServers?.[serverName] || buildMcpLaunchConfig(options);
316
+ const current = readJsonObjectFileSafe(filePath);
317
+ const existingServers =
318
+ current?.mcpServers && typeof current.mcpServers === "object" && !Array.isArray(current.mcpServers)
319
+ ? current.mcpServers
320
+ : {};
321
+ const existingEntry = existingServers[serverName];
322
+ const merged = {
323
+ ...current,
324
+ mcpServers: {
325
+ ...existingServers,
326
+ [serverName]: launchConfig
327
+ }
328
+ };
329
+
330
+ ensureDir(path.dirname(filePath));
331
+ fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
332
+ const updated = JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
333
+ return {
334
+ file: filePath,
335
+ server: serverName,
336
+ updated
337
+ };
338
+ }
339
+
340
+ function installExternalMcpConfigs(options = {}) {
341
+ const targets = resolveExternalMcpConfigTargets();
342
+ const applied = [];
343
+ const skipped = [];
344
+ for (const target of targets) {
345
+ try {
346
+ const existed = pathExists(target);
347
+ const merged = mergeMcpServerConfigFile(target, options);
348
+ applied.push({
349
+ file: target,
350
+ server: merged.server,
351
+ created: !existed,
352
+ updated: merged.updated
353
+ });
354
+ } catch (error) {
355
+ skipped.push({
356
+ file: target,
357
+ reason: error.message
358
+ });
359
+ }
360
+ }
361
+ return { targets, applied, skipped };
362
+ }
363
+
364
+ function getKnownExternalSkillBaseDirs() {
365
+ const home = os.homedir();
366
+ const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
367
+ return dedupePaths([
368
+ path.join(home, ".cursor", "skills"),
369
+ path.join(home, ".trae", "skills"),
370
+ path.join(home, ".trae-cn", "skills"),
371
+ path.join(home, ".claude", "skills"),
372
+ path.join(home, ".openclaw", "skills"),
373
+ path.join(appData, "Cursor", "User", "skills"),
374
+ path.join(appData, "Trae", "User", "skills"),
375
+ path.join(appData, "Trae CN", "User", "skills"),
376
+ path.join(appData, "OpenClaw", "User", "skills")
377
+ ]);
378
+ }
379
+
380
+ function resolveExternalSkillBaseDirs() {
381
+ const fromEnv = parsePathListFromEnv(process.env[EXTERNAL_SKILL_DIRS_ENV]);
382
+ const known = getKnownExternalSkillBaseDirs().filter((dirPath) => pathExists(dirPath));
383
+ return dedupePaths([...fromEnv, ...known]);
384
+ }
385
+
386
+ function mirrorSkillToExternalDirs() {
387
+ const baseDirs = resolveExternalSkillBaseDirs();
388
+ const mirrored = [];
389
+ const skipped = [];
390
+ for (const baseDir of baseDirs) {
391
+ try {
392
+ const targetDir = path.join(baseDir, skillName);
393
+ ensureDir(path.dirname(targetDir));
394
+ fs.cpSync(skillSourceDir, targetDir, { recursive: true, force: true });
395
+ mirrored.push({ base_dir: baseDir, target_dir: targetDir });
396
+ } catch (error) {
397
+ skipped.push({ base_dir: baseDir, reason: error.message });
398
+ }
399
+ }
400
+ return { baseDirs, mirrored, skipped };
401
+ }
232
402
 
233
403
  function readTextFile(filePath, label) {
234
404
  const resolved = path.resolve(String(filePath));
@@ -909,7 +1079,7 @@ async function launchChrome(options) {
909
1079
  };
910
1080
  }
911
1081
 
912
- function printHelp() {
1082
+ function printHelp() {
913
1083
  console.log("boss-recruit-mcp");
914
1084
  console.log("");
915
1085
  console.log("Usage:");
@@ -920,7 +1090,7 @@ function printHelp() {
920
1090
  console.log(" boss-recruit-mcp install-skill Install only the Codex skill");
921
1091
  console.log(" boss-recruit-mcp init-config Create ~/.codex/boss-recruit-mcp/screening-config.json if missing");
922
1092
  console.log(" boss-recruit-mcp set-port Persist preferred Chrome debug port to active screening-config");
923
- console.log(" boss-recruit-mcp mcp-config Generate MCP config JSON for Cursor/Trae/Claude Code/OpenClaw");
1093
+ console.log(" boss-recruit-mcp mcp-config Generate MCP config JSON for Cursor/Trae(含 trae-cn)/Claude Code/OpenClaw");
924
1094
  console.log(" boss-recruit-mcp doctor Check config, calibration, and runtime prerequisites");
925
1095
  console.log(" boss-recruit-mcp calibrate Auto-open Boss search page, then run favorite-button calibration");
926
1096
  console.log(" boss-recruit-mcp launch-chrome Reuse existing Chrome debug instance when possible; otherwise launch one, open Boss search, and check login state");
@@ -972,22 +1142,41 @@ function printMcpConfig(options = {}) {
972
1142
  console.log("2. Merge its mcpServers block into that client's MCP config.");
973
1143
  }
974
1144
 
975
- function installAll() {
976
- const skillTarget = installSkill();
977
- const configResult = ensureUserConfig();
978
- const mcpTemplateResult = writeMcpConfigFiles({ client: "all" });
979
- console.log(`Skill installed to: ${skillTarget}`);
980
- if (configResult.created) {
981
- console.log(`Config template created at: ${configResult.path}`);
982
- } else {
983
- console.log(`Config already exists at: ${configResult.path}`);
1145
+ function installAll() {
1146
+ const skillTarget = installSkill();
1147
+ const configResult = ensureUserConfig();
1148
+ const mcpTemplateResult = writeMcpConfigFiles({ client: "all" });
1149
+ const externalMcpResult = installExternalMcpConfigs({});
1150
+ const externalSkillResult = mirrorSkillToExternalDirs();
1151
+ console.log(`Skill installed to: ${skillTarget}`);
1152
+ if (configResult.created) {
1153
+ console.log(`Config template created at: ${configResult.path}`);
1154
+ } else {
1155
+ console.log(`Config already exists at: ${configResult.path}`);
984
1156
  }
985
1157
  console.log(`MCP config templates exported to: ${mcpTemplateResult.outputDir}`);
986
- for (const item of mcpTemplateResult.files) {
987
- console.log(`- ${item.client}: ${item.file}`);
988
- }
989
- console.log("");
990
- console.log("Next steps:");
1158
+ for (const item of mcpTemplateResult.files) {
1159
+ console.log(`- ${item.client}: ${item.file}`);
1160
+ }
1161
+ if (externalMcpResult.targets.length > 0) {
1162
+ console.log(`Auto-configured external MCP files: ${externalMcpResult.applied.length}`);
1163
+ for (const item of externalMcpResult.applied) {
1164
+ const action = item.created ? "created" : item.updated ? "updated" : "unchanged";
1165
+ console.log(`- ${item.file} (${action})`);
1166
+ }
1167
+ } else {
1168
+ console.log("No external MCP config target detected. Set BOSS_RECRUIT_MCP_CONFIG_TARGETS to auto-configure custom agents.");
1169
+ }
1170
+ if (externalSkillResult.baseDirs.length > 0) {
1171
+ console.log(`Mirrored skill to external dirs: ${externalSkillResult.mirrored.length}`);
1172
+ for (const item of externalSkillResult.mirrored) {
1173
+ console.log(`- ${item.target_dir}`);
1174
+ }
1175
+ } else {
1176
+ console.log("No external skill dir detected. Set BOSS_RECRUIT_EXTERNAL_SKILL_DIRS to mirror skill for non-Codex agents.");
1177
+ }
1178
+ console.log("");
1179
+ console.log("Next steps:");
991
1180
  console.log("1. Fill in baseUrl/apiKey/model in the config file above.");
992
1181
  console.log("2. Choose a client template from the exported MCP config files and merge it into your AI client config.");
993
1182
  console.log("3. Choose a Chrome remote-debugging port (9222 is recommended, but you can reuse an existing port).");