@xiaozhi-client/cli 1.9.4-beta.10
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 +98 -0
- package/package.json +31 -0
- package/project.json +75 -0
- package/src/Constants.ts +105 -0
- package/src/Container.ts +212 -0
- package/src/Types.ts +79 -0
- package/src/commands/CommandHandlerFactory.ts +98 -0
- package/src/commands/ConfigCommandHandler.ts +279 -0
- package/src/commands/EndpointCommandHandler.ts +158 -0
- package/src/commands/McpCommandHandler.ts +778 -0
- package/src/commands/ProjectCommandHandler.ts +254 -0
- package/src/commands/ServiceCommandHandler.ts +182 -0
- package/src/commands/__tests__/CommandHandlerFactory.test.ts +323 -0
- package/src/commands/__tests__/CommandRegistry.test.ts +287 -0
- package/src/commands/__tests__/ConfigCommandHandler.test.ts +844 -0
- package/src/commands/__tests__/EndpointCommandHandler.test.ts +426 -0
- package/src/commands/__tests__/McpCommandHandler.test.ts +753 -0
- package/src/commands/__tests__/ProjectCommandHandler.test.ts +230 -0
- package/src/commands/__tests__/ServiceCommands.integration.test.ts +408 -0
- package/src/commands/index.ts +351 -0
- package/src/errors/ErrorHandlers.ts +141 -0
- package/src/errors/ErrorMessages.ts +121 -0
- package/src/errors/__tests__/index.test.ts +186 -0
- package/src/errors/index.ts +163 -0
- package/src/global.d.ts +19 -0
- package/src/index.ts +53 -0
- package/src/interfaces/Command.ts +128 -0
- package/src/interfaces/CommandTypes.ts +95 -0
- package/src/interfaces/Config.ts +25 -0
- package/src/interfaces/Service.ts +99 -0
- package/src/services/DaemonManager.ts +318 -0
- package/src/services/ProcessManager.ts +235 -0
- package/src/services/ServiceManager.ts +319 -0
- package/src/services/TemplateManager.ts +382 -0
- package/src/services/__tests__/DaemonManager.test.ts +378 -0
- package/src/services/__tests__/DaemonMode.integration.test.ts +321 -0
- package/src/services/__tests__/ProcessManager.test.ts +296 -0
- package/src/services/__tests__/ServiceManager.test.ts +774 -0
- package/src/services/__tests__/TemplateManager.test.ts +337 -0
- package/src/types/backend.d.ts +48 -0
- package/src/utils/FileUtils.ts +320 -0
- package/src/utils/FormatUtils.ts +198 -0
- package/src/utils/PathUtils.ts +255 -0
- package/src/utils/PlatformUtils.ts +217 -0
- package/src/utils/Validation.ts +274 -0
- package/src/utils/VersionUtils.ts +141 -0
- package/src/utils/__tests__/FileUtils.test.ts +728 -0
- package/src/utils/__tests__/FormatUtils.test.ts +243 -0
- package/src/utils/__tests__/PathUtils.test.ts +1165 -0
- package/src/utils/__tests__/PlatformUtils.test.ts +723 -0
- package/src/utils/__tests__/Validation.test.ts +560 -0
- package/src/utils/__tests__/VersionUtils.test.ts +410 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +100 -0
- package/vitest.config.ts +97 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目管理命令处理器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import type { CommandOption } from "../interfaces/Command";
|
|
9
|
+
import { BaseCommandHandler } from "../interfaces/Command";
|
|
10
|
+
import type { IDIContainer } from "../interfaces/Config";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 项目管理命令处理器
|
|
14
|
+
*/
|
|
15
|
+
export class ProjectCommandHandler extends BaseCommandHandler {
|
|
16
|
+
override name = "create";
|
|
17
|
+
override description = "创建项目";
|
|
18
|
+
|
|
19
|
+
override options: CommandOption[] = [
|
|
20
|
+
{
|
|
21
|
+
flags: "-t, --template <templateName>",
|
|
22
|
+
description: "使用指定模板创建项目",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
constructor(container: IDIContainer) {
|
|
27
|
+
super(container);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 执行创建项目命令
|
|
32
|
+
*/
|
|
33
|
+
async execute(args: any[], options: any): Promise<void> {
|
|
34
|
+
this.validateArgs(args, 1);
|
|
35
|
+
const projectName = args[0];
|
|
36
|
+
|
|
37
|
+
await this.handleCreate(projectName, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 处理创建项目命令
|
|
42
|
+
*/
|
|
43
|
+
protected async handleCreate(
|
|
44
|
+
projectName: string,
|
|
45
|
+
options: any
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const spinner = ora("初始化项目...").start();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const templateManager = this.getService<any>("templateManager");
|
|
51
|
+
const fileUtils = this.getService<any>("fileUtils");
|
|
52
|
+
|
|
53
|
+
// 确定目标目录
|
|
54
|
+
const targetPath = path.join(process.cwd(), projectName);
|
|
55
|
+
|
|
56
|
+
// 检查目标目录是否已存在
|
|
57
|
+
if (await fileUtils.exists(targetPath)) {
|
|
58
|
+
spinner.fail(`目录 "${projectName}" 已存在`);
|
|
59
|
+
console.log(
|
|
60
|
+
chalk.yellow("💡 提示: 请选择不同的项目名称或删除现有目录")
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (options.template) {
|
|
66
|
+
// 使用模板创建项目
|
|
67
|
+
await this.createFromTemplate(
|
|
68
|
+
projectName,
|
|
69
|
+
options.template,
|
|
70
|
+
targetPath,
|
|
71
|
+
spinner,
|
|
72
|
+
templateManager
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
// 创建基本项目(只有配置文件)
|
|
76
|
+
await this.createBasicProject(
|
|
77
|
+
projectName,
|
|
78
|
+
targetPath,
|
|
79
|
+
spinner,
|
|
80
|
+
templateManager
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
spinner.fail(
|
|
85
|
+
`创建项目失败: ${error instanceof Error ? error.message : String(error)}`
|
|
86
|
+
);
|
|
87
|
+
this.handleError(error as Error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 从模板创建项目
|
|
93
|
+
*/
|
|
94
|
+
private async createFromTemplate(
|
|
95
|
+
projectName: string,
|
|
96
|
+
templateName: string,
|
|
97
|
+
targetPath: string,
|
|
98
|
+
spinner: any,
|
|
99
|
+
templateManager: any
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
spinner.text = "检查模板...";
|
|
102
|
+
|
|
103
|
+
// 获取可用模板列表
|
|
104
|
+
const availableTemplates = await templateManager.getAvailableTemplates();
|
|
105
|
+
|
|
106
|
+
if (availableTemplates.length === 0) {
|
|
107
|
+
spinner.fail("找不到可用模板");
|
|
108
|
+
console.log(chalk.yellow("💡 提示: 请确保 xiaozhi-client 正确安装"));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 使用局部变量避免重新赋值函数参数
|
|
113
|
+
let actualTemplateName = templateName;
|
|
114
|
+
|
|
115
|
+
// 验证模板是否存在
|
|
116
|
+
const isValidTemplate =
|
|
117
|
+
await templateManager.validateTemplate(actualTemplateName);
|
|
118
|
+
if (!isValidTemplate) {
|
|
119
|
+
spinner.fail(`模板 "${actualTemplateName}" 不存在`);
|
|
120
|
+
|
|
121
|
+
// 尝试找到相似的模板
|
|
122
|
+
const similarTemplate = this.findSimilarTemplate(
|
|
123
|
+
actualTemplateName,
|
|
124
|
+
availableTemplates
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (similarTemplate) {
|
|
128
|
+
console.log(
|
|
129
|
+
chalk.yellow(`💡 你是想使用模板 "${similarTemplate}" 吗?`)
|
|
130
|
+
);
|
|
131
|
+
const confirmed = await this.askUserConfirmation(
|
|
132
|
+
chalk.cyan("确认使用此模板?(y/n): ")
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (confirmed) {
|
|
136
|
+
actualTemplateName = similarTemplate;
|
|
137
|
+
} else {
|
|
138
|
+
this.showAvailableTemplates(availableTemplates);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
this.showAvailableTemplates(availableTemplates);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
spinner.text = `从模板 "${actualTemplateName}" 创建项目 "${projectName}"...`;
|
|
148
|
+
|
|
149
|
+
// 使用模板管理器创建项目
|
|
150
|
+
await templateManager.createProject({
|
|
151
|
+
templateName: actualTemplateName,
|
|
152
|
+
targetPath,
|
|
153
|
+
projectName,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
spinner.succeed(`项目 "${projectName}" 创建成功`);
|
|
157
|
+
|
|
158
|
+
console.log(chalk.green("✅ 项目创建完成!"));
|
|
159
|
+
console.log(chalk.yellow("📝 接下来的步骤:"));
|
|
160
|
+
console.log(chalk.gray(` cd ${projectName}`));
|
|
161
|
+
console.log(chalk.gray(" pnpm install # 安装依赖"));
|
|
162
|
+
console.log(chalk.gray(" # 编辑 xiaozhi.config.json 设置你的 MCP 端点"));
|
|
163
|
+
console.log(chalk.gray(" xiaozhi start # 启动服务"));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 创建基本项目
|
|
168
|
+
*/
|
|
169
|
+
private async createBasicProject(
|
|
170
|
+
projectName: string,
|
|
171
|
+
targetPath: string,
|
|
172
|
+
spinner: any,
|
|
173
|
+
templateManager: any
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
spinner.text = `创建基本项目 "${projectName}"...`;
|
|
176
|
+
|
|
177
|
+
// 使用模板管理器创建基本项目
|
|
178
|
+
await templateManager.createProject({
|
|
179
|
+
templateName: null, // 表示创建基本项目
|
|
180
|
+
targetPath,
|
|
181
|
+
projectName,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
spinner.succeed(`项目 "${projectName}" 创建成功`);
|
|
185
|
+
|
|
186
|
+
console.log(chalk.green("✅ 基本项目创建完成!"));
|
|
187
|
+
console.log(chalk.yellow("📝 接下来的步骤:"));
|
|
188
|
+
console.log(chalk.gray(` cd ${projectName}`));
|
|
189
|
+
console.log(
|
|
190
|
+
chalk.gray(" # 编辑 xiaozhi.config.json 设置你的 MCP 端点和服务")
|
|
191
|
+
);
|
|
192
|
+
console.log(chalk.gray(" xiaozhi start # 启动服务"));
|
|
193
|
+
console.log(
|
|
194
|
+
chalk.yellow("💡 提示: 使用 --template 选项可以从模板创建项目")
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 显示可用模板
|
|
200
|
+
*/
|
|
201
|
+
private showAvailableTemplates(templates: string[]): void {
|
|
202
|
+
console.log(chalk.yellow("可用的模板:"));
|
|
203
|
+
for (const template of templates) {
|
|
204
|
+
console.log(chalk.gray(` - ${template}`));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 查找相似模板
|
|
210
|
+
*/
|
|
211
|
+
private findSimilarTemplate(
|
|
212
|
+
input: string,
|
|
213
|
+
templates: string[]
|
|
214
|
+
): string | null {
|
|
215
|
+
const formatUtils = this.getService<any>("formatUtils");
|
|
216
|
+
|
|
217
|
+
let bestMatch = null;
|
|
218
|
+
let bestSimilarity = 0;
|
|
219
|
+
|
|
220
|
+
for (const template of templates) {
|
|
221
|
+
const similarity = formatUtils.calculateSimilarity(
|
|
222
|
+
input.toLowerCase(),
|
|
223
|
+
template.toLowerCase()
|
|
224
|
+
);
|
|
225
|
+
if (similarity > bestSimilarity && similarity > 0.6) {
|
|
226
|
+
bestSimilarity = similarity;
|
|
227
|
+
bestMatch = template;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return bestMatch;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 询问用户确认
|
|
236
|
+
*/
|
|
237
|
+
private async askUserConfirmation(prompt: string): Promise<boolean> {
|
|
238
|
+
const readline = await import("node:readline");
|
|
239
|
+
const rl = readline.createInterface({
|
|
240
|
+
input: process.stdin,
|
|
241
|
+
output: process.stdout,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
rl.question(prompt, (answer) => {
|
|
246
|
+
rl.close();
|
|
247
|
+
resolve(
|
|
248
|
+
answer.toLowerCase().trim() === "y" ||
|
|
249
|
+
answer.toLowerCase().trim() === "yes"
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 服务管理命令处理器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import consola from "consola";
|
|
6
|
+
import type { SubCommand } from "../interfaces/Command";
|
|
7
|
+
import { BaseCommandHandler } from "../interfaces/Command";
|
|
8
|
+
import type { IDIContainer } from "../interfaces/Config";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 服务管理命令处理器
|
|
12
|
+
*/
|
|
13
|
+
export class ServiceCommandHandler extends BaseCommandHandler {
|
|
14
|
+
override name = "service";
|
|
15
|
+
override description = "服务管理命令";
|
|
16
|
+
|
|
17
|
+
override subcommands: SubCommand[] = [
|
|
18
|
+
{
|
|
19
|
+
name: "start",
|
|
20
|
+
description: "启动服务",
|
|
21
|
+
options: [
|
|
22
|
+
{ flags: "-d, --daemon", description: "在后台运行服务" },
|
|
23
|
+
{ flags: "--debug", description: "启用调试模式 (输出DEBUG级别日志)" },
|
|
24
|
+
{
|
|
25
|
+
flags: "--stdio",
|
|
26
|
+
description: "以 stdio 模式运行 MCP Server (用于 Cursor 等客户端)",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
execute: async (args: any[], options: any) => {
|
|
30
|
+
await this.handleStart(options);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "stop",
|
|
35
|
+
description: "停止服务",
|
|
36
|
+
execute: async (args: any[], options: any) => {
|
|
37
|
+
await this.handleStop();
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "status",
|
|
42
|
+
description: "检查服务状态",
|
|
43
|
+
execute: async (args: any[], options: any) => {
|
|
44
|
+
await this.handleStatus();
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "restart",
|
|
49
|
+
description: "重启服务",
|
|
50
|
+
options: [{ flags: "-d, --daemon", description: "在后台运行服务" }],
|
|
51
|
+
execute: async (args: any[], options: any) => {
|
|
52
|
+
await this.handleRestart(options);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "attach",
|
|
57
|
+
description: "连接到后台服务查看日志",
|
|
58
|
+
execute: async (args: any[], options: any) => {
|
|
59
|
+
await this.handleAttach();
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
constructor(container: IDIContainer) {
|
|
65
|
+
super(container);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 主命令执行(显示帮助)
|
|
70
|
+
*/
|
|
71
|
+
async execute(args: any[], options: any): Promise<void> {
|
|
72
|
+
console.log("服务管理命令。使用 --help 查看可用的子命令。");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 处理启动命令
|
|
77
|
+
*/
|
|
78
|
+
private async handleStart(options: any): Promise<void> {
|
|
79
|
+
try {
|
|
80
|
+
// 处理--debug参数
|
|
81
|
+
if (options.debug) {
|
|
82
|
+
consola.level = "debug";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const serviceManager = this.getService<any>("serviceManager");
|
|
86
|
+
|
|
87
|
+
if (options.stdio) {
|
|
88
|
+
// stdio 模式已迁移到 HTTP 方式
|
|
89
|
+
this.showStdioMigrationGuide();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 传统模式
|
|
94
|
+
await serviceManager.start({
|
|
95
|
+
daemon: options.daemon || false,
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.handleError(error as Error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 处理停止命令
|
|
104
|
+
*/
|
|
105
|
+
private async handleStop(): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
const serviceManager = this.getService<any>("serviceManager");
|
|
108
|
+
await serviceManager.stop();
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.handleError(error as Error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 处理状态检查命令
|
|
116
|
+
*/
|
|
117
|
+
private async handleStatus(): Promise<void> {
|
|
118
|
+
try {
|
|
119
|
+
const serviceManager = this.getService<any>("serviceManager");
|
|
120
|
+
const status = await serviceManager.getStatus();
|
|
121
|
+
|
|
122
|
+
if (status.running) {
|
|
123
|
+
console.log(`✅ 服务正在运行 (PID: ${status.pid})`);
|
|
124
|
+
if (status.uptime) {
|
|
125
|
+
console.log(`⏱️ 运行时间: ${status.uptime}`);
|
|
126
|
+
}
|
|
127
|
+
if (status.mode) {
|
|
128
|
+
console.log(`🔧 运行模式: ${status.mode}`);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
console.log("❌ 服务未运行");
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
this.handleError(error as Error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 处理重启命令
|
|
140
|
+
*/
|
|
141
|
+
private async handleRestart(options: any): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
const serviceManager = this.getService<any>("serviceManager");
|
|
144
|
+
await serviceManager.restart({
|
|
145
|
+
daemon: options.daemon || false,
|
|
146
|
+
});
|
|
147
|
+
} catch (error) {
|
|
148
|
+
this.handleError(error as Error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 处理附加命令
|
|
154
|
+
*/
|
|
155
|
+
private async handleAttach(): Promise<void> {
|
|
156
|
+
try {
|
|
157
|
+
const daemonManager = this.getService<any>("daemonManager");
|
|
158
|
+
await daemonManager.attachToLogs();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.handleError(error as Error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 显示 stdio 模式迁移指南
|
|
166
|
+
*/
|
|
167
|
+
private showStdioMigrationGuide(): void {
|
|
168
|
+
console.log("\n❌ stdio 模式已废弃\n");
|
|
169
|
+
console.log("小智客户端已迁移到纯 HTTP 架构,请使用以下方式:\n");
|
|
170
|
+
|
|
171
|
+
console.log("1. 启动 Web 服务:");
|
|
172
|
+
console.log(" xiaozhi start\n");
|
|
173
|
+
|
|
174
|
+
console.log("2. 在 Cursor 中配置 HTTP 端点:");
|
|
175
|
+
console.log(' "mcpServers": {');
|
|
176
|
+
console.log(' "xiaozhi-client": {');
|
|
177
|
+
console.log(' "type": "streamableHttp",');
|
|
178
|
+
console.log(' "url": "http://localhost:9999/mcp"');
|
|
179
|
+
console.log(" }");
|
|
180
|
+
console.log(" }\n");
|
|
181
|
+
}
|
|
182
|
+
}
|