@reconcrap/boss-recommend-mcp 2.1.22 → 2.1.24

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 (66) hide show
  1. package/README.md +5 -0
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/package.json +14 -8
  4. package/scripts/install-macos.sh +280 -280
  5. package/scripts/postinstall.cjs +44 -44
  6. package/skills/boss-chat/README.md +43 -42
  7. package/skills/boss-chat/SKILL.md +107 -106
  8. package/skills/boss-recommend-pipeline/README.md +13 -13
  9. package/skills/boss-recommend-pipeline/SKILL.md +47 -47
  10. package/skills/boss-recruit-pipeline/README.md +19 -19
  11. package/skills/boss-recruit-pipeline/SKILL.md +89 -89
  12. package/src/chat-mcp.js +301 -127
  13. package/src/core/boss-cards/index.js +199 -199
  14. package/src/core/browser/index.js +291 -114
  15. package/src/core/capture/index.js +2930 -1201
  16. package/src/core/cv-acquisition/index.js +512 -238
  17. package/src/core/cv-capture-target/index.js +513 -299
  18. package/src/core/greet-quota/index.js +71 -71
  19. package/src/core/infinite-list/index.js +11 -2
  20. package/src/core/reporting/legacy-csv.js +12 -12
  21. package/src/core/run/detached-launcher.js +305 -0
  22. package/src/core/run/index.js +112 -42
  23. package/src/core/run/timing.js +33 -33
  24. package/src/core/run/windows-detached-worker.ps1 +106 -0
  25. package/src/core/screening/index.js +2135 -2135
  26. package/src/core/self-heal/index.js +989 -973
  27. package/src/core/self-heal/viewport.js +1505 -564
  28. package/src/detached-worker.js +99 -99
  29. package/src/domains/chat/action-journal.js +443 -0
  30. package/src/domains/chat/cards.js +137 -137
  31. package/src/domains/chat/constants.js +9 -9
  32. package/src/domains/chat/detail.js +1684 -401
  33. package/src/domains/chat/index.js +8 -7
  34. package/src/domains/chat/jobs.js +620 -620
  35. package/src/domains/chat/page-guard.js +157 -122
  36. package/src/domains/chat/roots.js +56 -56
  37. package/src/domains/chat/run-service.js +1801 -760
  38. package/src/domains/common/account-rights-panel.js +314 -314
  39. package/src/domains/common/recovery-settle.js +159 -159
  40. package/src/domains/recommend/actions.js +515 -472
  41. package/src/domains/recommend/cards.js +243 -243
  42. package/src/domains/recommend/colleague-contact.js +333 -333
  43. package/src/domains/recommend/constants.js +92 -92
  44. package/src/domains/recommend/detail.js +12 -3
  45. package/src/domains/recommend/filters.js +611 -611
  46. package/src/domains/recommend/index.js +9 -9
  47. package/src/domains/recommend/jobs.js +542 -542
  48. package/src/domains/recommend/location.js +736 -736
  49. package/src/domains/recommend/refresh.js +410 -329
  50. package/src/domains/recommend/roots.js +80 -80
  51. package/src/domains/recommend/run-service.js +1783 -592
  52. package/src/domains/recommend/scopes.js +246 -246
  53. package/src/domains/recruit/actions.js +277 -277
  54. package/src/domains/recruit/cards.js +74 -74
  55. package/src/domains/recruit/constants.js +236 -236
  56. package/src/domains/recruit/detail.js +588 -588
  57. package/src/domains/recruit/index.js +9 -9
  58. package/src/domains/recruit/instruction-parser.js +866 -866
  59. package/src/domains/recruit/refresh.js +45 -45
  60. package/src/domains/recruit/roots.js +68 -68
  61. package/src/domains/recruit/run-service.js +1817 -1620
  62. package/src/domains/recruit/search.js +3229 -3229
  63. package/src/index.js +124 -5
  64. package/src/parser.js +1296 -1296
  65. package/src/recommend-mcp.js +515 -80
  66. package/src/recommend-scheduler.js +66 -0
package/README.md CHANGED
@@ -177,6 +177,8 @@ boss-recommend-mcp schedule-status --schedule-id <schedule_id>
177
177
  - 不会对每位候选人重复确认
178
178
  - 推荐页详情处理完成后,会强制关闭详情页并确认已关闭
179
179
  - 简历提取优先使用 Network 响应;没有可解析 Network CV 时,回退到完整滚动截图序列再交给多模态模型判断
