@reconcrap/boss-recommend-mcp 2.0.0 → 2.0.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.
package/README.md CHANGED
@@ -85,6 +85,8 @@ boss-recommend-mcp list-jobs --slow-live --port 9222
85
85
  - 页面就绪(已登录且在 recommend 页)后,会先提取岗位栏全部岗位并要求用户确认本次岗位;确认后先点击岗位,再执行 search/screen
86
86
  - 在真正开始 search/screen 前,会进行最后一轮全参数总确认(岗位 + 全部筛选参数 + criteria + target_count + post_action + max_greet_count)
87
87
  - npm 全局安装后会自动执行 install:生成 skill、导出 MCP 模板,并自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
88
+ - 2.x installer 会迁移已存在的 legacy Boss MCP 配置:把 `boss-recommend` 指向统一 `@reconcrap/boss-recommend-mcp`,并从同一个 `mcp.json` 中移除旧 `boss-recruit-mcp` / standalone `boss-chat` / 本地 legacy Boss 路径;写入前会生成 `.boss-mcp-migration-*.bak` 备份
89
+ - 2.x installer 会刷新外部 agent skills:`boss-recommend-pipeline`、`boss-recruit-pipeline`、`boss-chat` 都来自当前包,旧 recruit/chat skill 会被覆盖为统一 MCP 路由
88
90
  - npm / npx 安装后会自动初始化 `screening-config.json` 模板(优先写入 workspace 的 `config/`,不可写时回退到用户目录)
89
91
  - npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs`、`~/.boss-recommend-mcp/boss-chat` 及其 `logs/runs/profiles/reports/artifacts/state`
90
92
  - `post_action` 必须在每次完整运行开始时确认一次
@@ -126,6 +128,37 @@ npm install
126
128
  node src/cli.js start
127
129
  ```
128
130
 
131
+ ### 迁移 legacy MCP / skills
132
+
133
+ 全局 npm 安装会自动运行 `boss-recommend-mcp install`。该安装器会在 Windows 和 macOS 上自动检测 Trae / Trae CN / OpenClaw 的常见配置目录:
134
+
135
+ - Windows: `%APPDATA%\Trae*\User\mcp.json`、`%USERPROFILE%\.trae*\mcp.json`、`%USERPROFILE%\.openclaw\mcp.json`、`%APPDATA%\OpenClaw\User\mcp.json`
136
+ - macOS: `~/Library/Application Support/Trae*/User/mcp.json`、`~/.trae*/mcp.json`、`~/.openclaw/mcp.json`、`~/Library/Application Support/OpenClaw/User/mcp.json`
137
+
138
+ 如果检测到 legacy Boss server entries,installer 会:
139
+
140
+ - 保留非 Boss MCP server。
141
+ - 写入统一 server:`boss-recommend -> npx -y @reconcrap/boss-recommend-mcp@<installed-version> start`
142
+ - 从同一个 `mcp.json` 删除旧 `boss-recruit-mcp`、standalone `boss-chat`、旧本地 Boss repo 路径,避免 agent 继续调用 legacy 包。
143
+ - 在原文件旁生成 `mcp.json.boss-mcp-migration-*.bak`。
144
+ - 同步外部 skills 目录里的 `boss-recommend-pipeline`、`boss-recruit-pipeline`、`boss-chat`。
145
+
146
+ 手动指定 agent:
147
+
148
+ ```bash
149
+ boss-recommend-mcp install --agent trae-cn
150
+ boss-recommend-mcp install --agent openclaw
151
+ boss-recommend-mcp doctor --agent trae-cn
152
+ boss-recommend-mcp doctor --agent openclaw
153
+ ```
154
+
155
+ 自定义路径:
156
+
157
+ ```bash
158
+ BOSS_RECOMMEND_MCP_CONFIG_TARGETS="/path/to/mcp.json" boss-recommend-mcp install
159
+ BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS="/path/to/skills" boss-recommend-mcp install
160
+ ```
161
+
129
162
  可选环境变量(用于跨 agent 自动配置):
130
163
 
131
164
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -31,6 +31,7 @@
31
31
  "test:core-run": "node src/test-core-run.js",
32
32
  "test:core-screening": "node src/test-core-screening.js",
33
33
  "test:core-self-heal": "node src/test-core-self-heal.js",
34
+ "test:installer-migration": "node src/test-installer-migration.js",
34
35
  "test:recommend-actions": "node src/test-recommend-actions.js",
35
36
  "test:recommend-domain": "node src/test-recommend-domain.js",
36
37
  "test:recommend-run-service": "node src/test-recommend-run-service.js",
@@ -9,7 +9,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
9
9
 
10
10
  当用户要在 Boss 聊天页单独跑筛选/沟通任务时,必须走内置的 chat 工具,而不是要求用户单独安装 `boss-chat`。
