@xiaozhi-client/cli 1.10.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 CHANGED
@@ -47,10 +47,10 @@ packages/cli/
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
- - `@/lib/config/manager` → `dist/backend/lib/config/manager.js`
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
- // 引用 backend 模块
69
- import { configManager } from "@/lib/config/manager";
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": "1.10.9",
3
+ "version": "2.0.0-beta.4",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  "commander": "^14.0.0",
23
23
  "consola": "^3.4.2",
24
24
  "ora": "^8.2.0",
25
- "@xiaozhi-client/config": "1.10.9"
25
+ "@xiaozhi-client/config": "2.0.0-beta.4"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^24.3.0",
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
  }
@@ -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(async (...args) => {
91
- try {
92
- // Commander.js 传递的最后一个参数是 Command 对象,包含选项值
93
- const command = args[args.length - 1];
94
- const options = command.opts(); // 获取解析后的选项
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(async (...args) => {
104
- try {
105
- const command = args[args.length - 1];
106
- const options = command.opts(); // 获取解析后的选项
107
- await handler.execute(args.slice(0, -1), options);
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(async (...args) => {
134
- try {
135
- const command = args[args.length - 1];
136
- const options = command.opts(); // 获取解析后的选项
137
- await handler.execute(args.slice(0, -1), options);
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(async (...args) => {
212
- try {
213
- // Commander.js 传递的最后一个参数是 Command 对象,包含选项值
214
- const command = args[args.length - 1];
215
- const options = command.opts(); // 获取解析后的选项
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
 
package/src/global.d.ts CHANGED
@@ -1,3 +1,11 @@
1
+ /**
2
+ * CLI 全局类型声明
3
+ *
4
+ * 扩展 Node.js 全局类型,包含以下内容:
5
+ * - Express 相关类型
6
+ * - WebSocket 相关类型
7
+ * - 环境变量类型扩展
8
+ */
1
9
  /// <reference types="node" />
2
10
  /// <reference types="express" />
3
11
  /// <reference types="ws" />
@@ -154,7 +154,7 @@ export class DaemonManagerImpl implements IDaemonManager {
154
154
  const tail = spawn(command, args, { stdio: "inherit" });
155
155
 
156
156
  // 处理中断信号
157
- process.on("SIGINT", () => {
157
+ process.once("SIGINT", () => {
158
158
  console.log("\n断开连接,服务继续在后台运行");
159
159
  tail.kill();
160
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)}`,
@@ -278,8 +278,8 @@ export class ServiceManagerImpl implements IServiceManager {
278
278
  process.exit(0);
279
279
  };
280
280
 
281
- process.on("SIGINT", cleanup);
282
- process.on("SIGTERM", cleanup);
281
+ process.once("SIGINT", cleanup);
282
+ process.once("SIGTERM", cleanup);
283
283
 
284
284
  await server.start();
285
285
  }
@@ -338,8 +338,8 @@ export class ServiceManagerImpl implements IServiceManager {
338
338
  process.exit(0);
339
339
  };
340
340
 
341
- process.on("SIGINT", cleanup);
342
- process.on("SIGTERM", cleanup);
341
+ process.once("SIGINT", cleanup);
342
+ process.once("SIGTERM", cleanup);
343
343
 
344
344
  // 保存 PID 信息
345
345
  this.processManager.savePidInfo(process.pid, "foreground");
@@ -139,43 +139,23 @@ describe("ProcessManagerImpl", () => {
139
139
  });
140
140
 
141
141
  describe("gracefulKillProcess", () => {
142
- let originalKill: typeof process.kill;
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(process.kill).toHaveBeenCalledWith(1234, "SIGTERM");
147
+ expect(mockPlatformUtils.killProcess).toHaveBeenCalledWith(
148
+ 1234,
149
+ "SIGTERM"
150
+ );
165
151
  });
166
152
 
167
- it("should force kill if graceful kill fails", async () => {
168
- (process.kill as any).mockImplementation((pid: number, signal: any) => {
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(process.kill).toHaveBeenCalledWith(1234, "SIGTERM");
178
- expect(process.kill).toHaveBeenCalledWith(1234, "SIGKILL");
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);
@@ -1,26 +1,38 @@
1
1
  /**
2
2
  * Backend 模块类型声明
3
- * 使用 any 类型避免递归解析 backend 代码
3
+ *
4
+ * 这些声明用于 CLI 包中引用 Backend 模块的类型
5
+ * 避免递归解析 backend 代码,同时提供类型安全
4
6
  */
5
7
 
6
- declare module "@/lib/config/manager.js" {
7
- export interface LocalMCPServerConfig {
8
- command: string;
9
- args: string[];
10
- env?: Record<string, string>;
11
- }
8
+ /**
9
+ * WebServer 类型声明
10
+ * 对应 apps/backend/WebServer.ts
11
+ */
12
+ declare module "@/WebServer.js" {
13
+ /**
14
+ * WebServer - Web 服务器主控制器
15
+ */
16
+ export class WebServer {
17
+ /**
18
+ * 创建 WebServer 实例
19
+ * @param port - 可选的端口号,不指定则使用配置文件中的端口
20
+ */
21
+ constructor(port?: number);
12
22
 
13
- export interface MCPServerConfig {
14
- type?: string;
15
- url?: string;
16
- command?: string;
17
- args?: string[];
18
- headers?: Record<string, string>;
19
- }
23
+ /**
24
+ * 启动 Web 服务器
25
+ */
26
+ start(): Promise<void>;
20
27
 
21
- export const configManager: any;
22
- }
28
+ /**
29
+ * 停止 Web 服务器
30
+ */
31
+ stop(): Promise<void>;
23
32
 
24
- declare module "@/WebServer.js" {
25
- export class WebServer {}
33
+ /**
34
+ * 销毁 WebServer 实例,清理所有资源
35
+ */
36
+ destroy(): void;
37
+ }
26
38
  }
package/tsup.config.ts CHANGED
@@ -11,7 +11,7 @@ export default defineConfig({
11
11
  index: "src/index.ts",
12
12
  },
13
13
  format: ["esm"],
14
- target: "node18",
14
+ target: "node20",
15
15
  outDir: "../../dist/cli",
16
16
  clean: true,
17
17
  sourcemap: true,