180
+ - recommend / search / chat 共用同一条截图安全路径:浏览器只截取可见视口(`captureBeyondViewport:false`、`fromSurface:true`、不传 `clip`),再由现有 Sharp 依赖在本地裁剪 CV 区域;滚动序列至少保留 20% 重叠,只有在连续两次滚动且图像与可用 DOM 锚点均无进展后才判定到底。达到页数上限但没有终止证据时会 fail closed,不会把可能截断的简历交给模型。
181
+ - `Page.captureScreenshot` 不会在 CDP 断线后自动重放;超时或连接关闭会放弃原 session、重新获取 document / iframe / CV target,并且每位候选人最多做一次工作流级恢复。视口健康度始终相对最初稳定基线比较宽度和高度,恢复后需要连续两次稳定读数。
180
182
  - recommend / search / 带 `criteria` 的 chat 正式运行默认全部使用 `screening-config.json` 配置的 LLM 筛选;chat 的 `criteria` 留空时进入收集简历模式,不需要 LLM 配置。deterministic/local scorer 只保留给明确测试场景,必须显式传 `debug_test_mode=true` 且 `screening_mode=deterministic` 或 `use_llm=false`。
181
183
  - `detail_limit=0`、`no_filter`、`filter_enabled=false`、后置动作 dry-run、chat 求简历 dry-run 等调试路径不会在正式 live run 默认启用;需要测试时必须显式传 `debug_test_mode=true`。
182
184
  - 提供显式运维自愈工具:只在手动调用 `run_recommend_self_heal` 时运行,不会接入正常 run / doctor / preflight 自动链路
@@ -464,6 +466,8 @@ node src/cli.js chat prepare-run --slow-live --port 9222
464
466
  说明:
465
467
 
466
468
  - `start_from` / `target_count` 为必填;`criteria` 可选,留空时 chat run 进入收集简历模式:不触发 LLM 筛选,针对没有在线/附件简历的人选发送求简历消息并点击求简历按钮
469
+ - 收集简历模式会为每位已识别的人选随机设定 `10-15` 秒的最低处理时长;已有在线/附件简历、已发送过简历请求或需要新发请求都适用。候选人处理本身已达到目标时不再额外补时,否则只等待剩余差额。
470
+ - 该最低处理时长独立于 `human_behavior.restLevel`:补时结束后仍按用户选择的 `low/medium/high` 执行原有休息预算;显式 `delay_ms` 也继续作为后续的额外延时。
467
471
  - `greeting_text` 可选(兼容 `greetingText`)
468
472
  - `profile` 可选,默认 `default`
469
473
  - `job` 与 `port` 继承 recommend run 已选岗位和调试端口
@@ -501,6 +505,7 @@ chat-only 交互建议:
501
505
  - 然后基于 `job_options` 让用户选择 `job`,并补齐 `start_from`、`target_count` 后调用 `start_boss_chat_run` 启动任务;若要筛选再求简历,提供 `criteria`,若只想收集缺失简历则留空。
502
506
  - `greeting_text` 可选;未传时使用 `screening-config.json.greetingMessage`,若未配置则使用默认招呼语(`Hi同学,能麻烦发下简历吗?`)。
503
507
  - `target_count` 支持正整数、`all`、`-1`;若用户给出 `全部候选人` / `所有候选人`,会自动按不限(扫到底)处理。
508
+ - `criteria` 留空的收集简历模式固定保证每位人选从识别到提交结果至少停留随机 `10-15` 秒;该产品级下限无需新增参数,且不会替代所选休息强度。
504
509
 
505
510
  Trae-CN / 长对话防循环建议:
