@xiaozhi-client/cli 1.9.4-beta.5

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/fix-imports.js +32 -0
  4. package/package.json +26 -0
  5. package/project.json +75 -0
  6. package/src/Constants.ts +105 -0
  7. package/src/Container.ts +212 -0
  8. package/src/Types.ts +79 -0
  9. package/src/commands/CommandHandlerFactory.ts +98 -0
  10. package/src/commands/ConfigCommandHandler.ts +279 -0
  11. package/src/commands/EndpointCommandHandler.ts +158 -0
  12. package/src/commands/McpCommandHandler.ts +778 -0
  13. package/src/commands/ProjectCommandHandler.ts +254 -0
  14. package/src/commands/ServiceCommandHandler.ts +182 -0
  15. package/src/commands/__tests__/CommandHandlerFactory.test.ts +323 -0
  16. package/src/commands/__tests__/CommandRegistry.test.ts +287 -0
  17. package/src/commands/__tests__/ConfigCommandHandler.test.ts +844 -0
  18. package/src/commands/__tests__/EndpointCommandHandler.test.ts +426 -0
  19. package/src/commands/__tests__/McpCommandHandler.test.ts +753 -0
  20. package/src/commands/__tests__/ProjectCommandHandler.test.ts +230 -0
  21. package/src/commands/__tests__/ServiceCommands.integration.test.ts +408 -0
  22. package/src/commands/index.ts +351 -0
  23. package/src/errors/ErrorHandlers.ts +141 -0
  24. package/src/errors/ErrorMessages.ts +121 -0
  25. package/src/errors/__tests__/index.test.ts +186 -0
  26. package/src/errors/index.ts +163 -0
  27. package/src/global.d.ts +19 -0
  28. package/src/index.ts +53 -0
  29. package/src/interfaces/Command.ts +128 -0
  30. package/src/interfaces/CommandTypes.ts +95 -0
  31. package/src/interfaces/Config.ts +25 -0
  32. package/src/interfaces/Service.ts +99 -0
  33. package/src/services/DaemonManager.ts +318 -0
  34. package/src/services/ProcessManager.ts +235 -0
  35. package/src/services/ServiceManager.ts +319 -0
  36. package/src/services/TemplateManager.ts +382 -0
  37. package/src/services/__tests__/DaemonManager.test.ts +378 -0
  38. package/src/services/__tests__/DaemonMode.integration.test.ts +321 -0
  39. package/src/services/__tests__/ProcessManager.test.ts +296 -0
  40. package/src/services/__tests__/ServiceManager.test.ts +774 -0
  41. package/src/services/__tests__/TemplateManager.test.ts +337 -0
  42. package/src/types/backend.d.ts +48 -0
  43. package/src/utils/FileUtils.ts +320 -0
  44. package/src/utils/FormatUtils.ts +198 -0
  45. package/src/utils/PathUtils.ts +255 -0
  46. package/src/utils/PlatformUtils.ts +217 -0
  47. package/src/utils/Validation.ts +274 -0
  48. package/src/utils/VersionUtils.ts +141 -0
  49. package/src/utils/__tests__/FileUtils.test.ts +728 -0
  50. package/src/utils/__tests__/FormatUtils.test.ts +243 -0
  51. package/src/utils/__tests__/PathUtils.test.ts +1165 -0
  52. package/src/utils/__tests__/PlatformUtils.test.ts +723 -0
  53. package/src/utils/__tests__/Validation.test.ts +560 -0
  54. package/src/utils/__tests__/VersionUtils.test.ts +410 -0
  55. package/tsconfig.json +32 -0
  56. package/tsconfig.tsbuildinfo +1 -0
  57. package/tsup.config.ts +107 -0
  58. package/vitest.config.ts +97 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 命令处理器工厂