11
11
 
12
- 适用范围是“chat-only 会话”。若用户意图包含推荐页找人(尤其是“先推荐再沟通”),必须让 `boss-recommend-pipeline` 接管,并通过 `follow_up.chat` 完成联动。
12
+ 适用范围是“chat-only 会话”。若用户意图包含推荐页找人(尤其是“先推荐再沟通”),必须先让 `boss-recommend-pipeline` 完成推荐页任务;推荐完成后如用户仍要聊天页处理,再显式走本 skill。
13
13
 
14
14
  ## Tool Routing
15
15
 
@@ -55,7 +55,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
55
55
  - LLM 配置必须复用 `boss-recommend-mcp` 的 `screening-config.json`;不要再向用户单独要 `baseUrl/apiKey/model`。
56
56
  - 路由护栏(强制):
57
57
  - 只在用户明确是 chat-only 任务时使用本 skill。
58
- - 只要用户提到推荐页、先找人后沟通、或需要推荐筛选阶段,禁止调用 `start_boss_chat_run`;必须交给 `boss-recommend-pipeline` 并走 `follow_up.chat`。
58
+ - 只要用户提到推荐页、先找人后沟通、或需要推荐筛选阶段,禁止直接调用 `start_boss_chat_run`;必须先交给 `boss-recommend-pipeline` 完成推荐页任务。
59
59
  - 不得在 recommend 任务尚未完成时并行启动独立 chat run。
60
60
  - `job` / `start_from` / `criteria` 缺一不可;缺参时只补缺口。
61
61
  - `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
@@ -78,8 +78,9 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
78
78
  ## Handoff Rule (Recommend -> Chat)
79
79
 
80
80
  - 若用户先运行了 recommend 流水线,并在手动状态检查时确认 recommend 已完成,且用户目标是“立即进入聊天沟通”:
81
- - 若该 recommend run **未配置** `follow_up.chat`:应立即调用 `start_boss_chat_run` 启动 chat(不要等下一次 30 分钟轮询)。
82
- - 若该 recommend run **已配置** `follow_up.chat`:不要再重复新开 chat run,改为查询同一父 run / run 状态。
81
+ - 先调用 `prepare_boss_chat_run` 获取聊天页岗位列表与缺参。
82
+ - 显式确认 `job/start_from/target_count/criteria` 后再调用 `start_boss_chat_run`。
83
+ - 不要查找或依赖 `follow_up.chat`;该自动衔接路径属于 legacy-only 行为。
83
84
 
84
85
  ## Response Style
85
86
 
@@ -7,20 +7,20 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
7
7
 
8
8
  ## Goal
9
9
 
10
- 当用户要在 Boss 推荐页筛人时,必须走 `start_recommend_pipeline_run`,并按“两阶段确认 -> 页面就绪 -> 岗位确认 -> 最终确认 -> 执行”的顺序完成。若用户还要求筛完后自动进入聊天页 follow-up,则必须把 chat 要求放进同一个 recommend run 的 `follow_up.chat`,不要要求单独再安装 `boss-chat`。
10
+ 当用户要在 Boss 推荐页筛人时,必须走 `start_recommend_pipeline_run`,并按“两阶段确认 -> 页面就绪 -> 岗位确认 -> 最终确认 -> 执行”的顺序完成。2.0 CDP-only 路径不再支持 legacy recommend -> chat 自动衔接;若用户要聊天页筛选或求简历,必须在推荐页任务完成后显式改用 `boss-chat` 工具。
11
11
 
12
12
  ## Hard Rules (Must Follow)
13
13
 
14
14
  - **路由**
15
15
  - 语义是推荐页(`recommend/推荐页/recommend page//web/chat/recommend`)时,只能走本 skill。
16
- - 语义是“推荐页找人 + 结束后沟通/聊天”时,仍然只能走本 skill;必须通过 `follow_up.chat` 挂到同一个 recommend run。
16
+ - 语义是“推荐页找人 + 结束后沟通/聊天”时,先完成 recommend run;不得配置 `follow_up.chat`,后续聊天任务必须显式交给 `boss-chat`。
17
17
  - 只有用户**明确**说搜索页(`search/搜索页//web/chat/search`)时,才可转 `boss-recruit-pipeline`。
18
18
  - recommend 失败时(如 `JOB_TRIGGER_NOT_FOUND/NO_RECOMMEND_IFRAME/BOSS_LOGIN_REQUIRED`)禁止降级到 recruit;先修 recommend 页面就绪/登录态。
19
19
 
20
20
  - **确认不可代填(强制)**
21
21
  - 禁止 agent 自行“设置合理参数”并代替用户确认。
22
22
  - 禁止在用户未明确回复前,把任意 `*_confirmed` 字段设为 `true`。
