@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.23

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 (67) hide show
  1. package/README.md +5 -2
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/config/screening-config.example.json +33 -33
  4. package/package.json +2 -1
  5. package/scripts/install-macos.sh +280 -280
  6. package/scripts/postinstall.cjs +44 -44
  7. package/skills/boss-chat/README.md +42 -42
  8. package/skills/boss-chat/SKILL.md +106 -106
  9. package/skills/boss-recommend-pipeline/README.md +13 -13
  10. package/skills/boss-recommend-pipeline/SKILL.md +219 -214
  11. package/skills/boss-recruit-pipeline/README.md +19 -19
  12. package/skills/boss-recruit-pipeline/SKILL.md +89 -89
  13. package/src/chat-mcp.js +127 -127
  14. package/src/chat-runtime-config.js +775 -775
  15. package/src/cli.js +573 -573
  16. package/src/core/boss-cards/index.js +199 -199
  17. package/src/core/browser/index.js +2416 -2385
  18. package/src/core/capture/index.js +1201 -1201
  19. package/src/core/cv-acquisition/index.js +238 -238
  20. package/src/core/cv-capture-target/index.js +299 -299
  21. package/src/core/greet-quota/index.js +71 -71
  22. package/src/core/infinite-list/index.js +1315 -1306
  23. package/src/core/reporting/legacy-csv.js +334 -332
  24. package/src/core/run/index.js +44 -27
  25. package/src/core/run/timing.js +33 -33
  26. package/src/core/screening/index.js +2135 -2135
  27. package/src/core/self-heal/index.js +973 -973
  28. package/src/core/self-heal/viewport.js +564 -564
  29. package/src/detached-worker.js +99 -99
  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 +113 -113
  33. package/src/domains/chat/index.js +7 -7
  34. package/src/domains/chat/jobs.js +620 -620
  35. package/src/domains/chat/page-guard.js +122 -122
  36. package/src/domains/chat/roots.js +56 -56
  37. package/src/domains/chat/run-service.js +571 -571
  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 +472 -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 +228 -159
  44. package/src/domains/recommend/detail.js +655 -646
  45. package/src/domains/recommend/filters.js +748 -377
  46. package/src/domains/recommend/index.js +4 -3
  47. package/src/domains/recommend/jobs.js +542 -542
  48. package/src/domains/recommend/location.js +736 -0
  49. package/src/domains/recommend/refresh.js +575 -351
  50. package/src/domains/recommend/roots.js +80 -80
  51. package/src/domains/recommend/run-service.js +1616 -878
  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 +1620 -1620
  62. package/src/domains/recruit/search.js +3229 -3229
  63. package/src/index.js +121 -4
  64. package/src/parser.js +376 -8
  65. package/src/recommend-mcp.js +1174 -914
  66. package/src/recommend-scheduler.js +535 -469
  67. package/src/recruit-mcp.js +2121 -2121
package/src/cli.js CHANGED
@@ -23,19 +23,19 @@ import {
23
23
  prepareBossChatRunTool,
24
24
  resumeBossChatRunTool
25
25
  } from "./chat-mcp.js";
26
- import {
27
- cancelRecommendPipelineRunTool,
28
- getRecommendPipelineRunTool,
29
- listRecommendJobsTool,
30
- pauseRecommendPipelineRunTool,
31
- prepareRecommendPipelineRunTool,
32
- resumeRecommendPipelineRunTool,
33
- startRecommendPipelineRunTool
34
- } from "./recommend-mcp.js";
35
- import {
36
- getRecommendScheduledRunTool,
37
- scheduleRecommendPipelineRunTool
38
- } from "./recommend-scheduler.js";
26
+ import {
27
+ cancelRecommendPipelineRunTool,
28
+ getRecommendPipelineRunTool,
29
+ listRecommendJobsTool,
30
+ pauseRecommendPipelineRunTool,
31
+ prepareRecommendPipelineRunTool,
32
+ resumeRecommendPipelineRunTool,
33
+ startRecommendPipelineRunTool
34
+ } from "./recommend-mcp.js";
35
+ import {
36
+ getRecommendScheduledRunTool,
37
+ scheduleRecommendPipelineRunTool
38
+ } from "./recommend-scheduler.js";
39
39
  import {
40
40
  getBossScreenConfigResolution,
41
41
  resolveBossChatRuntimeLayout as resolveCdpBossChatRuntimeLayout,
@@ -57,17 +57,17 @@ const bossLoginUrl = "https://www.zhipin.com/web/user/?ka=bticket";
57
57
  const chromeOnboardingUrlPattern = /^chrome:\/\/(welcome|intro|newtab|signin|history-sync|settings\/syncSetup)/i;
58
58
  const bossLoginUrlPattern = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com)/i;
59
59
  const bossLoginTitlePattern = /登录|signin|扫码登录|BOSS直聘登录/i;
60
- const supportedMcpClients = ["generic", "cursor", "trae", "claudecode", "openclaw", "qclaw"];
61
- const defaultMcpServerName = "boss-recommend";
62
- const bossChatMcpServerName = "boss-chat";
63
- const bossRecruitMcpServerName = "boss-recruit";
64
- const defaultMcpCommand = "npx";
65
- const recommendMcpPackageName = "@reconcrap/boss-recommend-mcp";
66
- const recommendMcpBinaryName = "boss-recommend-mcp";
67
- const globalMcpWrapperFileName = "boss-recommend-mcp-mcp-server";
68
- const mcpToolsetEnv = "BOSS_RECOMMEND_MCP_TOOLSET";
69
- const supportedMcpLaunchModes = ["npx", "global-wrapper"];
70
- const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
60
+ const supportedMcpClients = ["generic", "cursor", "trae", "claudecode", "openclaw", "qclaw"];
61
+ const defaultMcpServerName = "boss-recommend";
62
+ const bossChatMcpServerName = "boss-chat";
63
+ const bossRecruitMcpServerName = "boss-recruit";
64
+ const defaultMcpCommand = "npx";
65
+ const recommendMcpPackageName = "@reconcrap/boss-recommend-mcp";
66
+ const recommendMcpBinaryName = "boss-recommend-mcp";
67
+ const globalMcpWrapperFileName = "boss-recommend-mcp-mcp-server";
68
+ const mcpToolsetEnv = "BOSS_RECOMMEND_MCP_TOOLSET";
69
+ const supportedMcpLaunchModes = ["npx", "global-wrapper"];
70
+ const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
71
71
  const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
72
72
  const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
73
73
  const installConfigDefaults = Object.freeze({
@@ -77,13 +77,13 @@ const installConfigDefaults = Object.freeze({
77
77
  llmMaxRetries: 3,
78
78
  llmImageLimit: 8,
79
79
  llmImageDetail: "low",
80
- humanRestEnabled: true,
81
- humanBehavior: {
82
- enabled: true,
83
- profile: "paced_with_rests",
84
- restLevel: "low"
85
- }
86
- });
80
+ humanRestEnabled: true,
81
+ humanBehavior: {
82
+ enabled: true,
83
+ profile: "paced_with_rests",
84
+ restLevel: "low"
85
+ }
86
+ });
87
87
  const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
88
88
  const bossChatCliUnsupportedStartCode = "CHAT_CLI_ASYNC_UNSUPPORTED_CDP_ONLY";
89
89
  const calibrateUnsupportedCode = "CALIBRATE_UNSUPPORTED_CDP_ONLY";
@@ -137,22 +137,22 @@ function getCodexHome() {
137
137
  : path.join(os.homedir(), ".codex");
138
138
  }
139
139
 