3
+ */
4
+
5
+ import type {
6
+ CommandHandler,
7
+ ICommandHandlerFactory,
8
+ } from "../interfaces/Command";
9
+ import type { IDIContainer } from "../interfaces/Config";
10
+
11
+ /**
12
+ * 命令处理器工厂实现
13
+ */
14
+ export class CommandHandlerFactory implements ICommandHandlerFactory {
15
+ constructor(private container: IDIContainer) {}
16
+
17
+ /**
18
+ * 创建所有命令处理器
19
+ */
20
+ createHandlers(): CommandHandler[] {
21
+ return [
22
+ this.createHandler("service"),
23
+ this.createHandler("config"),
24
+ this.createHandler("project"),
25
+ this.createHandler("mcp"),
26
+ this.createHandler("endpoint"),
27
+ ];
28
+ }
29
+
30
+ /**
31
+ * 创建指定类型的命令处理器
32
+ */
33
+ createHandler(type: string): CommandHandler {
34
+ switch (type) {
35
+ case "service":
36
+ return this.createServiceCommandHandler();
37
+ case "config":
38
+ return this.createConfigCommandHandler();
39
+ case "project":
40
+ return this.createProjectCommandHandler();
41
+ case "mcp":
42
+ return this.createMcpCommandHandler();
43
+ case "endpoint":
44
+ return this.createEndpointCommandHandler();
45
+ default:
46
+ throw new Error(`未知的命令处理器类型: ${type}`);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 创建服务命令处理器
52
+ */
53
+ private createServiceCommandHandler(): CommandHandler {
54
+ // 动态导入以避免循环依赖
55
+ const {
56
+ ServiceCommandHandler,
57
+ } = require("@cli/commands/ServiceCommandHandler.js");
58
+ return new ServiceCommandHandler(this.container);
59
+ }
60
+
61
+ /**
62
+ * 创建配置命令处理器
63
+ */
64
+ private createConfigCommandHandler(): CommandHandler {
65
+ const {
66
+ ConfigCommandHandler,
67
+ } = require("@cli/commands/ConfigCommandHandler.js");
68
+ return new ConfigCommandHandler(this.container);
69
+ }
70
+
71
+ /**
72
+ * 创建项目命令处理器
73
+ */
74
+ private createProjectCommandHandler(): CommandHandler {
75
+ const {
76
+ ProjectCommandHandler,
77
+ } = require("@cli/commands/ProjectCommandHandler.js");
78
+ return new ProjectCommandHandler(this.container);
79
+ }
80
+
81
+ /**
82
+ * 创建MCP命令处理器
83
+ */
84
+ private createMcpCommandHandler(): CommandHandler {
85
+ const { McpCommandHandler } = require("@cli/commands/McpCommandHandler.js");
86
+ return new McpCommandHandler(this.container);
87
+ }
88
+
89
+ /**
90
+ * 创建端点命令处理器
91
+ */
92
+ private createEndpointCommandHandler(): CommandHandler {
93
+ const {
94
+ EndpointCommandHandler,
95
+ } = require("@cli/commands/EndpointCommandHandler.js");
96
+ return new EndpointCommandHandler(this.container);
97
+ }
98
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * 配置管理命令处理器
3
+ */
4
+
5
+ import path from "node:path";
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import type { SubCommand } from "../interfaces/Command";
9
+ import { BaseCommandHandler } from "../interfaces/Command";
10
+ import type { IDIContainer } from "../interfaces/Config";
11
+
12
+ /**
13
+ * 配置管理命令处理器
14
+ */
15
+ export class ConfigCommandHandler extends BaseCommandHandler {
16
+ override name = "config";
17
+ override description = "配置管理命令";
18
+
19
+ override subcommands: SubCommand[] = [
20
+ {
21
+ name: "init",
22
+ description: "初始化配置文件",
23
+ options: [
24
+ {
25
+ flags: "-f, --format <format>",
26
+ description: "配置文件格式 (json, json5, jsonc)",
27
+ defaultValue: "json",
28
+ },
29
+ ],
30
+ execute: async (args: any[], options: any) => {
31
+ await this.handleInit(options);
32
+ },
33
+ },
34
+ {
35
+ name: "get",
36
+ description: "查看配置值",
37
+ execute: async (args: any[], options: any) => {
38
+ this.validateArgs(args, 1);
39
+ await this.handleGet(args[0]);
40
+ },
41
+ },
42
+ {
43
+ name: "set",
44
+ description: "设置配置值",
45
+ execute: async (args: any[], options: any) => {
46
+ this.validateArgs(args, 2);
47
+ await this.handleSet(args[0], args[1]);
48
+ },
49
+ },
50
+ ];
51
+
52
+ constructor(container: IDIContainer) {
53
+ super(container);
54
+ }
55
+
56
+ /**
57
+ * 主命令执行(显示帮助)
58
+ */
59
+ async execute(args: any[], options: any): Promise<void> {
60
+ console.log("配置管理命令。使用 --help 查看可用的子命令。");
61
+ }
62
+
63
+ /**
64
+ * 处理初始化命令
65
+ */
66
+ private async handleInit(options: any): Promise<void> {
67
+ const spinner = ora("初始化配置...").start();
68
+
69
+ try {
70
+ const format = options.format as "json" | "json5" | "jsonc";
71
+ if (format !== "json" && format !== "json5" && format !== "jsonc") {
72
+ throw new Error("格式必须是 json, json5 或 jsonc");
73
+ }
74
+
75
+ const configManager = this.getService<any>("configManager");
76
+
77
+ if (configManager.configExists()) {
78
+ spinner.warn("配置文件已存在");
79
+ console.log(chalk.yellow("如需重新初始化,请先删除现有的配置文件"));
80
+ return;
81
+ }
82
+
83
+ configManager.initConfig(format);
84
+ spinner.succeed("配置文件初始化成功");
85
+
86
+ // 获取实际创建的配置文件路径
87
+ const configDir = process.env.XIAOZHI_CONFIG_DIR || process.cwd();
88
+ const configFileName = `xiaozhi.config.${format}`;
89
+ const configPath = path.join(configDir, configFileName);
90
+
91
+ console.log(chalk.green(`✅ 配置文件已创建: ${configFileName}`));
92
+ console.log(chalk.yellow("📝 请编辑配置文件设置你的 MCP 端点:"));
93
+ console.log(chalk.gray(` 配置文件路径: ${configPath}`));
94
+ console.log(chalk.yellow("💡 或者使用命令设置:"));
95
+ console.log(
96
+ chalk.gray(" xiaozhi config set mcpEndpoint <your-endpoint-url>")
97
+ );
98
+ } catch (error) {
99
+ spinner.fail(
100
+ `初始化配置失败: ${error instanceof Error ? error.message : String(error)}`
101
+ );
102
+ this.handleError(error as Error);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * 处理获取配置命令
108
+ */
109
+ private async handleGet(key: string): Promise<void> {
110
+ const spinner = ora("读取配置...").start();
111
+
112
+ try {
113
+ const configManager = this.getService<any>("configManager");
114
+
115
+ if (!configManager.configExists()) {
116
+ spinner.fail("配置文件不存在");
117
+ console.log(
118
+ chalk.yellow('💡 提示: 请先运行 "xiaozhi config init" 初始化配置')
119
+ );
120
+ return;
121
+ }
122
+
123
+ const config = configManager.getConfig();
124
+
125
+ switch (key) {
126
+ case "mcpEndpoint": {
127
+ spinner.succeed("配置信息");
128
+ const endpoints = configManager.getMcpEndpoints();
129
+ if (endpoints.length === 0) {
130
+ console.log(chalk.yellow("未配置任何 MCP 端点"));
131
+ } else if (endpoints.length === 1) {
132
+ console.log(chalk.green(`MCP 端点: ${endpoints[0]}`));
133
+ } else {
134
+ console.log(chalk.green(`MCP 端点 (${endpoints.length} 个):`));
135
+ endpoints.forEach((ep: string, index: number) => {
136
+ console.log(chalk.gray(` ${index + 1}. ${ep}`));
137
+ });
138
+ }
139
+ break;
140
+ }
141
+ case "mcpServers":
142
+ spinner.succeed("配置信息");
143
+ console.log(chalk.green("MCP 服务:"));
144
+ for (const [name, serverConfig] of Object.entries(
145
+ config.mcpServers
146
+ )) {
147
+ const server = serverConfig as any;
148
+ // 检查是否是 SSE 类型
149
+ if ("type" in server && server.type === "sse") {
150
+ console.log(chalk.gray(` ${name}: [SSE] ${server.url}`));
151
+ } else {
152
+ console.log(
153
+ chalk.gray(
154
+ ` ${name}: ${server.command} ${server.args.join(" ")}`
155
+ )
156
+ );
157
+ }
158
+ }
159
+ break;
160
+ case "connection": {
161
+ spinner.succeed("配置信息");
162
+ const connectionConfig = configManager.getConnectionConfig();
163
+ console.log(chalk.green("连接配置:"));
164
+ console.log(
165
+ chalk.gray(
166
+ ` 心跳检测间隔: ${connectionConfig.heartbeatInterval}ms`
167
+ )
168
+ );
169
+ console.log(
170
+ chalk.gray(` 心跳超时时间: ${connectionConfig.heartbeatTimeout}ms`)
171
+ );
172
+ console.log(
173
+ chalk.gray(` 重连间隔: ${connectionConfig.reconnectInterval}ms`)
174
+ );
175
+ break;
176
+ }
177
+ case "heartbeatInterval":
178
+ spinner.succeed("配置信息");
179
+ console.log(
180
+ chalk.green(
181
+ `心跳检测间隔: ${configManager.getHeartbeatInterval()}ms`
182
+ )
183
+ );
184
+ break;
185
+ case "heartbeatTimeout":
186
+ spinner.succeed("配置信息");
187
+ console.log(
188
+ chalk.green(
189
+ `心跳超时时间: ${configManager.getHeartbeatTimeout()}ms`
190
+ )
191
+ );
192
+ break;
193
+ case "reconnectInterval":
194
+ spinner.succeed("配置信息");
195
+ console.log(
196
+ chalk.green(`重连间隔: ${configManager.getReconnectInterval()}ms`)
197
+ );
198
+ break;
199
+ default:
200
+ spinner.fail(`未知的配置项: ${key}`);
201
+ console.log(
202
+ chalk.yellow(
203
+ "支持的配置项: mcpEndpoint, mcpServers, connection, heartbeatInterval, heartbeatTimeout, reconnectInterval"
204
+ )
205
+ );
206
+ }
207
+ } catch (error) {
208
+ spinner.fail(
209
+ `读取配置失败: ${error instanceof Error ? error.message : String(error)}`
210
+ );
211
+ this.handleError(error as Error);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * 处理设置配置命令
217
+ */
218
+ private async handleSet(key: string, value: string): Promise<void> {
219
+ const spinner = ora("更新配置...").start();
220
+
221
+ try {
222
+ const configManager = this.getService<any>("configManager");
223
+
224
+ if (!configManager.configExists()) {
225
+ spinner.fail("配置文件不存在");
226
+ console.log(
227
+ chalk.yellow('💡 提示: 请先运行 "xiaozhi config init" 初始化配置')
228
+ );
229
+ return;
230
+ }
231
+
232
+ switch (key) {
233
+ case "mcpEndpoint":
234
+ configManager.updateMcpEndpoint(value);
235
+ spinner.succeed(`MCP 端点已设置为: ${value}`);
236
+ break;
237
+ case "heartbeatInterval": {
238
+ const interval = Number.parseInt(value);
239
+ if (Number.isNaN(interval) || interval <= 0) {
240
+ throw new Error("心跳检测间隔必须是正整数");
241
+ }
242
+ configManager.updateHeartbeatInterval(interval);
243
+ spinner.succeed(`心跳检测间隔已设置为: ${interval}ms`);
244
+ break;
245
+ }
246
+ case "heartbeatTimeout": {
247
+ const timeout = Number.parseInt(value);
248
+ if (Number.isNaN(timeout) || timeout <= 0) {
249
+ throw new Error("心跳超时时间必须是正整数");
250
+ }
251
+ configManager.updateHeartbeatTimeout(timeout);
252
+ spinner.succeed(`心跳超时时间已设置为: ${timeout}ms`);
253
+ break;
254
+ }
255
+ case "reconnectInterval": {
256
+ const interval = Number.parseInt(value);
257
+ if (Number.isNaN(interval) || interval <= 0) {
258
+ throw new Error("重连间隔必须是正整数");
259
+ }
260
+ configManager.updateReconnectInterval(interval);
261
+ spinner.succeed(`重连间隔已设置为: ${interval}ms`);
262
+ break;
263
+ }
264
+ default:
265
+ spinner.fail(`不支持设置的配置项: ${key}`);
266
+ console.log(
267
+ chalk.yellow(
268
+ "支持设置的配置项: mcpEndpoint, heartbeatInterval, heartbeatTimeout, reconnectInterval"
269
+ )
270
+ );
271
+ }
272
+ } catch (error) {
273
+ spinner.fail(
274
+ `设置配置失败: ${error instanceof Error ? error.message : String(error)}`
275
+ );
276
+ this.handleError(error as Error);
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * 端点管理命令处理器
3
+ */
4
+
5
+ import chalk from "chalk";
6
+ import ora from "ora";
7
+ import type { SubCommand } from "../interfaces/Command";
8
+ import { BaseCommandHandler } from "../interfaces/Command";
9
+ import type { IDIContainer } from "../interfaces/Config";
10
+
11
+ /**
12
+ * 端点管理命令处理器
13
+ */
14
+ export class EndpointCommandHandler extends BaseCommandHandler {
15
+ override name = "endpoint";
16
+ override description = "管理 MCP 端点";
17
+
18
+ override subcommands: SubCommand[] = [
19
+ {
20
+ name: "list",
21
+ description: "列出所有 MCP 端点",
22
+ execute: async (args: any[], options: any) => {
23
+ await this.handleList();
24
+ },
25
+ },
26
+ {
27
+ name: "add",
28
+ description: "添加新的 MCP 端点",
29
+ execute: async (args: any[], options: any) => {
30
+ this.validateArgs(args, 1);
31
+ await this.handleAdd(args[0]);
32
+ },
33
+ },
34
+ {
35
+ name: "remove",
36
+ description: "移除指定的 MCP 端点",
37
+ execute: async (args: any[], options: any) => {
38
+ this.validateArgs(args, 1);
39
+ await this.handleRemove(args[0]);
40
+ },
41
+ },
42
+ {
43
+ name: "set",
44
+ description: "设置 MCP 端点(可以是单个或多个)",
45
+ execute: async (args: any[], options: any) => {
46
+ this.validateArgs(args, 1);
47
+ await this.handleSet(args);
48
+ },
49
+ },
50
+ ];
51
+
52
+ constructor(container: IDIContainer) {
53
+ super(container);
54
+ }
55
+
56
+ /**
57
+ * 主命令执行(显示帮助)
58
+ */
59
+ async execute(args: any[], options: any): Promise<void> {
60
+ console.log("MCP 端点管理命令。使用 --help 查看可用的子命令。");
61
+ }
62
+
63
+ /**
64
+ * 处理列出端点命令
65
+ */
66
+ protected async handleList(): Promise<void> {
67
+ const spinner = ora("读取端点配置...").start();
68
+
69
+ try {
70
+ const configManager = this.getService<any>("configManager");
71
+ const endpoints = configManager.getMcpEndpoints();
72
+ spinner.succeed("端点列表");
73
+
74
+ if (endpoints.length === 0) {
75
+ console.log(chalk.yellow("未配置任何 MCP 端点"));
76
+ } else {
77
+ console.log(chalk.green(`共 ${endpoints.length} 个端点:`));
78
+ endpoints.forEach((ep: string, index: number) => {
79
+ console.log(chalk.gray(` ${index + 1}. ${ep}`));
80
+ });
81
+ }
82
+ } catch (error) {
83
+ spinner.fail(
84
+ `读取端点失败: ${error instanceof Error ? error.message : String(error)}`
85
+ );
86
+ this.handleError(error as Error);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 处理添加端点命令
92
+ */
93
+ protected async handleAdd(url: string): Promise<void> {
94
+ const spinner = ora("添加端点...").start();
95
+
96
+ try {
97
+ const configManager = this.getService<any>("configManager");
98
+ configManager.addMcpEndpoint(url);
99
+ spinner.succeed(`成功添加端点: ${url}`);
100
+
101
+ const endpoints = configManager.getMcpEndpoints();
102
+ console.log(chalk.gray(`当前共 ${endpoints.length} 个端点`));
103
+ } catch (error) {
104
+ spinner.fail(
105
+ `添加端点失败: ${error instanceof Error ? error.message : String(error)}`
106
+ );
107
+ this.handleError(error as Error);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * 处理移除端点命令
113
+ */
114
+ protected async handleRemove(url: string): Promise<void> {
115
+ const spinner = ora("移除端点...").start();
116
+
117
+ try {
118
+ const configManager = this.getService<any>("configManager");
119
+ configManager.removeMcpEndpoint(url);
120
+ spinner.succeed(`成功移除端点: ${url}`);
121
+
122
+ const endpoints = configManager.getMcpEndpoints();
123
+ console.log(chalk.gray(`当前剩余 ${endpoints.length} 个端点`));
124
+ } catch (error) {
125
+ spinner.fail(
126
+ `移除端点失败: ${error instanceof Error ? error.message : String(error)}`
127
+ );
128
+ this.handleError(error as Error);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * 处理设置端点命令
134
+ */
135
+ protected async handleSet(urls: string[]): Promise<void> {
136
+ const spinner = ora("设置端点...").start();
137
+
138
+ try {
139
+ const configManager = this.getService<any>("configManager");
140
+
141
+ if (urls.length === 1) {
142
+ configManager.updateMcpEndpoint(urls[0]);
143
+ spinner.succeed(`成功设置端点: ${urls[0]}`);
144
+ } else {
145
+ configManager.updateMcpEndpoint(urls);
146
+ spinner.succeed(`成功设置 ${urls.length} 个端点`);
147
+ for (const [index, url] of urls.entries()) {
148
+ console.log(chalk.gray(` ${index + 1}. ${url}`));
149
+ }
150
+ }
151
+ } catch (error) {
152
+ spinner.fail(
153
+ `设置端点失败: ${error instanceof Error ? error.message : String(error)}`
154
+ );
155
+ this.handleError(error as Error);
156
+ }
157
+ }
158
+ }