@reglow/review-mcp 0.1.0
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 +238 -0
- package/catalog.mjs +537 -0
- package/cli-utils.mjs +29 -0
- package/index.mjs +192 -0
- package/integrations.mjs +270 -0
- package/knowledge.mjs +1130 -0
- package/package.json +48 -0
- package/server.mjs +742 -0
- package/setup.mjs +404 -0
- package/site.mjs +255 -0
- package/skills/review-components/BEST_PRACTICES.md +132 -0
- package/skills/review-components/COMPONENTS.md +216 -0
- package/skills/review-components/SKILL.md +138 -0
- package/skills/review-components/WORKFLOWS.md +435 -0
- package/skills.mjs +194 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* review-mcp —— ReView 组件库 MCP 服务器(stdio)
|
|
4
|
+
*
|
|
5
|
+
* 让 AI 助手在写 ReView(uni-app x / uts / uvue)代码时「有据可查」:组件文档、代码示例、
|
|
6
|
+
* 源码文件、函数实现、跨库检索与库级指南,全部来自本地 ReView 仓库
|
|
7
|
+
* (或运行目录下的 `uni_modules/reglow-review`,官方文档站点兜底)。
|
|
8
|
+
*
|
|
9
|
+
* 用法(客户端 mcpServers 配置中作为 command + args):
|
|
10
|
+
* npx -y @reglow/review-mcp # 自动识别本地仓库 / uni_modules,文档缺失时走官网兜底
|
|
11
|
+
* npx -y @reglow/review-mcp --root D:/path/to/review # 指定工作区(开发组件库时)
|
|
12
|
+
* npx -y @reglow/review-mcp --check # 自检:打印知识源与组件数量后退出
|
|
13
|
+
* npx -y @reglow/review-mcp --setup # 为 Trae/CodeBuddy/Qoder 一键写 MCP 配置 + 装 Skills
|
|
14
|
+
* npx -y @reglow/review-mcp --install-skills # 仅安装 Skills 知识包(等价于 review-skills)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
|
+
import { createKnowledgeResolver, createServer } from "./server.mjs";
|
|
22
|
+
import { NPM_NOT_PUBLISHED_HINT } from "./knowledge.mjs";
|
|
23
|
+
import { installSkills, mcpConfigSnippet, runSkillsCli } from "./skills.mjs";
|
|
24
|
+
import { DEFAULT_SETUP_TOOLS } from "./integrations.mjs";
|
|
25
|
+
import { renderToolList, runSetupCli } from "./setup.mjs";
|
|
26
|
+
|
|
27
|
+
const PACKAGE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const OWN_PACKAGE = JSON.parse(
|
|
29
|
+
fs.readFileSync(path.join(PACKAGE_DIR, "package.json"), "utf8"),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const USAGE = [
|
|
33
|
+
`review-mcp v${OWN_PACKAGE.version} —— ReView(uni-app x)组件库 MCP 服务器`,
|
|
34
|
+
"",
|
|
35
|
+
"用法:review-mcp [选项]",
|
|
36
|
+
"",
|
|
37
|
+
"选项:",
|
|
38
|
+
" --root <dir> ReView 仓库工作区路径(未指定时自动识别仓库 / 项目内的 uni_modules/reglow-review)",
|
|
39
|
+
" --version <ver> 指定知识版本;因 @reglow/review 未发布 npm,只会给出提示",
|
|
40
|
+
" --no-site 禁用官方文档站点兜底(完全离线)",
|
|
41
|
+
" --check 自检:打印知识源、版本与组件数量后退出",
|
|
42
|
+
` --setup [tools] 一键配置 AI 工具(MCP 配置 + Skills),默认 ${DEFAULT_SETUP_TOOLS.join(",")}`,
|
|
43
|
+
" 例:--setup trae,codebuddy,qoder / --setup all;支持 --scope user --dry-run --force",
|
|
44
|
+
" --install-skills 仅安装 Skills 知识包(可配合 --target / --all / --force)",
|
|
45
|
+
" --list-tools 列出支持的工具、MCP 配置路径与 Skills 目录",
|
|
46
|
+
" --print-config 打印 MCP 客户端配置片段后退出",
|
|
47
|
+
" -v, --version 打印本包版本号(不带值时)",
|
|
48
|
+
" -h, --help 显示帮助",
|
|
49
|
+
"",
|
|
50
|
+
`说明:${NPM_NOT_PUBLISHED_HINT}`,
|
|
51
|
+
"",
|
|
52
|
+
"MCP 客户端配置示例(Trae / Cursor / Claude Desktop 的 mcpServers):",
|
|
53
|
+
JSON.stringify(mcpConfigSnippet(), null, 2),
|
|
54
|
+
].join("\n");
|
|
55
|
+
|
|
56
|
+
function parseArgs(argv) {
|
|
57
|
+
const options = {
|
|
58
|
+
root: undefined,
|
|
59
|
+
knowledgeVersion: undefined,
|
|
60
|
+
allowSite: true,
|
|
61
|
+
};
|
|
62
|
+
const passthrough = [];
|
|
63
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
64
|
+
const arg = argv[i];
|
|
65
|
+
if (arg === "--root") options.root = argv[++i];
|
|
66
|
+
else if (arg.startsWith("--root="))
|
|
67
|
+
options.root = arg.slice("--root=".length);
|
|
68
|
+
else if (arg === "--version" || arg === "--default-version") {
|
|
69
|
+
const value = argv[i + 1];
|
|
70
|
+
if (value && !value.startsWith("-")) {
|
|
71
|
+
options.knowledgeVersion = value;
|
|
72
|
+
i += 1;
|
|
73
|
+
} else {
|
|
74
|
+
options.ownVersion = true;
|
|
75
|
+
}
|
|
76
|
+
} else if (arg.startsWith("--version=")) {
|
|
77
|
+
options.knowledgeVersion = arg.slice("--version=".length);
|
|
78
|
+
} else if (arg.startsWith("--default-version=")) {
|
|
79
|
+
options.knowledgeVersion = arg.slice("--default-version=".length);
|
|
80
|
+
} else if (arg === "--no-site") options.allowSite = false;
|
|
81
|
+
else if (arg === "--check") options.check = true;
|
|
82
|
+
else if (arg === "--print-config") options.printConfig = true;
|
|
83
|
+
else if (arg === "--help" || arg === "-h") options.help = true;
|
|
84
|
+
else if (arg === "-v") options.ownVersion = true;
|
|
85
|
+
else passthrough.push(arg);
|
|
86
|
+
}
|
|
87
|
+
options.passthrough = passthrough;
|
|
88
|
+
return options;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function main() {
|
|
92
|
+
const argv = process.argv.slice(2);
|
|
93
|
+
|
|
94
|
+
// 工具配置命令(`--setup [tools]` / `setup [tools]`)
|
|
95
|
+
if (argv.includes("--setup") || argv[0] === "setup") {
|
|
96
|
+
const index = argv.findIndex((arg) => arg === "--setup" || arg === "setup");
|
|
97
|
+
// 去掉 --setup 本体,其余参数(含位置形式的目标工具清单)原样交给 review-setup
|
|
98
|
+
process.exitCode = runSetupCli(argv.filter((_, i) => i !== index));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (argv.includes("--list-tools")) {
|
|
103
|
+
console.log(renderToolList({ cwd: process.cwd() }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Skills 安装命令(支持 `--install-skills` 与 `install-skills` 两种写法)
|
|
108
|
+
if (argv.includes("--install-skills") || argv[0] === "install-skills") {
|
|
109
|
+
const rest = argv.filter(
|
|
110
|
+
(arg) => arg !== "--install-skills" && arg !== "install-skills",
|
|
111
|
+
);
|
|
112
|
+
process.exitCode = runSkillsCli(rest);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const options = parseArgs(argv);
|
|
117
|
+
if (options.help) {
|
|
118
|
+
console.log(USAGE);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (options.ownVersion) {
|
|
122
|
+
console.log(OWN_PACKAGE.version);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (options.printConfig) {
|
|
126
|
+
console.log(JSON.stringify(mcpConfigSnippet(), null, 2));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const logger = (message) => console.error(message);
|
|
131
|
+
if (options.knowledgeVersion) {
|
|
132
|
+
logger(`[review-mcp] ⚠️ ${NPM_NOT_PUBLISHED_HINT}`);
|
|
133
|
+
}
|
|
134
|
+
const resolveKnowledge = createKnowledgeResolver({
|
|
135
|
+
root: options.root,
|
|
136
|
+
allowSite: options.allowSite,
|
|
137
|
+
logger,
|
|
138
|
+
});
|
|
139
|
+
const knowledge = await resolveKnowledge(options.knowledgeVersion);
|
|
140
|
+
|
|
141
|
+
if (options.check) {
|
|
142
|
+
const meta = knowledge.meta();
|
|
143
|
+
const components = knowledge.listComponents();
|
|
144
|
+
console.log(
|
|
145
|
+
JSON.stringify(
|
|
146
|
+
{
|
|
147
|
+
package: OWN_PACKAGE.name,
|
|
148
|
+
packageVersion: OWN_PACKAGE.version,
|
|
149
|
+
knowledgeSource: meta.source,
|
|
150
|
+
reviewVersion: meta.version,
|
|
151
|
+
versionDate: meta.versionDate,
|
|
152
|
+
versionSource: meta.versionSource,
|
|
153
|
+
root: meta.root,
|
|
154
|
+
warning: meta.warning,
|
|
155
|
+
componentCount: components.length,
|
|
156
|
+
documentedCount: knowledge.catalog.documentedCount,
|
|
157
|
+
categories: knowledge.catalog.categories.map((group) => ({
|
|
158
|
+
key: group.key,
|
|
159
|
+
label: group.label,
|
|
160
|
+
count: group.components.length,
|
|
161
|
+
})),
|
|
162
|
+
components: components.map((entry) => entry.name),
|
|
163
|
+
allowSiteFallback: options.allowSite,
|
|
164
|
+
},
|
|
165
|
+
null,
|
|
166
|
+
2,
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (options.allowSite === false) {
|
|
173
|
+
logger("[review-mcp] 已禁用官方文档站点兜底(--no-site)");
|
|
174
|
+
}
|
|
175
|
+
const server = createServer({
|
|
176
|
+
resolveKnowledge,
|
|
177
|
+
defaultKnowledge: knowledge,
|
|
178
|
+
});
|
|
179
|
+
const transport = new StdioServerTransport();
|
|
180
|
+
await server.connect(transport);
|
|
181
|
+
logger(
|
|
182
|
+
`[review-mcp] 已启动:knowledge=${knowledge.meta().source} version=${knowledge.meta().version} ` +
|
|
183
|
+
`components=${knowledge.listComponents().length}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main().catch((error) => {
|
|
188
|
+
console.error(`[review-mcp] 启动失败:${error?.stack ?? error}`);
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
export { installSkills };
|
package/integrations.mjs
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI 工具的集成规格(Trae / CodeBuddy / Qoder 等)
|
|
3
|
+
*
|
|
4
|
+
* 数据来自各工具官方文档(2026-09 核对),用于:
|
|
5
|
+
* - `review-setup` 一键写入 MCP 配置 + 安装 Skills;
|
|
6
|
+
* - `review-skills` 的 `--list` 展示;
|
|
7
|
+
* - 文档与错误提示中的「手动步骤」说明。
|
|
8
|
+
*
|
|
9
|
+
* 参考:
|
|
10
|
+
* - Trae:https://docs.trae.cn/ide/add-mcp-servers (项目级 `.trae/mcp.json`,需在设置中开启「启用项目级 MCP」)
|
|
11
|
+
* - CodeBuddy:https://www.codebuddy.ai/docs/cli/mcp (项目级 `.mcp.json`,支持 JSONC)
|
|
12
|
+
* https://www.codebuddy.ai/docs/zh/cli/skills (`.codebuddy/skills/`)
|
|
13
|
+
* - Qoder:https://docs.qoder.com/zh/cli/mcp-reference (项目级 `.mcp.json` / `.qoder/settings.json`,需批准)
|
|
14
|
+
* https://docs.qoder.com/cli/Skills (`.qoder/skills/`)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** MCP 服务器名(写入 mcpServers 的 key) */
|
|
18
|
+
export const DEFAULT_SERVER_NAME = "review-mcp";
|
|
19
|
+
|
|
20
|
+
/** npm 包名(npx 启动用) */
|
|
21
|
+
export const MCP_PACKAGE = "@reglow/review-mcp";
|
|
22
|
+
|
|
23
|
+
/** 默认的 stdio 启动参数 */
|
|
24
|
+
export const DEFAULT_ARGS = ["-y", MCP_PACKAGE];
|
|
25
|
+
|
|
26
|
+
/** 本 MCP 服务器在配置中的描述 */
|
|
27
|
+
const SERVER_DESCRIPTION =
|
|
28
|
+
"ReView(uni-app x / uts / uvue)移动端组件库:文档 / 示例 / 源码 / 函数查询(@reglow/review-mcp)";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 各工具集成规格
|
|
32
|
+
*
|
|
33
|
+
* - `skills.projectDir`:项目级 Skills 目录(相对项目根)
|
|
34
|
+
* - `skills.userDir`:用户级 Skills 目录(`~` 表示用户主目录)
|
|
35
|
+
* - `mcp.projectFile`:项目级 MCP 配置文件(相对项目根)
|
|
36
|
+
* - `mcp.userFile`:用户级 MCP 配置文件(可按平台覆盖)
|
|
37
|
+
* - `mcp.jsonc`:配置文件是否允许注释 / 尾逗号(合并时会先做 JSONC 解析)
|
|
38
|
+
* - `mcp.explicitType`:写入条目时是否带 `type: "stdio"`
|
|
39
|
+
* - `mcp.manualSteps`:一键写入后仍需人工完成的步骤
|
|
40
|
+
*/
|
|
41
|
+
export const TOOL_INTEGRATIONS = {
|
|
42
|
+
trae: {
|
|
43
|
+
label: "Trae",
|
|
44
|
+
docsUrl: "https://docs.trae.cn/ide/add-mcp-servers",
|
|
45
|
+
skills: { projectDir: ".trae/skills", userDir: "~/.trae/skills" },
|
|
46
|
+
mcp: {
|
|
47
|
+
projectFile: ".trae/mcp.json",
|
|
48
|
+
userFile: {
|
|
49
|
+
win32: "%APPDATA%/Trae/User/mcp.json",
|
|
50
|
+
darwin: "~/Library/Application Support/Trae/User/mcp.json",
|
|
51
|
+
linux: "~/.config/Trae/User/mcp.json",
|
|
52
|
+
},
|
|
53
|
+
explicitType: false,
|
|
54
|
+
manualSteps: [
|
|
55
|
+
"Trae 设置 → MCP:打开「启用项目级 MCP」开关(首次使用项目级配置必须开启)",
|
|
56
|
+
"若改用用户级配置:设置 → MCP → 添加 → 手动添加,或直接编辑上述用户级 mcp.json",
|
|
57
|
+
],
|
|
58
|
+
notes: [
|
|
59
|
+
"stdio 配置中 command 不能包含空格",
|
|
60
|
+
"支持 ${workspaceFolder} 变量与 START_MCP_TIMEOUT_MS / RUN_MCP_TIMEOUT_MS 超时环境变量",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
codebuddy: {
|
|
65
|
+
label: "CodeBuddy",
|
|
66
|
+
docsUrl: "https://www.codebuddy.ai/docs/cli/mcp",
|
|
67
|
+
skills: { projectDir: ".codebuddy/skills", userDir: "~/.codebuddy/skills" },
|
|
68
|
+
mcp: {
|
|
69
|
+
projectFile: ".mcp.json",
|
|
70
|
+
userFile: "~/.codebuddy/.mcp.json",
|
|
71
|
+
jsonc: true,
|
|
72
|
+
explicitType: true,
|
|
73
|
+
manualSteps: [
|
|
74
|
+
"IDE:对话面板右上 CodeBuddy Settings → MCP → Add MCP,把 .mcp.json 里的 review-mcp 条目粘贴进去(IDE 不走项目文件)",
|
|
75
|
+
"CLI:项目级 `.mcp.json` 直接生效,可用 `/mcp` 查看连接状态",
|
|
76
|
+
],
|
|
77
|
+
notes: [
|
|
78
|
+
"项目级优先读 `.mcp.json`,其次 `mcp.json`(已弃用)",
|
|
79
|
+
"支持 JSONC(注释 / 尾逗号);同名服务器优先级 local > project > user",
|
|
80
|
+
'如需免确认,可在 settings.json 配 permissions.allow: ["mcp__review-mcp__*"]',
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
qoder: {
|
|
85
|
+
label: "Qoder",
|
|
86
|
+
docsUrl: "https://docs.qoder.com/zh/cli/mcp-reference",
|
|
87
|
+
skills: { projectDir: ".qoder/skills", userDir: "~/.qoder/skills" },
|
|
88
|
+
mcp: {
|
|
89
|
+
projectFile: ".mcp.json",
|
|
90
|
+
alternativeProjectFile: ".qoder/settings.json",
|
|
91
|
+
userFile: "~/.qoder/settings.json",
|
|
92
|
+
explicitType: true,
|
|
93
|
+
manualSteps: [
|
|
94
|
+
"项目级 MCP 默认需要批准:首次调用时确认,或在 settings.json 中设置 mcp.enableAllProjectMcpServers: true",
|
|
95
|
+
"CLI 也可直接执行:qoder mcp add review-mcp -s project -- npx -y @reglow/review-mcp",
|
|
96
|
+
"修改后如 CLI 正在运行,执行 /mcp reload 重新发现",
|
|
97
|
+
],
|
|
98
|
+
notes: [
|
|
99
|
+
"同名服务器覆盖顺序:用户级 → 项目级 settings.json → 项目级 .mcp.json → 本地级 → CLI 参数",
|
|
100
|
+
"可选用 trust / alwaysAllow / includeTools / excludeTools 控制权限",
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
cursor: {
|
|
105
|
+
label: "Cursor",
|
|
106
|
+
docsUrl: "https://docs.cursor.com/context/model-context-protocol",
|
|
107
|
+
skills: { projectDir: ".cursor/skills", userDir: "~/.cursor/skills" },
|
|
108
|
+
mcp: {
|
|
109
|
+
projectFile: ".cursor/mcp.json",
|
|
110
|
+
userFile: "~/.cursor/mcp.json",
|
|
111
|
+
explicitType: false,
|
|
112
|
+
manualSteps: ["Cursor 设置 → MCP 中确认 review-mcp 已启用"],
|
|
113
|
+
notes: [],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
claude: {
|
|
117
|
+
label: "Claude Code",
|
|
118
|
+
docsUrl: "https://docs.claude.com/en/docs/claude-code/mcp",
|
|
119
|
+
skills: { projectDir: ".claude/skills", userDir: "~/.claude/skills" },
|
|
120
|
+
mcp: {
|
|
121
|
+
projectFile: ".mcp.json",
|
|
122
|
+
userFile: {
|
|
123
|
+
win32: "%APPDATA%/Claude/claude_desktop_config.json",
|
|
124
|
+
darwin: "~/Library/Application Support/Claude/claude_desktop_config.json",
|
|
125
|
+
linux: "~/.config/Claude/claude_desktop_config.json",
|
|
126
|
+
},
|
|
127
|
+
explicitType: false,
|
|
128
|
+
manualSteps: ["也可执行:claude mcp add review-mcp -- npx -y @reglow/review-mcp"],
|
|
129
|
+
notes: [],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
generic: {
|
|
133
|
+
label: "通用 .skills 目录",
|
|
134
|
+
docsUrl: "https://modelcontextprotocol.io/",
|
|
135
|
+
skills: { projectDir: ".skills", userDir: null },
|
|
136
|
+
mcp: {
|
|
137
|
+
projectFile: ".mcp.json",
|
|
138
|
+
userFile: null,
|
|
139
|
+
explicitType: true,
|
|
140
|
+
manualSteps: [
|
|
141
|
+
"按你所用的 MCP 客户端文档,把 .mcp.json 中的 mcpServers.review-mcp 条目加入其配置",
|
|
142
|
+
],
|
|
143
|
+
notes: [],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** 默认一键配置的目标(用户重点关注的三个工具) */
|
|
149
|
+
export const DEFAULT_SETUP_TOOLS = ["trae", "codebuddy", "qoder"];
|
|
150
|
+
|
|
151
|
+
/** 全部可配置工具(`--setup all`) */
|
|
152
|
+
export const ALL_TOOLS = Object.keys(TOOL_INTEGRATIONS);
|
|
153
|
+
|
|
154
|
+
/** 工具名归一化(大小写 / 别名) */
|
|
155
|
+
export function normalizeTool(name) {
|
|
156
|
+
const key = String(name ?? "")
|
|
157
|
+
.trim()
|
|
158
|
+
.toLowerCase();
|
|
159
|
+
if (key === "trae" || key === "trae-ide" || key === "traecli") return "trae";
|
|
160
|
+
if (
|
|
161
|
+
key === "codebuddy" ||
|
|
162
|
+
key === "code-buddy" ||
|
|
163
|
+
key === "tencent-codebuddy"
|
|
164
|
+
)
|
|
165
|
+
return "codebuddy";
|
|
166
|
+
if (key === "qoder" || key === "qoder-cli") return "qoder";
|
|
167
|
+
if (key === "claude" || key === "claude-code" || key === "claude-desktop")
|
|
168
|
+
return "claude";
|
|
169
|
+
if (key === "cursor") return "cursor";
|
|
170
|
+
if (key === "generic" || key === "mcp" || key === "all-mcp") return "generic";
|
|
171
|
+
return key;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 解析工具清单字符串(`trae,codebuddy,qoder` / `all`) */
|
|
175
|
+
export function parseTools(input) {
|
|
176
|
+
if (!input) return [...DEFAULT_SETUP_TOOLS];
|
|
177
|
+
const raw = String(input)
|
|
178
|
+
.split(/[,\s]+/)
|
|
179
|
+
.map((item) => item.trim())
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
if (raw.length === 0) return [...DEFAULT_SETUP_TOOLS];
|
|
182
|
+
const expanded = raw.includes("all") ? ALL_TOOLS : raw;
|
|
183
|
+
const tools = [];
|
|
184
|
+
for (const item of expanded) {
|
|
185
|
+
const tool = normalizeTool(item);
|
|
186
|
+
if (!TOOL_INTEGRATIONS[tool]) {
|
|
187
|
+
throw new Error(`未知工具「${item}」,可选:${ALL_TOOLS.join(" / ")}`);
|
|
188
|
+
}
|
|
189
|
+
if (!tools.includes(tool)) tools.push(tool);
|
|
190
|
+
}
|
|
191
|
+
return tools;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 展开 `~` 与 `%VAR%` / `$VAR`,得到绝对路径(统一使用 `/` 分隔,Windows 下 Node 同样接受) */
|
|
195
|
+
export function expandPath(
|
|
196
|
+
target,
|
|
197
|
+
{ cwd = process.cwd(), env = process.env, platform = process.platform } = {},
|
|
198
|
+
) {
|
|
199
|
+
let value = String(target ?? "");
|
|
200
|
+
value = value.replace(/%([^%]+)%/g, (_m, name) => env[name] ?? `%${name}%`);
|
|
201
|
+
if (value.startsWith("~/") || value === "~") {
|
|
202
|
+
const home = env.USERPROFILE || env.HOME || cwd;
|
|
203
|
+
value = value.replace(/^~/, home);
|
|
204
|
+
}
|
|
205
|
+
if (!value) return null;
|
|
206
|
+
const combined = /^([a-zA-Z]:[\\/]|[\\/]{1,2})/.test(value)
|
|
207
|
+
? value
|
|
208
|
+
: `${cwd}/${value}`;
|
|
209
|
+
return combined
|
|
210
|
+
.replace(/\\/g, "/")
|
|
211
|
+
.replace(/\/{2,}/g, "/")
|
|
212
|
+
.replace(/^([a-zA-Z]):\//, "$1:/");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** 取某工具在指定 scope 下的配置文件绝对路径 */
|
|
216
|
+
export function resolveConfigPath(tool, scope = "project", options = {}) {
|
|
217
|
+
const spec = TOOL_INTEGRATIONS[tool];
|
|
218
|
+
if (!spec) throw new Error(`未知工具「${tool}」`);
|
|
219
|
+
const cwd = options.cwd ?? process.cwd();
|
|
220
|
+
const platform = options.platform ?? process.platform;
|
|
221
|
+
if (scope === "user") {
|
|
222
|
+
const userFile = spec.mcp.userFile;
|
|
223
|
+
if (!userFile) return null;
|
|
224
|
+
const raw = typeof userFile === "string" ? userFile : userFile[platform];
|
|
225
|
+
if (!raw) return null;
|
|
226
|
+
return expandPath(raw, { ...options, cwd });
|
|
227
|
+
}
|
|
228
|
+
return expandPath(spec.mcp.projectFile, { ...options, cwd });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** 取某工具在指定 scope 下的 Skills 目录绝对路径 */
|
|
232
|
+
export function resolveSkillsDir(tool, scope = "project", options = {}) {
|
|
233
|
+
const spec = TOOL_INTEGRATIONS[tool];
|
|
234
|
+
if (!spec) throw new Error(`未知工具「${tool}」`);
|
|
235
|
+
const cwd = options.cwd ?? process.cwd();
|
|
236
|
+
const dir = scope === "user" ? spec.skills.userDir : spec.skills.projectDir;
|
|
237
|
+
if (!dir) return null;
|
|
238
|
+
return expandPath(dir, { ...options, cwd });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 构造写入 mcpServers 的服务器条目
|
|
243
|
+
*
|
|
244
|
+
* @param {string} tool 工具名
|
|
245
|
+
* @param {{ args?: string[], env?: Record<string, string> }} [options]
|
|
246
|
+
*/
|
|
247
|
+
export function buildMcpEntry(tool, options = {}) {
|
|
248
|
+
const spec = TOOL_INTEGRATIONS[tool];
|
|
249
|
+
if (!spec) throw new Error(`未知工具「${tool}」`);
|
|
250
|
+
const entry = {
|
|
251
|
+
command: "npx",
|
|
252
|
+
args: [...(options.args ?? DEFAULT_ARGS)],
|
|
253
|
+
};
|
|
254
|
+
if (spec.mcp.explicitType) {
|
|
255
|
+
entry.type = "stdio";
|
|
256
|
+
entry.description = SERVER_DESCRIPTION;
|
|
257
|
+
}
|
|
258
|
+
if (options.env && Object.keys(options.env).length > 0)
|
|
259
|
+
entry.env = { ...options.env };
|
|
260
|
+
return entry;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** `review-skills --list` 用的目标表(兼容旧行为:预设名 → 目录) */
|
|
264
|
+
export function skillTargetsFromIntegrations() {
|
|
265
|
+
const targets = {};
|
|
266
|
+
for (const [tool, spec] of Object.entries(TOOL_INTEGRATIONS)) {
|
|
267
|
+
targets[tool] = { dir: spec.skills.projectDir, label: spec.label };
|
|
268
|
+
}
|
|
269
|
+
return targets;
|
|
270
|
+
}
|