140
- function getStateHome() {
141
- return process.env.BOSS_RECOMMEND_HOME
142
- ? path.resolve(process.env.BOSS_RECOMMEND_HOME)
143
- : path.join(os.homedir(), ".boss-recommend-mcp");
144
- }
145
-
146
- function getGlobalMcpWrapperPath(options = {}) {
147
- if (typeof options["mcp-wrapper-path"] === "string" && options["mcp-wrapper-path"].trim()) {
148
- return path.resolve(options["mcp-wrapper-path"].trim());
149
- }
150
- return path.join(getStateHome(), "bin", globalMcpWrapperFileName);
151
- }
152
-
153
- function ensureDir(targetPath) {
154
- fs.mkdirSync(targetPath, { recursive: true });
155
- }
140
+ function getStateHome() {
141
+ return process.env.BOSS_RECOMMEND_HOME
142
+ ? path.resolve(process.env.BOSS_RECOMMEND_HOME)
143
+ : path.join(os.homedir(), ".boss-recommend-mcp");
144
+ }
145
+
146
+ function getGlobalMcpWrapperPath(options = {}) {
147
+ if (typeof options["mcp-wrapper-path"] === "string" && options["mcp-wrapper-path"].trim()) {
148
+ return path.resolve(options["mcp-wrapper-path"].trim());
149
+ }
150
+ return path.join(getStateHome(), "bin", globalMcpWrapperFileName);
151
+ }
152
+
153
+ function ensureDir(targetPath) {
154
+ fs.mkdirSync(targetPath, { recursive: true });
155
+ }
156
156
 