506
511
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
2
- import { runCli } from "../src/cli.js";
3
-
4
- await runCli(process.argv);
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../src/cli.js";
3
+
4
+ await runCli(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.1.22",
3
+ "version": "2.1.24",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -30,6 +30,7 @@
30
30
  "test:core-infinite-list": "node src/test-core-infinite-list.js",
31
31
  "test:core-reporting": "node src/test-core-reporting.js",
32
32
  "test:core-run": "node src/test-core-run.js",
33
+ "test:detached-launcher": "node src/test-detached-launcher.js",
33
34
  "test:core-screening": "node src/test-core-screening.js",
34
35
  "test:core-self-heal": "node src/test-core-self-heal.js",
35
36
  "test:installer-migration": "node src/test-installer-migration.js",
@@ -37,13 +38,16 @@
37
38
  "test:recommend-domain": "node src/test-recommend-domain.js",
38
39
  "test:recommend-run-service": "node src/test-recommend-run-service.js",
39
40
  "test:recommend-mcp": "node src/test-recommend-mcp.js",
41
+ "test:live-recommend-mcp-smoke": "node src/test-live-recommend-mcp-smoke.js",
40
42
  "test:recruit-domain": "node src/test-recruit-domain.js",
41
43
  "test:recruit-mcp": "node src/test-recruit-mcp.js",
44
+ "test:chat-action-journal": "node src/test-chat-action-journal.js",
42
45
  "test:chat-domain": "node src/test-chat-domain.js",
43
46
  "test:chat-run-service": "node src/test-chat-run-service.js",
44
47
  "test:chat-mcp": "node src/test-chat-mcp.js",
45
48
  "test:async": "node src/test-index-async.js",
46
49
  "test:runtime-scan": "node src/test-runtime-scan.js",
50
+ "test:live-capture-acceptance": "node src/test-live-capture-acceptance.js",
47
51
  "scan:runtime": "node scripts/scan-forbidden-runtime.js",
48
52
  "scan:runtime:json": "node scripts/scan-forbidden-runtime.js --json",
49
53
  "scan:runtime:strict": "node scripts/scan-forbidden-runtime.js --fail-on-findings",
@@ -51,6 +55,7 @@
51
55
  "scan:runtime:package:strict": "node scripts/scan-forbidden-runtime.js --package-surface --fail-on-legacy",
52
56
  "scan:legacy-boundary": "node scripts/scan-legacy-boundary.js",
53
57
  "scan:package-boundary": "node scripts/scan-package-boundary.js",
58
+ "scan:screenshot-safety": "node scripts/scan-screenshot-safety.js",
54
59
  "gate:phase9-static": "node scripts/phase9-static-gate.js",
55
60
  "gate:phase10-complete": "node scripts/phase10-completion-gate.js",
56
61
  "live:cdp-smoke": "node scripts/live-cdp-smoke.js",
@@ -64,10 +69,11 @@
64
69
  "live:self-heal": "node scripts/live-self-heal-smoke.js",
65
70
  "live:recommend-actions": "node scripts/live-recommend-actions-smoke.js",
66
71
  "live:recommend-phase10-full": "node scripts/live-recommend-phase10-full.js",
67
- "live:recommend-domain": "node scripts/live-recommend-domain-smoke.js",
68
- "live:recommend-run-service": "node scripts/live-recommend-run-service-smoke.js",
69
- "live:recommend-mcp": "node scripts/live-recommend-mcp-smoke.js",
70
- "live:search-phase10-full": "node scripts/live-search-phase10-full.js",
72
+ "live:recommend-domain": "node scripts/live-recommend-domain-smoke.js",
73
+ "live:recommend-run-service": "node scripts/live-recommend-run-service-smoke.js",
74
+ "live:recommend-mcp": "node scripts/live-recommend-mcp-smoke.js",
75
+ "live:recommend-viewport-collapse": "node scripts/live-recommend-viewport-collapse-acceptance.js",
76
+ "live:search-phase10-full": "node scripts/live-search-phase10-full.js",
71
77
  "live:recruit-domain": "node scripts/live-recruit-domain-smoke.js",
72
78
  "live:recruit-run-service": "node scripts/live-recruit-run-service-smoke.js",
73
79
  "live:recruit-mcp": "node scripts/live-recruit-mcp-smoke.js",
@@ -82,9 +88,9 @@
82
88
  "files": [
83
89
  "bin",
84
90
  "config/screening-config.example.json",
85
- "skills",
86
- "scripts/install-macos.sh",
87
- "scripts/postinstall.cjs",
91
+ "skills",
92
+ "scripts/install-macos.sh",
93
+ "scripts/postinstall.cjs",
88
94
  "src/core",
89
95
  "src/domains",
90
96
  "src/chat-mcp.js",
@@ -1,280 +1,280 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- PACKAGE_NAME="@reconcrap/boss-recommend-mcp"
5
- DEFAULT_NODE_VERSION="${BOSS_RECOMMEND_NODE_VERSION:-22}"
6
- DEFAULT_NVM_VERSION="${BOSS_RECOMMEND_NVM_VERSION:-v0.40.4}"
7
-
8
- NODE_VERSION="$DEFAULT_NODE_VERSION"
9
- NVM_VERSION="$DEFAULT_NVM_VERSION"
10
- AGENT="${BOSS_RECOMMEND_AGENT:-}"
11
- DRY_RUN=0
12
- SKIP_DOCTOR=0
13
- SKIP_LLM_CONFIG=0
14
- BASE_URL="${BOSS_RECOMMEND_BASE_URL:-https://api.openai.com/v1}"
15
- API_KEY="${BOSS_RECOMMEND_API_KEY:-}"
16
- MODEL="${BOSS_RECOMMEND_MODEL:-gpt-4.1-mini}"
17
- THINKING_LEVEL="${BOSS_RECOMMEND_THINKING_LEVEL:-low}"
18
- GREETING_MESSAGE="${BOSS_RECOMMEND_GREETING_MESSAGE:-Hi同学,能麻烦发下简历吗?}"
19
-
20
- usage() {
21
- cat <<'EOF'
22
- Install boss-recommend-mcp on macOS with nvm-backed npm globals.
23
-
24
- Usage:
25
- install-macos.sh [--agent <agent|all>] [--node-version <version>] [--nvm-version <tag>] [--dry-run] [--skip-doctor] [--skip-llm-config]
26
-
27
- Examples:
28
- curl -fsSL https://raw.githubusercontent.com/reconcrap-cpu/boss-recommend-mcp/main/scripts/install-macos.sh | bash
29
- curl -fsSL https://raw.githubusercontent.com/reconcrap-cpu/boss-recommend-mcp/main/scripts/install-macos.sh | bash -s -- --agent openclaw
30
-
31
- Supported agents: cursor, trae, trae-cn, claude, openclaw, qclaw, all
32
-
33
- LLM config can be supplied non-interactively with:
34
- BOSS_RECOMMEND_BASE_URL, BOSS_RECOMMEND_API_KEY, BOSS_RECOMMEND_MODEL,
35
- BOSS_RECOMMEND_THINKING_LEVEL, BOSS_RECOMMEND_GREETING_MESSAGE
36
- EOF
37
- }
38
-
39
- log() {
40
- printf '%s\n' "[boss-recommend-mcp] $*"
41
- }
42
-
43
- die() {
44
- printf '%s\n' "[boss-recommend-mcp] ERROR: $*" >&2
45
- exit 1
46
- }
47
-
48
- run() {
49
- log "+ $*"
50
- if [ "$DRY_RUN" -eq 0 ]; then
51
- "$@"
52
- fi
53
- }
54
-
55
- while [ "$#" -gt 0 ]; do
56
- case "$1" in
57
- --agent)
58
- [ "$#" -ge 2 ] || die "--agent requires a value"
59
- AGENT="$2"
60
- shift 2
61
- ;;
62
- --node-version)
63
- [ "$#" -ge 2 ] || die "--node-version requires a value"
64
- NODE_VERSION="$2"
65
- shift 2
66
- ;;
67
- --nvm-version)
68
- [ "$#" -ge 2 ] || die "--nvm-version requires a value"
69
- NVM_VERSION="$2"
70
- shift 2
71
- ;;
72
- --dry-run)
73
- DRY_RUN=1
74
- shift
75
- ;;
76
- --skip-doctor)
77
- SKIP_DOCTOR=1
78
- shift
79
- ;;
80
- --skip-llm-config)
81
- SKIP_LLM_CONFIG=1
82
- shift
83
- ;;
84
- --base-url)
85
- [ "$#" -ge 2 ] || die "--base-url requires a value"
86
- BASE_URL="$2"
87
- shift 2
88
- ;;
89
- --api-key)
90
- [ "$#" -ge 2 ] || die "--api-key requires a value"
91
- API_KEY="$2"
92
- shift 2
93
- ;;
94
- --model)
95
- [ "$#" -ge 2 ] || die "--model requires a value"
96
- MODEL="$2"
97
- shift 2
98
- ;;
99
- --thinking-level)
100
- [ "$#" -ge 2 ] || die "--thinking-level requires a value"
101
- THINKING_LEVEL="$2"
102
- shift 2
103
- ;;
104
- --greeting-message)
105
- [ "$#" -ge 2 ] || die "--greeting-message requires a value"
106
- GREETING_MESSAGE="$2"
107
- shift 2
108
- ;;
109
- -h|--help)
110
- usage
111
- exit 0
112
- ;;
113
- *)
114
- die "Unknown option: $1"
115
- ;;
116
- esac
117
- done
118
-
119
- prompt_text() {
120
- local label="$1"
121
- local default_value="$2"
122
- local secret="${3:-0}"
123
- local value
124
- if [ ! -r /dev/tty ]; then
125
- return 1
126
- fi
127
- if [ "$secret" -eq 1 ]; then
128
- printf '%s' "${label}: " > /dev/tty
129
- IFS= read -r -s value < /dev/tty || return 1
130
- printf '\n' > /dev/tty
131
- elif [ -n "$default_value" ]; then
132
- printf '%s' "${label} [${default_value}]: " > /dev/tty
133
- IFS= read -r value < /dev/tty || return 1
134
- if [ -z "$value" ]; then
135
- value="$default_value"
136
- fi
137
- else
138
- printf '%s' "${label}: " > /dev/tty
139
- IFS= read -r value < /dev/tty || return 1
140
- fi
141
- printf '%s' "$value"
142
- }
143
-
144
- if [ "$(uname -s)" != "Darwin" ]; then
145
- if [ "$DRY_RUN" -eq 1 ]; then
146
- log "dry-run on non-macOS host; skipping platform enforcement"
147
- else
148
- die "This bootstrap script is for macOS. Use npm install -g ${PACKAGE_NAME}@latest on other platforms."
149
- fi
150
- fi
151
-
152
- export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
153
-
154
- load_nvm() {
155
- if [ -s "$NVM_DIR/nvm.sh" ]; then
156
- # shellcheck disable=SC1090
157
- . "$NVM_DIR/nvm.sh"
158
- elif [ -s "$HOME/.nvm/nvm.sh" ]; then
159
- export NVM_DIR="$HOME/.nvm"
160
- # shellcheck disable=SC1091
161
- . "$HOME/.nvm/nvm.sh"
162
- fi
163
- }
164
-
165
- install_nvm() {
166
- if command -v nvm >/dev/null 2>&1; then
167
- return
168
- fi
169
- log "nvm was not found; installing ${NVM_VERSION} into ${NVM_DIR}"
170
- if [ "$DRY_RUN" -eq 1 ]; then
171
- log "+ curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash"
172
- return
173
- fi
174
- curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash
175
- load_nvm
176
- command -v nvm >/dev/null 2>&1 || die "nvm installed but is not available in this shell. Open a new terminal and rerun this script."
177
- }
178
-
179
- load_nvm
180
- install_nvm
181
-
182
- run nvm install "$NODE_VERSION"
183
- run nvm alias default "$NODE_VERSION"
184
- run nvm use "$NODE_VERSION"
185
-
186
- if [ "$DRY_RUN" -eq 0 ]; then
187
- NODE_PATH="$(command -v node || true)"
188
- NPM_PATH="$(command -v npm || true)"
189
- NPM_PREFIX="$(npm config get prefix || true)"
190
- log "node: ${NODE_PATH}"
191
- log "npm: ${NPM_PATH}"
192
- log "npm prefix: ${NPM_PREFIX}"
193
-
194
- case "$NPM_PREFIX" in
195
- /usr/local|/usr/local/*)
196
- die "npm is still using /usr/local. Reload nvm and rerun this script; do not use sudo npm install -g."
197
- ;;
198
- esac
199
- case "$NPM_PREFIX" in
200
- "$NVM_DIR"/*)
201
- ;;
202
- *)
203
- die "npm prefix is not under ${NVM_DIR}. Current prefix: ${NPM_PREFIX}"
204
- ;;
205
- esac
206
- else
207
- log "+ verify: npm config get prefix must be under ${NVM_DIR}, not /usr/local"
208
- fi
209
-
210
- run npm -g i "${PACKAGE_NAME}@latest"
211
- run boss-recommend-mcp where
212
-
213
- INSTALL_ARGS=(install --mcp-launch global-wrapper)
214
- DOCTOR_ARGS=(doctor)
215
- if [ -n "$AGENT" ]; then
216
- INSTALL_ARGS+=(--agent "$AGENT")
217
- DOCTOR_ARGS+=(--agent "$AGENT")
218
- fi
219
-
220
- run boss-recommend-mcp "${INSTALL_ARGS[@]}"
221
-
222
- if [ "$SKIP_LLM_CONFIG" -eq 0 ]; then
223
- log "LLM screening config is required before running candidate screening."
224
- if [ "$DRY_RUN" -eq 0 ]; then
225
- if [ -r /dev/tty ]; then
226
- BASE_URL="$(prompt_text "LLM base URL" "$BASE_URL")"
227
- MODEL="$(prompt_text "LLM model" "$MODEL")"
228
- THINKING_LEVEL="$(prompt_text "LLM thinking level" "$THINKING_LEVEL")"
229
- GREETING_MESSAGE="$(prompt_text "Greeting message for candidates" "$GREETING_MESSAGE")"
230
- if [ -z "$API_KEY" ]; then
231
- API_KEY="$(prompt_text "LLM API key (input hidden)" "" 1)"
232
- fi
233
- fi
234
- if [ -z "$BASE_URL" ] || [ -z "$API_KEY" ] || [ -z "$MODEL" ]; then
235
- cat >&2 <<EOF
236
- [boss-recommend-mcp] LLM config is still required.
237
- [boss-recommend-mcp] Ask the user for:
238
- 1. LLM base URL
239
- 2. LLM API key
240
- 3. LLM model name
241
- [boss-recommend-mcp] Then run:
242
- boss-recommend-mcp config set --base-url <baseUrl> --api-key <apiKey> --model <model> --thinking-level ${THINKING_LEVEL}
243
- EOF
244
- exit 2
245
- fi
246
- fi
247
-
248
- CONFIG_ARGS=(config set --base-url "$BASE_URL" --api-key "$API_KEY" --model "$MODEL")
249
- if [ -n "$THINKING_LEVEL" ]; then
250
- CONFIG_ARGS+=(--thinking-level "$THINKING_LEVEL")
251
- fi
252
- if [ -n "$GREETING_MESSAGE" ]; then
253
- CONFIG_ARGS+=(--greeting-message "$GREETING_MESSAGE")
254
- fi
255
-
256
- if [ "$DRY_RUN" -eq 1 ]; then
257
- log "+ boss-recommend-mcp config set --base-url ${BASE_URL:-<baseUrl>} --api-key <hidden> --model ${MODEL:-<model>} --thinking-level ${THINKING_LEVEL:-<level>}"
258
- else
259
- log "+ boss-recommend-mcp config set --base-url ${BASE_URL} --api-key <hidden> --model ${MODEL}"
260
- boss-recommend-mcp "${CONFIG_ARGS[@]}"
261
- fi
262
- else
263
- log "Skipping LLM config because --skip-llm-config was provided."
264
- fi
265
-
266
- if [ "$SKIP_DOCTOR" -eq 0 ]; then
267
- log "+ boss-recommend-mcp ${DOCTOR_ARGS[*]}"
268
- if [ "$DRY_RUN" -eq 0 ]; then
269
- if ! boss-recommend-mcp "${DOCTOR_ARGS[@]}"; then
270
- log "doctor reported follow-up work. The npm package install still completed."
271
- fi
272
- fi
273
- fi
274
-
275
- cat <<EOF
276
- [boss-recommend-mcp] install complete.
277
- [boss-recommend-mcp] Future upgrades:
278
- npm -g i ${PACKAGE_NAME}@latest
279
- boss-recommend-mcp where
280
- EOF
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PACKAGE_NAME="@reconcrap/boss-recommend-mcp"
5
+ DEFAULT_NODE_VERSION="${BOSS_RECOMMEND_NODE_VERSION:-22}"
6
+ DEFAULT_NVM_VERSION="${BOSS_RECOMMEND_NVM_VERSION:-v0.40.4}"
7
+
8
+ NODE_VERSION="$DEFAULT_NODE_VERSION"
9
+ NVM_VERSION="$DEFAULT_NVM_VERSION"
10
+ AGENT="${BOSS_RECOMMEND_AGENT:-}"
11
+ DRY_RUN=0
12
+ SKIP_DOCTOR=0
13
+ SKIP_LLM_CONFIG=0
14
+ BASE_URL="${BOSS_RECOMMEND_BASE_URL:-https://api.openai.com/v1}"
15
+ API_KEY="${BOSS_RECOMMEND_API_KEY:-}"
16
+ MODEL="${BOSS_RECOMMEND_MODEL:-gpt-4.1-mini}"
17
+ THINKING_LEVEL="${BOSS_RECOMMEND_THINKING_LEVEL:-low}"
18
+ GREETING_MESSAGE="${BOSS_RECOMMEND_GREETING_MESSAGE:-Hi同学,能麻烦发下简历吗?}"
19
+
20
+ usage() {
21
+ cat <<'EOF'
22
+ Install boss-recommend-mcp on macOS with nvm-backed npm globals.
23
+
24
+ Usage:
25
+ install-macos.sh [--agent <agent|all>] [--node-version <version>] [--nvm-version <tag>] [--dry-run] [--skip-doctor] [--skip-llm-config]
26
+
27
+ Examples:
28
+ curl -fsSL https://raw.githubusercontent.com/reconcrap-cpu/boss-recommend-mcp/main/scripts/install-macos.sh | bash
29
+ curl -fsSL https://raw.githubusercontent.com/reconcrap-cpu/boss-recommend-mcp/main/scripts/install-macos.sh | bash -s -- --agent openclaw
30
+
31
+ Supported agents: cursor, trae, trae-cn, claude, openclaw, qclaw, all
32
+
33
+ LLM config can be supplied non-interactively with:
34
+ BOSS_RECOMMEND_BASE_URL, BOSS_RECOMMEND_API_KEY, BOSS_RECOMMEND_MODEL,
35
+ BOSS_RECOMMEND_THINKING_LEVEL, BOSS_RECOMMEND_GREETING_MESSAGE
36
+ EOF
37
+ }
38
+
39
+ log() {
40
+ printf '%s\n' "[boss-recommend-mcp] $*"
41
+ }
42
+
43
+ die() {
44
+ printf '%s\n' "[boss-recommend-mcp] ERROR: $*" >&2
45
+ exit 1
46
+ }
47
+
48
+ run() {
49
+ log "+ $*"
50
+ if [ "$DRY_RUN" -eq 0 ]; then
51
+ "$@"
52
+ fi
53
+ }
54
+
55
+ while [ "$#" -gt 0 ]; do
56
+ case "$1" in
57
+ --agent)
58
+ [ "$#" -ge 2 ] || die "--agent requires a value"
59
+ AGENT="$2"
60
+ shift 2
61
+ ;;
62
+ --node-version)
63
+ [ "$#" -ge 2 ] || die "--node-version requires a value"
64
+ NODE_VERSION="$2"
65
+ shift 2
66
+ ;;
67
+ --nvm-version)
68
+ [ "$#" -ge 2 ] || die "--nvm-version requires a value"
69
+ NVM_VERSION="$2"
70
+ shift 2
71
+ ;;
72
+ --dry-run)
73
+ DRY_RUN=1
74
+ shift
75
+ ;;
76
+ --skip-doctor)
77
+ SKIP_DOCTOR=1
78
+ shift
79
+ ;;
80
+ --skip-llm-config)
81
+ SKIP_LLM_CONFIG=1
82
+ shift
83
+ ;;
84
+ --base-url)
85
+ [ "$#" -ge 2 ] || die "--base-url requires a value"
86
+ BASE_URL="$2"
87
+ shift 2
88
+ ;;
89
+ --api-key)
90
+ [ "$#" -ge 2 ] || die "--api-key requires a value"
91
+ API_KEY="$2"
92
+ shift 2
93
+ ;;
94
+ --model)
95
+ [ "$#" -ge 2 ] || die "--model requires a value"
96
+ MODEL="$2"
97
+ shift 2
98
+ ;;
99
+ --thinking-level)
100
+ [ "$#" -ge 2 ] || die "--thinking-level requires a value"
101
+ THINKING_LEVEL="$2"
102
+ shift 2
103
+ ;;
104
+ --greeting-message)
105
+ [ "$#" -ge 2 ] || die "--greeting-message requires a value"
106
+ GREETING_MESSAGE="$2"
107
+ shift 2
108
+ ;;
109
+ -h|--help)
110
+ usage
111
+ exit 0
112
+ ;;
113
+ *)
114
+ die "Unknown option: $1"
115
+ ;;
116
+ esac
117
+ done
118
+
119
+ prompt_text() {
120
+ local label="$1"
121
+ local default_value="$2"
122
+ local secret="${3:-0}"
123
+ local value
124
+ if [ ! -r /dev/tty ]; then
125
+ return 1
126
+ fi
127
+ if [ "$secret" -eq 1 ]; then
128
+ printf '%s' "${label}: " > /dev/tty
129
+ IFS= read -r -s value < /dev/tty || return 1
130
+ printf '\n' > /dev/tty
131
+ elif [ -n "$default_value" ]; then
132
+ printf '%s' "${label} [${default_value}]: " > /dev/tty
133
+ IFS= read -r value < /dev/tty || return 1
134
+ if [ -z "$value" ]; then
135
+ value="$default_value"
136
+ fi
137
+ else
138
+ printf '%s' "${label}: " > /dev/tty
139
+ IFS= read -r value < /dev/tty || return 1
140
+ fi
141
+ printf '%s' "$value"
142
+ }
143
+
144
+ if [ "$(uname -s)" != "Darwin" ]; then
145
+ if [ "$DRY_RUN" -eq 1 ]; then
146
+ log "dry-run on non-macOS host; skipping platform enforcement"
147
+ else
148
+ die "This bootstrap script is for macOS. Use npm install -g ${PACKAGE_NAME}@latest on other platforms."
149
+ fi
150
+ fi
151
+
152
+ export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
153
+
154
+ load_nvm() {
155
+ if [ -s "$NVM_DIR/nvm.sh" ]; then
156
+ # shellcheck disable=SC1090
157
+ . "$NVM_DIR/nvm.sh"
158
+ elif [ -s "$HOME/.nvm/nvm.sh" ]; then
159
+ export NVM_DIR="$HOME/.nvm"
160
+ # shellcheck disable=SC1091
161
+ . "$HOME/.nvm/nvm.sh"
162
+ fi
163
+ }
164
+
165
+ install_nvm() {
166
+ if command -v nvm >/dev/null 2>&1; then
167
+ return
168
+ fi
169
+ log "nvm was not found; installing ${NVM_VERSION} into ${NVM_DIR}"
170
+ if [ "$DRY_RUN" -eq 1 ]; then
171
+ log "+ curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash"
172
+ return
173
+ fi
174
+ curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash
175
+ load_nvm
176
+ command -v nvm >/dev/null 2>&1 || die "nvm installed but is not available in this shell. Open a new terminal and rerun this script."
177
+ }
178
+
179
+ load_nvm
180
+ install_nvm
181
+
182
+ run nvm install "$NODE_VERSION"
183
+ run nvm alias default "$NODE_VERSION"
184
+ run nvm use "$NODE_VERSION"
185
+
186
+ if [ "$DRY_RUN" -eq 0 ]; then
187
+ NODE_PATH="$(command -v node || true)"
188
+ NPM_PATH="$(command -v npm || true)"
189
+ NPM_PREFIX="$(npm config get prefix || true)"
190
+ log "node: ${NODE_PATH}"
191
+ log "npm: ${NPM_PATH}"
192
+ log "npm prefix: ${NPM_PREFIX}"
193
+
194
+ case "$NPM_PREFIX" in
195
+ /usr/local|/usr/local/*)
196
+ die "npm is still using /usr/local. Reload nvm and rerun this script; do not use sudo npm install -g."
197
+ ;;
198
+ esac
199
+ case "$NPM_PREFIX" in
200
+ "$NVM_DIR"/*)
201
+ ;;
202
+ *)
203
+ die "npm prefix is not under ${NVM_DIR}. Current prefix: ${NPM_PREFIX}"
204
+ ;;
205
+ esac
206
+ else
207
+ log "+ verify: npm config get prefix must be under ${NVM_DIR}, not /usr/local"
208
+ fi
209
+
210
+ run npm -g i "${PACKAGE_NAME}@latest"
211
+ run boss-recommend-mcp where
212
+
213
+ INSTALL_ARGS=(install --mcp-launch global-wrapper)
214
+ DOCTOR_ARGS=(doctor)
215
+ if [ -n "$AGENT" ]; then
216
+ INSTALL_ARGS+=(--agent "$AGENT")
217
+ DOCTOR_ARGS+=(--agent "$AGENT")
218
+ fi
219
+
220
+ run boss-recommend-mcp "${INSTALL_ARGS[@]}"
221
+
222
+ if [ "$SKIP_LLM_CONFIG" -eq 0 ]; then
223
+ log "LLM screening config is required before running candidate screening."
224
+ if [ "$DRY_RUN" -eq 0 ]; then
225
+ if [ -r /dev/tty ]; then
226
+ BASE_URL="$(prompt_text "LLM base URL" "$BASE_URL")"
227
+ MODEL="$(prompt_text "LLM model" "$MODEL")"
228
+ THINKING_LEVEL="$(prompt_text "LLM thinking level" "$THINKING_LEVEL")"
229
+ GREETING_MESSAGE="$(prompt_text "Greeting message for candidates" "$GREETING_MESSAGE")"
230
+ if [ -z "$API_KEY" ]; then
231
+ API_KEY="$(prompt_text "LLM API key (input hidden)" "" 1)"
232
+ fi
233
+ fi
234
+ if [ -z "$BASE_URL" ] || [ -z "$API_KEY" ] || [ -z "$MODEL" ]; then
235
+ cat >&2 <<EOF
236
+ [boss-recommend-mcp] LLM config is still required.
237
+ [boss-recommend-mcp] Ask the user for:
238
+ 1. LLM base URL
239
+ 2. LLM API key
240
+ 3. LLM model name
241
+ [boss-recommend-mcp] Then run:
242
+ boss-recommend-mcp config set --base-url <baseUrl> --api-key <apiKey> --model <model> --thinking-level ${THINKING_LEVEL}
243
+ EOF
244
+ exit 2
245
+ fi
246
+ fi
247
+
248
+ CONFIG_ARGS=(config set --base-url "$BASE_URL" --api-key "$API_KEY" --model "$MODEL")
249
+ if [ -n "$THINKING_LEVEL" ]; then
250
+ CONFIG_ARGS+=(--thinking-level "$THINKING_LEVEL")
251
+ fi
252
+ if [ -n "$GREETING_MESSAGE" ]; then
253
+ CONFIG_ARGS+=(--greeting-message "$GREETING_MESSAGE")
254
+ fi
255
+
256
+ if [ "$DRY_RUN" -eq 1 ]; then
257
+ log "+ boss-recommend-mcp config set --base-url ${BASE_URL:-<baseUrl>} --api-key <hidden> --model ${MODEL:-<model>} --thinking-level ${THINKING_LEVEL:-<level>}"
258
+ else
259
+ log "+ boss-recommend-mcp config set --base-url ${BASE_URL} --api-key <hidden> --model ${MODEL}"
260
+ boss-recommend-mcp "${CONFIG_ARGS[@]}"
261
+ fi
262
+ else
263
+ log "Skipping LLM config because --skip-llm-config was provided."
264
+ fi
265
+
266
+ if [ "$SKIP_DOCTOR" -eq 0 ]; then
267
+ log "+ boss-recommend-mcp ${DOCTOR_ARGS[*]}"
268
+ if [ "$DRY_RUN" -eq 0 ]; then
269
+ if ! boss-recommend-mcp "${DOCTOR_ARGS[@]}"; then
270
+ log "doctor reported follow-up work. The npm package install still completed."
271
+ fi
272
+ fi
273
+ fi
274
+
275
+ cat <<EOF
276
+ [boss-recommend-mcp] install complete.
277
+ [boss-recommend-mcp] Future upgrades:
278
+ npm -g i ${PACKAGE_NAME}@latest
279
+ boss-recommend-mcp where
280
+ EOF