congmao-cli 1.0.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 +2 -0
- package/bin/index.js +20 -0
- package/package.json +42 -0
- package/src/commands/create.js +171 -0
- package/src/commands/mcp.js +157 -0
- package/src/mcp/CURSOR_SETUP.md +259 -0
- package/src/mcp/README.md +134 -0
- package/src/mcp/USAGE_EXAMPLES.md +181 -0
- package/src/mcp/get-cursor-config.js +58 -0
- package/src/mcp/package.json +23 -0
- package/src/mcp/pnpm-lock.yaml +2441 -0
- package/src/mcp/src/handlers.js +211 -0
- package/src/mcp/src/http-server.js +332 -0
- package/src/mcp/src/index.js +195 -0
- package/src/mcp/src/schemas.js +54 -0
- package/src/mcp/test-create-project.js +49 -0
- package/src/mcp/test-mcp.js +89 -0
- package/src/mcp/test-show-tree.js +66 -0
- package/src/template/Uni-app/README.md +19 -0
- package/src/template/Uni-app/babel.config.js +81 -0
- package/src/template/Uni-app/package.json +107 -0
- package/src/template/Uni-app/postcss.config.js +27 -0
- package/src/template/Uni-app/public/index.html +25 -0
- package/src/template/Uni-app/shims-uni.d.ts +11 -0
- package/src/template/Uni-app/shims-vue.d.ts +4 -0
- package/src/template/Uni-app/src/App.vue +17 -0
- package/src/template/Uni-app/src/main.js +12 -0
- package/src/template/Uni-app/src/manifest.json +75 -0
- package/src/template/Uni-app/src/pages/index/index.vue +49 -0
- package/src/template/Uni-app/src/pages.json +16 -0
- package/src/template/Uni-app/src/static/logo.png +0 -0
- package/src/template/Uni-app/src/uni.promisify.adaptor.js +13 -0
- package/src/template/Uni-app/src/uni.scss +76 -0
- package/src/template/Uni-app/yarn.lock +11466 -0
- package/src/template/Vue2/.editorconfig +14 -0
- package/src/template/Vue2/.env +2 -0
- package/src/template/Vue2/.env.development +2 -0
- package/src/template/Vue2/.env.site +2 -0
- package/src/template/Vue2/.prettierrc.js +39 -0
- package/src/template/Vue2/.stylelintignore +8 -0
- package/src/template/Vue2/README-zh_CN.md +115 -0
- package/src/template/Vue2/commitlint.config.js +1 -0
- package/src/template/Vue2/docs/docs-starter.png +0 -0
- package/src/template/Vue2/docs/docs-startup.png +0 -0
- package/src/template/Vue2/docs/docs-structure.png +0 -0
- package/src/template/Vue2/globals.d.ts +13 -0
- package/src/template/Vue2/index.html +27 -0
- package/src/template/Vue2/jsx.d.ts +13 -0
- package/src/template/Vue2/mock/index.ts +147 -0
- package/src/template/Vue2/package.json +91 -0
- package/src/template/Vue2/package.json.ejs +91 -0
- package/src/template/Vue2/public/favicon.ico +0 -0
- package/src/template/Vue2/shims-vue.d.ts +5 -0
- package/src/template/Vue2/src/App.vue +19 -0
- package/src/template/Vue2/src/assets/assets-login-bg-black.png +0 -0
- package/src/template/Vue2/src/assets/assets-login-bg-white.png +0 -0
- package/src/template/Vue2/src/assets/assets-logo-full.svg +39 -0
- package/src/template/Vue2/src/assets/assets-product-1.svg +5 -0
- package/src/template/Vue2/src/assets/assets-product-2.svg +5 -0
- package/src/template/Vue2/src/assets/assets-product-3.svg +5 -0
- package/src/template/Vue2/src/assets/assets-product-4.svg +5 -0
- package/src/template/Vue2/src/assets/assets-result-403.svg +32 -0
- package/src/template/Vue2/src/assets/assets-result-404.svg +36 -0
- package/src/template/Vue2/src/assets/assets-result-500.svg +32 -0
- package/src/template/Vue2/src/assets/assets-result-ie.svg +33 -0
- package/src/template/Vue2/src/assets/assets-result-maintenance.svg +49 -0
- package/src/template/Vue2/src/assets/assets-result-wifi.svg +23 -0
- package/src/template/Vue2/src/assets/assets-setting-auto.svg +13 -0
- package/src/template/Vue2/src/assets/assets-setting-dark.svg +5 -0
- package/src/template/Vue2/src/assets/assets-setting-light.svg +13 -0
- package/src/template/Vue2/src/assets/assets-t-logo.svg +41 -0
- package/src/template/Vue2/src/assets/assets-tencent-logo.png +0 -0
- package/src/template/Vue2/src/components/color/index.vue +35 -0
- package/src/template/Vue2/src/components/product-card/index.vue +121 -0
- package/src/template/Vue2/src/components/result/index.vue +118 -0
- package/src/template/Vue2/src/components/thumbnail/index.vue +49 -0
- package/src/template/Vue2/src/components/trend/index.vue +105 -0
- package/src/template/Vue2/src/config/color.ts +30 -0
- package/src/template/Vue2/src/config/global.ts +2 -0
- package/src/template/Vue2/src/config/host.ts +26 -0
- package/src/template/Vue2/src/config/style.ts +14 -0
- package/src/template/Vue2/src/constants/index.ts +46 -0
- package/src/template/Vue2/src/interface.ts +39 -0
- package/src/template/Vue2/src/layouts/blank.vue +12 -0
- package/src/template/Vue2/src/layouts/components/Breadcrumb.vue +39 -0
- package/src/template/Vue2/src/layouts/components/Content.vue +43 -0
- package/src/template/Vue2/src/layouts/components/Footer.vue +27 -0
- package/src/template/Vue2/src/layouts/components/Header.vue +321 -0
- package/src/template/Vue2/src/layouts/components/LayoutContent.vue +168 -0
- package/src/template/Vue2/src/layouts/components/LayoutHeader.vue +52 -0
- package/src/template/Vue2/src/layouts/components/LayoutSidebar.vue +51 -0
- package/src/template/Vue2/src/layouts/components/MenuContent.vue +108 -0
- package/src/template/Vue2/src/layouts/components/Notice.vue +221 -0
- package/src/template/Vue2/src/layouts/components/Search.vue +134 -0
- package/src/template/Vue2/src/layouts/components/SideNav.vue +150 -0
- package/src/template/Vue2/src/layouts/index.vue +100 -0
- package/src/template/Vue2/src/layouts/setting.vue +404 -0
- package/src/template/Vue2/src/main.js +9 -0
- package/src/template/Vue2/src/main.jsx +51 -0
- package/src/template/Vue2/src/pages/dashboard/base/components/MiddleChart.vue +158 -0
- package/src/template/Vue2/src/pages/dashboard/base/components/OutputOverview.vue +189 -0
- package/src/template/Vue2/src/pages/dashboard/base/components/RankList.vue +111 -0
- package/src/template/Vue2/src/pages/dashboard/base/components/TopPanel.vue +246 -0
- package/src/template/Vue2/src/pages/dashboard/base/index.ts +702 -0
- package/src/template/Vue2/src/pages/dashboard/base/index.vue +44 -0
- package/src/template/Vue2/src/pages/dashboard/detail/index.ts +267 -0
- package/src/template/Vue2/src/pages/dashboard/detail/index.vue +242 -0
- package/src/template/Vue2/src/pages/detail/advanced/components/Product.vue +167 -0
- package/src/template/Vue2/src/pages/detail/advanced/index.less +74 -0
- package/src/template/Vue2/src/pages/detail/advanced/index.vue +219 -0
- package/src/template/Vue2/src/pages/detail/base/index.less +105 -0
- package/src/template/Vue2/src/pages/detail/base/index.vue +46 -0
- package/src/template/Vue2/src/pages/detail/deploy/index.ts +204 -0
- package/src/template/Vue2/src/pages/detail/deploy/index.vue +224 -0
- package/src/template/Vue2/src/pages/detail/secondary/index.less +71 -0
- package/src/template/Vue2/src/pages/detail/secondary/index.vue +131 -0
- package/src/template/Vue2/src/pages/form/base/index.less +57 -0
- package/src/template/Vue2/src/pages/form/base/index.vue +254 -0
- package/src/template/Vue2/src/pages/form/step/index.less +37 -0
- package/src/template/Vue2/src/pages/form/step/index.vue +259 -0
- package/src/template/Vue2/src/pages/frame/doc/index.vue +86 -0
- package/src/template/Vue2/src/pages/frame/tdesign/index.vue +86 -0
- package/src/template/Vue2/src/pages/list/base/index.vue +267 -0
- package/src/template/Vue2/src/pages/list/card/index.vue +221 -0
- package/src/template/Vue2/src/pages/list/components/CommonTable.vue +313 -0
- package/src/template/Vue2/src/pages/list/filter/index.vue +15 -0
- package/src/template/Vue2/src/pages/list/tree/index.vue +174 -0
- package/src/template/Vue2/src/pages/login/components/components-header.vue +74 -0
- package/src/template/Vue2/src/pages/login/components/components-login.vue +154 -0
- package/src/template/Vue2/src/pages/login/components/components-register.vue +144 -0
- package/src/template/Vue2/src/pages/login/index.less +202 -0
- package/src/template/Vue2/src/pages/login/index.vue +53 -0
- package/src/template/Vue2/src/pages/nest-menu/Index.vue +10 -0
- package/src/template/Vue2/src/pages/result/403/index.vue +14 -0
- package/src/template/Vue2/src/pages/result/404/index.vue +14 -0
- package/src/template/Vue2/src/pages/result/500/index.vue +14 -0
- package/src/template/Vue2/src/pages/result/browser-incompatible/index.vue +77 -0
- package/src/template/Vue2/src/pages/result/fail/index.vue +57 -0
- package/src/template/Vue2/src/pages/result/maintenance/index.vue +14 -0
- package/src/template/Vue2/src/pages/result/network-error/index.vue +24 -0
- package/src/template/Vue2/src/pages/result/success/index.vue +59 -0
- package/src/template/Vue2/src/pages/user/index.less +148 -0
- package/src/template/Vue2/src/pages/user/index.ts +157 -0
- package/src/template/Vue2/src/pages/user/index.vue +204 -0
- package/src/template/Vue2/src/permission.js +56 -0
- package/src/template/Vue2/src/router/index.js +43 -0
- package/src/template/Vue2/src/router/modules/base.ts +29 -0
- package/src/template/Vue2/src/router/modules/components.ts +175 -0
- package/src/template/Vue2/src/router/modules/others.ts +55 -0
- package/src/template/Vue2/src/service/service-advance.ts +233 -0
- package/src/template/Vue2/src/service/service-base.ts +205 -0
- package/src/template/Vue2/src/service/service-detail-base.ts +84 -0
- package/src/template/Vue2/src/service/service-detail-deploy.ts +234 -0
- package/src/template/Vue2/src/service/service-detail.ts +57 -0
- package/src/template/Vue2/src/service/service-user.ts +64 -0
- package/src/template/Vue2/src/store/index.ts +22 -0
- package/src/template/Vue2/src/store/modules/notification.ts +90 -0
- package/src/template/Vue2/src/store/modules/permission.ts +66 -0
- package/src/template/Vue2/src/store/modules/setting.ts +122 -0
- package/src/template/Vue2/src/store/modules/tab-router.ts +83 -0
- package/src/template/Vue2/src/store/modules/user.ts +98 -0
- package/src/template/Vue2/src/style/font-family.less +6 -0
- package/src/template/Vue2/src/style/index.less +5 -0
- package/src/template/Vue2/src/style/layout.less +201 -0
- package/src/template/Vue2/src/style/reset.less +78 -0
- package/src/template/Vue2/src/style/variables.less +27 -0
- package/src/template/Vue2/src/utils/charts.ts +38 -0
- package/src/template/Vue2/src/utils/color.ts +118 -0
- package/src/template/Vue2/src/utils/date.ts +12 -0
- package/src/template/Vue2/src/utils/request.ts +60 -0
- package/src/template/Vue2/stylelint.config.js +5 -0
- package/src/template/Vue2/tsconfig.json +26 -0
- package/src/template/Vue2/vite.config.js +58 -0
- package/src/template/Vue3/package.json.ejs +8 -0
- package/src/template/Vue3/pages.json +10 -0
- package/src/template/Vue3/src/main.js +7 -0
- package/src/utils/copy.js +17 -0
- package/src/utils/eslint.js +205 -0
- package/src/utils/logo.js +18 -0
- package/src/utils/render.js +20 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import copyDir from "../../utils/copy.js";
|
|
5
|
+
import renderTemplates from "../../utils/render.js";
|
|
6
|
+
import setupESLint from "../../utils/eslint.js";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 根据模板类型获取启动命令
|
|
13
|
+
*/
|
|
14
|
+
function getStartCommand(templateName, needESLint) {
|
|
15
|
+
const commands = {
|
|
16
|
+
'Uni-app': {
|
|
17
|
+
install: 'pnpm install',
|
|
18
|
+
lint: needESLint ? 'pnpm run lint' : null,
|
|
19
|
+
dev: 'pnpm run dev:h5'
|
|
20
|
+
},
|
|
21
|
+
'Vue2': {
|
|
22
|
+
install: 'pnpm install',
|
|
23
|
+
lint: needESLint ? 'pnpm run lint' : null,
|
|
24
|
+
dev: 'pnpm run dev'
|
|
25
|
+
},
|
|
26
|
+
'Vue3': {
|
|
27
|
+
install: 'pnpm install',
|
|
28
|
+
lint: needESLint ? 'pnpm run lint' : null,
|
|
29
|
+
dev: 'pnpm run dev'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return commands[templateName] || commands['Vue2'];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 快速创建项目
|
|
38
|
+
*/
|
|
39
|
+
export async function createProject(input) {
|
|
40
|
+
try {
|
|
41
|
+
const {
|
|
42
|
+
projectName,
|
|
43
|
+
projectType,
|
|
44
|
+
vueVersion,
|
|
45
|
+
needESLint = true,
|
|
46
|
+
targetPath = process.cwd(),
|
|
47
|
+
} = input;
|
|
48
|
+
|
|
49
|
+
// 验证项目名称
|
|
50
|
+
if (!/^[a-z0-9-]+$/.test(projectName)) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: "项目名称只能包含小写字母、数字和连字符",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 确定模板名称
|
|
58
|
+
let templateName;
|
|
59
|
+
if (projectType === 'mobile' || projectType === 'miniprogram') {
|
|
60
|
+
templateName = 'Uni-app';
|
|
61
|
+
} else if (projectType === 'admin') {
|
|
62
|
+
if (!vueVersion) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: "后台管理系统需要指定Vue版本",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
templateName = vueVersion;
|
|
69
|
+
} else {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
error: `不支持的项目类型: ${projectType}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 检查目标目录
|
|
77
|
+
const targetDir = path.resolve(targetPath, projectName);
|
|
78
|
+
if (fs.existsSync(targetDir)) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: `目录已存在: ${targetDir}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 获取模板目录
|
|
86
|
+
const templateDir = path.resolve(
|
|
87
|
+
__dirname,
|
|
88
|
+
`../../template/${templateName}`
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!fs.existsSync(templateDir)) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: `模板不存在: ${templateDir}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 复制模板
|
|
99
|
+
copyDir(templateDir, targetDir);
|
|
100
|
+
|
|
101
|
+
// 渲染模板
|
|
102
|
+
renderTemplates(targetDir, { projectName, needESLint });
|
|
103
|
+
|
|
104
|
+
// 配置 ESLint
|
|
105
|
+
if (needESLint) {
|
|
106
|
+
await setupESLint(targetDir, templateName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 获取启动命令
|
|
110
|
+
const startCmd = getStartCommand(templateName, needESLint);
|
|
111
|
+
let installCmd = `cd ${projectName}\n${startCmd.install}`;
|
|
112
|
+
|
|
113
|
+
if (startCmd.lint) {
|
|
114
|
+
installCmd += `\n${startCmd.lint}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
installCmd += `\n${startCmd.dev}`;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
message: `项目创建成功!\n\n${installCmd}`,
|
|
122
|
+
projectPath: targetDir,
|
|
123
|
+
templateName,
|
|
124
|
+
startCommands: installCmd,
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: `创建项目失败: ${error.message}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 生成项目结构树
|
|
136
|
+
*/
|
|
137
|
+
function generateTree(dir, prefix = "", maxDepth = 3, currentDepth = 0, ignorePatterns = []) {
|
|
138
|
+
if (currentDepth >= maxDepth) {
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const items = fs.readdirSync(dir).filter(item => {
|
|
143
|
+
// 忽略隐藏文件和指定模式
|
|
144
|
+
if (item.startsWith('.')) return false;
|
|
145
|
+
return !ignorePatterns.some(pattern => item.includes(pattern));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
let tree = "";
|
|
149
|
+
items.forEach((item, index) => {
|
|
150
|
+
const itemPath = path.join(dir, item);
|
|
151
|
+
const isLast = index === items.length - 1;
|
|
152
|
+
const stat = fs.statSync(itemPath);
|
|
153
|
+
|
|
154
|
+
const connector = isLast ? "└── " : "├── ";
|
|
155
|
+
tree += prefix + connector + item + "\n";
|
|
156
|
+
|
|
157
|
+
if (stat.isDirectory() && currentDepth < maxDepth - 1) {
|
|
158
|
+
const nextPrefix = prefix + (isLast ? " " : "│ ");
|
|
159
|
+
tree += generateTree(itemPath, nextPrefix, maxDepth, currentDepth + 1, ignorePatterns);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return tree;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 显示项目结构树
|
|
168
|
+
*/
|
|
169
|
+
export async function showProjectTree(input) {
|
|
170
|
+
try {
|
|
171
|
+
const {
|
|
172
|
+
projectPath,
|
|
173
|
+
maxDepth = 3,
|
|
174
|
+
ignorePatterns = ["node_modules", ".git", "dist", "build"],
|
|
175
|
+
} = input;
|
|
176
|
+
|
|
177
|
+
const resolvedPath = path.isAbsolute(projectPath)
|
|
178
|
+
? projectPath
|
|
179
|
+
: path.resolve(process.cwd(), projectPath);
|
|
180
|
+
|
|
181
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
error: `路径不存在: ${resolvedPath}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!fs.statSync(resolvedPath).isDirectory()) {
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: `路径不是目录: ${resolvedPath}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const tree = generateTree(resolvedPath, "", maxDepth, 0, ignorePatterns);
|
|
196
|
+
const projectName = path.basename(resolvedPath);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
message: `${projectName}\n${tree}`,
|
|
201
|
+
tree,
|
|
202
|
+
projectPath: resolvedPath,
|
|
203
|
+
};
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: `生成项目结构树失败: ${error.message}`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP 版本的 MCP 服务器
|
|
5
|
+
* 通过 HTTP 端口提供服务
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import http from "node:http";
|
|
9
|
+
import packageJson from "../package.json" assert { type: "json" };
|
|
10
|
+
import { createProject, showProjectTree } from "./handlers.js";
|
|
11
|
+
import { validateCreateProject, validateShowProjectTree } from "./schemas.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 获取可用工具列表
|
|
15
|
+
*/
|
|
16
|
+
function getToolsList() {
|
|
17
|
+
return {
|
|
18
|
+
tools: [
|
|
19
|
+
{
|
|
20
|
+
name: "create-project",
|
|
21
|
+
description:
|
|
22
|
+
"快速创建项目。支持创建移动端(Uni-app)、小程序(Uni-app)、后台管理系统(Vue2/Vue3)项目。可以自动配置ESLint。",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
projectName: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "项目名称,只能包含小写字母、数字和连字符",
|
|
29
|
+
},
|
|
30
|
+
projectType: {
|
|
31
|
+
type: "string",
|
|
32
|
+
enum: ["mobile", "miniprogram", "admin"],
|
|
33
|
+
description: "项目类型:mobile-移动端, miniprogram-小程序, admin-后台管理系统",
|
|
34
|
+
},
|
|
35
|
+
vueVersion: {
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: ["Vue2", "Vue3"],
|
|
38
|
+
description: "Vue版本(仅当projectType为admin时需要)",
|
|
39
|
+
},
|
|
40
|
+
needESLint: {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "是否配置ESLint,默认为true",
|
|
43
|
+
default: true,
|
|
44
|
+
},
|
|
45
|
+
targetPath: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "目标路径,默认为当前工作目录",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ["projectName", "projectType"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "show-project-tree",
|
|
55
|
+
description:
|
|
56
|
+
"查看项目结构树。可以显示指定项目的目录结构,支持自定义深度和忽略模式。",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
projectPath: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "项目路径,可以是相对路径或绝对路径",
|
|
63
|
+
},
|
|
64
|
+
maxDepth: {
|
|
65
|
+
type: "number",
|
|
66
|
+
description: "最大深度,默认为3",
|
|
67
|
+
default: 3,
|
|
68
|
+
},
|
|
69
|
+
ignorePatterns: {
|
|
70
|
+
type: "array",
|
|
71
|
+
items: {
|
|
72
|
+
type: "string",
|
|
73
|
+
},
|
|
74
|
+
description: "忽略的目录模式,默认为['node_modules', '.git', 'dist', 'build']",
|
|
75
|
+
default: ["node_modules", ".git", "dist", "build"],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ["projectPath"],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 处理工具调用
|
|
87
|
+
*/
|
|
88
|
+
async function handleToolCall(name, args) {
|
|
89
|
+
try {
|
|
90
|
+
switch (name) {
|
|
91
|
+
case "create-project": {
|
|
92
|
+
const validatedInput = validateCreateProject(args);
|
|
93
|
+
const result = await createProject(validatedInput);
|
|
94
|
+
|
|
95
|
+
if (result.success) {
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: result.message || "项目创建成功!",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `错误: ${result.error}`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
isError: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case "show-project-tree": {
|
|
118
|
+
const validatedInput = validateShowProjectTree(args);
|
|
119
|
+
const result = await showProjectTree(validatedInput);
|
|
120
|
+
|
|
121
|
+
if (result.success) {
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: "text",
|
|
126
|
+
text: result.message || result.tree || "项目结构树",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
} else {
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: `错误: ${result.error}`,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
default:
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: `未知的工具: ${name}`,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
isError: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return {
|
|
156
|
+
content: [
|
|
157
|
+
{
|
|
158
|
+
type: "text",
|
|
159
|
+
text: `工具执行失败: ${error.message}`,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
isError: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 查找可用端口
|
|
169
|
+
*/
|
|
170
|
+
export async function findAvailablePort(startPort = 3000, maxPort = 3100) {
|
|
171
|
+
const net = await import("node:net");
|
|
172
|
+
|
|
173
|
+
for (let port = startPort; port <= maxPort; port++) {
|
|
174
|
+
const isAvailable = await new Promise((resolve) => {
|
|
175
|
+
const server = net.createServer();
|
|
176
|
+
server.listen(port, () => {
|
|
177
|
+
server.once("close", () => resolve(true));
|
|
178
|
+
server.close();
|
|
179
|
+
});
|
|
180
|
+
server.on("error", () => resolve(false));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (isAvailable) {
|
|
184
|
+
return port;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 如果指定范围内没有可用端口,让系统自动分配(端口0)
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 创建 HTTP 服务器
|
|
194
|
+
*/
|
|
195
|
+
export async function createHTTPServer(port = 0) {
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
198
|
+
// 设置 CORS 头
|
|
199
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
200
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
201
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
202
|
+
|
|
203
|
+
if (req.method === "OPTIONS") {
|
|
204
|
+
res.writeHead(200);
|
|
205
|
+
res.end();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
210
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
211
|
+
res.end(JSON.stringify({ status: "ok", service: "cm-cli-mcp" }));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (req.method === "POST" && req.url === "/mcp") {
|
|
216
|
+
try {
|
|
217
|
+
let body = "";
|
|
218
|
+
req.on("data", (chunk) => {
|
|
219
|
+
body += chunk.toString();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
req.on("end", async () => {
|
|
223
|
+
try {
|
|
224
|
+
const request = JSON.parse(body);
|
|
225
|
+
let response;
|
|
226
|
+
|
|
227
|
+
// 处理 MCP 协议请求
|
|
228
|
+
if (request.method === "tools/list") {
|
|
229
|
+
// 列出可用工具
|
|
230
|
+
const result = getToolsList();
|
|
231
|
+
response = {
|
|
232
|
+
jsonrpc: "2.0",
|
|
233
|
+
id: request.id,
|
|
234
|
+
result: result,
|
|
235
|
+
};
|
|
236
|
+
} else if (request.method === "tools/call") {
|
|
237
|
+
// 调用工具
|
|
238
|
+
const { name, arguments: args } = request.params || {};
|
|
239
|
+
const result = await handleToolCall(name, args);
|
|
240
|
+
response = {
|
|
241
|
+
jsonrpc: "2.0",
|
|
242
|
+
id: request.id,
|
|
243
|
+
result: result,
|
|
244
|
+
};
|
|
245
|
+
} else {
|
|
246
|
+
response = {
|
|
247
|
+
jsonrpc: "2.0",
|
|
248
|
+
id: request.id,
|
|
249
|
+
error: {
|
|
250
|
+
code: -32601,
|
|
251
|
+
message: `Method not found: ${request.method}`,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
257
|
+
res.end(JSON.stringify(response));
|
|
258
|
+
} catch (error) {
|
|
259
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
260
|
+
res.end(JSON.stringify({
|
|
261
|
+
jsonrpc: "2.0",
|
|
262
|
+
id: request?.id || null,
|
|
263
|
+
error: {
|
|
264
|
+
code: -32603,
|
|
265
|
+
message: error.message
|
|
266
|
+
}
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
272
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
278
|
+
res.end(JSON.stringify({ error: "Not Found" }));
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
httpServer.listen(port, "0.0.0.0", () => {
|
|
282
|
+
resolve(httpServer);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
httpServer.on("error", (error) => {
|
|
286
|
+
reject(error);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 启动 HTTP 服务器
|
|
293
|
+
*/
|
|
294
|
+
export async function startHTTPServer(requestedPort = 0) {
|
|
295
|
+
try {
|
|
296
|
+
let port = requestedPort;
|
|
297
|
+
|
|
298
|
+
// 如果端口为0或未指定,自动查找可用端口
|
|
299
|
+
if (port === 0 || !port) {
|
|
300
|
+
port = await findAvailablePort(3000, 3100);
|
|
301
|
+
// 如果找不到可用端口,使用系统自动分配(端口0)
|
|
302
|
+
if (port === 0) {
|
|
303
|
+
port = 0; // 让系统自动分配
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const httpServer = await createHTTPServer(port);
|
|
308
|
+
const address = httpServer.address();
|
|
309
|
+
const actualPort = address.port;
|
|
310
|
+
const url = `http://localhost:${actualPort}`;
|
|
311
|
+
|
|
312
|
+
console.error(`CM CLI MCP HTTP 服务器已启动`);
|
|
313
|
+
console.error(`监听地址: ${url}`);
|
|
314
|
+
console.error(`健康检查: ${url}/health`);
|
|
315
|
+
console.error(`MCP 端点: ${url}/mcp`);
|
|
316
|
+
|
|
317
|
+
return { httpServer, url, port: actualPort };
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error.code === "EADDRINUSE") {
|
|
320
|
+
// 如果指定端口被占用,尝试自动查找可用端口
|
|
321
|
+
console.error(`端口 ${requestedPort} 已被占用,正在查找可用端口...`);
|
|
322
|
+
const availablePort = await findAvailablePort(3000, 3100);
|
|
323
|
+
if (availablePort === 0) {
|
|
324
|
+
throw new Error(`无法找到可用端口(3000-3100 范围)`);
|
|
325
|
+
}
|
|
326
|
+
console.error(`找到可用端口: ${availablePort}`);
|
|
327
|
+
return startHTTPServer(availablePort);
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|