157
157
  function pathExists(targetPath) {
158
158
  try {
@@ -635,20 +635,20 @@ function parseBossChatTargetCountOption(raw) {
635
635
  return parsed ?? text;
636
636
  }
637
637
 
638
- function parseBooleanOption(raw, fallback = undefined) {
639
- if (raw === undefined || raw === null || raw === "") return fallback;
640
- if (raw === true) return true;
641
- const normalized = String(raw).trim().toLowerCase();
642
- if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
643
- if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
644
- return fallback;
645
- }
646
-
647
- function parseRestLevelOption(raw) {
648
- if (raw === undefined || raw === null || raw === "") return undefined;
649
- const normalized = String(raw).trim().toLowerCase();
650
- return ["low", "medium", "high"].includes(normalized) ? normalized : undefined;
651
- }
638
+ function parseBooleanOption(raw, fallback = undefined) {
639
+ if (raw === undefined || raw === null || raw === "") return fallback;
640
+ if (raw === true) return true;
641
+ const normalized = String(raw).trim().toLowerCase();
642
+ if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
643
+ if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
644
+ return fallback;
645
+ }
646
+
647
+ function parseRestLevelOption(raw) {
648
+ if (raw === undefined || raw === null || raw === "") return undefined;
649
+ const normalized = String(raw).trim().toLowerCase();
650
+ return ["low", "medium", "high"].includes(normalized) ? normalized : undefined;
651
+ }
652
652
 
653
653
  function normalizePageScope(value) {
654
654
  const normalized = String(value || "").trim().toLowerCase();
@@ -758,87 +758,87 @@ function parseMcpClientTargets(rawValue) {
758
758
  return unique;
759
759
  }
760
760
 
761
- function isPlainObject(value) {
762
- return value && typeof value === "object" && !Array.isArray(value);
763
- }
764
-
765
- function normalizeMcpLaunchMode(options = {}) {
766
- const raw = String(options["mcp-launch"] || options.mcpLaunch || "").trim().toLowerCase().replace(/_/g, "-");
767
- if (!raw) return "npx";
768
- if (raw === "default") return "npx";
769
- if (raw === "global" || raw === "wrapper") return "global-wrapper";
770
- if (supportedMcpLaunchModes.includes(raw)) return raw;
771
- throw new Error(`Unsupported --mcp-launch value: ${raw}. Supported: ${supportedMcpLaunchModes.join(", ")}`);
772
- }
773
-
774
- function buildGlobalMcpWrapperScript() {
775
- return `#!/usr/bin/env bash
776
- set -euo pipefail
777
-
778
- export NVM_DIR="\${NVM_DIR:-$HOME/.nvm}"
779
- if [ -s "$NVM_DIR/nvm.sh" ]; then
780
- . "$NVM_DIR/nvm.sh"
781
- elif [ -s "$HOME/.nvm/nvm.sh" ]; then
782
- export NVM_DIR="$HOME/.nvm"
783
- . "$NVM_DIR/nvm.sh"
784
- fi
785
-
786
- if ! command -v ${recommendMcpBinaryName} >/dev/null 2>&1; then
787
- echo "${recommendMcpBinaryName} not found on PATH. Install or reload nvm, then run: npm -g i ${recommendMcpPackageName}@latest" >&2
788
- exit 127
789
- fi
790
-
791
- exec ${recommendMcpBinaryName} start "$@"
792
- `;
793
- }
794
-
795
- function ensureGlobalMcpWrapper(options = {}) {
796
- const wrapperPath = getGlobalMcpWrapperPath(options);
797
- ensureDir(path.dirname(wrapperPath));
798
- fs.writeFileSync(wrapperPath, buildGlobalMcpWrapperScript(), "utf8");
799
- try {
800
- fs.chmodSync(wrapperPath, 0o755);
801
- } catch {
802
- // Some filesystems ignore POSIX executable bits; the path is still valid for POSIX hosts.
803
- }
804
- return wrapperPath;
805
- }
806
-
807
- function shouldDefaultRecommendDetachedMcpEnv(options = {}) {
808
- const client = normalizeMcpClientName(options.client);
809
- const agent = normalizeAgentName(options.agent);
761
+ function isPlainObject(value) {
762
+ return value && typeof value === "object" && !Array.isArray(value);
763
+ }
764
+
765
+ function normalizeMcpLaunchMode(options = {}) {
766
+ const raw = String(options["mcp-launch"] || options.mcpLaunch || "").trim().toLowerCase().replace(/_/g, "-");
767
+ if (!raw) return "npx";
768
+ if (raw === "default") return "npx";
769
+ if (raw === "global" || raw === "wrapper") return "global-wrapper";
770
+ if (supportedMcpLaunchModes.includes(raw)) return raw;
771
+ throw new Error(`Unsupported --mcp-launch value: ${raw}. Supported: ${supportedMcpLaunchModes.join(", ")}`);
772
+ }
773
+
774
+ function buildGlobalMcpWrapperScript() {
775
+ return `#!/usr/bin/env bash
776
+ set -euo pipefail
777
+
778
+ export NVM_DIR="\${NVM_DIR:-$HOME/.nvm}"
779
+ if [ -s "$NVM_DIR/nvm.sh" ]; then
780
+ . "$NVM_DIR/nvm.sh"
781
+ elif [ -s "$HOME/.nvm/nvm.sh" ]; then
782
+ export NVM_DIR="$HOME/.nvm"
783
+ . "$NVM_DIR/nvm.sh"
784
+ fi
785
+
786
+ if ! command -v ${recommendMcpBinaryName} >/dev/null 2>&1; then
787
+ echo "${recommendMcpBinaryName} not found on PATH. Install or reload nvm, then run: npm -g i ${recommendMcpPackageName}@latest" >&2
788
+ exit 127
789
+ fi
790
+
791
+ exec ${recommendMcpBinaryName} start "$@"
792
+ `;
793
+ }
794
+
795
+ function ensureGlobalMcpWrapper(options = {}) {
796
+ const wrapperPath = getGlobalMcpWrapperPath(options);
797
+ ensureDir(path.dirname(wrapperPath));
798
+ fs.writeFileSync(wrapperPath, buildGlobalMcpWrapperScript(), "utf8");
799
+ try {
800
+ fs.chmodSync(wrapperPath, 0o755);
801
+ } catch {
802
+ // Some filesystems ignore POSIX executable bits; the path is still valid for POSIX hosts.
803
+ }
804
+ return wrapperPath;
805
+ }
806
+
807
+ function shouldDefaultRecommendDetachedMcpEnv(options = {}) {
808
+ const client = normalizeMcpClientName(options.client);
809
+ const agent = normalizeAgentName(options.agent);
810
810
  return client === "openclaw"
811
811
  || client === "qclaw"
812
812
  || agent === "openclaw"
813
- || agent === "qclaw";
814
- }
815
-
816
- function normalizeMcpToolsetOption(value) {
817
- const raw = String(value || "").trim().toLowerCase().replace(/[\s_]+/g, "-");
818
- if (!raw || raw === "all") return "";
819
- if (raw === "boss-recommend" || raw === "recommend-page") return "recommend";
820
- if (raw === "boss-chat" || raw === "chat-only" || raw === "chat-page") return "chat";
821
- if (raw === "boss-recruit" || raw === "search" || raw === "search-page" || raw === "recruit-page") return "recruit";
822
- if (["recommend", "chat", "recruit"].includes(raw)) return raw;
823
- throw new Error(`Unsupported --toolset value: ${raw}. Supported: recommend, chat, recruit, all`);
824
- }
825
-
826
- function getMcpToolsetEnv(options = {}) {
827
- const toolset = normalizeMcpToolsetOption(options.toolset || options["toolset"]);
828
- return toolset ? { [mcpToolsetEnv]: toolset } : {};
829
- }
830
-
831
- function shouldUseSplitBossMcpServers(options = {}) {
832
- if (options["server-name"] || options.serverName || options.toolset || options["toolset"]) return false;
833
- if (options.split === true || options["split-tools"] === true || options.splitTools === true) return true;
834
- const client = normalizeMcpClientName(options.client);
835
- const agent = normalizeAgentName(options.agent);
836
- return client === "trae" || agent === "trae" || agent === "trae-cn";
837
- }
838
-
839
- function getDefaultMcpEnv(options = {}) {
840
- return shouldDefaultRecommendDetachedMcpEnv(options)
841
- ? { ...detachedRecommendMcpEnv }
813
+ || agent === "qclaw";
814
+ }
815
+
816
+ function normalizeMcpToolsetOption(value) {
817
+ const raw = String(value || "").trim().toLowerCase().replace(/[\s_]+/g, "-");
818
+ if (!raw || raw === "all") return "";
819
+ if (raw === "boss-recommend" || raw === "recommend-page") return "recommend";
820
+ if (raw === "boss-chat" || raw === "chat-only" || raw === "chat-page") return "chat";
821
+ if (raw === "boss-recruit" || raw === "search" || raw === "search-page" || raw === "recruit-page") return "recruit";
822
+ if (["recommend", "chat", "recruit"].includes(raw)) return raw;
823
+ throw new Error(`Unsupported --toolset value: ${raw}. Supported: recommend, chat, recruit, all`);
824
+ }
825
+
826
+ function getMcpToolsetEnv(options = {}) {
827
+ const toolset = normalizeMcpToolsetOption(options.toolset || options["toolset"]);
828
+ return toolset ? { [mcpToolsetEnv]: toolset } : {};
829
+ }
830
+
831
+ function shouldUseSplitBossMcpServers(options = {}) {
832
+ if (options["server-name"] || options.serverName || options.toolset || options["toolset"]) return false;
833
+ if (options.split === true || options["split-tools"] === true || options.splitTools === true) return true;
834
+ const client = normalizeMcpClientName(options.client);
835
+ const agent = normalizeAgentName(options.agent);
836
+ return client === "trae" || agent === "trae" || agent === "trae-cn";
837
+ }
838
+
839
+ function getDefaultMcpEnv(options = {}) {
840
+ return shouldDefaultRecommendDetachedMcpEnv(options)
841
+ ? { ...detachedRecommendMcpEnv }
842
842
  : {};
843
843
  }
844
844
 
@@ -849,29 +849,29 @@ function getAgentConfigOutputDir(options = {}) {
849
849
  return path.join(getStateHome(), "agent-mcp-configs");
850
850
  }
851
851
 
852
- function buildMcpLaunchConfig(options = {}) {
853
- const mcpLaunchMode = normalizeMcpLaunchMode(options);
854
- if (mcpLaunchMode === "global-wrapper") {
855
- const args = parseJsonOption(options["args-json"], "args-json");
856
- const env = parseJsonOption(options["env-json"], "env-json");
857
- const launchConfig = {
858
- command: ensureGlobalMcpWrapper(options),
859
- args: Array.isArray(args) ? args : []
860
- };
861
- const mergedEnv = {
862
- ...getDefaultMcpEnv(options),
863
- ...getMcpToolsetEnv(options),
864
- ...(isPlainObject(env) ? env : {})
865
- };
866
- if (Object.keys(mergedEnv).length > 0) {
867
- launchConfig.env = mergedEnv;
868
- }
869
- return launchConfig;
870
- }
871
-
872
- const command = typeof options.command === "string" && options.command.trim()
873
- ? options.command.trim()
874
- : defaultMcpCommand;
852
+ function buildMcpLaunchConfig(options = {}) {
853
+ const mcpLaunchMode = normalizeMcpLaunchMode(options);
854
+ if (mcpLaunchMode === "global-wrapper") {
855
+ const args = parseJsonOption(options["args-json"], "args-json");
856
+ const env = parseJsonOption(options["env-json"], "env-json");
857
+ const launchConfig = {
858
+ command: ensureGlobalMcpWrapper(options),
859
+ args: Array.isArray(args) ? args : []
860
+ };
861
+ const mergedEnv = {
862
+ ...getDefaultMcpEnv(options),
863
+ ...getMcpToolsetEnv(options),
864
+ ...(isPlainObject(env) ? env : {})
865
+ };
866
+ if (Object.keys(mergedEnv).length > 0) {
867
+ launchConfig.env = mergedEnv;
868
+ }
869
+ return launchConfig;
870
+ }
871
+
872
+ const command = typeof options.command === "string" && options.command.trim()
873
+ ? options.command.trim()
874
+ : defaultMcpCommand;
875
875
  const args = parseJsonOption(options["args-json"], "args-json");
876
876
  const env = parseJsonOption(options["env-json"], "env-json");
877
877
  const launchArgs = Array.isArray(args) && args.length > 0
@@ -880,36 +880,36 @@ function buildMcpLaunchConfig(options = {}) {
880
880
  ? ["start"]
881
881
  : buildDefaultMcpArgs(options);
882
882
  const launchConfig = { command, args: launchArgs };
883
- const mergedEnv = {
884
- ...getDefaultMcpEnv(options),
885
- ...getMcpToolsetEnv(options),
886
- ...(isPlainObject(env) ? env : {})
887
- };
883
+ const mergedEnv = {
884
+ ...getDefaultMcpEnv(options),
885
+ ...getMcpToolsetEnv(options),
886
+ ...(isPlainObject(env) ? env : {})
887
+ };
888
888
  if (Object.keys(mergedEnv).length > 0) {
889
889
  launchConfig.env = mergedEnv;
890
890
  }
891
891
  return launchConfig;
892
- }
893
-
894
- function buildBossMcpServerEntries(options = {}) {
895
- if (shouldUseSplitBossMcpServers(options)) {
896
- return {
897
- [defaultMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "recommend" }),
898
- [bossChatMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "chat" }),
899
- [bossRecruitMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "recruit" })
900
- };
901
- }
902
- const serverName = typeof options["server-name"] === "string" && options["server-name"].trim()
903
- ? options["server-name"].trim()
904
- : defaultMcpServerName;
905
- return {
906
- [serverName]: buildMcpLaunchConfig(options)
907
- };
908
- }
909
-
910
- function mergeExistingMcpEntryEnv(existingEntry, launchConfig) {
911
- if (!isPlainObject(existingEntry?.env) || !isPlainObject(launchConfig)) {
912
- return launchConfig;
892
+ }
893
+
894
+ function buildBossMcpServerEntries(options = {}) {
895
+ if (shouldUseSplitBossMcpServers(options)) {
896
+ return {
897
+ [defaultMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "recommend" }),
898
+ [bossChatMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "chat" }),
899
+ [bossRecruitMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "recruit" })
900
+ };
901
+ }
902
+ const serverName = typeof options["server-name"] === "string" && options["server-name"].trim()
903
+ ? options["server-name"].trim()
904
+ : defaultMcpServerName;
905
+ return {
906
+ [serverName]: buildMcpLaunchConfig(options)
907
+ };
908
+ }
909
+
910
+ function mergeExistingMcpEntryEnv(existingEntry, launchConfig) {
911
+ if (!isPlainObject(existingEntry?.env) || !isPlainObject(launchConfig)) {
912
+ return launchConfig;
913
913
  }
914
914
  return {
915
915
  ...launchConfig,
@@ -918,21 +918,21 @@ function mergeExistingMcpEntryEnv(existingEntry, launchConfig) {
918
918
  ...(isPlainObject(launchConfig.env) ? launchConfig.env : {})
919
919
  }
920
920
  };
921
- }
922
-
923
- function buildMcpConfigFileContent(options = {}) {
924
- const servers = buildBossMcpServerEntries(options);
925
- if (normalizeMcpClientName(options.client) === "qclaw") {
926
- return {
927
- mcp: {
928
- servers
929
- }
930
- };
931
- }
932
- return {
933
- mcpServers: servers
934
- };
935
- }
921
+ }
922
+
923
+ function buildMcpConfigFileContent(options = {}) {
924
+ const servers = buildBossMcpServerEntries(options);
925
+ if (normalizeMcpClientName(options.client) === "qclaw") {
926
+ return {
927
+ mcp: {
928
+ servers
929
+ }
930
+ };
931
+ }
932
+ return {
933
+ mcpServers: servers
934
+ };
935
+ }
936
936
 
