@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 +33 -0
- package/package.json +2 -1
- package/skills/boss-chat/SKILL.md +5 -4
- package/skills/boss-recommend-pipeline/SKILL.md +17 -31
- package/skills/boss-recruit-pipeline/README.md +17 -0
- package/skills/boss-recruit-pipeline/SKILL.md +55 -0
- package/src/cli.js +159 -29
- package/src/domains/recruit/cards.js +0 -1
- package/src/domains/recruit/roots.js +0 -1
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.
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
82
|
-
-
|
|
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`,并按“两阶段确认 -> 页面就绪 -> 岗位确认 -> 最终确认 ->
|
|
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
|
-
- 语义是“推荐页找人 +
|
|
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
|
|
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`(岗位 +
|
|
63
|
-
|
|
64
|
-
##
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
- `
|
|
71
|
-
|
|
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
|
|
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 '{...}']
|
|
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
|
|
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
|
|
799
|
-
const traeDirNames =
|
|
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
|
-
|
|
806
|
-
]);
|
|
807
|
-
const
|
|
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: [
|
|
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
|
-
...
|
|
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
|
|
885
|
-
const traeDirNames =
|
|
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
|
-
|
|
892
|
-
]);
|
|
893
|
-
const
|
|
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"),
|
|
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"),
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|