23
- - 禁止在用户未明确回复前,自行填充 `page_scope/school_tag/degree/gender/recent_not_view/criteria/target_count/post_action/max_greet_count/job/follow_up.chat.*`。
23
+ - 禁止在用户未明确回复前,自行填充 `page_scope/school_tag/degree/gender/recent_not_view/criteria/target_count/post_action/max_greet_count/job`。
24
24
  - 若工具返回 `pending_questions`,必须逐项向用户提问并等待用户回复;不得跳过提问直接执行。
25
25
 
26
26
  - **岗位确认时机**
@@ -59,27 +59,16 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
59
59
  必须确认:
60
60
 
61
61
  - `job`(来自 `job_options`,必须全量展示)
62
- - `final_review`(岗位 + 全参数总确认;若有 `follow_up.chat`,必须把 chat summary 一并展示)
63
-
64
- ## Follow-up Chat
65
-
66
- 当用户要求“推荐页跑完后自动开始聊天页任务”时:
67
-
68
- - 仍然只走 `start_recommend_pipeline_run`
69
- - chat 配置放入顶层 `follow_up.chat`
70
- - `follow_up.chat` 必填:
71
- - `criteria`
72
- - `start_from`: `unread|all`
73
- - `target_count`
74
- - `follow_up.chat` 可选:
75
- - `greeting_text`(兼容 `greetingText`,自定义首条打招呼消息)
76
- - `profile`(默认 `default`)
77
- - `dry_run/no_state/safe_pacing/batch_rest_enabled`
78
- - `greeting_text` 未传时,boss-chat 会自动按默认优先级回退:本次显式值 > profile 历史值 > 内置默认招呼语
79
- - `job` / `port` 继承 recommend run 上下文;不要单独向用户再要一份
80
- - LLM 配置固定复用 recommend 的 `screening-config.json`;不要单独向用户再要 `baseUrl/apiKey/model`
81
- - 缺少 `follow_up.chat` 必填项时,按 `pending_questions` 补缺口;不要额外发起一轮独立的 chat 确认流程
82
- - 不允许提前调用 `start_boss_chat_run`;必须等 recommend run 完成并由 `follow_up.chat` 自动衔接
62
+ - `final_review`(岗位 + 全参数总确认)
63
+
64
+ ## Chat Handoff
65
+
66
+ 当用户要求“推荐页跑完后继续聊天页任务”时:
67
+
68
+ - 本次 recommend run 只提交 recommend 参数,不要写 `follow_up.chat`。
69
+ - recommend 完成并由用户确认要继续后,切换到 `boss-chat` skill。
70
+ - `boss-chat` 会重新调用 `prepare_boss_chat_run` 获取聊天页岗位列表,并显式确认 `job/start_from/target_count/criteria`。
71
+ - 不得在 recommend run 尚未完成时并行启动 `start_boss_chat_run`。
83
72
 
84
73
  ## Closed vs Open Questions
85
74
 
@@ -104,7 +93,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
104
93
  - 关键输入:
105
94
  - `confirmation`:`page_confirmed/page_value/filters_confirmed/school_tag_confirmed.../job_confirmed/job_value/final_confirmed`
106
95
  - `overrides`:`page_scope/school_tag/degree/gender/recent_not_view/criteria/job/target_count/post_action/max_greet_count`
107
- - `follow_up.chat`:仅在“recommend 完成后自动进入 boss-chat”场景传入
96
+ - 不要传 `follow_up.chat`;该路径属于 legacy-only 行为
108
97
 
109
98
  最小策略:
110
99
 
@@ -116,16 +105,13 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
116
105
  ## Async Run Policy
117
106
 
118
107
  - 用户未明确要求“持续跟进”时,不自动 `sleep + get_recommend_pipeline_run`。
119
- - 若 run 已进入 `chat_followup`,默认也不得自动轮询子 chat 状态;除非用户明确指定轮询频率/间隔。
120
108
  - 用户要求查进度时,再用 `get_recommend_pipeline_run`。
121
109
  - **长任务轮询节奏(强制)**:
122
- - 推荐+聊天联动任务可能运行数小时,禁止高频轮询。
110
+ - 推荐任务可能运行数小时,禁止高频轮询。
123
111
  - 默认最小轮询间隔为 **30 分钟**(除非用户明确要求更频繁)。
124
112
  - 若刚启动 run(拿到 `ACCEPTED + run_id`),不得立即进入连续轮询。
125
113
  - `pause/resume/cancel` 必须复用同一 `run_id`,不要重复 `start`。