937
937
  function writeMcpConfigFiles(options = {}) {
938
938
  const clients = parseMcpClientTargets(options.client);
@@ -1076,28 +1076,28 @@ function getMcpServersFromConfig(config = {}, useQClawShape = false) {
1076
1076
  return {};
1077
1077
  }
1078
1078
 
1079
- function mergeMcpServerConfigFile(filePath, options = {}) {
1080
- const current = readJsonObjectFileSafe(filePath);
1081
- const useQClawShape = isQClawMcpConfigTarget(filePath, options, current);
1082
- const nextConfig = buildMcpConfigFileContent({ ...options, client: useQClawShape ? "qclaw" : options.client });
1083
- const nextServers = useQClawShape ? nextConfig.mcp?.servers : nextConfig.mcpServers;
1084
- const serverNames = Object.keys(nextServers || {});
1085
- const nextBossServerNames = new Set(serverNames);
1086
- const existingServers = getMcpServersFromConfig(current, useQClawShape);
1087
- const mergedBossServers = {};
1088
- for (const serverName of serverNames) {
1089
- mergedBossServers[serverName] = mergeExistingMcpEntryEnv(
1090
- existingServers[serverName],
1091
- nextServers?.[serverName] || buildMcpLaunchConfig(options)
1092
- );
1093
- }
1094
- const retainedServers = {};
1095
- const migratedLegacyServers = [];
1096
- for (const [name, config] of Object.entries(existingServers)) {
1097
- if (nextBossServerNames.has(name)) continue;
1098
- if (isBossMcpServerEntry(name, config)) {
1099
- migratedLegacyServers.push(name);
1100
- continue;
1079
+ function mergeMcpServerConfigFile(filePath, options = {}) {
1080
+ const current = readJsonObjectFileSafe(filePath);
1081
+ const useQClawShape = isQClawMcpConfigTarget(filePath, options, current);
1082
+ const nextConfig = buildMcpConfigFileContent({ ...options, client: useQClawShape ? "qclaw" : options.client });
1083
+ const nextServers = useQClawShape ? nextConfig.mcp?.servers : nextConfig.mcpServers;
1084
+ const serverNames = Object.keys(nextServers || {});
1085
+ const nextBossServerNames = new Set(serverNames);
1086
+ const existingServers = getMcpServersFromConfig(current, useQClawShape);
1087
+ const mergedBossServers = {};
1088
+ for (const serverName of serverNames) {
1089
+ mergedBossServers[serverName] = mergeExistingMcpEntryEnv(
1090
+ existingServers[serverName],
1091
+ nextServers?.[serverName] || buildMcpLaunchConfig(options)
1092
+ );
1093
+ }
1094
+ const retainedServers = {};
1095
+ const migratedLegacyServers = [];
1096
+ for (const [name, config] of Object.entries(existingServers)) {
1097
+ if (nextBossServerNames.has(name)) continue;
1098
+ if (isBossMcpServerEntry(name, config)) {
1099
+ migratedLegacyServers.push(name);
1100
+ continue;
1101
1101
  }
1102
1102
  retainedServers[name] = config;
1103
1103
  }
@@ -1105,20 +1105,20 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
1105
1105
  ? {
1106
1106
  ...current,
1107
1107
  mcp: {
1108
- ...(current?.mcp && typeof current.mcp === "object" && !Array.isArray(current.mcp) ? current.mcp : {}),
1109
- servers: {
1110
- ...retainedServers,
1111
- ...mergedBossServers
1112
- }
1113
- }
1114
- }
1115
- : {
1116
- ...current,
1117
- mcpServers: {
1118
- ...retainedServers,
1119
- ...mergedBossServers
1120
- }
1121
- };
1108
+ ...(current?.mcp && typeof current.mcp === "object" && !Array.isArray(current.mcp) ? current.mcp : {}),
1109
+ servers: {
1110
+ ...retainedServers,
1111
+ ...mergedBossServers
1112
+ }
1113
+ }
1114
+ }
1115
+ : {
1116
+ ...current,
1117
+ mcpServers: {
1118
+ ...retainedServers,
1119
+ ...mergedBossServers
1120
+ }
1121
+ };
1122
1122
 
1123
1123
  ensureDir(path.dirname(filePath));
1124
1124
  const before = pathExists(filePath) ? fs.readFileSync(filePath, "utf8") : "";
@@ -1128,15 +1128,15 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
1128
1128
  backupFile = `${filePath}.boss-mcp-migration-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`;
1129
1129
  fs.writeFileSync(backupFile, before, "utf8");
1130
1130
  }
1131
- fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
1132
- const updated = before.trim() !== next.trim();
1133
- return {
1134
- file: filePath,
1135
- server: serverNames[0] || defaultMcpServerName,
1136
- servers: serverNames,
1137
- config_shape: useQClawShape ? "qclaw" : "mcpServers",
1138
- updated,
1139
- migrated_legacy_servers: migratedLegacyServers,
1131
+ fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
1132
+ const updated = before.trim() !== next.trim();
1133
+ return {
1134
+ file: filePath,
1135
+ server: serverNames[0] || defaultMcpServerName,
1136
+ servers: serverNames,
1137
+ config_shape: useQClawShape ? "qclaw" : "mcpServers",
1138
+ updated,
1139
+ migrated_legacy_servers: migratedLegacyServers,
1140
1140
  backup_file: backupFile
1141
1141
  };
1142
1142
  }
@@ -1149,13 +1149,13 @@ function installExternalMcpConfigs(options = {}) {
1149
1149
  try {
1150
1150
  const existed = pathExists(target);
1151
1151
  const merged = mergeMcpServerConfigFile(target, options);
1152
- applied.push({
1153
- file: target,
1154
- server: merged.server,
1155
- servers: merged.servers,
1156
- created: !existed,
1157
- updated: merged.updated,
1158
- migrated_legacy_servers: merged.migrated_legacy_servers,
1152
+ applied.push({
1153
+ file: target,
1154
+ server: merged.server,
1155
+ servers: merged.servers,
1156
+ created: !existed,
1157
+ updated: merged.updated,
1158
+ migrated_legacy_servers: merged.migrated_legacy_servers,
1159
1159
  backup_file: merged.backup_file
1160
1160
  });
1161
1161
  } catch (error) {
@@ -1398,33 +1398,33 @@ async function resolveCliConfigTarget(options = {}) {
1398
1398
  };
1399
1399
  }
1400
1400
 
1401
- function applyMissingInstallConfigDefaults(config = {}) {
1402
- const nextConfig = { ...config };
1403
- const patchedKeys = [];
1404
- for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
1401
+ function applyMissingInstallConfigDefaults(config = {}) {
1402
+ const nextConfig = { ...config };
1403
+ const patchedKeys = [];
1404
+ for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
1405
1405
  if (!Object.prototype.hasOwnProperty.call(nextConfig, key)) {
1406
1406
  nextConfig[key] = defaultValue;
1407
- patchedKeys.push(key);
1408
- }
1409
- }
1410
- if (
1411
- nextConfig.humanBehavior
1412
- && typeof nextConfig.humanBehavior === "object"
1413
- && !Array.isArray(nextConfig.humanBehavior)
1414
- && !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "restLevel")
1415
- && !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "rest_level")
1416
- ) {
1417
- nextConfig.humanBehavior = {
1418
- ...nextConfig.humanBehavior,
1419
- restLevel: "low"
1420
- };
1421
- patchedKeys.push("humanBehavior.restLevel");
1422
- }
1423
- return {
1424
- nextConfig,
1425
- patchedKeys
1426
- };
1427
- }
1407
+ patchedKeys.push(key);
1408
+ }
1409
+ }
1410
+ if (
1411
+ nextConfig.humanBehavior
1412
+ && typeof nextConfig.humanBehavior === "object"
1413
+ && !Array.isArray(nextConfig.humanBehavior)
1414
+ && !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "restLevel")
1415
+ && !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "rest_level")
1416
+ ) {
1417
+ nextConfig.humanBehavior = {
1418
+ ...nextConfig.humanBehavior,
1419
+ restLevel: "low"
1420
+ };
1421
+ patchedKeys.push("humanBehavior.restLevel");
1422
+ }
1423
+ return {
1424
+ nextConfig,
1425
+ patchedKeys
1426
+ };
1427
+ }
1428
1428
 
