@xiaozhi-client/cli 1.10.8-beta.9 → 2.0.0-beta.4
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 +9 -6
- package/package.json +3 -2
- package/src/Container.ts +0 -34
- package/src/commands/ConfigCommandHandler.ts +12 -5
- package/src/commands/EndpointCommandHandler.ts +12 -5
- package/src/commands/ProjectCommandHandler.ts +9 -2
- package/src/commands/ServiceCommandHandler.ts +15 -8
- package/src/commands/index.ts +42 -38
- package/src/errors/ErrorHandlers.ts +19 -22
- package/src/global.d.ts +8 -0
- package/src/interfaces/Service.ts +25 -3
- package/src/services/DaemonManager.ts +5 -2
- package/src/services/ProcessManager.ts +1 -27
- package/src/services/ServiceManager.ts +11 -6
- package/src/services/TemplateManager.ts +7 -22
- package/src/services/__tests__/ProcessManager.test.ts +11 -31
- package/src/services/__tests__/ServiceManager.test.ts +0 -22
- package/src/types/backend.d.ts +32 -42
- package/src/utils/Validation.ts +8 -5
- package/tsup.config.ts +1 -1
package/README.md
CHANGED
|
@@ -42,15 +42,15 @@ packages/cli/
|
|
|
42
42
|
|
|
43
43
|
- **入口点**:`src/index.ts` → `../../dist/cli/index.js`
|
|
44
44
|
- **格式**:ESM (ES Modules)
|
|
45
|
-
- **目标**:Node.js
|
|
45
|
+
- **目标**:Node.js 20+
|
|
46
46
|
- **打包工具**:tsup (esbuild)
|
|
47
47
|
|
|
48
48
|
## 外部依赖
|
|
49
49
|
|
|
50
|
-
CLI 包通过 external 配置引用 backend
|
|
50
|
+
CLI 包通过 external 配置引用 backend 模块和 workspace 包:
|
|
51
51
|
|
|
52
52
|
- `@/WebServer` → `dist/backend/WebServer.js` (通过 WebServerLauncher)
|
|
53
|
-
-
|
|
53
|
+
- `@xiaozhi-client/config` → `dist/config/index.js` (配置管理)
|
|
54
54
|
|
|
55
55
|
## 导入方式
|
|
56
56
|
|
|
@@ -62,11 +62,14 @@ import { DIContainer } from "./Container";
|
|
|
62
62
|
import { CommandRegistry } from "./commands/index";
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
###
|
|
65
|
+
### 外部依赖(使用 workspace 包)
|
|
66
66
|
|
|
67
67
|
```typescript
|
|
68
|
-
//
|
|
69
|
-
import { configManager } from "
|
|
68
|
+
// 引用配置管理(从 workspace 包导入)
|
|
69
|
+
import { configManager } from "@xiaozhi-client/config";
|
|
70
|
+
|
|
71
|
+
// 引用 WebServer(使用路径别名,运行时解析为 backend 模块)
|
|
72
|
+
import { WebServer } from "@/WebServer";
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
## 开发命令
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaozhi-client/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"commander": "^14.0.0",
|
|
23
23
|
"consola": "^3.4.2",
|
|
24
24
|
"ora": "^8.2.0",
|
|
25
|
-
"@xiaozhi-client/config": "
|
|
25
|
+
"@xiaozhi-client/config": "2.0.0-beta.4"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^24.3.0",
|
|
29
|
+
"tsup": "^8.5.0",
|
|
29
30
|
"typescript": "^5.9.2"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
package/src/Container.ts
CHANGED
|
@@ -166,40 +166,6 @@ export class DIContainer implements IDIContainer {
|
|
|
166
166
|
return new TemplateManagerModule.TemplateManagerImpl();
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
-
// 注册命令层(稍后在命令层实现时添加)
|
|
170
|
-
// container.register('serviceCommand', () => {
|
|
171
|
-
// const { ServiceCommand } = require('./commands/ServiceCommand.js');
|
|
172
|
-
// const serviceManager = container.get('serviceManager');
|
|
173
|
-
// const processManager = container.get('processManager');
|
|
174
|
-
// return new ServiceCommand(serviceManager, processManager);
|
|
175
|
-
// });
|
|
176
|
-
|
|
177
|
-
// container.register('configCommand', () => {
|
|
178
|
-
// const { ConfigCommand } = require('./commands/ConfigCommand.js');
|
|
179
|
-
// const configManager = container.get('configManager');
|
|
180
|
-
// const validation = container.get('validation');
|
|
181
|
-
// return new ConfigCommand(configManager, validation);
|
|
182
|
-
// });
|
|
183
|
-
|
|
184
|
-
// container.register('projectCommand', () => {
|
|
185
|
-
// const { ProjectCommand } = require('./commands/ProjectCommand.js');
|
|
186
|
-
// const templateManager = container.get('templateManager');
|
|
187
|
-
// const fileUtils = container.get('fileUtils');
|
|
188
|
-
// return new ProjectCommand(templateManager, fileUtils);
|
|
189
|
-
// });
|
|
190
|
-
|
|
191
|
-
// container.register('mcpCommand', () => {
|
|
192
|
-
// const { McpCommand } = require('./commands/McpCommand.js');
|
|
193
|
-
// return new McpCommand();
|
|
194
|
-
// });
|
|
195
|
-
|
|
196
|
-
// container.register('infoCommand', () => {
|
|
197
|
-
// const { InfoCommand } = require('./commands/InfoCommand.js');
|
|
198
|
-
// const versionUtils = container.get('versionUtils');
|
|
199
|
-
// const platformUtils = container.get('platformUtils');
|
|
200
|
-
// return new InfoCommand(versionUtils, platformUtils);
|
|
201
|
-
// });
|
|
202
|
-
|
|
203
169
|
return container;
|
|
204
170
|
}
|
|
205
171
|
}
|
|
@@ -7,6 +7,10 @@ import chalk from "chalk";
|
|
|
7
7
|
import ora from "ora";
|
|
8
8
|
import type { SubCommand } from "../interfaces/Command";
|
|
9
9
|
import { BaseCommandHandler } from "../interfaces/Command";
|
|
10
|
+
import type {
|
|
11
|
+
CommandArguments,
|
|
12
|
+
CommandOptions,
|
|
13
|
+
} from "../interfaces/CommandTypes";
|
|
10
14
|
import type { IDIContainer } from "../interfaces/Config";
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -27,14 +31,14 @@ export class ConfigCommandHandler extends BaseCommandHandler {
|
|
|
27
31
|
defaultValue: "json",
|
|
28
32
|
},
|
|
29
33
|
],
|
|
30
|
-
execute: async (args:
|
|
34
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
31
35
|
await this.handleInit(options);
|
|
32
36
|
},
|
|
33
37
|
},
|
|
34
38
|
{
|
|
35
39
|
name: "get",
|
|
36
40
|
description: "查看配置值",
|
|
37
|
-
execute: async (args:
|
|
41
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
38
42
|
this.validateArgs(args, 1);
|
|
39
43
|
await this.handleGet(args[0]);
|
|
40
44
|
},
|
|
@@ -42,7 +46,7 @@ export class ConfigCommandHandler extends BaseCommandHandler {
|
|
|
42
46
|
{
|
|
43
47
|
name: "set",
|
|
44
48
|
description: "设置配置值",
|
|
45
|
-
execute: async (args:
|
|
49
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
46
50
|
this.validateArgs(args, 2);
|
|
47
51
|
await this.handleSet(args[0], args[1]);
|
|
48
52
|
},
|
|
@@ -56,14 +60,17 @@ export class ConfigCommandHandler extends BaseCommandHandler {
|
|
|
56
60
|
/**
|
|
57
61
|
* 主命令执行(显示帮助)
|
|
58
62
|
*/
|
|
59
|
-
async execute(
|
|
63
|
+
override async execute(
|
|
64
|
+
args: CommandArguments,
|
|
65
|
+
options: CommandOptions
|
|
66
|
+
): Promise<void> {
|
|
60
67
|
console.log("配置管理命令。使用 --help 查看可用的子命令。");
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
71
|
* 处理初始化命令
|
|
65
72
|
*/
|
|
66
|
-
private async handleInit(options:
|
|
73
|
+
private async handleInit(options: CommandOptions): Promise<void> {
|
|
67
74
|
const spinner = ora("初始化配置...").start();
|
|
68
75
|
|
|
69
76
|
try {
|
|
@@ -6,6 +6,10 @@ import chalk from "chalk";
|
|
|
6
6
|
import ora from "ora";
|
|
7
7
|
import type { SubCommand } from "../interfaces/Command";
|
|
8
8
|
import { BaseCommandHandler } from "../interfaces/Command";
|
|
9
|
+
import type {
|
|
10
|
+
CommandArguments,
|
|
11
|
+
CommandOptions,
|
|
12
|
+
} from "../interfaces/CommandTypes";
|
|
9
13
|
import type { IDIContainer } from "../interfaces/Config";
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -19,14 +23,14 @@ export class EndpointCommandHandler extends BaseCommandHandler {
|
|
|
19
23
|
{
|
|
20
24
|
name: "list",
|
|
21
25
|
description: "列出所有 MCP 端点",
|
|
22
|
-
execute: async (args:
|
|
26
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
23
27
|
await this.handleList();
|
|
24
28
|
},
|
|
25
29
|
},
|
|
26
30
|
{
|
|
27
31
|
name: "add",
|
|
28
32
|
description: "添加新的 MCP 端点",
|
|
29
|
-
execute: async (args:
|
|
33
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
30
34
|
this.validateArgs(args, 1);
|
|
31
35
|
await this.handleAdd(args[0]);
|
|
32
36
|
},
|
|
@@ -34,7 +38,7 @@ export class EndpointCommandHandler extends BaseCommandHandler {
|
|
|
34
38
|
{
|
|
35
39
|
name: "remove",
|
|
36
40
|
description: "移除指定的 MCP 端点",
|
|
37
|
-
execute: async (args:
|
|
41
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
38
42
|
this.validateArgs(args, 1);
|
|
39
43
|
await this.handleRemove(args[0]);
|
|
40
44
|
},
|
|
@@ -42,7 +46,7 @@ export class EndpointCommandHandler extends BaseCommandHandler {
|
|
|
42
46
|
{
|
|
43
47
|
name: "set",
|
|
44
48
|
description: "设置 MCP 端点(可以是单个或多个)",
|
|
45
|
-
execute: async (args:
|
|
49
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
46
50
|
this.validateArgs(args, 1);
|
|
47
51
|
await this.handleSet(args);
|
|
48
52
|
},
|
|
@@ -56,7 +60,10 @@ export class EndpointCommandHandler extends BaseCommandHandler {
|
|
|
56
60
|
/**
|
|
57
61
|
* 主命令执行(显示帮助)
|
|
58
62
|
*/
|
|
59
|
-
async execute(
|
|
63
|
+
override async execute(
|
|
64
|
+
args: CommandArguments,
|
|
65
|
+
options: CommandOptions
|
|
66
|
+
): Promise<void> {
|
|
60
67
|
console.log("MCP 端点管理命令。使用 --help 查看可用的子命令。");
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -7,6 +7,10 @@ import chalk from "chalk";
|
|
|
7
7
|
import ora from "ora";
|
|
8
8
|
import type { CommandOption } from "../interfaces/Command";
|
|
9
9
|
import { BaseCommandHandler } from "../interfaces/Command";
|
|
10
|
+
import type {
|
|
11
|
+
CommandArguments,
|
|
12
|
+
CommandOptions,
|
|
13
|
+
} from "../interfaces/CommandTypes";
|
|
10
14
|
import type { IDIContainer } from "../interfaces/Config";
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -30,7 +34,10 @@ export class ProjectCommandHandler extends BaseCommandHandler {
|
|
|
30
34
|
/**
|
|
31
35
|
* 执行创建项目命令
|
|
32
36
|
*/
|
|
33
|
-
async execute(
|
|
37
|
+
override async execute(
|
|
38
|
+
args: CommandArguments,
|
|
39
|
+
options: CommandOptions
|
|
40
|
+
): Promise<void> {
|
|
34
41
|
this.validateArgs(args, 1);
|
|
35
42
|
const projectName = args[0];
|
|
36
43
|
|
|
@@ -42,7 +49,7 @@ export class ProjectCommandHandler extends BaseCommandHandler {
|
|
|
42
49
|
*/
|
|
43
50
|
protected async handleCreate(
|
|
44
51
|
projectName: string,
|
|
45
|
-
options:
|
|
52
|
+
options: CommandOptions
|
|
46
53
|
): Promise<void> {
|
|
47
54
|
const spinner = ora("初始化项目...").start();
|
|
48
55
|
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import type { SubCommand } from "../interfaces/Command";
|
|
7
7
|
import { BaseCommandHandler } from "../interfaces/Command";
|
|
8
|
+
import type {
|
|
9
|
+
CommandArguments,
|
|
10
|
+
CommandOptions,
|
|
11
|
+
} from "../interfaces/CommandTypes";
|
|
8
12
|
import type { IDIContainer } from "../interfaces/Config";
|
|
9
13
|
|
|
10
14
|
/**
|
|
@@ -26,21 +30,21 @@ export class ServiceCommandHandler extends BaseCommandHandler {
|
|
|
26
30
|
description: "以 stdio 模式运行 MCP Server (用于 Cursor 等客户端)",
|
|
27
31
|
},
|
|
28
32
|
],
|
|
29
|
-
execute: async (args:
|
|
33
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
30
34
|
await this.handleStart(options);
|
|
31
35
|
},
|
|
32
36
|
},
|
|
33
37
|
{
|
|
34
38
|
name: "stop",
|
|
35
39
|
description: "停止服务",
|
|
36
|
-
execute: async (args:
|
|
40
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
37
41
|
await this.handleStop();
|
|
38
42
|
},
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
name: "status",
|
|
42
46
|
description: "检查服务状态",
|
|
43
|
-
execute: async (args:
|
|
47
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
44
48
|
await this.handleStatus();
|
|
45
49
|
},
|
|
46
50
|
},
|
|
@@ -48,14 +52,14 @@ export class ServiceCommandHandler extends BaseCommandHandler {
|
|
|
48
52
|
name: "restart",
|
|
49
53
|
description: "重启服务",
|
|
50
54
|
options: [{ flags: "-d, --daemon", description: "在后台运行服务" }],
|
|
51
|
-
execute: async (args:
|
|
55
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
52
56
|
await this.handleRestart(options);
|
|
53
57
|
},
|
|
54
58
|
},
|
|
55
59
|
{
|
|
56
60
|
name: "attach",
|
|
57
61
|
description: "连接到后台服务查看日志",
|
|
58
|
-
execute: async (args:
|
|
62
|
+
execute: async (args: CommandArguments, options: CommandOptions) => {
|
|
59
63
|
await this.handleAttach();
|
|
60
64
|
},
|
|
61
65
|
},
|
|
@@ -68,14 +72,17 @@ export class ServiceCommandHandler extends BaseCommandHandler {
|
|
|
68
72
|
/**
|
|
69
73
|
* 主命令执行(显示帮助)
|
|
70
74
|
*/
|
|
71
|
-
async execute(
|
|
75
|
+
override async execute(
|
|
76
|
+
args: CommandArguments,
|
|
77
|
+
options: CommandOptions
|
|
78
|
+
): Promise<void> {
|
|
72
79
|
console.log("服务管理命令。使用 --help 查看可用的子命令。");
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
/**
|
|
76
83
|
* 处理启动命令
|
|
77
84
|
*/
|
|
78
|
-
private async handleStart(options:
|
|
85
|
+
private async handleStart(options: CommandOptions): Promise<void> {
|
|
79
86
|
try {
|
|
80
87
|
// 处理--debug参数
|
|
81
88
|
if (options.debug) {
|
|
@@ -138,7 +145,7 @@ export class ServiceCommandHandler extends BaseCommandHandler {
|
|
|
138
145
|
/**
|
|
139
146
|
* 处理重启命令
|
|
140
147
|
*/
|
|
141
|
-
private async handleRestart(options:
|
|
148
|
+
private async handleRestart(options: CommandOptions): Promise<void> {
|
|
142
149
|
try {
|
|
143
150
|
const serviceManager = this.getService<any>("serviceManager");
|
|
144
151
|
await serviceManager.restart({
|
package/src/commands/index.ts
CHANGED
|
@@ -23,6 +23,28 @@ export class CommandRegistry implements ICommandRegistry {
|
|
|
23
23
|
this.handlerFactory = new CommandHandlerFactory(container);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 包装命令处理函数,统一处理错误
|
|
28
|
+
* @param handler 命令处理函数
|
|
29
|
+
* @returns 包装后的处理函数
|
|
30
|
+
*/
|
|
31
|
+
private wrapCommandHandler(
|
|
32
|
+
handler: (args: string[], options: Record<string, unknown>) => Promise<void>
|
|
33
|
+
) {
|
|
34
|
+
return async (...args: unknown[]) => {
|
|
35
|
+
try {
|
|
36
|
+
// Commander.js 传递的最后一个参数是 Command 对象,包含选项值
|
|
37
|
+
const command = args[args.length - 1] as {
|
|
38
|
+
opts: () => Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
const options = command.opts();
|
|
41
|
+
await handler(args.slice(0, -1) as string[], options);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
ErrorHandler.handle(error as Error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
/**
|
|
27
49
|
* 注册所有命令到 Commander 程序
|
|
28
50
|
*/
|
|
@@ -87,28 +109,19 @@ export class CommandRegistry implements ICommandRegistry {
|
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
// 设置子命令处理函数
|
|
90
|
-
cmd.action(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
await subcommand.execute(args.slice(0, -1), options);
|
|
96
|
-
} catch (error) {
|
|
97
|
-
ErrorHandler.handle(error as Error);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
112
|
+
cmd.action(
|
|
113
|
+
this.wrapCommandHandler(async (args, options) => {
|
|
114
|
+
await subcommand.execute(args, options);
|
|
115
|
+
})
|
|
116
|
+
);
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
// 设置主命令的默认行为
|
|
103
|
-
commandGroup.action(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
} catch (error) {
|
|
109
|
-
ErrorHandler.handle(error as Error);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
120
|
+
commandGroup.action(
|
|
121
|
+
this.wrapCommandHandler(async (args, options) => {
|
|
122
|
+
await handler.execute(args, options);
|
|
123
|
+
})
|
|
124
|
+
);
|
|
112
125
|
} else {
|
|
113
126
|
// 没有子命令,注册为普通命令
|
|
114
127
|
let commandName = handler.name;
|
|
@@ -130,15 +143,11 @@ export class CommandRegistry implements ICommandRegistry {
|
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
// 设置主命令处理函数
|
|
133
|
-
command.action(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} catch (error) {
|
|
139
|
-
ErrorHandler.handle(error as Error);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
146
|
+
command.action(
|
|
147
|
+
this.wrapCommandHandler(async (args, options) => {
|
|
148
|
+
await handler.execute(args, options);
|
|
149
|
+
})
|
|
150
|
+
);
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
|
|
@@ -208,16 +217,11 @@ export class CommandRegistry implements ICommandRegistry {
|
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
// 设置命令处理函数
|
|
211
|
-
command.action(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await subcommand.execute(args.slice(0, -1), options);
|
|
217
|
-
} catch (error) {
|
|
218
|
-
ErrorHandler.handle(error as Error);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
220
|
+
command.action(
|
|
221
|
+
this.wrapCommandHandler(async (args, options) => {
|
|
222
|
+
await subcommand.execute(args, options);
|
|
223
|
+
})
|
|
224
|
+
);
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -66,6 +66,23 @@ export class ErrorHandler {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* 错误处理包装器
|
|
71
|
+
*/
|
|
72
|
+
private static wrapError(error: unknown, context: string): CLIError {
|
|
73
|
+
if (error instanceof CLIError) {
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
if (error instanceof Error) {
|
|
77
|
+
return new CLIError(
|
|
78
|
+
`${context}失败: ${error.message}`,
|
|
79
|
+
"OPERATION_FAILED",
|
|
80
|
+
1
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return new CLIError(`${context}失败: 未知错误`, "OPERATION_FAILED", 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
/**
|
|
70
87
|
* 异步操作错误处理包装器
|
|
71
88
|
*/
|
|
@@ -76,17 +93,7 @@ export class ErrorHandler {
|
|
|
76
93
|
try {
|
|
77
94
|
return await operation();
|
|
78
95
|
} catch (error) {
|
|
79
|
-
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
if (error instanceof Error) {
|
|
83
|
-
throw new CLIError(
|
|
84
|
-
`${context}失败: ${error.message}`,
|
|
85
|
-
"OPERATION_FAILED",
|
|
86
|
-
1
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
throw new CLIError(`${context}失败: 未知错误`, "OPERATION_FAILED", 1);
|
|
96
|
+
throw ErrorHandler.wrapError(error, context);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -97,17 +104,7 @@ export class ErrorHandler {
|
|
|
97
104
|
try {
|
|
98
105
|
return operation();
|
|
99
106
|
} catch (error) {
|
|
100
|
-
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
-
if (error instanceof Error) {
|
|
104
|
-
throw new CLIError(
|
|
105
|
-
`${context}失败: ${error.message}`,
|
|
106
|
-
"OPERATION_FAILED",
|
|
107
|
-
1
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
throw new CLIError(`${context}失败: 未知错误`, "OPERATION_FAILED", 1);
|
|
107
|
+
throw ErrorHandler.wrapError(error, context);
|
|
111
108
|
}
|
|
112
109
|
}
|
|
113
110
|
|
package/src/global.d.ts
CHANGED
|
@@ -80,20 +80,42 @@ export interface DaemonManager {
|
|
|
80
80
|
stopDaemon(): Promise<void>;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* 模板信息接口
|
|
85
|
+
*/
|
|
86
|
+
export interface TemplateInfo {
|
|
87
|
+
name: string;
|
|
88
|
+
path: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
version?: string;
|
|
91
|
+
author?: string;
|
|
92
|
+
files: string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 模板创建选项
|
|
97
|
+
*/
|
|
98
|
+
export interface TemplateCreateOptions {
|
|
99
|
+
templateName?: string;
|
|
100
|
+
targetPath: string;
|
|
101
|
+
projectName: string;
|
|
102
|
+
variables?: Record<string, string>;
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
/**
|
|
84
106
|
* 模板管理器接口
|
|
85
107
|
*/
|
|
86
108
|
export interface TemplateManager {
|
|
87
109
|
/** 获取可用模板列表 */
|
|
88
|
-
getAvailableTemplates(): Promise<
|
|
110
|
+
getAvailableTemplates(): Promise<TemplateInfo[]>;
|
|
89
111
|
/** 复制模板到目标目录 */
|
|
90
112
|
copyTemplate(templateName: string, targetPath: string): Promise<void>;
|
|
91
113
|
/** 验证模板是否存在 */
|
|
92
114
|
validateTemplate(templateName: string): Promise<boolean>;
|
|
93
115
|
/** 获取模板信息 */
|
|
94
|
-
getTemplateInfo(templateName: string): Promise<
|
|
116
|
+
getTemplateInfo(templateName: string): Promise<TemplateInfo | null>;
|
|
95
117
|
/** 创建项目 */
|
|
96
|
-
createProject(options:
|
|
118
|
+
createProject(options: TemplateCreateOptions): Promise<void>;
|
|
97
119
|
/** 清除模板缓存 */
|
|
98
120
|
clearCache(): void;
|
|
99
121
|
}
|
|
@@ -7,6 +7,7 @@ import { spawn } from "node:child_process";
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import type { WebServer } from "@/WebServer";
|
|
9
9
|
import consola from "consola";
|
|
10
|
+
import { RETRY_CONSTANTS } from "../Constants";
|
|
10
11
|
import { ProcessError, ServiceError } from "../errors/index";
|
|
11
12
|
import type {
|
|
12
13
|
DaemonManager as IDaemonManager,
|
|
@@ -116,7 +117,9 @@ export class DaemonManagerImpl implements IDaemonManager {
|
|
|
116
117
|
if (status.running) {
|
|
117
118
|
await this.stopDaemon();
|
|
118
119
|
// 等待一下确保完全停止
|
|
119
|
-
await new Promise((resolve) =>
|
|
120
|
+
await new Promise((resolve) =>
|
|
121
|
+
setTimeout(resolve, RETRY_CONSTANTS.DEFAULT_INTERVAL)
|
|
122
|
+
);
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// 重新启动守护进程
|
|
@@ -151,7 +154,7 @@ export class DaemonManagerImpl implements IDaemonManager {
|
|
|
151
154
|
const tail = spawn(command, args, { stdio: "inherit" });
|
|
152
155
|
|
|
153
156
|
// 处理中断信号
|
|
154
|
-
process.
|
|
157
|
+
process.once("SIGINT", () => {
|
|
155
158
|
console.log("\n断开连接,服务继续在后台运行");
|
|
156
159
|
tail.kill();
|
|
157
160
|
process.exit(0);
|
|
@@ -153,33 +153,7 @@ export class ProcessManagerImpl implements IProcessManager {
|
|
|
153
153
|
*/
|
|
154
154
|
async gracefulKillProcess(pid: number): Promise<void> {
|
|
155
155
|
try {
|
|
156
|
-
|
|
157
|
-
process.kill(pid, "SIGTERM");
|
|
158
|
-
|
|
159
|
-
// 等待进程停止
|
|
160
|
-
let attempts = 0;
|
|
161
|
-
const maxAttempts = 30; // 3秒超时
|
|
162
|
-
|
|
163
|
-
while (attempts < maxAttempts) {
|
|
164
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
process.kill(pid, 0);
|
|
168
|
-
attempts++;
|
|
169
|
-
} catch {
|
|
170
|
-
// 进程已停止
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// 如果还在运行,强制停止
|
|
176
|
-
try {
|
|
177
|
-
process.kill(pid, 0);
|
|
178
|
-
process.kill(pid, "SIGKILL");
|
|
179
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
180
|
-
} catch {
|
|
181
|
-
// 进程已停止
|
|
182
|
-
}
|
|
156
|
+
await PlatformUtils.killProcess(pid, "SIGTERM");
|
|
183
157
|
} catch (error) {
|
|
184
158
|
throw new ProcessError(
|
|
185
159
|
`无法停止进程: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { ConfigManager } from "@xiaozhi-client/config";
|
|
6
6
|
import { ConfigInitializer } from "@xiaozhi-client/config";
|
|
7
7
|
import consola from "consola";
|
|
8
|
+
import { RETRY_CONSTANTS } from "../Constants";
|
|
8
9
|
import { ConfigError, ServiceError } from "../errors/index";
|
|
9
10
|
import type {
|
|
10
11
|
ServiceManager as IServiceManager,
|
|
@@ -51,7 +52,9 @@ export class ServiceManagerImpl implements IServiceManager {
|
|
|
51
52
|
this.processManager.cleanupPidFile();
|
|
52
53
|
|
|
53
54
|
// 等待一下确保完全停止
|
|
54
|
-
await new Promise((resolve) =>
|
|
55
|
+
await new Promise((resolve) =>
|
|
56
|
+
setTimeout(resolve, RETRY_CONSTANTS.DEFAULT_INTERVAL)
|
|
57
|
+
);
|
|
55
58
|
|
|
56
59
|
consola.success("现有服务已停止,正在启动新服务...");
|
|
57
60
|
} catch (stopError) {
|
|
@@ -127,7 +130,9 @@ export class ServiceManagerImpl implements IServiceManager {
|
|
|
127
130
|
if (status.running) {
|
|
128
131
|
await this.stop();
|
|
129
132
|
// 等待一下确保完全停止
|
|
130
|
-
await new Promise((resolve) =>
|
|
133
|
+
await new Promise((resolve) =>
|
|
134
|
+
setTimeout(resolve, RETRY_CONSTANTS.DEFAULT_INTERVAL)
|
|
135
|
+
);
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
// 重新启动服务
|
|
@@ -273,8 +278,8 @@ export class ServiceManagerImpl implements IServiceManager {
|
|
|
273
278
|
process.exit(0);
|
|
274
279
|
};
|
|
275
280
|
|
|
276
|
-
process.
|
|
277
|
-
process.
|
|
281
|
+
process.once("SIGINT", cleanup);
|
|
282
|
+
process.once("SIGTERM", cleanup);
|
|
278
283
|
|
|
279
284
|
await server.start();
|
|
280
285
|
}
|
|
@@ -333,8 +338,8 @@ export class ServiceManagerImpl implements IServiceManager {
|
|
|
333
338
|
process.exit(0);
|
|
334
339
|
};
|
|
335
340
|
|
|
336
|
-
process.
|
|
337
|
-
process.
|
|
341
|
+
process.once("SIGINT", cleanup);
|
|
342
|
+
process.once("SIGTERM", cleanup);
|
|
338
343
|
|
|
339
344
|
// 保存 PID 信息
|
|
340
345
|
this.processManager.savePidInfo(process.pid, "foreground");
|
|
@@ -5,32 +5,17 @@
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { FileError, ValidationError } from "../errors/index";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
TemplateManager as ITemplateManager,
|
|
10
|
+
TemplateCreateOptions,
|
|
11
|
+
TemplateInfo,
|
|
12
|
+
} from "../interfaces/Service";
|
|
9
13
|
import { FileUtils } from "../utils/FileUtils";
|
|
10
14
|
import { PathUtils } from "../utils/PathUtils";
|
|
11
15
|
import { Validation } from "../utils/Validation";
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
export interface TemplateInfo {
|
|
17
|
-
name: string;
|
|
18
|
-
path: string;
|
|
19
|
-
description?: string;
|
|
20
|
-
version?: string;
|
|
21
|
-
author?: string;
|
|
22
|
-
files: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 模板创建选项
|
|
27
|
-
*/
|
|
28
|
-
export interface TemplateCreateOptions {
|
|
29
|
-
templateName?: string;
|
|
30
|
-
targetPath: string;
|
|
31
|
-
projectName: string;
|
|
32
|
-
variables?: Record<string, string>;
|
|
33
|
-
}
|
|
17
|
+
// 重新导出类型以保持向后兼容
|
|
18
|
+
export type { TemplateInfo, TemplateCreateOptions };
|
|
34
19
|
|
|
35
20
|
/**
|
|
36
21
|
* 模板管理器实现
|
|
@@ -139,43 +139,23 @@ describe("ProcessManagerImpl", () => {
|
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
describe("gracefulKillProcess", () => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
beforeEach(() => {
|
|
145
|
-
originalKill = process.kill;
|
|
146
|
-
process.kill = vi.fn();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
afterEach(() => {
|
|
150
|
-
process.kill = originalKill;
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("should gracefully kill process", async () => {
|
|
154
|
-
let killCallCount = 0;
|
|
155
|
-
(process.kill as any).mockImplementation((pid: number, signal: any) => {
|
|
156
|
-
killCallCount++;
|
|
157
|
-
if (killCallCount > 1) {
|
|
158
|
-
throw new Error("ESRCH"); // Process stopped
|
|
159
|
-
}
|
|
160
|
-
});
|
|
142
|
+
it("should delegate to PlatformUtils with SIGTERM signal", async () => {
|
|
143
|
+
mockPlatformUtils.killProcess.mockResolvedValue();
|
|
161
144
|
|
|
162
145
|
await processManager.gracefulKillProcess(1234);
|
|
163
146
|
|
|
164
|
-
expect(
|
|
147
|
+
expect(mockPlatformUtils.killProcess).toHaveBeenCalledWith(
|
|
148
|
+
1234,
|
|
149
|
+
"SIGTERM"
|
|
150
|
+
);
|
|
165
151
|
});
|
|
166
152
|
|
|
167
|
-
it("should
|
|
168
|
-
|
|
169
|
-
if (signal === "SIGKILL") {
|
|
170
|
-
throw new Error("ESRCH"); // Process stopped
|
|
171
|
-
}
|
|
172
|
-
// Process still running for SIGTERM and signal 0
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
await processManager.gracefulKillProcess(1234);
|
|
153
|
+
it("should throw ProcessError when kill fails", async () => {
|
|
154
|
+
mockPlatformUtils.killProcess.mockRejectedValue(new Error("Kill failed"));
|
|
176
155
|
|
|
177
|
-
expect(
|
|
178
|
-
|
|
156
|
+
await expect(processManager.gracefulKillProcess(1234)).rejects.toThrow(
|
|
157
|
+
"无法停止进程"
|
|
158
|
+
);
|
|
179
159
|
});
|
|
180
160
|
});
|
|
181
161
|
|
|
@@ -74,28 +74,6 @@ vi.mock("../../utils/PathUtils.js", () => ({
|
|
|
74
74
|
},
|
|
75
75
|
}));
|
|
76
76
|
|
|
77
|
-
// Mock ConfigManager for WebServer
|
|
78
|
-
vi.mock("@/lib/config/manager.js", () => {
|
|
79
|
-
const mockConfig = {
|
|
80
|
-
mcpEndpoint: "ws://localhost:3000",
|
|
81
|
-
mcpServers: {},
|
|
82
|
-
webServer: { port: 9999 },
|
|
83
|
-
};
|
|
84
|
-
const mockConfigManager = {
|
|
85
|
-
configExists: vi.fn().mockReturnValue(true),
|
|
86
|
-
getConfig: vi.fn().mockReturnValue(mockConfig),
|
|
87
|
-
loadConfig: vi.fn().mockResolvedValue(mockConfig),
|
|
88
|
-
getToolCallLogConfig: vi.fn().mockReturnValue({ enabled: false }),
|
|
89
|
-
getMcpServers: vi.fn().mockReturnValue({}),
|
|
90
|
-
getMcpEndpoint: vi.fn().mockReturnValue("ws://localhost:3000"),
|
|
91
|
-
getConfigDir: vi.fn().mockReturnValue("/mock/config"),
|
|
92
|
-
};
|
|
93
|
-
return {
|
|
94
|
-
configManager: mockConfigManager,
|
|
95
|
-
ConfigManager: vi.fn().mockImplementation(() => mockConfigManager),
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
|
|
99
77
|
// Mock fs
|
|
100
78
|
vi.mock("node:fs", () => {
|
|
101
79
|
const mockExistsSync = vi.fn().mockReturnValue(true);
|
package/src/types/backend.d.ts
CHANGED
|
@@ -1,48 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Backend 模块类型声明
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* 这些声明用于 CLI 包中引用 Backend 模块的类型
|
|
5
|
+
* 避免递归解析 backend 代码,同时提供类型安全
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
env?: Record<string, string>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface MCPServerConfig {
|
|
14
|
-
type?: string;
|
|
15
|
-
url?: string;
|
|
16
|
-
command?: string;
|
|
17
|
-
args?: string[];
|
|
18
|
-
headers?: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const configManager: any;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
declare module "@/lib/config/manager.js" {
|
|
25
|
-
export interface LocalMCPServerConfig {
|
|
26
|
-
command: string;
|
|
27
|
-
args: string[];
|
|
28
|
-
env?: Record<string, string>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface MCPServerConfig {
|
|
32
|
-
type?: string;
|
|
33
|
-
url?: string;
|
|
34
|
-
command?: string;
|
|
35
|
-
args?: string[];
|
|
36
|
-
headers?: Record<string, string>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const configManager: any;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
declare module "@/WebServer" {
|
|
43
|
-
export class WebServer {}
|
|
44
|
-
}
|
|
45
|
-
|
|
8
|
+
/**
|
|
9
|
+
* WebServer 类型声明
|
|
10
|
+
* 对应 apps/backend/WebServer.ts
|
|
11
|
+
*/
|
|
46
12
|
declare module "@/WebServer.js" {
|
|
47
|
-
|
|
13
|
+
/**
|
|
14
|
+
* WebServer - Web 服务器主控制器
|
|
15
|
+
*/
|
|
16
|
+
export class WebServer {
|
|
17
|
+
/**
|
|
18
|
+
* 创建 WebServer 实例
|
|
19
|
+
* @param port - 可选的端口号,不指定则使用配置文件中的端口
|
|
20
|
+
*/
|
|
21
|
+
constructor(port?: number);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 启动 Web 服务器
|
|
25
|
+
*/
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 停止 Web 服务器
|
|
30
|
+
*/
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 销毁 WebServer 实例,清理所有资源
|
|
35
|
+
*/
|
|
36
|
+
destroy(): void;
|
|
37
|
+
}
|
|
48
38
|
}
|
package/src/utils/Validation.ts
CHANGED
|
@@ -37,7 +37,10 @@ export class Validation {
|
|
|
37
37
|
/**
|
|
38
38
|
* 验证必填字段
|
|
39
39
|
*/
|
|
40
|
-
static validateRequired(
|
|
40
|
+
static validateRequired(
|
|
41
|
+
value: string | number | boolean | object | null | undefined,
|
|
42
|
+
fieldName: string
|
|
43
|
+
): void {
|
|
41
44
|
if (value === undefined || value === null || value === "") {
|
|
42
45
|
throw ValidationError.requiredField(fieldName);
|
|
43
46
|
}
|
|
@@ -169,7 +172,7 @@ export class Validation {
|
|
|
169
172
|
/**
|
|
170
173
|
* 验证 JSON 格式
|
|
171
174
|
*/
|
|
172
|
-
static validateJson(jsonString: string, fieldName = "json"):
|
|
175
|
+
static validateJson(jsonString: string, fieldName = "json"): unknown {
|
|
173
176
|
try {
|
|
174
177
|
return JSON.parse(jsonString);
|
|
175
178
|
} catch (error) {
|
|
@@ -206,8 +209,8 @@ export class Validation {
|
|
|
206
209
|
/**
|
|
207
210
|
* 验证数组长度
|
|
208
211
|
*/
|
|
209
|
-
static validateArrayLength(
|
|
210
|
-
array:
|
|
212
|
+
static validateArrayLength<T>(
|
|
213
|
+
array: T[],
|
|
211
214
|
fieldName: string,
|
|
212
215
|
options: { min?: number; max?: number } = {}
|
|
213
216
|
): void {
|
|
@@ -230,7 +233,7 @@ export class Validation {
|
|
|
230
233
|
* 验证对象属性
|
|
231
234
|
*/
|
|
232
235
|
static validateObjectProperties(
|
|
233
|
-
obj: Record<string,
|
|
236
|
+
obj: Record<string, unknown>,
|
|
234
237
|
requiredProps: string[],
|
|
235
238
|
fieldName = "object"
|
|
236
239
|
): void {
|