126
- - **完成后衔接(强制)**:
127
- - 若本次 run 已带 `follow_up.chat`:chat 由同一 run 自动衔接,禁止再单独重复启动 chat run。
128
- - 若用户手动触发 `get_recommend_pipeline_run` 且发现 recommend 已完成、而当前会话目标是“继续聊天沟通”且尚未启动 chat:应立即启动 chat(无需等待下一轮 30 分钟轮询)。
114
+ - **完成后衔接(强制)**:若用户手动触发 `get_recommend_pipeline_run` 且发现 recommend 已完成、而当前会话目标是“继续聊天沟通”且尚未启动 chat:切换到 `boss-chat` 并重新走 chat-only 参数确认。
129
115
 
130
116
  ## Preflight and Recovery
131
117
 
@@ -150,7 +136,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
150
136
 
151
137
  MCP 不可用时:
152
138
 
153
- `npx -y @reconcrap/boss-recommend-mcp@latest run --instruction "..." [--confirmation-json '{...}'] [--overrides-json '{...}'] [--follow-up-json '{...}']`
139
+ `npx -y @reconcrap/boss-recommend-mcp@latest run --instruction "..." [--confirmation-json '{...}'] [--overrides-json '{...}']`
154
140
 
155
141
  禁止错误回退:
156
142
 
@@ -0,0 +1,17 @@
1
+ # boss-recruit-pipeline
2
+
3
+ Bundled search/recruit-page automation skill shipped with `boss-recommend-mcp` 2.x.
4
+
5
+ Package: `@reconcrap/boss-recommend-mcp` (npm)
6
+ Source: `https://github.com/reconcrap-cpu/boss-recommend-mcp`
7
+
8
+ This skill intentionally replaces legacy `boss-recruit-mcp` skill installs. It routes Boss search/recruit tasks to the unified CDP-only MCP tools:
9
+
10
+ - `run_recruit_pipeline`
11
+ - `start_recruit_pipeline_run`
12
+ - `get_recruit_pipeline_run`
13
+ - `pause_recruit_pipeline_run`
14
+ - `resume_recruit_pipeline_run`
15
+ - `cancel_recruit_pipeline_run`
16
+
17
+ Do not call the old `@reconcrap/boss-recruit-mcp` package from this skill.
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: "boss-recruit-pipeline"
3
+ description: "Use when users want Boss search/recruit-page screening via the unified boss-recommend-mcp package. Replaces the legacy boss-recruit-mcp skill."
4
+ ---
5
+
6
+ # Boss Recruit Pipeline Skill
7
+
8
+ ## Goal
9
+
10
+ 当用户要在 Boss 搜索页 / 招聘搜索页筛人时,必须走 `@reconcrap/boss-recommend-mcp` 2.x 内置的 recruit/search MCP 工具,不要安装或调用旧的 `@reconcrap/boss-recruit-mcp`。
11
+
12
+ ## Tool Routing
13
+
14
+ - 同步启动:`run_recruit_pipeline`
15
+ - 异步启动:`start_recruit_pipeline_run`
16
+ - 查询进度:`get_recruit_pipeline_run`
17
+ - 暂停:`pause_recruit_pipeline_run`
18
+ - 继续:`resume_recruit_pipeline_run`
19
+ - 取消:`cancel_recruit_pipeline_run`
20
+
21
+ ## Hard Rules
22
+
23
+ - 只在用户明确说搜索页、search、recruit、招聘搜索、`/web/chat/search` 时使用本 skill。
24
+ - 如果用户说推荐页、recommend、`/web/chat/recommend`,必须交给 `boss-recommend-pipeline`。
25
+ - 如果用户说聊天页、未读、全部聊天、求简历,必须交给 `boss-chat`。
26
+ - 禁止调用旧包:`@reconcrap/boss-recruit-mcp`、`boss-recruit-mcp`、旧本地 recruit repo、旧 vendor 脚本。
27
+ - 浏览器自动化必须走 CDP-only 2.x MCP 工具;不得要求用户启用 legacy page-JS 或 `Runtime.evaluate` 路径。
28
+ - 若用户未提供岗位,必须先询问岗位。搜索页岗位选择在关键词输入框旁边;不要猜测默认岗位。
29
+ - 若用户提供城市、学历、学校、关键词、过滤已看、人选目标数、筛选条件、post action、max greet 等参数,必须逐项传入或确认。
30
+ - `post_action=greet` 时必须确认 `max_greet_count`;不要默认等于 `target_count`。
31
+ - 搜索页和推荐页一样支持多选筛选条件;不要把多选降级成单选。
32
+
33
+ ## Required Inputs
34
+
35
+ - `job`
36
+ - `keyword` 或用户明确的搜索意图
37
+ - `criteria`
38
+ - `target_count`
39
+
40
+ 常用可选项:
41
+
42
+ - `city`
43
+ - `degree`
44
+ - `school_tag`
45
+ - `recent_not_view`
46
+ - `post_action`
47
+ - `max_greet_count`
48
+ - `port`
49
+
50
+ ## Response Style
51
+
52
+ - 用结构化中文确认参数。
53
+ - 缺参时只补缺口,不要改写用户的筛选条件。
54
+ - 拿到 `ACCEPTED + run_id` 后默认停止本轮,不主动高频轮询。
55
+ - 查询、暂停、恢复、取消时必须复用同一个 `run_id`。
package/src/cli.js CHANGED
@@ -35,7 +35,9 @@ const currentFilePath = fileURLToPath(import.meta.url);
35
35
  const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