1429
1429
  async function ensureUserConfig(options = {}) {
1430
1430
  const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
@@ -2741,33 +2741,33 @@ function printHelp() {
2741
2741
  console.log("boss-recommend-mcp");
2742
2742
  console.log("");
2743
2743
  console.log("Usage:");
2744
- console.log(" boss-recommend-mcp Start the MCP server");
2745
- console.log(" boss-recommend-mcp start Start the MCP server");
2746
- console.log(" boss-recommend-mcp prepare-run Validate a cron-ready recommend run payload without starting screening");
2747
- console.log(" boss-recommend-mcp schedule-run Create a package-owned delayed recommend run");
2748
- console.log(" boss-recommend-mcp schedule-status Check a package-owned delayed recommend run");
2749
- console.log(" boss-recommend-mcp run Start a CDP-only recommend run through the shared run service");
2750
- console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
2744
+ console.log(" boss-recommend-mcp Start the MCP server");
2745
+ console.log(" boss-recommend-mcp start Start the MCP server");
2746
+ console.log(" boss-recommend-mcp prepare-run Validate a cron-ready recommend run payload without starting screening");
2747
+ console.log(" boss-recommend-mcp schedule-run Create a package-owned delayed recommend run");
2748
+ console.log(" boss-recommend-mcp schedule-status Check a package-owned delayed recommend run");
2749
+ console.log(" boss-recommend-mcp run Start a CDP-only recommend run through the shared run service");
2750
+ console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
2751
2751
  console.log(" boss-recommend-mcp chat <subcommand> Run CDP-only boss-chat health/prepare/status commands");
2752
2752
  console.log(" boss-recommend-mcp install Install/migrate skills and MCP configs; replaces legacy Boss MCP routes (supports --agent trae-cn/openclaw/qclaw/...)");
2753
2753
  console.log(" boss-recommend-mcp install-skill Install bundled Codex skills (recommend/recruit/chat)");
2754
2754
  console.log(" boss-recommend-mcp init-config Create screening-config.json if missing (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
2755
2755
  console.log(" boss-recommend-mcp config set Write baseUrl/apiKey/model (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
2756
2756
  console.log(" boss-recommend-mcp set-port Persist preferred Chrome debug port to screening-config.json");
2757
- console.log(" boss-recommend-mcp mcp-config Generate MCP config JSON for Cursor/Trae(含 trae-cn)/Claude Code/OpenClaw/QClaw");
2758
- console.log(" boss-recommend-mcp doctor Check config/runtime/calibration prerequisites (supports --agent trae-cn/qclaw/cursor/...)");
2759
- console.log(" boss-recommend-mcp calibrate Disabled until CDP-only featured calibration is live-verified");
2760
- console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
2761
- console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
2762
- console.log(" boss-recommend-mcp install --mcp-launch global-wrapper Use ~/.boss-recommend-mcp/bin wrapper so npm global upgrades affect MCP hosts");
2763
- console.log(" boss-recommend-mcp start --toolset recommend Start a narrowed MCP server (all|recommend|chat|recruit)");
2764
- console.log("");
2765
- console.log("Run command:");
2766
- console.log(" boss-recommend-mcp prepare-run --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2767
- console.log(" boss-recommend-mcp schedule-run --schedule-delay-minutes 10 --instruction-file boss-recommend-instruction.txt --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2768
- console.log(" boss-recommend-mcp schedule-status --schedule-id <id>");
2769
- console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2770
- console.log(" boss-recommend-mcp run --detached --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2757
+ console.log(" boss-recommend-mcp mcp-config Generate MCP config JSON for Cursor/Trae(含 trae-cn)/Claude Code/OpenClaw/QClaw");
2758
+ console.log(" boss-recommend-mcp doctor Check config/runtime/calibration prerequisites (supports --agent trae-cn/qclaw/cursor/...)");
2759
+ console.log(" boss-recommend-mcp calibrate Disabled until CDP-only featured calibration is live-verified");
2760
+ console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
2761
+ console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
2762
+ console.log(" boss-recommend-mcp install --mcp-launch global-wrapper Use ~/.boss-recommend-mcp/bin wrapper so npm global upgrades affect MCP hosts");
2763
+ console.log(" boss-recommend-mcp start --toolset recommend Start a narrowed MCP server (all|recommend|chat|recruit)");
2764
+ console.log("");
2765
+ console.log("Run command:");
2766
+ console.log(" boss-recommend-mcp prepare-run --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2767
+ console.log(" boss-recommend-mcp schedule-run --schedule-delay-minutes 10 --instruction-file boss-recommend-instruction.txt --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2768
+ console.log(" boss-recommend-mcp schedule-status --schedule-id <id>");
2769
+ console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2770
+ console.log(" boss-recommend-mcp run --detached --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
2771
2771
  console.log(" boss-recommend-mcp list-jobs --slow-live --port 9222");
2772
2772
  console.log(" boss-recommend-mcp chat prepare-run --slow-live --port 9222 # CDP-only preflight; start runs through MCP start_boss_chat_run");
2773
2773
  console.log(" boss-recommend-mcp config set --base-url <url> --api-key <key> --model <model> [--thinking-level off|low|medium|high|current] [--greeting-message <text>] [--openai-organization <id>] [--openai-project <id>]");
@@ -2790,15 +2790,15 @@ function printMcpConfig(options = {}) {
2790
2790
  }
2791
2791
  }
2792
2792
 
2793
- async function installAll(options = {}) {
2794
- const runtimeDirsResult = await ensureRuntimeDirectories(options);
2795
- const skillResults = installSkill();
2796
- const configResult = await ensureUserConfig(options);
2797
- const mcpLaunchMode = normalizeMcpLaunchMode(options);
2798
- const mcpTemplateResult = writeMcpConfigFiles({ ...options, agent: undefined, client: "all" });
2799
- const externalMcpResult = installExternalMcpConfigs(options);
2800
- const externalSkillResult = mirrorSkillToExternalDirs(options);
2801
- console.log(
2793
+ async function installAll(options = {}) {
2794
+ const runtimeDirsResult = await ensureRuntimeDirectories(options);
2795
+ const skillResults = installSkill();
2796
+ const configResult = await ensureUserConfig(options);
2797
+ const mcpLaunchMode = normalizeMcpLaunchMode(options);
2798
+ const mcpTemplateResult = writeMcpConfigFiles({ ...options, agent: undefined, client: "all" });
2799
+ const externalMcpResult = installExternalMcpConfigs(options);
2800
+ const externalSkillResult = mirrorSkillToExternalDirs(options);
2801
+ console.log(
2802
2802
  `Runtime directories prepared: created=${runtimeDirsResult.created.length}, existing=${runtimeDirsResult.existed.length}, failed=${runtimeDirsResult.failed.length}`
2803
2803
  );
2804
2804
  console.log(`- recommend runtime: ${runtimeDirsResult.stateHome}`);
@@ -2826,13 +2826,13 @@ async function installAll(options = {}) {
2826
2826
  console.warn(`screening-config.json skip default patch: ${configResult.patch_error}`);
2827
2827
  }
2828
2828
  console.log(`请在该目录修改 baseUrl/apiKey/model 并替换占位词后再运行:${path.dirname(configResult.path)}`);
2829
- console.log(`MCP config templates exported to: ${mcpTemplateResult.outputDir}`);
2830
- for (const item of mcpTemplateResult.files) {
2831
- console.log(`- ${item.client}: ${item.file}`);
2832
- }
2833
- if (mcpLaunchMode === "global-wrapper") {
2834
- console.log(`Upgrade-stable MCP wrapper: ${getGlobalMcpWrapperPath(options)}`);
2835
- }
2829
+ console.log(`MCP config templates exported to: ${mcpTemplateResult.outputDir}`);
2830
+ for (const item of mcpTemplateResult.files) {
2831
+ console.log(`- ${item.client}: ${item.file}`);
2832
+ }
2833
+ if (mcpLaunchMode === "global-wrapper") {
2834
+ console.log(`Upgrade-stable MCP wrapper: ${getGlobalMcpWrapperPath(options)}`);
2835
+ }
2836
2836
  if (externalMcpResult.targets.length > 0) {
2837
2837
  console.log(`Auto-configured external MCP files: ${externalMcpResult.applied.length}`);
2838
2838
  for (const item of externalMcpResult.applied) {
@@ -2865,37 +2865,37 @@ async function installAll(options = {}) {
2865
2865
  }
2866
2866
  }
2867
2867
 
2868
- function buildRecommendRunCliInput(options = {}) {
2869
- const instruction = getRunInstruction(options);
2870
- const confirmation = getRunConfirmation(options);
2871
- const overrides = getRunOverrides(options);
2872
- const followUp = getRunFollowUp(options);
2873
- const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
2874
-
2875
- const args = {
2876
- instruction,
2877
- confirmation: confirmation ?? undefined,
2878
- overrides: overrides ?? undefined,
2879
- follow_up: followUp ?? undefined,
2868
+ function buildRecommendRunCliInput(options = {}) {
2869
+ const instruction = getRunInstruction(options);
2870
+ const confirmation = getRunConfirmation(options);
2871
+ const overrides = getRunOverrides(options);
2872
+ const followUp = getRunFollowUp(options);
2873
+ const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
2874
+
2875
+ const args = {
2876
+ instruction,
2877
+ confirmation: confirmation ?? undefined,
2878
+ overrides: overrides ?? undefined,
2879
+ follow_up: followUp ?? undefined,
2880
2880
  host: typeof options.host === "string" && options.host.trim() ? options.host.trim() : undefined,
2881
2881
  port,
2882
2882
  target_url_includes: typeof options["target-url-includes"] === "string" && options["target-url-includes"].trim()
2883
2883
  ? options["target-url-includes"].trim()
2884
2884
  : undefined,
2885
2885
  allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
2886
- slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
2887
- };
2888
- const restLevel = parseRestLevelOption(
2889
- options["rest-level"]
2890
- ?? options.rest_level
2891
- ?? options["human-behavior-rest-level"]
2892
- ?? options.human_behavior_rest_level
2893
- );
2894
- if (restLevel) {
2895
- args.human_behavior = {
2896
- restLevel
2897
- };
2898
- }
2886
+ slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
2887
+ };
2888
+ const restLevel = parseRestLevelOption(
2889
+ options["rest-level"]
2890
+ ?? options.rest_level
2891
+ ?? options["human-behavior-rest-level"]
2892
+ ?? options.human_behavior_rest_level
2893
+ );
2894
+ if (restLevel) {
2895
+ args.human_behavior = {
2896
+ restLevel
2897
+ };
2898
+ }
2899
2899
 
2900
2900
  const optionalPassthrough = [
2901
2901
  "detail_limit",
@@ -2928,129 +2928,129 @@ function buildRecommendRunCliInput(options = {}) {
2928
2928
  "llm_image_limit",
2929
2929
  "llm_image_detail"
2930
2930
  ];
2931
- for (const key of optionalPassthrough) {
2932
- const kebab = key.replace(/_/g, "-");
2933
- if (options[key] !== undefined) args[key] = options[key];
2934
- else if (options[kebab] !== undefined) args[key] = options[kebab];
2935
- }
2936
-
2937
- return {
2938
- args,
2939
- port
2940
- };
2941
- }
2942
-
2943
- async function preparePipelineOnce(options = {}) {
2944
- const workspaceRoot = getWorkspaceRoot(options);
2945
- const { args, port } = buildRecommendRunCliInput(options);
2946
- const result = prepareRecommendPipelineRunTool({
2947
- workspaceRoot,
2948
- args
2949
- });
2950
- printJson({
2951
- ...result,
2952
- cli: {
2953
- command: "prepare-run",
2954
- cdp_only: true,
2955
- shared_run_service: true,
2956
- workspace_root: workspaceRoot,
2957
- port
2958
- }
2959
- });
2960
- if (result.status !== "READY" || result.cron_ready !== true) {
2961
- process.exitCode = 1;
2962
- }
2963
- }
2964
-
2965
- function addScheduleOptions(args, options = {}) {
2966
- const scheduleId = String(options["schedule-id"] || options.schedule_id || options.scheduleId || "").trim();
2967
- if (scheduleId) args.schedule_id = scheduleId;
2968
- const runAt = String(options["schedule-run-at"] || options.schedule_run_at || options.scheduleRunAt || options["run-at"] || options.run_at || "").trim();
2969
- if (runAt) args.schedule_run_at = runAt;
2970
- const delayMinutes = parseNonNegativeInteger(options["schedule-delay-minutes"] ?? options.schedule_delay_minutes ?? options.scheduleDelayMinutes);
2971
- if (delayMinutes !== undefined) args.schedule_delay_minutes = delayMinutes;
2972
- const delaySeconds = parseNonNegativeInteger(options["schedule-delay-seconds"] ?? options.schedule_delay_seconds ?? options.scheduleDelaySeconds);
2973
- if (delaySeconds !== undefined) args.schedule_delay_seconds = delaySeconds;
2974
- return args;
2975
- }
2976
-
2977
- async function schedulePipelineOnce(options = {}) {
2978
- const workspaceRoot = getWorkspaceRoot(options);
2979
- const { args, port } = buildRecommendRunCliInput(options);
2980
- addScheduleOptions(args, options);
2981
- const result = await scheduleRecommendPipelineRunTool({
2982
- workspaceRoot,
2983
- args
2984
- });
2985
- printJson({
2986
- ...result,
2987
- cli: {
2988
- ...(result.cli || {}),
2989
- command: "schedule-run",
2990
- cdp_only: true,
2991
- package_owned_scheduler: true,
2992
- workspace_root: workspaceRoot,
2993
- port
2994
- }
2995
- });
2996
- if (result.status !== "SCHEDULED") {
2997
- process.exitCode = 1;
2998
- }
2999
- }
3000
-
3001
- function scheduleStatusCli(options = {}) {
3002
- const scheduleId = String(options["schedule-id"] || options.schedule_id || options.scheduleId || "").trim();
3003
- const result = getRecommendScheduledRunTool({
3004
- args: {
3005
- schedule_id: scheduleId
3006
- }
3007
- });
3008
- printJson({
3009
- ...result,
3010
- cli: {
3011
- command: "schedule-status",
3012
- cdp_only: true,
3013
- package_owned_scheduler: true
3014
- }
3015
- });
3016
- if (result.status !== "OK") {
3017
- process.exitCode = 1;
3018
- }
3019
- }
3020
-
3021
- function isTerminalRecommendCliState(state) {
3022
- return ["completed", "failed", "canceled"].includes(String(state || "").trim().toLowerCase());
3023
- }
3024
-
3025
- async function monitorDetachedRecommendCliRunControls(runId) {
3026
- const normalizedRunId = String(runId || "").trim();
3027
- if (!normalizedRunId) return;
3028
- while (true) {
3029
- const payload = getRecommendPipelineRunTool({
3030
- args: {
3031
- run_id: normalizedRunId
3032
- }
3033
- });
3034
- const state = String(payload?.run?.state || payload?.run?.status || "").trim().toLowerCase();
3035
- if (isTerminalRecommendCliState(state)) return;
3036
- const control = payload?.run?.control || {};
3037
- if (control.cancel_requested === true) {
3038
- cancelRecommendPipelineRunTool({ args: { run_id: normalizedRunId } });
3039
- } else if (control.pause_requested === true && state === "running") {
3040
- pauseRecommendPipelineRunTool({ args: { run_id: normalizedRunId } });
3041
- } else if (control.pause_requested === false && state === "paused") {
3042
- resumeRecommendPipelineRunTool({ args: { run_id: normalizedRunId } });
3043
- }
3044
- await sleepMs(1000);
3045
- }
3046
- }
3047
-
3048
- async function runPipelineOnce(options = {}) {
3049
- const workspaceRoot = getWorkspaceRoot(options);
3050
- const { args, port } = buildRecommendRunCliInput(options);
3051
- const result = await startRecommendPipelineRunTool({
3052
- workspaceRoot,
3053
- args
2931
+ for (const key of optionalPassthrough) {
2932
+ const kebab = key.replace(/_/g, "-");
2933
+ if (options[key] !== undefined) args[key] = options[key];
2934
+ else if (options[kebab] !== undefined) args[key] = options[kebab];
2935
+ }
2936
+
2937
+ return {
2938
+ args,
2939
+ port
2940
+ };
2941
+ }
2942
+
2943
+ async function preparePipelineOnce(options = {}) {
2944
+ const workspaceRoot = getWorkspaceRoot(options);
2945
+ const { args, port } = buildRecommendRunCliInput(options);
2946
+ const result = prepareRecommendPipelineRunTool({
2947
+ workspaceRoot,
2948
+ args
2949
+ });
2950
+ printJson({
2951
+ ...result,
2952
+ cli: {
2953
+ command: "prepare-run",
2954
+ cdp_only: true,
2955
+ shared_run_service: true,
2956
+ workspace_root: workspaceRoot,
2957
+ port
2958
+ }
2959
+ });
2960
+ if (result.status !== "READY" || result.cron_ready !== true) {
2961
+ process.exitCode = 1;
2962
+ }
2963
+ }
2964
+
2965
+ function addScheduleOptions(args, options = {}) {
2966
+ const scheduleId = String(options["schedule-id"] || options.schedule_id || options.scheduleId || "").trim();
2967
+ if (scheduleId) args.schedule_id = scheduleId;
2968
+ const runAt = String(options["schedule-run-at"] || options.schedule_run_at || options.scheduleRunAt || options["run-at"] || options.run_at || "").trim();
2969
+ if (runAt) args.schedule_run_at = runAt;
2970
+ const delayMinutes = parseNonNegativeInteger(options["schedule-delay-minutes"] ?? options.schedule_delay_minutes ?? options.scheduleDelayMinutes);
2971
+ if (delayMinutes !== undefined) args.schedule_delay_minutes = delayMinutes;
2972
+ const delaySeconds = parseNonNegativeInteger(options["schedule-delay-seconds"] ?? options.schedule_delay_seconds ?? options.scheduleDelaySeconds);
2973
+ if (delaySeconds !== undefined) args.schedule_delay_seconds = delaySeconds;
2974
+ return args;
2975
+ }
2976
+
2977
+ async function schedulePipelineOnce(options = {}) {
2978
+ const workspaceRoot = getWorkspaceRoot(options);
2979
+ const { args, port } = buildRecommendRunCliInput(options);
2980
+ addScheduleOptions(args, options);
2981
+ const result = await scheduleRecommendPipelineRunTool({
2982
+ workspaceRoot,
2983
+ args
2984
+ });
2985
+ printJson({
2986
+ ...result,
2987
+ cli: {
2988
+ ...(result.cli || {}),
2989
+ command: "schedule-run",
2990
+ cdp_only: true,
2991
+ package_owned_scheduler: true,
2992
+ workspace_root: workspaceRoot,
2993
+ port
2994
+ }
2995
+ });
2996
+ if (result.status !== "SCHEDULED") {
2997
+ process.exitCode = 1;
2998
+ }
2999
+ }
3000
+
3001
+ function scheduleStatusCli(options = {}) {
3002
+ const scheduleId = String(options["schedule-id"] || options.schedule_id || options.scheduleId || "").trim();
3003
+ const result = getRecommendScheduledRunTool({
3004
+ args: {
3005
+ schedule_id: scheduleId
3006
+ }
3007
+ });
3008
+ printJson({
3009
+ ...result,
3010
+ cli: {
3011
+ command: "schedule-status",
3012
+ cdp_only: true,
3013
+ package_owned_scheduler: true
3014
+ }
3015
+ });
3016
+ if (result.status !== "OK") {
3017
+ process.exitCode = 1;
3018
+ }
3019
+ }
3020
+
3021
+ function isTerminalRecommendCliState(state) {
3022
+ return ["completed", "failed", "canceled"].includes(String(state || "").trim().toLowerCase());
3023
+ }
3024
+
3025
+ async function monitorDetachedRecommendCliRunControls(runId) {
3026
+ const normalizedRunId = String(runId || "").trim();
3027
+ if (!normalizedRunId) return;
3028
+ while (true) {
3029
+ const payload = getRecommendPipelineRunTool({
3030
+ args: {
3031
+ run_id: normalizedRunId
3032
+ }
3033
+ });
3034
+ const state = String(payload?.run?.state || payload?.run?.status || "").trim().toLowerCase();
3035
+ if (isTerminalRecommendCliState(state)) return;
3036
+ const control = payload?.run?.control || {};
3037
+ if (control.cancel_requested === true) {
3038
+ cancelRecommendPipelineRunTool({ args: { run_id: normalizedRunId } });
3039
+ } else if (control.pause_requested === true && state === "running") {
3040
+ pauseRecommendPipelineRunTool({ args: { run_id: normalizedRunId } });
3041
+ } else if (control.pause_requested === false && state === "paused") {
3042
+ resumeRecommendPipelineRunTool({ args: { run_id: normalizedRunId } });
3043
+ }
3044
+ await sleepMs(1000);
3045
+ }
3046
+ }
3047
+
3048
+ async function runPipelineOnce(options = {}) {
3049
+ const workspaceRoot = getWorkspaceRoot(options);
3050
+ const { args, port } = buildRecommendRunCliInput(options);
3051
+ const result = await startRecommendPipelineRunTool({
3052
+ workspaceRoot,
3053
+ args
3054
3054
  });
3055
3055
  printJson({
3056
3056
  ...result,
@@ -3061,13 +3061,13 @@ async function runPipelineOnce(options = {}) {
3061
3061
  workspace_root: workspaceRoot,
3062
3062
  port
3063
3063
  }
3064
- });
3065
- if (result.status !== "ACCEPTED") {
3066
- process.exitCode = 1;
3067
- } else if (process.env[detachedRecommendRunChildEnv] === "1") {
3068
- await monitorDetachedRecommendCliRunControls(result.run_id);
3069
- }
3070
- }
3064
+ });
3065
+ if (result.status !== "ACCEPTED") {
3066
+ process.exitCode = 1;
3067
+ } else if (process.env[detachedRecommendRunChildEnv] === "1") {
3068
+ await monitorDetachedRecommendCliRunControls(result.run_id);
3069
+ }
3070
+ }
3071
3071
 
3072
3072
  function buildRecommendJobListCliInput(options = {}) {
3073
3073
  const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
@@ -3088,22 +3088,22 @@ async function listRecommendJobsCli(options = {}) {
3088
3088
  }));
3089
3089
  }
3090
3090
 
3091
- function buildBossChatCliInput(options = {}) {
3092
- const greetingTextRaw =
3093
- options["greeting-text"]
3094
- ?? options.greeting_text
3095
- ?? options.greetingText
3096
- ?? options.greeting;
3097
- const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
3098
- const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
3099
- const host = String(options.host || "").trim();
3100
- const restLevel = parseRestLevelOption(
3101
- options["rest-level"]
3102
- ?? options.rest_level
3103
- ?? options["human-behavior-rest-level"]
3104
- ?? options.human_behavior_rest_level
3105
- );
3106
- return {
3091
+ function buildBossChatCliInput(options = {}) {
3092
+ const greetingTextRaw =
3093
+ options["greeting-text"]
3094
+ ?? options.greeting_text
3095
+ ?? options.greetingText
3096
+ ?? options.greeting;
3097
+ const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
3098
+ const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
3099
+ const host = String(options.host || "").trim();
3100
+ const restLevel = parseRestLevelOption(
3101
+ options["rest-level"]
3102
+ ?? options.rest_level
3103
+ ?? options["human-behavior-rest-level"]
3104
+ ?? options.human_behavior_rest_level
3105
+ );
3106
+ return {
3107
3107
  profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
3108
3108
  job: typeof options.job === "string" ? options.job.trim() : undefined,
3109
3109
  start_from: String(options["start-from"] || options.start_from || "").trim().toLowerCase() || undefined,
@@ -3121,18 +3121,18 @@ function buildBossChatCliInput(options = {}) {
3121
3121
  dry_run: options["dry-run"] === true || options.dryRun === true,
3122
3122
  no_state: options["no-state"] === true || options.noState === true,
3123
3123
  human_behavior_enabled: parseBooleanOption(options["human-behavior-enabled"] ?? options.human_behavior_enabled),
3124
- human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
3125
- ? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
3126
- : undefined,
3127
- human_behavior: restLevel
3128
- ? {
3129
- restLevel
3130
- }
3131
- : undefined,
3132
- safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
3133
- batch_rest_enabled: parseBooleanOption(options["batch-rest"] ?? options.batch_rest_enabled)
3134
- };
3135
- }
3124
+ human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
3125
+ ? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
3126
+ : undefined,
3127
+ human_behavior: restLevel
3128
+ ? {
3129
+ restLevel
3130
+ }
3131
+ : undefined,
3132
+ safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
3133
+ batch_rest_enabled: parseBooleanOption(options["batch-rest"] ?? options.batch_rest_enabled)
3134
+ };
3135
+ }
3136
3136
 
3137
3137
  function getBossChatCliRunTarget(options = {}) {
3138
3138
  return {
@@ -3221,11 +3221,11 @@ export async function runCli(argv = process.argv) {
3221
3221
  case "mcp":
3222
3222
  startServer();
3223
3223
  break;
3224
- case "run":
3225
- try {
3226
- if (
3227
- (options.detached === true || options.background === true)
3228
- && process.env[detachedRecommendRunChildEnv] !== "1"
3224
+ case "run":
3225
+ try {
3226
+ if (
3227
+ (options.detached === true || options.background === true)
3228
+ && process.env[detachedRecommendRunChildEnv] !== "1"
3229
3229
  ) {
3230
3230
  await runPipelineDetached(argv.slice(3), options);
3231
3231
  } else {
@@ -3240,59 +3240,59 @@ export async function runCli(argv = process.argv) {
3240
3240
  retryable: false
3241
3241
  }
3242
3242
  });
3243
- process.exitCode = 1;
3244
- }
3245
- break;
3246
- case "prepare-run":
3247
- case "prepare":
3248
- try {
3249
- await preparePipelineOnce(options);
3250
- } catch (error) {
3251
- printJson({
3252
- status: "FAILED",
3253
- cron_ready: false,
3254
- error: {
3255
- code: "INVALID_CLI_INPUT",
3256
- message: error.message || "Invalid CLI input",
3257
- retryable: false
3258
- }
3259
- });
3260
- process.exitCode = 1;
3261
- }
3262
- break;
3263
- case "schedule-run":
3264
- case "schedule":
3265
- try {
3266
- await schedulePipelineOnce(options);
3267
- } catch (error) {
3268
- printJson({
3269
- status: "FAILED",
3270
- schedule_created: false,
3271
- error: {
3272
- code: "INVALID_CLI_INPUT",
3273
- message: error.message || "Invalid CLI input",
3274
- retryable: false
3275
- }
3276
- });
3277
- process.exitCode = 1;
3278
- }
3279
- break;
3280
- case "schedule-status":
3281
- case "scheduled-run":
3282
- try {
3283
- scheduleStatusCli(options);
3284
- } catch (error) {
3285
- printJson({
3286
- status: "FAILED",
3287
- error: {
3288
- code: "INVALID_CLI_INPUT",
3289
- message: error.message || "Invalid CLI input",
3290
- retryable: false
3291
- }
3292
- });
3293
- process.exitCode = 1;
3294
- }
3295
- break;
3243
+ process.exitCode = 1;
3244
+ }
3245
+ break;
3246
+ case "prepare-run":
3247
+ case "prepare":
3248
+ try {
3249
+ await preparePipelineOnce(options);
3250
+ } catch (error) {
3251
+ printJson({
3252
+ status: "FAILED",
3253
+ cron_ready: false,
3254
+ error: {
3255
+ code: "INVALID_CLI_INPUT",
3256
+ message: error.message || "Invalid CLI input",
3257
+ retryable: false
3258
+ }
3259
+ });
3260
+ process.exitCode = 1;
3261
+ }
3262
+ break;
3263
+ case "schedule-run":
3264
+ case "schedule":
3265
+ try {
3266
+ await schedulePipelineOnce(options);
3267
+ } catch (error) {
3268
+ printJson({
3269
+ status: "FAILED",
3270
+ schedule_created: false,
3271
+ error: {
3272
+ code: "INVALID_CLI_INPUT",
3273
+ message: error.message || "Invalid CLI input",
3274
+ retryable: false
3275
+ }
3276
+ });
3277
+ process.exitCode = 1;
3278
+ }
3279
+ break;
3280
+ case "schedule-status":
3281
+ case "scheduled-run":
3282
+ try {
3283
+ scheduleStatusCli(options);
3284
+ } catch (error) {
3285
+ printJson({
3286
+ status: "FAILED",
3287
+ error: {
3288
+ code: "INVALID_CLI_INPUT",
3289
+ message: error.message || "Invalid CLI input",
3290
+ retryable: false
3291
+ }
3292
+ });
3293
+ process.exitCode = 1;
3294
+ }
3295
+ break;
3296
3296
  case "list-jobs":
3297
3297
  case "jobs":
3298
3298
  case "recommend-jobs":
@@ -3436,14 +3436,14 @@ export async function runCli(argv = process.argv) {
3436
3436
  }
3437
3437
 
3438
3438
  export const __testables = {
3439
- buildRecommendJobListCliInput,
3440
- buildBossChatCliInput,
3441
- buildDefaultMcpArgs,
3442
- buildMcpLaunchConfig,
3443
- buildMcpConfigFileContent,
3444
- ensureGlobalMcpWrapper,
3445
- getGlobalMcpWrapperPath,
3446
- collectRuntimeDirectories,
3439
+ buildRecommendJobListCliInput,
3440
+ buildBossChatCliInput,
3441
+ buildDefaultMcpArgs,
3442
+ buildMcpLaunchConfig,
3443
+ buildMcpConfigFileContent,
3444
+ ensureGlobalMcpWrapper,
3445
+ getGlobalMcpWrapperPath,
3446
+ collectRuntimeDirectories,
3447
3447
  ensureBossChatRuntimeReady: ensureBossChatRuntimeReadyLocal,
3448
3448
  ensureRuntimeDirectories,
3449
3449
  getBossChatCliRunTarget,
@@ -3453,13 +3453,13 @@ export const __testables = {
3453
3453
  installSkill,
3454
3454
  isInstalledPackageRoot,
3455
3455
  mergeMcpServerConfigFile,
3456
- resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
3457
- runBossChatCliCommand,
3458
- preparePipelineOnce,
3459
- schedulePipelineOnce,
3460
- scheduleStatusCli,
3461
- runPipelineOnce
3462
- };
3456
+ resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
3457
+ runBossChatCliCommand,
3458
+ preparePipelineOnce,
3459
+ schedulePipelineOnce,
3460
+ scheduleStatusCli,
3461
+ runPipelineOnce
3462
+ };
3463
3463
 
3464
3464
  if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
3465
3465
  await runCli(process.argv);