36
36
  const packageJsonPath = path.join(packageRoot, "package.json");
37
37
  const skillName = "boss-recommend-pipeline";
38
- const bundledSkillNames = [skillName, "boss-chat"];
38
+ const recruitSkillName = "boss-recruit-pipeline";
39
+ const chatSkillName = "boss-chat";
40
+ const bundledSkillNames = [skillName, recruitSkillName, chatSkillName];
39
41
  const exampleConfigPath = path.join(packageRoot, "config", "screening-config.example.json");
40
42
  const bossUrl = "https://www.zhipin.com/web/chat/recommend";
41
43
  const bossLoginUrl = "https://www.zhipin.com/web/user/?ka=bticket";
@@ -724,7 +726,7 @@ function buildMcpLaunchConfig(options = {}) {
724
726
  ? args
725
727
  : command === "boss-recommend-mcp"
726
728
  ? ["start"]
727
- : buildDefaultMcpArgs();
729
+ : buildDefaultMcpArgs(options);
728
730
  const launchConfig = { command, args: launchArgs };
729
731
  if (env && typeof env === "object" && !Array.isArray(env) && Object.keys(env).length > 0) {
730
732
  launchConfig.env = env;
@@ -793,24 +795,56 @@ function parseAgentTargets(rawValue) {
793
795
  return unique;
794
796
  }
795
797
 
798
+ function getExternalAppSupportBaseDirs() {
799
+ const home = os.homedir();
800
+ if (process.platform === "win32") {
801
+ return dedupePaths([
802
+ process.env.APPDATA || "",
803
+ path.join(home, "AppData", "Roaming")
804
+ ]);
805
+ }
806
+ if (process.platform === "darwin") {
807
+ return dedupePaths([
808
+ path.join(home, "Library", "Application Support")
809
+ ]);
810
+ }
811
+ return dedupePaths([
812
+ process.env.XDG_CONFIG_HOME || "",
813
+ path.join(home, ".config")
814
+ ]);
815
+ }
816
+
817
+ function buildAppUserPaths({ dirNames = [], tail = [] } = {}) {
818
+ const paths = [];
819
+ for (const baseDir of getExternalAppSupportBaseDirs()) {
820
+ const discovered = discoverAppDataDirsByPattern(baseDir, /^trae(?:[\s\-_]?cn)?$/i);
821
+ const names = dedupeLower([...dirNames, ...discovered]);
822
+ for (const dirName of names) {
823
+ paths.push(path.join(baseDir, dirName, "User", ...tail));
824
+ }
825
+ }
826
+ return dedupePaths(paths);
827
+ }
828
+
796
829
  function getKnownExternalMcpConfigPathsByAgent() {
797
830
  const home = os.homedir();
798
- const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
799
- const traeDirNames = dedupeLower([
831
+ const appBases = getExternalAppSupportBaseDirs();
832
+ const traeDirNames = [
800
833
  "Trae",
801
834
  "Trae CN",
802
835
  "TraeCN",
803
836
  "trae-cn",
804
- "trae_cn",
805
- ...discoverAppDataDirsByPattern(appData, /^trae(?:[\s\-_]?cn)?$/i)
806
- ]);
807
- const traeConfigPaths = traeDirNames.map((dir) => path.join(appData, dir, "User", "mcp.json"));
837
+ "trae_cn"
838
+ ];
839
+ const traeConfigPaths = buildAppUserPaths({ dirNames: traeDirNames, tail: ["mcp.json"] });
840
+ const cursorConfigPaths = appBases.map((baseDir) => path.join(baseDir, "Cursor", "User", "mcp.json"));
841
+ const openClawConfigPaths = appBases.map((baseDir) => path.join(baseDir, "OpenClaw", "User", "mcp.json"));
808
842
  return {
809
- cursor: [path.join(appData, "Cursor", "User", "mcp.json"), path.join(home, ".cursor", "mcp.json")],
843
+ cursor: [...cursorConfigPaths, path.join(home, ".cursor", "mcp.json")],
810
844
  trae: [...traeConfigPaths, path.join(home, ".trae", "mcp.json"), path.join(home, ".trae-cn", "mcp.json")],
811
845
  "trae-cn": [...traeConfigPaths, path.join(home, ".trae-cn", "mcp.json"), path.join(home, ".trae", "mcp.json")],
812
846
  claude: [path.join(home, ".claude", "mcp.json")],
813
- openclaw: [path.join(home, ".openclaw", "mcp.json")]
847
+ openclaw: [path.join(home, ".openclaw", "mcp.json"), ...openClawConfigPaths]
814
848
  };
815
849
  }
816
850
 
@@ -837,21 +871,40 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
837
871
  ? current.mcpServers
838
872
  : {};
839
873
  const existingEntry = existingServers[serverName];
874
+ const retainedServers = {};
875
+ const migratedLegacyServers = [];
876
+ for (const [name, config] of Object.entries(existingServers)) {
877
+ if (name === serverName) continue;
878
+ if (isBossMcpServerEntry(name, config)) {
879
+ migratedLegacyServers.push(name);
880
+ continue;
881
+ }
882
+ retainedServers[name] = config;
883
+ }
840
884
  const merged = {
841
885
  ...current,
842
886
  mcpServers: {
843
- ...existingServers,
887
+ ...retainedServers,
844
888
  [serverName]: launchConfig
845
889
  }
846
890
  };
847
891
 
848
892
  ensureDir(path.dirname(filePath));
893
+ const before = pathExists(filePath) ? fs.readFileSync(filePath, "utf8") : "";
894
+ const next = JSON.stringify(merged, null, 2);
895
+ let backupFile = null;
896
+ if (before && before.trim() !== next.trim()) {
897
+ backupFile = `${filePath}.boss-mcp-migration-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`;
898
+ fs.writeFileSync(backupFile, before, "utf8");
899
+ }
849
900
  fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
850
- const updated = JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
901
+ const updated = before.trim() !== next.trim() || JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
851
902
  return {
852
903
  file: filePath,
853
904
  server: serverName,
854
- updated
905
+ updated,
906
+ migrated_legacy_servers: migratedLegacyServers,
907
+ backup_file: backupFile
855
908
  };
856
909
  }
857
910
 
@@ -867,7 +920,9 @@ function installExternalMcpConfigs(options = {}) {
867
920
  file: target,
868
921
  server: merged.server,
869
922
  created: !existed,
870
- updated: merged.updated
923
+ updated: merged.updated,
924
+ migrated_legacy_servers: merged.migrated_legacy_servers,
925
+ backup_file: merged.backup_file
871
926
  });
872
927
  } catch (error) {
873
928
  skipped.push({
@@ -881,25 +936,30 @@ function installExternalMcpConfigs(options = {}) {
881
936
 
882
937
  function getKnownExternalSkillBaseDirsByAgent() {
883
938
  const home = os.homedir();
884
- const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
885
- const traeDirNames = dedupeLower([
939
+ const appBases = getExternalAppSupportBaseDirs();
940
+ const traeDirNames = [
886
941
  "Trae",
887
942
  "Trae CN",
888
943
  "TraeCN",
889
944
  "trae-cn",
890
- "trae_cn",
891
- ...discoverAppDataDirsByPattern(appData, /^trae(?:[\s\-_]?cn)?$/i)
892
- ]);
893
- const traeSkillDirs = traeDirNames.map((dir) => path.join(appData, dir, "User", "skills"));
945
+ "trae_cn"
946
+ ];
947
+ const traeSkillDirs = buildAppUserPaths({ dirNames: traeDirNames, tail: ["skills"] });
948
+ const cursorSkillDirs = appBases.map((baseDir) => path.join(baseDir, "Cursor", "User", "skills"));
949
+ const openClawSkillDirs = appBases.map((baseDir) => path.join(baseDir, "OpenClaw", "User", "skills"));
894
950
  return {
895
- cursor: [path.join(home, ".cursor", "skills"), path.join(appData, "Cursor", "User", "skills")],
951
+ cursor: [path.join(home, ".cursor", "skills"), ...cursorSkillDirs],
896
952
  trae: [path.join(home, ".trae", "skills"), path.join(home, ".trae-cn", "skills"), ...traeSkillDirs],
897
953
  "trae-cn": [path.join(home, ".trae-cn", "skills"), path.join(home, ".trae", "skills"), ...traeSkillDirs],
898
954
  claude: [path.join(home, ".claude", "skills")],
899
- openclaw: [path.join(home, ".openclaw", "skills"), path.join(appData, "OpenClaw", "User", "skills")]
955
+ openclaw: [path.join(home, ".openclaw", "skills"), ...openClawSkillDirs]
900
956
  };
901
957
  }
902
958
 
959
+ function serializeMcpLaunchConfig(launchConfig) {
960
+ return JSON.stringify(launchConfig || {}).toLowerCase().replace(/\\/g, "/");
961
+ }
962
+
903
963
  function isRecommendMcpLaunchConfig(launchConfig) {
904
964
  if (!launchConfig || typeof launchConfig !== "object") return false;
905
965
  const command = String(launchConfig.command || "").toLowerCase();
@@ -913,14 +973,33 @@ function isRecommendMcpLaunchConfig(launchConfig) {
913
973
  );
914
974
  }
915
975
 
976
+ function isBossMcpServerEntry(name, launchConfig) {
977
+ const lowerName = String(name || "").toLowerCase();
978
+ const serialized = serializeMcpLaunchConfig(launchConfig);
979
+ return (
980
+ /boss[-_\s]?(recommend|recruit|chat)/i.test(lowerName)
981
+ || serialized.includes(recommendMcpPackageName.toLowerCase())
982
+ || serialized.includes("@reconcrap/boss-recruit-mcp")
983
+ || serialized.includes("@reconcrap/boss-chat")
984
+ || serialized.includes("boss-recommend-mcp")
985
+ || serialized.includes("boss-recruit-mcp")
986
+ || serialized.includes("boss-chat")
987
+ || serialized.includes("boss recommend pipeline")
988
+ || serialized.includes("boss recruit pipeline")
989
+ );
990
+ }
991
+
916
992
  function inspectMcpServerEntries(filePath) {
917
993
  if (!pathExists(filePath)) {
918
994
  return {
919
995
  exists: false,
920
996
  has_boss_recommend: false,
921
997
  has_boss_recruit: false,
998
+ has_boss_chat: false,
922
999
  recommend_server_names: [],
923
- recruit_server_names: []
1000
+ recruit_server_names: [],
1001
+ chat_server_names: [],
1002
+ boss_server_names: []
924
1003
  };
925
1004
  }
926
1005
  const parsed = readJsonObjectFileSafe(filePath);
@@ -929,8 +1008,13 @@ function inspectMcpServerEntries(filePath) {
929
1008
  : {};
930
1009
  const recommendNames = [];
931
1010
  const recruitNames = [];
1011
+ const chatNames = [];
1012
+ const bossNames = [];
932
1013
  for (const [name, config] of Object.entries(servers)) {
933
1014
  const lowerName = String(name || "").toLowerCase();
1015
+ if (isBossMcpServerEntry(name, config)) {
1016
+ bossNames.push(name);
1017
+ }
934
1018
  if (isRecommendMcpLaunchConfig(config) || lowerName.includes("boss-recommend")) {
935
1019
  recommendNames.push(name);
936
1020
  }
@@ -942,13 +1026,23 @@ function inspectMcpServerEntries(filePath) {
942
1026
  ) {
943
1027
  recruitNames.push(name);
944
1028
  }
1029
+ if (
1030
+ lowerName.includes("boss-chat")
1031
+ || serialized.includes("@reconcrap/boss-chat")
1032
+ || serialized.includes("boss-chat")
1033
+ ) {
1034
+ chatNames.push(name);
1035
+ }
945
1036
  }
946
1037
  return {
947
1038
  exists: true,
948
1039
  has_boss_recommend: recommendNames.length > 0,
949
1040
  has_boss_recruit: recruitNames.length > 0,
1041
+ has_boss_chat: chatNames.length > 0,
950
1042
  recommend_server_names: recommendNames,
951
- recruit_server_names: recruitNames
1043
+ recruit_server_names: recruitNames,
1044
+ chat_server_names: chatNames,
1045
+ boss_server_names: bossNames
952
1046
  };
953
1047
  }
954
1048
 
@@ -972,9 +1066,16 @@ function mirrorSkillToExternalDirs(options = {}) {
972
1066
  for (const bundledSkillName of bundledSkillNames) {
973
1067
  try {
974
1068
  const targetDir = path.join(baseDir, bundledSkillName);
1069
+ const legacyBeforeCopy = isLegacyBossSkillDir(targetDir);
975
1070
  ensureDir(path.dirname(targetDir));
976
1071
  fs.cpSync(getSkillSourceDir(bundledSkillName), targetDir, { recursive: true, force: true });
977
- mirrored.push({ base_dir: baseDir, target_dir: targetDir, skill: bundledSkillName });
1072
+ fs.writeFileSync(path.join(targetDir, ".installed-version"), `${packageVersion}\n`, "utf8");
1073
+ mirrored.push({
1074
+ base_dir: baseDir,
1075
+ target_dir: targetDir,
1076
+ skill: bundledSkillName,
1077
+ replaced_legacy: legacyBeforeCopy
1078
+ });
978
1079
  } catch (error) {
979
1080
  skipped.push({ base_dir: baseDir, skill: bundledSkillName, reason: error.message });
980
1081
  }
@@ -983,6 +1084,23 @@ function mirrorSkillToExternalDirs(options = {}) {
983
1084
  return { baseDirs, mirrored, skipped };
984
1085
  }
985
1086
 
1087
+ function isLegacyBossSkillDir(targetDir) {
1088
+ const skillFile = path.join(targetDir, "SKILL.md");
1089
+ if (!pathExists(skillFile)) return false;
1090
+ try {
1091
+ const content = fs.readFileSync(skillFile, "utf8").toLowerCase();
1092
+ return (
1093
+ content.includes("@reconcrap/boss-recruit-mcp")
1094
+ || content.includes("@reconcrap/boss-chat")
1095
+ || content.includes("boss-screen-cli")
1096
+ || content.includes(`runtime.${"evaluate"}`)
1097
+ || content.includes("page js")
1098
+ );
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+ }
1103
+
986
1104
  function syncSkillAssets(options = {}) {
987
1105
  const force = options.force === true;
988
1106
  const results = [];
@@ -2180,8 +2298,8 @@ function printHelp() {
2180
2298
  console.log(" boss-recommend-mcp run Disabled until the one-shot CLI has a CDP-only async replacement");
2181
2299
  console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
2182
2300
  console.log(" boss-recommend-mcp chat <subcommand> Run CDP-only boss-chat health/prepare/status commands");
2183
- console.log(" boss-recommend-mcp install Install skill/MCP templates and auto-init screening-config.json (supports --agent trae-cn/cursor/...)");
2184
- console.log(" boss-recommend-mcp install-skill Install bundled Codex skills");
2301
+ console.log(" boss-recommend-mcp install Install/migrate skills and MCP configs; replaces legacy Boss MCP routes (supports --agent trae-cn/openclaw/...)");
2302
+ console.log(" boss-recommend-mcp install-skill Install bundled Codex skills (recommend/recruit/chat)");
2185
2303
  console.log(" boss-recommend-mcp init-config Create screening-config.json if missing (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
2186
2304
  console.log(" boss-recommend-mcp config set Write baseUrl/apiKey/model (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
2187
2305
  console.log(" boss-recommend-mcp set-port Persist preferred Chrome debug port to screening-config.json");
@@ -2257,7 +2375,14 @@ async function installAll(options = {}) {
2257
2375
  console.log(`Auto-configured external MCP files: ${externalMcpResult.applied.length}`);
2258
2376
  for (const item of externalMcpResult.applied) {
2259
2377
  const action = item.created ? "created" : item.updated ? "updated" : "unchanged";
2260
- console.log(`- ${item.file} (${action})`);
2378
+ const migrated = Array.isArray(item.migrated_legacy_servers) && item.migrated_legacy_servers.length > 0
2379
+ ? `; migrated legacy servers: ${item.migrated_legacy_servers.join(", ")}`
2380
+ : "";
2381
+ const backup = item.backup_file ? `; backup: ${item.backup_file}` : "";
2382
+ console.log(`- ${item.file} (${action}${migrated}${backup})`);
2383
+ }
2384
+ for (const item of externalMcpResult.skipped) {
2385
+ console.warn(`External MCP warning: ${item.file} -> ${item.reason}`);
2261
2386
  }
2262
2387
  } else {
2263
2388
  console.log("No external MCP config target detected. Set BOSS_RECOMMEND_MCP_CONFIG_TARGETS to auto-configure custom agents.");
@@ -2265,7 +2390,10 @@ async function installAll(options = {}) {
2265
2390
  if (externalSkillResult.baseDirs.length > 0) {
2266
2391
  console.log(`Mirrored skill to external dirs: ${externalSkillResult.mirrored.length}`);
2267
2392
  for (const item of externalSkillResult.mirrored) {
2268
- console.log(`- ${item.target_dir}`);
2393
+ console.log(`- ${item.target_dir}${item.replaced_legacy ? " (replaced legacy skill)" : ""}`);
2394
+ }
2395
+ for (const item of externalSkillResult.skipped) {
2396
+ console.warn(`External skill warning: ${item.base_dir} / ${item.skill} -> ${item.reason}`);
2269
2397
  }
2270
2398
  } else {
2271
2399
  console.log("No external skill dir detected. Set BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS to mirror skill for non-Codex agents.");
@@ -2635,8 +2763,10 @@ export const __testables = {
2635
2763
  getBossChatCliRunTarget,
2636
2764
  getDefaultMcpPackageSpecifier,
2637
2765
  getRunFollowUp,
2766
+ inspectMcpServerEntries,
2638
2767
  installSkill,
2639
2768
  isInstalledPackageRoot,
2769
+ mergeMcpServerConfigFile,
2640
2770
  resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
2641
2771
  runBossChatCliCommand,
2642
2772
  runPipelineOnce
@@ -64,4 +64,3 @@ export async function readFirstRecruitCardCandidate(client, frameNodeId, options
64
64
  candidate
65
65
  };
66
66
  }
67
-
@@ -65,4 +65,3 @@ export async function queryFirstAcrossRoots(client, roots, selectors) {
65
65
  }
66
66
  return null;
67
67
  }
68
-